Skip to content

Docker Installer

hatch.installers.docker_installer

Installer for Docker image dependencies.

This module implements installation logic for Docker images using docker-py library, with support for version constraints, registry management, and comprehensive error handling.

Classes

DockerInstaller

Bases: DependencyInstaller

Installer for Docker image dependencies.

Handles installation and removal of Docker images using the docker-py library. Supports version constraint mapping to Docker tags and progress reporting during image pull operations.

Source code in hatch/installers/docker_installer.py
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
class DockerInstaller(DependencyInstaller):
    """Installer for Docker image dependencies.

    Handles installation and removal of Docker images using the docker-py library.
    Supports version constraint mapping to Docker tags and progress reporting during
    image pull operations.
    """

    def __init__(self):
        """Initialize the DockerInstaller.

        Raises:
            InstallationError: If docker-py library is not available.
        """
        if not DOCKER_AVAILABLE:
            logger.error("Docker installer requires docker-py library")
        self._docker_client = None

    @property
    def installer_type(self) -> str:
        """Get the installer type identifier.

        Returns:
            str: The installer type "docker".
        """
        return "docker"

    @property
    def supported_schemes(self) -> List[str]:
        """Get the list of supported registry schemes.

        Returns:
            List[str]: List of supported schemes, currently only ["dockerhub"].
        """
        return ["dockerhub"]

    def can_install(self, dependency: Dict[str, Any]) -> bool:
        """Check if this installer can handle the given dependency.

        Args:
            dependency (Dict[str, Any]): The dependency specification.

        Returns:
            bool: True if the dependency can be installed, False otherwise.
        """
        if dependency.get("type") != "docker":
            return False

        return self._is_docker_available()

    def validate_dependency(self, dependency: Dict[str, Any]) -> bool:
        """Validate a Docker dependency specification.

        Args:
            dependency (Dict[str, Any]): The dependency specification to validate.

        Returns:
            bool: True if the dependency is valid, False otherwise.
        """
        required_fields = ["name", "version_constraint"]

        # Check required fields
        if not all(field in dependency for field in required_fields):
            logger.error(
                f"Docker dependency missing required fields. Required: {required_fields}"
            )
            return False

        # Validate type
        if dependency.get("type") != "docker":
            logger.error(
                f"Invalid dependency type: {dependency.get('type')}, expected 'docker'"
            )
            return False

        # Validate registry if specified
        registry = dependency.get("registry", "unknown")
        if registry not in self.supported_schemes:
            logger.error(
                f"Unsupported registry: {registry}, supported: {self.supported_schemes}"
            )
            return False

        # Validate version constraint format
        version_constraint = dependency.get("version_constraint", "")
        if not self._validate_version_constraint(version_constraint):
            logger.error(f"Invalid version constraint format: {version_constraint}")
            return False

        return True

    def install(
        self,
        dependency: Dict[str, Any],
        context: InstallationContext,
        progress_callback: Optional[Callable[[str, float, str], None]] = None,
    ) -> InstallationResult:
        """Install a Docker image dependency.

        Args:
            dependency (Dict[str, Any]): The dependency specification.
            context (InstallationContext): Installation context and configuration.
            progress_callback (Optional[Callable[[str, float, str], None]]): Progress reporting callback.

        Returns:
            InstallationResult: Result of the installation operation.

        Raises:
            InstallationError: If installation fails.
        """
        if not self.validate_dependency(dependency):
            raise InstallationError(
                f"Invalid Docker dependency specification: {dependency}",
                dependency_name=dependency.get("name", "unknown"),
                error_code="DOCKER_DEPENDENCY_INVALID",
                cause=ValueError("Dependency validation failed"),
            )

        image_name = dependency["name"]
        version_constraint = dependency["version_constraint"]
        # registry = dependency.get("registry", "dockerhub")  # Reserved for future use

        if progress_callback:
            progress_callback(
                f"Starting Docker image pull: {image_name}", 0.0, "starting"
            )

        # Handle simulation mode
        if context.simulation_mode:
            logger.info(
                f"[SIMULATION] Would pull Docker image: {image_name}:{version_constraint}"
            )
            if progress_callback:
                progress_callback(f"Simulated pull: {image_name}", 100.0, "completed")
            return InstallationResult(
                dependency_name=image_name,
                status=InstallationStatus.COMPLETED,
                installed_version=version_constraint,
                artifacts=[],
                metadata={
                    "message": f"Simulated installation of Docker image: {image_name}:{version_constraint}",
                },
            )

        try:
            # Resolve version constraint to Docker tag
            docker_tag = self._resolve_docker_tag(version_constraint)
            full_image_name = f"{image_name}:{docker_tag}"

            # Pull the Docker image
            self._pull_docker_image(full_image_name, progress_callback)

            if progress_callback:
                progress_callback(f"Completed pull: {image_name}", 100.0, "completed")

            return InstallationResult(
                dependency_name=image_name,
                status=InstallationStatus.COMPLETED,
                installed_version=docker_tag,
                artifacts=[full_image_name],
                metadata={
                    "message": f"Successfully installed Docker image: {full_image_name}",
                },
            )

        except Exception as e:
            error_msg = f"Failed to install Docker image {image_name}: {str(e)}"
            logger.error(error_msg)
            if progress_callback:
                progress_callback(f"Failed: {image_name}", 0.0, "error")
            raise InstallationError(
                error_msg,
                dependency_name=image_name,
                error_code="DOCKER_INSTALL_ERROR",
                cause=e,
            )

    def uninstall(
        self,
        dependency: Dict[str, Any],
        context: InstallationContext,
        progress_callback: Optional[Callable[[str, float, str], None]] = None,
    ) -> InstallationResult:
        """Uninstall a Docker image dependency.

        Args:
            dependency (Dict[str, Any]): The dependency specification.
            context (InstallationContext): Installation context and configuration.
            progress_callback (Optional[Callable[[str, float, str], None]]): Progress reporting callback.

        Returns:
            InstallationResult: Result of the uninstallation operation.

        Raises:
            InstallationError: If uninstallation fails.
        """
        if not self.validate_dependency(dependency):
            raise InstallationError(
                f"Invalid Docker dependency specification: {dependency}"
            )

        image_name = dependency["name"]
        version_constraint = dependency["version_constraint"]

        if progress_callback:
            progress_callback(
                f"Starting Docker image removal: {image_name}", 0.0, "starting"
            )

        # Handle simulation mode
        if context.simulation_mode:
            logger.info(
                f"[SIMULATION] Would remove Docker image: {image_name}:{version_constraint}"
            )
            if progress_callback:
                progress_callback(
                    f"Simulated removal: {image_name}", 100.0, "completed"
                )
            return InstallationResult(
                dependency_name=image_name,
                status=InstallationStatus.COMPLETED,
                installed_version=version_constraint,
                artifacts=[],
                metadata={
                    "message": f"Simulated removal of Docker image: {image_name}:{version_constraint}",
                },
            )

        try:
            # Resolve version constraint to Docker tag
            docker_tag = self._resolve_docker_tag(version_constraint)
            full_image_name = f"{image_name}:{docker_tag}"

            # Remove the Docker image
            self._remove_docker_image(full_image_name, context, progress_callback)

            if progress_callback:
                progress_callback(
                    f"Completed removal: {image_name}", 100.0, "completed"
                )

            return InstallationResult(
                dependency_name=image_name,
                status=InstallationStatus.COMPLETED,
                installed_version=docker_tag,
                artifacts=[],
                metadata={
                    "message": f"Successfully removed Docker image: {full_image_name}",
                },
            )

        except Exception as e:
            error_msg = f"Failed to remove Docker image {image_name}: {str(e)}"
            logger.error(error_msg)
            if progress_callback:
                progress_callback(f"Failed removal: {image_name}", 0.0, "error")
            raise InstallationError(
                error_msg,
                dependency_name=image_name,
                error_code="DOCKER_UNINSTALL_ERROR",
                cause=e,
            )

    def cleanup_failed_installation(
        self,
        dependency: Dict[str, Any],
        context: InstallationContext,
        artifacts: Optional[List[Path]] = None,
    ) -> None:
        """Clean up artifacts from a failed installation.

        Args:
            dependency (Dict[str, Any]): The dependency that failed to install.
            context (InstallationContext): Installation context.
            artifacts (Optional[List[Path]]): List of artifacts to clean up.
        """
        if not artifacts:
            return

        logger.info(
            f"Cleaning up failed Docker installation for {dependency.get('name', 'unknown')}"
        )

        for artifact in artifacts:
            if isinstance(artifact, str):  # Docker image name
                try:
                    self._remove_docker_image(artifact, context, None, force=True)
                    logger.info(f"Cleaned up Docker image: {artifact}")
                except Exception as e:
                    logger.warning(f"Failed to clean up Docker image {artifact}: {e}")

    def _is_docker_available(self) -> bool:
        """Check if Docker daemon is available.

        We use the global DOCKER_DAEMON_AVAILABLE flag to determine
        if Docker is available. It is set to True if the docker-py
        library is available and the Docker daemon is reachable.

        Returns:
            bool: True if Docker daemon is available, False otherwise.
        """
        return DOCKER_DAEMON_AVAILABLE

    def _get_docker_client(self):
        """Get or create Docker client.

        Returns:
            docker.DockerClient: Docker client instance.

        Raises:
            InstallationError: If Docker client cannot be created.
        """
        if not DOCKER_AVAILABLE:
            raise InstallationError(
                "Docker library not available",
                error_code="DOCKER_LIBRARY_NOT_AVAILABLE",
                cause=ImportError("docker-py library is required for Docker support"),
            )

        if not DOCKER_DAEMON_AVAILABLE:
            raise InstallationError(
                "Docker daemon not available",
                error_code="DOCKER_DAEMON_NOT_AVAILABLE",
                cause=DOCKER_DAEMON_ERROR,
            )
        if self._docker_client is None:
            self._docker_client = docker.from_env()
        return self._docker_client

    def _validate_version_constraint(self, version_constraint: str) -> bool:
        """Validate version constraint format.

        Args:
            version_constraint (str): Version constraint to validate.

        Returns:
            bool: True if valid, False otherwise.
        """
        if not version_constraint or not isinstance(version_constraint, str):
            return False

        # Accept "latest" as a valid constraint
        if version_constraint.strip() == "latest":
            return True

        constraint = version_constraint.strip()

        # Accept bare version numbers (e.g. 1.25.0) as valid
        try:
            Version(constraint)
            return True
        except Exception:
            pass

        # Accept valid PEP 440 specifiers (e.g. >=1.25.0, ==1.25.0)
        try:
            SpecifierSet(constraint)
            return True
        except Exception:
            logger.error(f"Invalid version constraint format: {version_constraint}")
            return False

    def _resolve_docker_tag(self, version_constraint: str) -> str:
        """Resolve version constraint to Docker tag.

        Args:
            version_constraint (str): Version constraint specification.

        Returns:
            str: Docker tag to use.
        """
        constraint = version_constraint.strip()
        # Handle simple cases
        if constraint == "latest":
            return "latest"

        # Accept bare version numbers as tags
        try:
            Version(constraint)
            return constraint
        except Exception:
            pass

        # Try to parse as a version specifier
        try:
            spec = SpecifierSet(constraint)
        except InvalidSpecifier:
            logger.warning(
                f"Invalid version constraint '{constraint}', defaulting to 'latest'"
            )
            return "latest"

        return next(
            iter(spec)
        ).version  # always returns the first matching spec's version

    def _pull_docker_image(
        self,
        image_name: str,
        progress_callback: Optional[Callable[[str, float, str], None]],
    ):
        """Pull Docker image with progress reporting.

        Args:
            image_name (str): Full image name with tag.
            progress_callback (Optional[Callable[[str, float, str], None]]): Progress callback.

        Raises:
            InstallationError: If pull fails.
        """
        try:
            client = self._get_docker_client()

            if progress_callback:
                progress_callback(f"Pulling {image_name}", 50.0, "pulling")

            # Pull the image
            client.images.pull(image_name)

            logger.info(f"Successfully pulled Docker image: {image_name}")

        except ImageNotFound as e:
            raise InstallationError(
                f"Docker image not found: {image_name}",
                error_code="DOCKER_IMAGE_NOT_FOUND",
                cause=e,
            )
        except APIError as e:
            raise InstallationError(
                f"Docker API error while pulling {image_name}: {e}",
                error_code="DOCKER_API_ERROR",
                cause=e,
            )
        except DockerException as e:
            raise InstallationError(
                f"Docker error while pulling {image_name}: {e}",
                error_code="DOCKER_ERROR",
                cause=e,
            )

    def _remove_docker_image(
        self,
        image_name: str,
        context: InstallationContext,
        progress_callback: Optional[Callable[[str, float, str], None]],
        force: bool = False,
    ):
        """Remove Docker image.

        Args:
            image_name (str): Full image name with tag.
            context (InstallationContext): Installation context.
            progress_callback (Optional[Callable[[str, float, str], None]]): Progress callback.
            force (bool): Whether to force removal even if image is in use.

        Raises:
            InstallationError: If removal fails.
        """
        try:
            client = self._get_docker_client()

            if progress_callback:
                progress_callback(f"Removing {image_name}", 50.0, "removing")

            # Check if image is in use (unless forcing)
            if not force and self._is_image_in_use(image_name):
                raise InstallationError(
                    f"Cannot remove Docker image {image_name} as it is in use by running containers",
                    error_code="DOCKER_IMAGE_IN_USE",
                )

            # Remove the image
            client.images.remove(image_name, force=force)

            logger.info(f"Successfully removed Docker image: {image_name}")

        except ImageNotFound:
            logger.warning(
                f"Docker image not found during removal: {image_name}. Nothing to remove."
            )
        except APIError as e:
            raise InstallationError(
                f"Docker API error while removing {image_name}: {e}",
                error_code="DOCKER_API_ERROR",
                cause=e,
            )
        except DockerException as e:
            raise InstallationError(
                f"Docker error while removing {image_name}: {e}",
                error_code="DOCKER_ERROR",
                cause=e,
            )

    def _is_image_in_use(self, image_name: str) -> bool:
        """Check if Docker image is in use by running containers.

        Args:
            image_name (str): Image name to check.

        Returns:
            bool: True if image is in use, False otherwise.
        """
        try:
            client = self._get_docker_client()
            containers = client.containers.list(all=True)

            for container in containers:
                if container.image.tags and any(
                    tag == image_name for tag in container.image.tags
                ):
                    return True

            return False

        except Exception as e:
            logger.warning(
                f"Could not check if image {image_name} is in use: {e}\n Assuming NOT in use."
            )
            return False  # Assume not in use if we can't check

    def get_installation_info(
        self, dependency: Dict[str, Any], context: InstallationContext
    ) -> Dict[str, Any]:
        """Get information about Docker image installation.

        Args:
            dependency (Dict[str, Any]): The dependency specification.
            context (InstallationContext): Installation context.

        Returns:
            Dict[str, Any]: Installation information including availability and status.
        """
        image_name = dependency.get("name", "unknown")
        version_constraint = dependency.get("version_constraint", "latest")

        info = {
            "installer_type": self.installer_type,
            "dependency_name": image_name,
            "version_constraint": version_constraint,
            "docker_available": self._is_docker_available(),
            "can_install": self.can_install(dependency),
        }

        if self._is_docker_available():
            try:
                docker_tag = self._resolve_docker_tag(version_constraint)
                full_image_name = f"{image_name}:{docker_tag}"

                client = self._get_docker_client()
                try:
                    image = client.images.get(full_image_name)
                    info["installed"] = True
                    info["image_id"] = image.id
                    info["image_tags"] = image.tags
                except ImageNotFound:
                    info["installed"] = False

            except Exception as e:
                info["error"] = str(e)

        return info
Attributes
installer_type property

Get the installer type identifier.

Returns:

Name Type Description
str str

The installer type "docker".

supported_schemes property

Get the list of supported registry schemes.

Returns:

Type Description
List[str]

List[str]: List of supported schemes, currently only ["dockerhub"].

Functions
__init__()

Initialize the DockerInstaller.

Raises:

Type Description
InstallationError

If docker-py library is not available.

Source code in hatch/installers/docker_installer.py
def __init__(self):
    """Initialize the DockerInstaller.

    Raises:
        InstallationError: If docker-py library is not available.
    """
    if not DOCKER_AVAILABLE:
        logger.error("Docker installer requires docker-py library")
    self._docker_client = None
can_install(dependency)

Check if this installer can handle the given dependency.

Parameters:

Name Type Description Default
dependency Dict[str, Any]

The dependency specification.

required

Returns:

Name Type Description
bool bool

True if the dependency can be installed, False otherwise.

Source code in hatch/installers/docker_installer.py
def can_install(self, dependency: Dict[str, Any]) -> bool:
    """Check if this installer can handle the given dependency.

    Args:
        dependency (Dict[str, Any]): The dependency specification.

    Returns:
        bool: True if the dependency can be installed, False otherwise.
    """
    if dependency.get("type") != "docker":
        return False

    return self._is_docker_available()
cleanup_failed_installation(dependency, context, artifacts=None)

Clean up artifacts from a failed installation.

Parameters:

Name Type Description Default
dependency Dict[str, Any]

The dependency that failed to install.

required
context InstallationContext

Installation context.

required
artifacts Optional[List[Path]]

List of artifacts to clean up.

None
Source code in hatch/installers/docker_installer.py
def cleanup_failed_installation(
    self,
    dependency: Dict[str, Any],
    context: InstallationContext,
    artifacts: Optional[List[Path]] = None,
) -> None:
    """Clean up artifacts from a failed installation.

    Args:
        dependency (Dict[str, Any]): The dependency that failed to install.
        context (InstallationContext): Installation context.
        artifacts (Optional[List[Path]]): List of artifacts to clean up.
    """
    if not artifacts:
        return

    logger.info(
        f"Cleaning up failed Docker installation for {dependency.get('name', 'unknown')}"
    )

    for artifact in artifacts:
        if isinstance(artifact, str):  # Docker image name
            try:
                self._remove_docker_image(artifact, context, None, force=True)
                logger.info(f"Cleaned up Docker image: {artifact}")
            except Exception as e:
                logger.warning(f"Failed to clean up Docker image {artifact}: {e}")
get_installation_info(dependency, context)

Get information about Docker image installation.

Parameters:

Name Type Description Default
dependency Dict[str, Any]

The dependency specification.

required
context InstallationContext

Installation context.

required

Returns:

Type Description
Dict[str, Any]

Dict[str, Any]: Installation information including availability and status.

Source code in hatch/installers/docker_installer.py
def get_installation_info(
    self, dependency: Dict[str, Any], context: InstallationContext
) -> Dict[str, Any]:
    """Get information about Docker image installation.

    Args:
        dependency (Dict[str, Any]): The dependency specification.
        context (InstallationContext): Installation context.

    Returns:
        Dict[str, Any]: Installation information including availability and status.
    """
    image_name = dependency.get("name", "unknown")
    version_constraint = dependency.get("version_constraint", "latest")

    info = {
        "installer_type": self.installer_type,
        "dependency_name": image_name,
        "version_constraint": version_constraint,
        "docker_available": self._is_docker_available(),
        "can_install": self.can_install(dependency),
    }

    if self._is_docker_available():
        try:
            docker_tag = self._resolve_docker_tag(version_constraint)
            full_image_name = f"{image_name}:{docker_tag}"

            client = self._get_docker_client()
            try:
                image = client.images.get(full_image_name)
                info["installed"] = True
                info["image_id"] = image.id
                info["image_tags"] = image.tags
            except ImageNotFound:
                info["installed"] = False

        except Exception as e:
            info["error"] = str(e)

    return info
install(dependency, context, progress_callback=None)

Install a Docker image dependency.

Parameters:

Name Type Description Default
dependency Dict[str, Any]

The dependency specification.

required
context InstallationContext

Installation context and configuration.

required
progress_callback Optional[Callable[[str, float, str], None]]

Progress reporting callback.

None

Returns:

Name Type Description
InstallationResult InstallationResult

Result of the installation operation.

Raises:

Type Description
InstallationError

If installation fails.

Source code in hatch/installers/docker_installer.py
def install(
    self,
    dependency: Dict[str, Any],
    context: InstallationContext,
    progress_callback: Optional[Callable[[str, float, str], None]] = None,
) -> InstallationResult:
    """Install a Docker image dependency.

    Args:
        dependency (Dict[str, Any]): The dependency specification.
        context (InstallationContext): Installation context and configuration.
        progress_callback (Optional[Callable[[str, float, str], None]]): Progress reporting callback.

    Returns:
        InstallationResult: Result of the installation operation.

    Raises:
        InstallationError: If installation fails.
    """
    if not self.validate_dependency(dependency):
        raise InstallationError(
            f"Invalid Docker dependency specification: {dependency}",
            dependency_name=dependency.get("name", "unknown"),
            error_code="DOCKER_DEPENDENCY_INVALID",
            cause=ValueError("Dependency validation failed"),
        )

    image_name = dependency["name"]
    version_constraint = dependency["version_constraint"]
    # registry = dependency.get("registry", "dockerhub")  # Reserved for future use

    if progress_callback:
        progress_callback(
            f"Starting Docker image pull: {image_name}", 0.0, "starting"
        )

    # Handle simulation mode
    if context.simulation_mode:
        logger.info(
            f"[SIMULATION] Would pull Docker image: {image_name}:{version_constraint}"
        )
        if progress_callback:
            progress_callback(f"Simulated pull: {image_name}", 100.0, "completed")
        return InstallationResult(
            dependency_name=image_name,
            status=InstallationStatus.COMPLETED,
            installed_version=version_constraint,
            artifacts=[],
            metadata={
                "message": f"Simulated installation of Docker image: {image_name}:{version_constraint}",
            },
        )

    try:
        # Resolve version constraint to Docker tag
        docker_tag = self._resolve_docker_tag(version_constraint)
        full_image_name = f"{image_name}:{docker_tag}"

        # Pull the Docker image
        self._pull_docker_image(full_image_name, progress_callback)

        if progress_callback:
            progress_callback(f"Completed pull: {image_name}", 100.0, "completed")

        return InstallationResult(
            dependency_name=image_name,
            status=InstallationStatus.COMPLETED,
            installed_version=docker_tag,
            artifacts=[full_image_name],
            metadata={
                "message": f"Successfully installed Docker image: {full_image_name}",
            },
        )

    except Exception as e:
        error_msg = f"Failed to install Docker image {image_name}: {str(e)}"
        logger.error(error_msg)
        if progress_callback:
            progress_callback(f"Failed: {image_name}", 0.0, "error")
        raise InstallationError(
            error_msg,
            dependency_name=image_name,
            error_code="DOCKER_INSTALL_ERROR",
            cause=e,
        )
uninstall(dependency, context, progress_callback=None)

Uninstall a Docker image dependency.

Parameters:

Name Type Description Default
dependency Dict[str, Any]

The dependency specification.

required
context InstallationContext

Installation context and configuration.

required
progress_callback Optional[Callable[[str, float, str], None]]

Progress reporting callback.

None

Returns:

Name Type Description
InstallationResult InstallationResult

Result of the uninstallation operation.

Raises:

Type Description
InstallationError

If uninstallation fails.

Source code in hatch/installers/docker_installer.py
def uninstall(
    self,
    dependency: Dict[str, Any],
    context: InstallationContext,
    progress_callback: Optional[Callable[[str, float, str], None]] = None,
) -> InstallationResult:
    """Uninstall a Docker image dependency.

    Args:
        dependency (Dict[str, Any]): The dependency specification.
        context (InstallationContext): Installation context and configuration.
        progress_callback (Optional[Callable[[str, float, str], None]]): Progress reporting callback.

    Returns:
        InstallationResult: Result of the uninstallation operation.

    Raises:
        InstallationError: If uninstallation fails.
    """
    if not self.validate_dependency(dependency):
        raise InstallationError(
            f"Invalid Docker dependency specification: {dependency}"
        )

    image_name = dependency["name"]
    version_constraint = dependency["version_constraint"]

    if progress_callback:
        progress_callback(
            f"Starting Docker image removal: {image_name}", 0.0, "starting"
        )

    # Handle simulation mode
    if context.simulation_mode:
        logger.info(
            f"[SIMULATION] Would remove Docker image: {image_name}:{version_constraint}"
        )
        if progress_callback:
            progress_callback(
                f"Simulated removal: {image_name}", 100.0, "completed"
            )
        return InstallationResult(
            dependency_name=image_name,
            status=InstallationStatus.COMPLETED,
            installed_version=version_constraint,
            artifacts=[],
            metadata={
                "message": f"Simulated removal of Docker image: {image_name}:{version_constraint}",
            },
        )

    try:
        # Resolve version constraint to Docker tag
        docker_tag = self._resolve_docker_tag(version_constraint)
        full_image_name = f"{image_name}:{docker_tag}"

        # Remove the Docker image
        self._remove_docker_image(full_image_name, context, progress_callback)

        if progress_callback:
            progress_callback(
                f"Completed removal: {image_name}", 100.0, "completed"
            )

        return InstallationResult(
            dependency_name=image_name,
            status=InstallationStatus.COMPLETED,
            installed_version=docker_tag,
            artifacts=[],
            metadata={
                "message": f"Successfully removed Docker image: {full_image_name}",
            },
        )

    except Exception as e:
        error_msg = f"Failed to remove Docker image {image_name}: {str(e)}"
        logger.error(error_msg)
        if progress_callback:
            progress_callback(f"Failed removal: {image_name}", 0.0, "error")
        raise InstallationError(
            error_msg,
            dependency_name=image_name,
            error_code="DOCKER_UNINSTALL_ERROR",
            cause=e,
        )
validate_dependency(dependency)

Validate a Docker dependency specification.

Parameters:

Name Type Description Default
dependency Dict[str, Any]

The dependency specification to validate.

required

Returns:

Name Type Description
bool bool

True if the dependency is valid, False otherwise.

Source code in hatch/installers/docker_installer.py
def validate_dependency(self, dependency: Dict[str, Any]) -> bool:
    """Validate a Docker dependency specification.

    Args:
        dependency (Dict[str, Any]): The dependency specification to validate.

    Returns:
        bool: True if the dependency is valid, False otherwise.
    """
    required_fields = ["name", "version_constraint"]

    # Check required fields
    if not all(field in dependency for field in required_fields):
        logger.error(
            f"Docker dependency missing required fields. Required: {required_fields}"
        )
        return False

    # Validate type
    if dependency.get("type") != "docker":
        logger.error(
            f"Invalid dependency type: {dependency.get('type')}, expected 'docker'"
        )
        return False

    # Validate registry if specified
    registry = dependency.get("registry", "unknown")
    if registry not in self.supported_schemes:
        logger.error(
            f"Unsupported registry: {registry}, supported: {self.supported_schemes}"
        )
        return False

    # Validate version constraint format
    version_constraint = dependency.get("version_constraint", "")
    if not self._validate_version_constraint(version_constraint):
        logger.error(f"Invalid version constraint format: {version_constraint}")
        return False

    return True