Skip to content

Dependency Installation Orchestrator

hatch.installers.dependency_installation_orchestrator

Dependency installation orchestrator for coordinating package installation.

This module provides centralized orchestration for all dependency installation across different dependency types, with centralized user consent management and delegation to specific installers.

Classes

DependencyInstallationError

Bases: Exception

Exception raised for dependency installation-related errors.

Source code in hatch/installers/dependency_installation_orchestrator.py
class DependencyInstallationError(Exception):
    """Exception raised for dependency installation-related errors."""

    pass

DependencyInstallerOrchestrator

Orchestrates dependency installation across all supported dependency types.

This class coordinates the installation of dependencies by: 1. Resolving all dependencies for a given package using the validator 2. Aggregating installation plans across all dependency types 3. Managing centralized user consent 4. Delegating to appropriate installers via the registry 5. Handling installation order and error recovery

The orchestrator strictly uses PackageService for all metadata access to ensure compatibility across different package schema versions.

Source code in hatch/installers/dependency_installation_orchestrator.py
 42
 43
 44
 45
 46
 47
 48
 49
 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
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
class DependencyInstallerOrchestrator:
    """Orchestrates dependency installation across all supported dependency types.

    This class coordinates the installation of dependencies by:
    1. Resolving all dependencies for a given package using the validator
    2. Aggregating installation plans across all dependency types
    3. Managing centralized user consent
    4. Delegating to appropriate installers via the registry
    5. Handling installation order and error recovery

    The orchestrator strictly uses PackageService for all metadata access to ensure
    compatibility across different package schema versions.
    """

    def __init__(
        self,
        package_loader: HatchPackageLoader,
        registry_service: RegistryService,
        registry_data: Dict[str, Any],
    ):
        """Initialize the dependency installation orchestrator.

        Args:
            package_loader (HatchPackageLoader): Package loader for file operations.
            registry_service (RegistryService): Service for registry operations.
            registry_data (Dict[str, Any]): Registry data for dependency resolution.
        """
        self.logger = logging.getLogger("hatch.dependency_orchestrator")
        self.package_loader = package_loader
        self.registry_service = registry_service
        self.registry_data = registry_data

        # Python executable configuration for context
        self._python_env_vars = Optional[
            Dict[str, str]
        ]  # Environment variables for Python execution

        # These will be set during package resolution
        self.package_service: Optional[PackageService] = None
        self.dependency_graph_builder: Optional[HatchDependencyGraphBuilder] = None
        self._resolved_package_path: Optional[Path] = None
        self._resolved_package_type: Optional[str] = None
        self._resolved_package_location: Optional[str] = None

    def set_python_env_vars(self, python_env_vars: Dict[str, str]) -> None:
        """Set the environment variables for the Python executable.

        Args:
            python_env_vars (Dict[str, str]): Environment variables to set for Python execution.
        """
        self._python_env_vars = python_env_vars

    def get_python_env_vars(self) -> Optional[Dict[str, str]]:
        """Get the configured environment variables for the Python executable.

        Returns:
            Dict[str, str]: Environment variables for Python execution, None if not configured.
        """
        return self._python_env_vars

    def install_single_dep(
        self, dep: Dict[str, Any], context: InstallationContext
    ) -> Dict[str, Any]:
        """Install a single dependency into the specified environment context.

        This method installs a single dependency using the appropriate installer from the registry.
        It extracts the core installation logic from _execute_install_plan for reuse in other contexts.
        This method operates with auto_approve=True and does not require user consent.

        Args:
            dep (Dict[str, Any]): Dependency dictionary following the schema for the dependency type.
                                 For Python dependencies, should include: name, version_constraint, package_manager.
                                 Example: {"name": "numpy", "version_constraint": "*", "package_manager": "pip", "type": "python"}
            context (InstallationContext): Installation context with environment path and configuration.

        Returns:
            Dict[str, Any]: Installed package information containing:
                - name: Package name
                - version: Installed version
                - type: Dependency type
                - source: Package source URI

        Raises:
            DependencyInstallationError: If installation fails or dependency type is not supported.
        """
        # Ensure dependency has type information
        dep_type = dep.get("type")
        if not dep_type:
            raise DependencyInstallationError(f"Dependency missing 'type' field: {dep}")

        # Check if installer is registered for this dependency type
        if not installer_registry.is_registered(dep_type):
            raise DependencyInstallationError(
                f"No installer registered for dependency type: {dep_type}"
            )

        installer = installer_registry.get_installer(dep_type)

        try:
            self.logger.info(f"Installing {dep_type} dependency: {dep['name']}")
            self.logger.debug(f"Dependency details: {dep}")
            result = installer.install(dep, context)
            if result.status == InstallationStatus.COMPLETED:
                installed_package = {
                    "name": dep["name"],
                    "version": dep.get("resolved_version", dep.get("version")),
                    "type": dep_type,
                    "source": dep.get("uri", "unknown"),
                }
                self.logger.info(
                    f"Successfully installed {dep_type} dependency: {dep['name']}"
                )
                return installed_package
            else:
                raise DependencyInstallationError(
                    f"Failed to install {dep['name']}: {result.error_message}"
                )

        except InstallationError as e:
            self.logger.error(
                f"Installation error for {dep_type} dependency {dep['name']}: {e.error_code}\n{e.message}"
            )
            raise DependencyInstallationError(
                f"Installation error for {dep['name']}: {e}"
            ) from e

        except Exception as e:
            self.logger.error(
                f"Error installing {dep_type} dependency {dep['name']}: {e}"
            )
            raise DependencyInstallationError(
                f"Error installing {dep['name']}: {e}"
            ) from e

    def install_dependencies(
        self,
        package_path_or_name: str,
        env_path: Path,
        env_name: str,
        existing_packages: Dict[str, str],
        version_constraint: Optional[str] = None,
        force_download: bool = False,
        auto_approve: bool = False,
    ) -> Tuple[bool, List[Dict[str, Any]]]:
        """Install all dependencies for a package with centralized consent management.

        This method orchestrates the complete dependency installation process by
        leveraging existing validator components and the installer registry. It handles
        all dependency types (hatch, python, system, docker) and provides centralized
        user consent management.

        Args:
            package_path_or_name (str): Path to local package or name of remote package.
            env_path (Path): Path to the environment directory.
            env_name (str): Name of the environment.
            existing_packages (Dict[str, str]): Currently installed packages {name: version}.
            version_constraint (str, optional): Version constraint for remote packages. Defaults to None.
            force_download (bool, optional): Force download even if package is cached. Defaults to False.
            auto_approve (bool, optional): Skip user consent prompt for automation. Defaults to False.

        Returns:
            Tuple[bool, List[Dict[str, Any]]]: Success status and list of installed packages.

        Raises:
            DependencyInstallationError: If installation fails at any stage.
        """
        try:
            # Step 1: Resolve package and load metadata using PackageService
            self._resolve_and_load_package(
                package_path_or_name, version_constraint, force_download
            )

            # Step 2: Get all dependencies organized by type
            dependencies_by_type = self._get_all_dependencies()

            # Step 3: Filter for missing dependencies by type and track satisfied ones
            (
                missing_dependencies_by_type,
                satisfied_dependencies_by_type,
            ) = self._filter_missing_dependencies_by_type(
                dependencies_by_type, existing_packages
            )

            # Step 4: Aggregate installation plan
            install_plan = self._aggregate_install_plan(
                missing_dependencies_by_type, satisfied_dependencies_by_type
            )

            # Step 5: Print installation summary for user review
            self._print_installation_summary(install_plan)

            # Step 6: Request user consent
            if not auto_approve:
                if not self._request_user_consent(install_plan):
                    self.logger.info("Installation cancelled by user")
                    return False, []
            else:
                self.logger.warning(
                    "Auto-approval enabled, proceeding with installation without user consent"
                )

            # Step 7: Execute installation plan using installer registry
            installed_packages = self._execute_install_plan(
                install_plan, env_path, env_name
            )

            return True, installed_packages

        except Exception as e:
            self.logger.error(f"Dependency installation failed: {e}")
            raise DependencyInstallationError(f"Installation failed: {e}") from e

    def _resolve_and_load_package(
        self,
        package_path_or_name: str,
        version_constraint: Optional[str] = None,
        force_download: bool = False,
    ) -> None:
        """Resolve package information and load metadata using PackageService.

        Args:
            package_path_or_name (str): Path to local package or name of remote package.
            version_constraint (str, optional): Version constraint for remote packages.
            force_download (bool, optional): Force download even if package is cached.

        Raises:
            DependencyInstallationError: If package cannot be resolved or loaded.
        """
        path = Path(package_path_or_name)

        if path.exists() and path.is_dir():
            # Local package
            metadata_path = path / "hatch_metadata.json"
            if not metadata_path.exists():
                raise DependencyInstallationError(
                    f"Local package missing hatch_metadata.json: {path}"
                )

            with open(metadata_path, "r") as f:
                metadata = json.load(f)

            self._resolved_package_path = path
            self._resolved_package_type = "local"
            self._resolved_package_location = str(path.resolve())

        else:
            # Remote package
            if not self.registry_service.package_exists(package_path_or_name):
                raise DependencyInstallationError(
                    f"Package {package_path_or_name} does not exist in registry"
                )

            try:
                compatible_version = self.registry_service.find_compatible_version(
                    package_path_or_name, version_constraint
                )
            except VersionConstraintError as e:
                raise DependencyInstallationError(
                    f"Version constraint error: {e}"
                ) from e

            location = self.registry_service.get_package_uri(
                package_path_or_name, compatible_version
            )
            downloaded_path = self.package_loader.download_package(
                location,
                package_path_or_name,
                compatible_version,
                force_download=force_download,
            )

            metadata_path = downloaded_path / "hatch_metadata.json"
            with open(metadata_path, "r") as f:
                metadata = json.load(f)

            self._resolved_package_path = downloaded_path
            self._resolved_package_type = "remote"
            self._resolved_package_location = location

        # Load metadata using PackageService for schema-aware access
        self.package_service = PackageService(metadata)
        if not self.package_service.is_loaded():
            raise DependencyInstallationError("Failed to load package metadata")

    def _get_install_ready_hatch_dependencies(self) -> List[Dict[str, Any]]:
        """Get install-ready Hatch dependencies using validator components.

        This method only processes Hatch package dependencies, not python, system, or docker.

        Returns:
            List[Dict[str, Any]]: List of install-ready Hatch dependencies.

        Raises:
            DependencyInstallationError: If dependency resolution fails.
        """
        try:
            # Use validator components for Hatch dependency resolution
            self.dependency_graph_builder = HatchDependencyGraphBuilder(
                self.package_service, self.registry_service
            )

            context = ValidationContext(
                package_dir=self._resolved_package_path,
                registry_data=self.registry_data,
                allow_local_dependencies=True,
            )

            # This only returns Hatch dependencies in install order
            hatch_dependencies = (
                self.dependency_graph_builder.get_install_ready_dependencies(context)
            )
            return hatch_dependencies

        except Exception as e:
            raise DependencyInstallationError(
                f"Error building Hatch dependency graph: {e}"
            ) from e

    def _get_all_dependencies(self) -> Dict[str, List[Dict[str, Any]]]:
        """Get all dependencies from package metadata organized by type.

        Returns:
            Dict[str, List[Dict[str, Any]]]: Dependencies organized by type (hatch, python, system, docker).

        Raises:
            DependencyInstallationError: If dependency extraction fails.
        """
        try:
            # Get all dependencies using PackageService
            all_deps = self.package_service.get_dependencies()

            dependencies_by_type = {
                "system": [],
                "python": [],
                "hatch": [],
                "docker": [],
            }

            # Get Hatch dependencies using validator (properly ordered)
            dependencies_by_type["hatch"] = self._get_install_ready_hatch_dependencies()
            # Adding the type information to each Hatch dependency
            for dep in dependencies_by_type["hatch"]:
                dep["type"] = "hatch"

            # Get other dependency types directly from PackageService
            for dep_type in ["python", "system", "docker"]:
                raw_deps = all_deps.get(dep_type, [])
                for dep in raw_deps:
                    # Add type information and ensure required fields
                    dep_with_type = dep.copy()
                    dep_with_type["type"] = dep_type
                    if not installer_registry.can_install(dep_type, dep_with_type):
                        raise DependencyInstallationError(
                            f"No registered installer can handle dependency with type '{dep_type}': {dep_with_type}"
                        )

                    dependencies_by_type[dep_type].append(dep_with_type)

            return dependencies_by_type

        except Exception as e:
            raise DependencyInstallationError(
                f"Error extracting dependencies: {e}"
            ) from e

    def _filter_missing_dependencies_by_type(
        self,
        dependencies_by_type: Dict[str, List[Dict[str, Any]]],
        existing_packages: Dict[str, str],
    ) -> Tuple[Dict[str, List[Dict[str, Any]]], Dict[str, List[Dict[str, Any]]]]:
        """Filter dependencies by type to find those not already installed and track satisfied ones.

        For non-Hatch dependencies, we always include them in missing list as the third-party
        package manager will handle version checking and installation.

        Args:
            dependencies_by_type (Dict[str, List[Dict[str, Any]]]): All dependencies organized by type.
            existing_packages (Dict[str, str]): Currently installed packages {name: version}.

        Returns:
            Tuple[Dict[str, List[Dict[str, Any]]], Dict[str, List[Dict[str, Any]]]]:
                (missing_dependencies_by_type, satisfied_dependencies_by_type)
        """
        missing_deps_by_type = {}
        satisfied_deps_by_type = {}

        for dep_type, dependencies in dependencies_by_type.items():
            missing_deps = []
            satisfied_deps = []

            for dep in dependencies:
                dep_name = dep.get("name")

                # For non-Hatch dependencies, always consider them as needing installation
                # as the third-party package manager will handle version compatibility
                if dep_type != "hatch":
                    missing_deps.append(dep)
                    continue

                # Hatch dependency processing
                if dep_name not in existing_packages:
                    missing_deps.append(dep)
                    continue

                # Check version constraints for Hatch dependencies
                constraint = dep.get("version_constraint")
                installed_version = existing_packages[dep_name]

                if constraint:
                    (
                        is_compatible,
                        compatibility_msg,
                    ) = VersionConstraintValidator.is_version_compatible(
                        installed_version, constraint
                    )
                    if not is_compatible:
                        missing_deps.append(dep)
                    else:
                        # Add satisfied dependency with installation info
                        satisfied_dep = dep.copy()
                        satisfied_dep["installed_version"] = installed_version
                        satisfied_dep["compatibility_status"] = compatibility_msg
                        satisfied_deps.append(satisfied_dep)
                else:
                    # No constraint specified, any installed version satisfies
                    satisfied_dep = dep.copy()
                    satisfied_dep["installed_version"] = installed_version
                    satisfied_dep[
                        "compatibility_status"
                    ] = "No version constraint specified"
                    satisfied_deps.append(satisfied_dep)

            missing_deps_by_type[dep_type] = missing_deps
            satisfied_deps_by_type[dep_type] = satisfied_deps

        return missing_deps_by_type, satisfied_deps_by_type

    def _aggregate_install_plan(
        self,
        missing_dependencies_by_type: Dict[str, List[Dict[str, Any]]],
        satisfied_dependencies_by_type: Dict[str, List[Dict[str, Any]]],
    ) -> Dict[str, Any]:
        """Aggregate installation plan across all dependency types.

        Args:
            missing_dependencies_by_type (Dict[str, List[Dict[str, Any]]]): Missing dependencies by type.
            satisfied_dependencies_by_type (Dict[str, List[Dict[str, Any]]]): Already satisfied dependencies by type.

        Returns:
            Dict[str, Any]: Complete installation plan with dependencies grouped by type.
        """
        # Use PackageService for all metadata access
        plan = {
            "main_package": {
                "name": self.package_service.get_field("name"),
                "version": self.package_service.get_field("version"),
                "type": self._resolved_package_type,
                "location": self._resolved_package_location,
            },
            "dependencies_to_install": missing_dependencies_by_type,
            "dependencies_satisfied": satisfied_dependencies_by_type,
            "total_to_install": 1
            + sum(len(deps) for deps in missing_dependencies_by_type.values()),
            "total_satisfied": sum(
                len(deps) for deps in satisfied_dependencies_by_type.values()
            ),
        }

        return plan

    def _print_installation_summary(self, install_plan: Dict[str, Any]) -> None:
        """Print a summary of the installation plan for user review.

        Args:
            install_plan (Dict[str, Any]): Complete installation plan.
        """
        print("\n" + "=" * 60)
        print("DEPENDENCY INSTALLATION PLAN")
        print("=" * 60)

        main_pkg = install_plan["main_package"]
        print(f"Main Package: {main_pkg['name']} v{main_pkg['version']}")
        print(f"Package Type: {main_pkg['type']}")

        # Show satisfied dependencies first
        total_satisfied = install_plan.get("total_satisfied", 0)
        if total_satisfied > 0:
            print(f"\nDependencies already satisfied: {total_satisfied}")

            for dep_type, deps in install_plan.get(
                "dependencies_satisfied", {}
            ).items():
                if deps:
                    print(f"\n{dep_type.title()} Dependencies (Satisfied):")
                    for dep in deps:
                        installed_version = dep.get("installed_version", "unknown")
                        constraint = dep.get("version_constraint", "any")
                        compatibility = dep.get("compatibility_status", "")
                        print(
                            f"  ✓ {dep['name']} {constraint} (installed: {installed_version})"
                        )
                        if (
                            compatibility
                            and compatibility != "No version constraint specified"
                        ):
                            print(f"    {compatibility}")

        # Show dependencies to install
        total_to_install = sum(
            len(deps)
            for deps in install_plan.get("dependencies_to_install", {}).values()
        )
        if total_to_install > 0:
            print(f"\nDependencies to install: {total_to_install}")

            for dep_type, deps in install_plan.get(
                "dependencies_to_install", {}
            ).items():
                if deps:
                    print(f"\n{dep_type.title()} Dependencies (To Install):")
                    for dep in deps:
                        constraint = dep.get("version_constraint", "any")
                        print(f"  → {dep['name']} {constraint}")
        else:
            print("\nNo additional dependencies to install.")

        print(f"\nTotal packages to install: {install_plan.get('total_to_install', 1)}")
        if total_satisfied > 0:
            print(f"Total dependencies already satisfied: {total_satisfied}")
        print("=" * 60)

    def _request_user_consent(self, install_plan: Dict[str, Any]) -> bool:
        """Request user consent for the installation plan with non-TTY support.

        Args:
            install_plan (Dict[str, Any]): Complete installation plan.

        Returns:
            bool: True if user approves, False otherwise.
        """
        # Check for non-interactive mode indicators
        if not sys.stdin.isatty() or os.getenv("HATCH_AUTO_APPROVE", "").lower() in (
            "1",
            "true",
            "yes",
        ):
            self.logger.info("Auto-approving installation (non-interactive mode)")
            return True

        # Interactive mode - request user input
        try:
            while True:
                response = input("\nProceed with installation? [y/N]: ").strip().lower()
                if response in ["y", "yes"]:
                    return True
                elif response in ["n", "no", ""]:
                    return False
                else:
                    print("Please enter 'y' for yes or 'n' for no.")
        except (EOFError, KeyboardInterrupt):
            self.logger.info("Installation cancelled by user")
            return False

    def _execute_install_plan(
        self, install_plan: Dict[str, Any], env_path: Path, env_name: str
    ) -> List[Dict[str, Any]]:
        """Execute the installation plan using the installer registry.

        Args:
            install_plan (Dict[str, Any]): Installation plan to execute.
            env_path (Path): Environment path for installation.
            env_name (str): Environment name.

        Returns:
            List[Dict[str, Any]]: List of successfully installed packages.

        Raises:
            DependencyInstallationError: If installation fails.
        """
        installed_packages = []

        # Create comprehensive installation context
        context = InstallationContext(
            environment_path=env_path,
            environment_name=env_name,
            temp_dir=env_path / ".tmp",
            cache_dir=(
                self.package_loader.cache_dir
                if hasattr(self.package_loader, "cache_dir")
                else None
            ),
            parallel_enabled=False,  # Future enhancement
            force_reinstall=False,  # Future enhancement
            simulation_mode=False,  # Future enhancement
            extra_config={
                "package_loader": self.package_loader,
                "registry_service": self.registry_service,
                "registry_data": self.registry_data,
                "main_package_path": self._resolved_package_path,
                "main_package_type": self._resolved_package_type,
            },
        )

        # Configure Python environment variables if available
        if self._python_env_vars:
            context.set_config("python_env_vars", self._python_env_vars)

        try:
            # Install dependencies by type using appropriate installers
            for dep_type, dependencies in install_plan[
                "dependencies_to_install"
            ].items():
                if not dependencies:
                    continue

                if not installer_registry.is_registered(dep_type):
                    self.logger.warning(
                        f"No installer registered for dependency type: {dep_type}"
                    )
                    continue

                # Verify installer exists (validation only)
                installer_registry.get_installer(dep_type)

                for dep in dependencies:
                    # Use the extracted install_single_dep method
                    installed_package = self.install_single_dep(dep, context)
                    installed_packages.append(installed_package)

            # Install main package last
            main_pkg_info = self._install_main_package(context)
            installed_packages.append(main_pkg_info)

            return installed_packages

        except Exception as e:
            self.logger.error(f"Installation execution failed: {e}")
            raise DependencyInstallationError(
                f"Installation execution failed: {e}"
            ) from e

    def _install_main_package(self, context: InstallationContext) -> Dict[str, Any]:
        """Install the main package using package_loader directly.

        The main package installation bypasses the installer registry and uses
        the package_loader directly since it's not a dependency but the primary package.

        Args:
            context (InstallationContext): Installation context.

        Returns:
            Dict[str, Any]: Installed package information.

        Raises:
            DependencyInstallationError: If main package installation fails.
        """
        try:
            # Get package information using PackageService
            package_name = self.package_service.get_field("name")
            package_version = self.package_service.get_field("version")

            # Install using package_loader directly
            if self._resolved_package_type == "local":
                # For local packages, install from resolved path
                installed_path = self.package_loader.install_local_package(
                    source_path=self._resolved_package_path,
                    target_dir=context.environment_path,
                    package_name=package_name,
                )
            else:
                # For remote packages, install from downloaded path
                installed_path = self.package_loader.install_local_package(
                    source_path=self._resolved_package_path,  # Downloaded path
                    target_dir=context.environment_path,
                    package_name=package_name,
                )

            self.logger.info(
                f"Successfully installed main package {package_name} to {installed_path}"
            )

            return {
                "name": package_name,
                "version": package_version,
                "type": "hatch",
                "source": self._resolved_package_location,
            }

        except Exception as e:
            raise DependencyInstallationError(
                f"Failed to install main package: {e}"
            ) from e
Functions
__init__(package_loader, registry_service, registry_data)

Initialize the dependency installation orchestrator.

Parameters:

Name Type Description Default
package_loader HatchPackageLoader

Package loader for file operations.

required
registry_service RegistryService

Service for registry operations.

required
registry_data Dict[str, Any]

Registry data for dependency resolution.

required
Source code in hatch/installers/dependency_installation_orchestrator.py
def __init__(
    self,
    package_loader: HatchPackageLoader,
    registry_service: RegistryService,
    registry_data: Dict[str, Any],
):
    """Initialize the dependency installation orchestrator.

    Args:
        package_loader (HatchPackageLoader): Package loader for file operations.
        registry_service (RegistryService): Service for registry operations.
        registry_data (Dict[str, Any]): Registry data for dependency resolution.
    """
    self.logger = logging.getLogger("hatch.dependency_orchestrator")
    self.package_loader = package_loader
    self.registry_service = registry_service
    self.registry_data = registry_data

    # Python executable configuration for context
    self._python_env_vars = Optional[
        Dict[str, str]
    ]  # Environment variables for Python execution

    # These will be set during package resolution
    self.package_service: Optional[PackageService] = None
    self.dependency_graph_builder: Optional[HatchDependencyGraphBuilder] = None
    self._resolved_package_path: Optional[Path] = None
    self._resolved_package_type: Optional[str] = None
    self._resolved_package_location: Optional[str] = None
get_python_env_vars()

Get the configured environment variables for the Python executable.

Returns:

Type Description
Optional[Dict[str, str]]

Dict[str, str]: Environment variables for Python execution, None if not configured.

Source code in hatch/installers/dependency_installation_orchestrator.py
def get_python_env_vars(self) -> Optional[Dict[str, str]]:
    """Get the configured environment variables for the Python executable.

    Returns:
        Dict[str, str]: Environment variables for Python execution, None if not configured.
    """
    return self._python_env_vars
install_dependencies(package_path_or_name, env_path, env_name, existing_packages, version_constraint=None, force_download=False, auto_approve=False)

Install all dependencies for a package with centralized consent management.

This method orchestrates the complete dependency installation process by leveraging existing validator components and the installer registry. It handles all dependency types (hatch, python, system, docker) and provides centralized user consent management.

Parameters:

Name Type Description Default
package_path_or_name str

Path to local package or name of remote package.

required
env_path Path

Path to the environment directory.

required
env_name str

Name of the environment.

required
existing_packages Dict[str, str]

Currently installed packages {name: version}.

required
version_constraint str

Version constraint for remote packages. Defaults to None.

None
force_download bool

Force download even if package is cached. Defaults to False.

False
auto_approve bool

Skip user consent prompt for automation. Defaults to False.

False

Returns:

Type Description
Tuple[bool, List[Dict[str, Any]]]

Tuple[bool, List[Dict[str, Any]]]: Success status and list of installed packages.

Raises:

Type Description
DependencyInstallationError

If installation fails at any stage.

Source code in hatch/installers/dependency_installation_orchestrator.py
def install_dependencies(
    self,
    package_path_or_name: str,
    env_path: Path,
    env_name: str,
    existing_packages: Dict[str, str],
    version_constraint: Optional[str] = None,
    force_download: bool = False,
    auto_approve: bool = False,
) -> Tuple[bool, List[Dict[str, Any]]]:
    """Install all dependencies for a package with centralized consent management.

    This method orchestrates the complete dependency installation process by
    leveraging existing validator components and the installer registry. It handles
    all dependency types (hatch, python, system, docker) and provides centralized
    user consent management.

    Args:
        package_path_or_name (str): Path to local package or name of remote package.
        env_path (Path): Path to the environment directory.
        env_name (str): Name of the environment.
        existing_packages (Dict[str, str]): Currently installed packages {name: version}.
        version_constraint (str, optional): Version constraint for remote packages. Defaults to None.
        force_download (bool, optional): Force download even if package is cached. Defaults to False.
        auto_approve (bool, optional): Skip user consent prompt for automation. Defaults to False.

    Returns:
        Tuple[bool, List[Dict[str, Any]]]: Success status and list of installed packages.

    Raises:
        DependencyInstallationError: If installation fails at any stage.
    """
    try:
        # Step 1: Resolve package and load metadata using PackageService
        self._resolve_and_load_package(
            package_path_or_name, version_constraint, force_download
        )

        # Step 2: Get all dependencies organized by type
        dependencies_by_type = self._get_all_dependencies()

        # Step 3: Filter for missing dependencies by type and track satisfied ones
        (
            missing_dependencies_by_type,
            satisfied_dependencies_by_type,
        ) = self._filter_missing_dependencies_by_type(
            dependencies_by_type, existing_packages
        )

        # Step 4: Aggregate installation plan
        install_plan = self._aggregate_install_plan(
            missing_dependencies_by_type, satisfied_dependencies_by_type
        )

        # Step 5: Print installation summary for user review
        self._print_installation_summary(install_plan)

        # Step 6: Request user consent
        if not auto_approve:
            if not self._request_user_consent(install_plan):
                self.logger.info("Installation cancelled by user")
                return False, []
        else:
            self.logger.warning(
                "Auto-approval enabled, proceeding with installation without user consent"
            )

        # Step 7: Execute installation plan using installer registry
        installed_packages = self._execute_install_plan(
            install_plan, env_path, env_name
        )

        return True, installed_packages

    except Exception as e:
        self.logger.error(f"Dependency installation failed: {e}")
        raise DependencyInstallationError(f"Installation failed: {e}") from e
install_single_dep(dep, context)

Install a single dependency into the specified environment context.

This method installs a single dependency using the appropriate installer from the registry. It extracts the core installation logic from _execute_install_plan for reuse in other contexts. This method operates with auto_approve=True and does not require user consent.

Parameters:

Name Type Description Default
dep Dict[str, Any]

Dependency dictionary following the schema for the dependency type. For Python dependencies, should include: name, version_constraint, package_manager. Example: {"name": "numpy", "version_constraint": "*", "package_manager": "pip", "type": "python"}

required
context InstallationContext

Installation context with environment path and configuration.

required

Returns:

Type Description
Dict[str, Any]

Dict[str, Any]: Installed package information containing: - name: Package name - version: Installed version - type: Dependency type - source: Package source URI

Raises:

Type Description
DependencyInstallationError

If installation fails or dependency type is not supported.

Source code in hatch/installers/dependency_installation_orchestrator.py
def install_single_dep(
    self, dep: Dict[str, Any], context: InstallationContext
) -> Dict[str, Any]:
    """Install a single dependency into the specified environment context.

    This method installs a single dependency using the appropriate installer from the registry.
    It extracts the core installation logic from _execute_install_plan for reuse in other contexts.
    This method operates with auto_approve=True and does not require user consent.

    Args:
        dep (Dict[str, Any]): Dependency dictionary following the schema for the dependency type.
                             For Python dependencies, should include: name, version_constraint, package_manager.
                             Example: {"name": "numpy", "version_constraint": "*", "package_manager": "pip", "type": "python"}
        context (InstallationContext): Installation context with environment path and configuration.

    Returns:
        Dict[str, Any]: Installed package information containing:
            - name: Package name
            - version: Installed version
            - type: Dependency type
            - source: Package source URI

    Raises:
        DependencyInstallationError: If installation fails or dependency type is not supported.
    """
    # Ensure dependency has type information
    dep_type = dep.get("type")
    if not dep_type:
        raise DependencyInstallationError(f"Dependency missing 'type' field: {dep}")

    # Check if installer is registered for this dependency type
    if not installer_registry.is_registered(dep_type):
        raise DependencyInstallationError(
            f"No installer registered for dependency type: {dep_type}"
        )

    installer = installer_registry.get_installer(dep_type)

    try:
        self.logger.info(f"Installing {dep_type} dependency: {dep['name']}")
        self.logger.debug(f"Dependency details: {dep}")
        result = installer.install(dep, context)
        if result.status == InstallationStatus.COMPLETED:
            installed_package = {
                "name": dep["name"],
                "version": dep.get("resolved_version", dep.get("version")),
                "type": dep_type,
                "source": dep.get("uri", "unknown"),
            }
            self.logger.info(
                f"Successfully installed {dep_type} dependency: {dep['name']}"
            )
            return installed_package
        else:
            raise DependencyInstallationError(
                f"Failed to install {dep['name']}: {result.error_message}"
            )

    except InstallationError as e:
        self.logger.error(
            f"Installation error for {dep_type} dependency {dep['name']}: {e.error_code}\n{e.message}"
        )
        raise DependencyInstallationError(
            f"Installation error for {dep['name']}: {e}"
        ) from e

    except Exception as e:
        self.logger.error(
            f"Error installing {dep_type} dependency {dep['name']}: {e}"
        )
        raise DependencyInstallationError(
            f"Error installing {dep['name']}: {e}"
        ) from e
set_python_env_vars(python_env_vars)

Set the environment variables for the Python executable.

Parameters:

Name Type Description Default
python_env_vars Dict[str, str]

Environment variables to set for Python execution.

required
Source code in hatch/installers/dependency_installation_orchestrator.py
def set_python_env_vars(self, python_env_vars: Dict[str, str]) -> None:
    """Set the environment variables for the Python executable.

    Args:
        python_env_vars (Dict[str, str]): Environment variables to set for Python execution.
    """
    self._python_env_vars = python_env_vars