openenv/echo_env
6
1"""Import third-party environments into OpenEnv wrappers."""2 3from __future__ import annotations4 5import shutil6from pathlib import Path7from typing import Annotated8 9import typer10from openenv.cli.importers import DEFAULT_IMPORTERS, ImporterRegistry11 12from .._cli_utils import console13from .init import _generate_uv_lock, _validate_env_name14 15 16def _select_match(17 registry: ImporterRegistry,18 source: Path,19 source_type: str | None,20 env_class: str | None,21):22 try:23 matches = registry.detect(source, source_type=source_type)24 except ValueError as e:25 raise typer.BadParameter(str(e)) from e26 if not matches:27 supported = ", ".join(registry.supported_types)28 raise typer.BadParameter(29 f"No supported environment found in {source}. Supported source types: {supported}."30 )31 32 if env_class:33 matches = [34 match35 for match in matches36 if match[1].class_name == env_class or match[1].qualified_name == env_class37 ]38 if not matches:39 raise typer.BadParameter(f"No detected environment matched {env_class!r}.")40 41 detected_types = {importer.source_type for importer, _ in matches}42 if source_type is None and len(detected_types) > 1:43 raise typer.BadParameter(44 "Multiple source formats were detected. Re-run with --type "45 f"({'/'.join(sorted(detected_types))})."46 )47 48 if len(matches) > 1:49 choices = ", ".join(detected.qualified_name for _, detected in matches)50 raise typer.BadParameter(51 f"Multiple environment entrypoints were detected ({choices}). "52 "Re-run with --env-class."53 )54 55 return matches[0]56 57 58def import_env(59 source: Annotated[60 str,61 typer.Argument(help="Local source repository or directory to import"),62 ],63 name: Annotated[64 str,65 typer.Option("--name", "-n", help="Name for the generated OpenEnv package"),66 ],67 output_dir: Annotated[68 str,69 typer.Option(70 "--output-dir",71 "-o",72 help="Directory where the generated package will be created",73 ),74 ],75 env_class: Annotated[76 str | None,77 typer.Option(78 "--env-class",79 help="Environment class name or module:Class when detection is ambiguous",80 ),81 ] = None,82 source_type: Annotated[83 str | None,84 typer.Option(85 "--type",86 help="Optional source type override, such as 'ors'",87 ),88 ] = None,89) -> None:90 """Deterministically import a third-party environment into OpenEnv."""91 env_name = _validate_env_name(name)92 source_path = Path(source).expanduser().resolve()93 if not source_path.exists() or not source_path.is_dir():94 raise typer.BadParameter(f"Source must be an existing directory: {source_path}")95 96 base_dir = Path(output_dir).expanduser().resolve()97 env_dir = base_dir / env_name98 try:99 env_dir.relative_to(source_path)100 except ValueError:101 pass102 else:103 raise typer.BadParameter("Output directory must not be inside the source tree")104 105 if env_dir.exists():106 if env_dir.is_file():107 raise typer.BadParameter(f"Path '{env_dir}' exists and is a file")108 if any(env_dir.iterdir()):109 raise typer.BadParameter(110 f"Directory '{env_dir}' already exists and is not empty."111 )112 113 registry = ImporterRegistry(DEFAULT_IMPORTERS)114 importer, detected = _select_match(115 registry,116 source_path,117 source_type=source_type,118 env_class=env_class,119 )120 121 try:122 env_dir.mkdir(parents=True, exist_ok=True)123 console.print(124 "[bold cyan]Importing environment[/bold cyan] "125 f"{detected.qualified_name} as '{env_name}' ({importer.source_type})"126 )127 importer.generate(128 source=source_path,129 destination=env_dir,130 env_name=env_name,131 detected=detected,132 )133 134 console.print("[bold green]OK[/bold green] Generated OpenEnv wrapper")135 if _generate_uv_lock(env_dir):136 console.print("[green]OK[/green] Generated uv.lock")137 else:138 console.print("[yellow]Warning:[/yellow] Could not generate uv.lock")139 140 console.print(f"[bold green]Environment created at: {env_dir}[/bold green]")141 except Exception as e:142 if env_dir.exists() and env_dir.is_dir():143 shutil.rmtree(env_dir, ignore_errors=True)144 console.print(f"[bold red]Error:[/bold red] {e}")145 raise typer.Exit(1) from e146 