lumen-app 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. lumen_app/__init__.py +7 -0
  2. lumen_app/core/__init__.py +0 -0
  3. lumen_app/core/config.py +661 -0
  4. lumen_app/core/installer.py +274 -0
  5. lumen_app/core/loader.py +45 -0
  6. lumen_app/core/router.py +87 -0
  7. lumen_app/core/server.py +389 -0
  8. lumen_app/core/service.py +49 -0
  9. lumen_app/core/tests/__init__.py +1 -0
  10. lumen_app/core/tests/test_core_integration.py +561 -0
  11. lumen_app/core/tests/test_env_checker.py +487 -0
  12. lumen_app/proto/README.md +12 -0
  13. lumen_app/proto/ml_service.proto +88 -0
  14. lumen_app/proto/ml_service_pb2.py +66 -0
  15. lumen_app/proto/ml_service_pb2.pyi +136 -0
  16. lumen_app/proto/ml_service_pb2_grpc.py +251 -0
  17. lumen_app/server.py +362 -0
  18. lumen_app/utils/env_checker.py +752 -0
  19. lumen_app/utils/installation/__init__.py +25 -0
  20. lumen_app/utils/installation/env_manager.py +152 -0
  21. lumen_app/utils/installation/micromamba_installer.py +459 -0
  22. lumen_app/utils/installation/package_installer.py +149 -0
  23. lumen_app/utils/installation/verifier.py +95 -0
  24. lumen_app/utils/logger.py +181 -0
  25. lumen_app/utils/mamba/cuda.yaml +12 -0
  26. lumen_app/utils/mamba/default.yaml +6 -0
  27. lumen_app/utils/mamba/openvino.yaml +7 -0
  28. lumen_app/utils/mamba/tensorrt.yaml +13 -0
  29. lumen_app/utils/package_resolver.py +309 -0
  30. lumen_app/utils/preset_registry.py +219 -0
  31. lumen_app/web/__init__.py +3 -0
  32. lumen_app/web/api/__init__.py +1 -0
  33. lumen_app/web/api/config.py +229 -0
  34. lumen_app/web/api/hardware.py +201 -0
  35. lumen_app/web/api/install.py +608 -0
  36. lumen_app/web/api/server.py +253 -0
  37. lumen_app/web/core/__init__.py +1 -0
  38. lumen_app/web/core/server_manager.py +348 -0
  39. lumen_app/web/core/state.py +264 -0
  40. lumen_app/web/main.py +145 -0
  41. lumen_app/web/models/__init__.py +28 -0
  42. lumen_app/web/models/config.py +63 -0
  43. lumen_app/web/models/hardware.py +64 -0
  44. lumen_app/web/models/install.py +134 -0
  45. lumen_app/web/models/server.py +95 -0
  46. lumen_app/web/static/assets/index-CGuhGHC9.css +1 -0
  47. lumen_app/web/static/assets/index-DN6HmxWS.js +56 -0
  48. lumen_app/web/static/index.html +14 -0
  49. lumen_app/web/static/vite.svg +1 -0
  50. lumen_app/web/websockets/__init__.py +1 -0
  51. lumen_app/web/websockets/logs.py +159 -0
  52. lumen_app-0.4.2.dist-info/METADATA +23 -0
  53. lumen_app-0.4.2.dist-info/RECORD +56 -0
  54. lumen_app-0.4.2.dist-info/WHEEL +5 -0
  55. lumen_app-0.4.2.dist-info/entry_points.txt +3 -0
  56. lumen_app-0.4.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,25 @@
1
+ """Installation utilities for Lumen.
2
+
3
+ This module provides classes for managing micromamba installation,
4
+ Python environment creation, package installation, and verification.
5
+ """
6
+
7
+ from .env_manager import PythonEnvManager
8
+ from .micromamba_installer import (
9
+ MicromambaCheckResult,
10
+ MicromambaInstaller,
11
+ MicromambaStatus,
12
+ MirrorSelector,
13
+ )
14
+ from .package_installer import LumenPackageInstaller
15
+ from .verifier import InstallationVerifier
16
+
17
+ __all__ = [
18
+ "MicromambaInstaller",
19
+ "MicromambaCheckResult",
20
+ "MicromambaStatus",
21
+ "MirrorSelector",
22
+ "PythonEnvManager",
23
+ "LumenPackageInstaller",
24
+ "InstallationVerifier",
25
+ ]
@@ -0,0 +1,152 @@
1
+ """Python environment manager for creating and managing micromamba environments."""
2
+
3
+ import logging
4
+ import subprocess
5
+ from pathlib import Path
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class PythonEnvManager:
11
+ """Manages Python environment creation and pip operations."""
12
+
13
+ ENV_NAME = "lumen_env"
14
+
15
+ def __init__(self, cache_dir: Path | str, micromamba_exe: Path | str):
16
+ """Initialize environment manager.
17
+
18
+ Args:
19
+ cache_dir: Cache directory
20
+ micromamba_exe: Path to micromamba executable
21
+ """
22
+ self.cache_dir = Path(cache_dir).expanduser()
23
+ self.micromamba_exe = Path(micromamba_exe)
24
+ self.mamba_configs_dir = Path(__file__).parent.parent / "mamba"
25
+ self.target_name = "micromamba"
26
+
27
+ logger.debug(
28
+ f"[PythonEnvManager] Initialized with cache_dir={self.cache_dir}, "
29
+ f"micromamba_exe={self.micromamba_exe}"
30
+ )
31
+
32
+ def create_env(self, yaml_config: str = "default") -> Path:
33
+ """Create Python environment using micromamba.
34
+
35
+ Args:
36
+ yaml_config: Mamba yaml config identifier (e.g., "cuda", "openvino", "default")
37
+
38
+ Returns:
39
+ Path to created environment
40
+
41
+ Raises:
42
+ Exception: If environment creation fails
43
+ """
44
+ logger.info(
45
+ f"[PythonEnvManager] Creating environment '{self.ENV_NAME}' "
46
+ f"with yaml_config={yaml_config}"
47
+ )
48
+
49
+ env_path = self.get_env_path()
50
+
51
+ # Check if environment already exists
52
+ if env_path.exists():
53
+ logger.info(f"[PythonEnvManager] Environment already exists: {env_path}")
54
+ return env_path
55
+
56
+ # Ensure parent directory exists
57
+ env_path.parent.mkdir(parents=True, exist_ok=True)
58
+
59
+ # Determine which yaml file to use
60
+ yaml_file = self.mamba_configs_dir / f"{yaml_config}.yaml"
61
+
62
+ if not yaml_file.exists():
63
+ raise Exception(f"YAML config file not found: {yaml_file}")
64
+
65
+ # Create environment from yaml config file
66
+ cmd = [
67
+ str(self.micromamba_exe),
68
+ "create",
69
+ "-y",
70
+ "-p", # Use path instead of name
71
+ str(env_path),
72
+ "-f",
73
+ str(yaml_file),
74
+ ]
75
+ logger.debug(
76
+ f"[PythonEnvManager] Creating environment from yaml: {' '.join(cmd)}"
77
+ )
78
+
79
+ try:
80
+ result = subprocess.run(
81
+ cmd,
82
+ capture_output=True,
83
+ text=True,
84
+ timeout=600, # 10 minutes
85
+ )
86
+
87
+ if result.returncode != 0:
88
+ error_msg = result.stderr or result.stdout
89
+ logger.error(
90
+ f"[PythonEnvManager] Environment creation failed: {error_msg}"
91
+ )
92
+ raise Exception(f"Failed to create environment: {error_msg}")
93
+
94
+ logger.info(f"[PythonEnvManager] Environment created: {env_path}")
95
+ return env_path
96
+
97
+ except subprocess.TimeoutExpired:
98
+ logger.error("[PythonEnvManager] Environment creation timed out")
99
+ raise Exception("Environment creation timed out")
100
+
101
+ def get_env_path(self) -> Path:
102
+ """Get the path to the lumen_env environment.
103
+
104
+ Returns:
105
+ Path to environment directory
106
+ """
107
+ env_path = self.cache_dir / self.target_name / "envs" / self.ENV_NAME
108
+ return env_path
109
+
110
+ def run_pip(self, *args: str) -> subprocess.CompletedProcess:
111
+ """Run pip command in the lumen_env environment.
112
+
113
+ Args:
114
+ *args: Pip command arguments (e.g., "install", "package")
115
+
116
+ Returns:
117
+ Completed process result
118
+
119
+ Raises:
120
+ Exception: If command fails
121
+ """
122
+ env_path = self.get_env_path()
123
+ cmd = [
124
+ str(self.micromamba_exe),
125
+ "run",
126
+ "-p", # Use path instead of name
127
+ str(env_path),
128
+ "pip",
129
+ *args,
130
+ ]
131
+
132
+ logger.debug(f"[PythonEnvManager] Running pip: {' '.join(cmd)}")
133
+
134
+ try:
135
+ result = subprocess.run(
136
+ cmd,
137
+ capture_output=True,
138
+ text=True,
139
+ timeout=600, # 10 minutes
140
+ )
141
+ return result
142
+ except subprocess.TimeoutExpired:
143
+ logger.error("[PythonEnvManager] Pip command timed out")
144
+ raise Exception("Pip command timed out")
145
+
146
+ def env_exists(self) -> bool:
147
+ """Check if lumen_env exists.
148
+
149
+ Returns:
150
+ True if environment exists
151
+ """
152
+ return self.get_env_path().exists()
@@ -0,0 +1,459 @@
1
+ """Micromamba installer for managing micromamba installation and checking.
2
+
3
+ This module provides functionality to:
4
+ 1. Check if micromamba is installed and accessible
5
+ 2. Download and install micromamba with region-based mirror support
6
+ 3. Get the path to micromamba executable
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ import platform
12
+ import subprocess
13
+ from dataclasses import dataclass
14
+ from enum import Enum
15
+ from pathlib import Path
16
+
17
+ from lumen_resources.lumen_config import Region
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class MicromambaStatus(Enum):
23
+ """Micromamba installation status."""
24
+
25
+ INSTALLED = "installed"
26
+ NOT_INSTALLED = "not_installed"
27
+ INCOMPATIBLE = "incompatible"
28
+
29
+
30
+ @dataclass
31
+ class MicromambaCheckResult:
32
+ """Result of micromamba availability check.
33
+
34
+ Attributes:
35
+ status: Installation status
36
+ version: Version string if installed
37
+ executable_path: Path to micromamba executable
38
+ details: Additional details
39
+ """
40
+
41
+ status: MicromambaStatus
42
+ version: str | None = None
43
+ executable_path: str | None = None
44
+ details: str = ""
45
+
46
+
47
+ class MirrorSelector:
48
+ """Selects mirror URLs based on region for micromamba downloads."""
49
+
50
+ GITHUB_MIRROR_CN = "https://gh-proxy.org/https://github.com"
51
+
52
+ def get_micromamba_urls(self, base_url: str, region: Region) -> list[str]:
53
+ """Get micromamba download URLs with mirror fallback.
54
+
55
+ Args:
56
+ base_url: Original GitHub URL
57
+ region: Region.cn or Region.other
58
+
59
+ Returns:
60
+ List of URLs to try (mirror first if cn, then original)
61
+ """
62
+ urls = []
63
+ if region == Region.cn:
64
+ # Apply ghproxy mirror for Chinese users
65
+ mirror_url = base_url.replace("https://github.com", self.GITHUB_MIRROR_CN)
66
+ urls.append(mirror_url)
67
+ urls.append(base_url)
68
+ return urls
69
+
70
+
71
+ class MicromambaInstaller:
72
+ """Manages micromamba installation and retrieval.
73
+
74
+ This class provides a complete solution for:
75
+ - Checking if micromamba is installed
76
+ - Downloading and installing micromamba with mirror support
77
+ - Getting the executable path
78
+ """
79
+
80
+ def __init__(self, cache_dir: Path | str, region: Region = Region.other):
81
+ """Initialize installer.
82
+
83
+ Args:
84
+ cache_dir: Cache directory for micromamba installation
85
+ region: Region for mirror selection
86
+ """
87
+ self.cache_dir = Path(cache_dir).expanduser()
88
+ self.target_name = "micromamba"
89
+ self.region = region
90
+ self.mirror_selector = MirrorSelector()
91
+
92
+ logger.debug(
93
+ f"[MicromambaInstaller] Initialized: cache_dir={self.cache_dir}, region={region.value}"
94
+ )
95
+
96
+ def check(self, custom_path: str | None = None) -> MicromambaCheckResult:
97
+ """Check if micromamba is installed and accessible.
98
+
99
+ Args:
100
+ custom_path: Optional path to micromamba executable.
101
+ If None, checks PATH and install directory.
102
+
103
+ Returns:
104
+ MicromambaCheckResult with status and details
105
+ """
106
+ logger.debug("[MicromambaInstaller] Checking micromamba availability")
107
+
108
+ # Determine executable path
109
+ if custom_path:
110
+ exe_path = Path(custom_path)
111
+ logger.debug(f"[MicromambaInstaller] Using custom path: {custom_path}")
112
+ else:
113
+ # First check if it's in our install directory
114
+ exe_path = self.get_executable()
115
+ if not exe_path.exists():
116
+ # Fall back to PATH
117
+ exe_path = Path("micromamba")
118
+ logger.debug("[MicromambaInstaller] Checking PATH for micromamba")
119
+
120
+ # Try to run micromamba --version
121
+ try:
122
+ result = subprocess.run(
123
+ [str(exe_path), "--version"],
124
+ capture_output=True,
125
+ text=True,
126
+ timeout=5,
127
+ )
128
+
129
+ if result.returncode == 0:
130
+ version = result.stdout.strip().split()[-1]
131
+ logger.info(
132
+ f"[MicromambaInstaller] Micromamba found: version {version}"
133
+ )
134
+ return MicromambaCheckResult(
135
+ status=MicromambaStatus.INSTALLED,
136
+ version=version,
137
+ executable_path=str(exe_path),
138
+ details=f"version {version}",
139
+ )
140
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
141
+ logger.debug(f"[MicromambaInstaller] Check failed: {e}")
142
+
143
+ logger.info("[MicromambaInstaller] Micromamba not available")
144
+ return MicromambaCheckResult(
145
+ status=MicromambaStatus.NOT_INSTALLED,
146
+ details=f"micromamba not found at {exe_path}",
147
+ )
148
+
149
+ def install(self, dry_run: bool = False) -> Path:
150
+ """Download and install micromamba to cache_dir.
151
+
152
+ Args:
153
+ dry_run: If True, only print commands without executing
154
+
155
+ Returns:
156
+ Path to micromamba executable
157
+
158
+ Raises:
159
+ Exception: If installation fails
160
+ """
161
+ logger.info(f"[MicromambaInstaller] Installing micromamba (dry_run={dry_run})")
162
+
163
+ install_dir = self.cache_dir / self.target_name
164
+ install_dir.mkdir(parents=True, exist_ok=True)
165
+
166
+ # Determine executable path based on platform
167
+ exe_path = self._get_executable_path(install_dir)
168
+
169
+ # Check if already installed AND executable
170
+ if exe_path.exists():
171
+ # Verify it's executable
172
+ if platform.system() != "Windows" and not os.access(exe_path, os.X_OK):
173
+ logger.warning(
174
+ f"[MicromambaInstaller] File exists but not executable: {exe_path}"
175
+ )
176
+ logger.info("[MicromambaInstaller] Fixing permissions...")
177
+ try:
178
+ subprocess.run(
179
+ ["chmod", "+x", str(exe_path)],
180
+ capture_output=True,
181
+ text=True,
182
+ timeout=10,
183
+ )
184
+ logger.info(f"[MicromambaInstaller] Fixed permissions: {exe_path}")
185
+ except Exception as e:
186
+ logger.warning(
187
+ f"[MicromambaInstaller] Failed to fix permissions: {e}"
188
+ )
189
+ # Fall through to re-download
190
+
191
+ # Test if it works
192
+ try:
193
+ result = subprocess.run(
194
+ [str(exe_path), "--version"],
195
+ capture_output=True,
196
+ text=True,
197
+ timeout=5,
198
+ )
199
+ if result.returncode == 0:
200
+ logger.info(
201
+ f"[MicromambaInstaller] Already installed and working: {exe_path}"
202
+ )
203
+ return exe_path
204
+ else:
205
+ logger.warning(
206
+ f"[MicromambaInstaller] File exists but not working: {result.stderr}"
207
+ )
208
+ except Exception as e:
209
+ logger.warning(
210
+ f"[MicromambaInstaller] Executable test failed: {e}, re-downloading..."
211
+ )
212
+ # Remove broken file and re-download
213
+ exe_path.unlink()
214
+
215
+ # Build installation command based on platform
216
+ system = platform.system()
217
+
218
+ if system == "Windows":
219
+ success, message = self._install_windows(install_dir, dry_run)
220
+ else:
221
+ success, message = self._install_unix(install_dir, exe_path, dry_run)
222
+
223
+ if not success:
224
+ raise Exception(f"Failed to install micromamba: {message}")
225
+
226
+ # Verify installation
227
+ if not exe_path.exists():
228
+ raise Exception(
229
+ f"Installation succeeded but executable not found: {exe_path}"
230
+ )
231
+
232
+ logger.info(f"[MicromambaInstaller] Successfully installed: {exe_path}")
233
+ return exe_path
234
+
235
+ def ensure_installed(self) -> Path:
236
+ """Ensure micromamba is installed, installing if necessary.
237
+
238
+ Returns:
239
+ Path to micromamba executable
240
+
241
+ Raises:
242
+ Exception: If installation check or install fails
243
+ """
244
+ result = self.check()
245
+
246
+ if result.status == MicromambaStatus.INSTALLED:
247
+ logger.debug("[MicromambaInstaller] Already installed, skipping")
248
+ if result.executable_path is None:
249
+ raise Exception("Executable path is None despite being installed")
250
+ return Path(result.executable_path)
251
+
252
+ logger.info("[MicromambaInstaller] Not installed, installing now")
253
+ return self.install()
254
+
255
+ def get_executable(self) -> Path:
256
+ """Get micromamba executable path in install directory.
257
+
258
+ Returns:
259
+ Path to micromamba executable
260
+ """
261
+ install_dir = self.cache_dir / self.target_name
262
+ return self._get_executable_path(install_dir)
263
+
264
+ def _get_executable_path(self, install_dir: Path) -> Path:
265
+ """Get executable path for current platform.
266
+
267
+ Args:
268
+ install_dir: Installation directory
269
+
270
+ Returns:
271
+ Path to micromamba executable
272
+ """
273
+ if platform.system() == "Windows":
274
+ return install_dir / "bin" / "micromamba.exe"
275
+ else:
276
+ return install_dir / "bin" / "micromamba"
277
+
278
+ def _install_windows(self, install_dir: Path, dry_run: bool) -> tuple[bool, str]:
279
+ """Install micromamba on Windows.
280
+
281
+ Args:
282
+ install_dir: Installation directory
283
+ dry_run: If True, only print command without executing
284
+
285
+ Returns:
286
+ Tuple of (success, message)
287
+ """
288
+ logger.debug("[MicromambaInstaller] Installing on Windows")
289
+
290
+ install_script = install_dir / "install.ps1"
291
+ cmd = [
292
+ "powershell",
293
+ "-Command",
294
+ f"Invoke-WebRequest -Uri https://micro.mamba.pm/install.ps1 -OutFile {install_script}; "
295
+ f"& {install_script} -prefix {install_dir} -batch",
296
+ ]
297
+
298
+ if dry_run:
299
+ logger.info(f"[MicromambaInstaller] Would run: {' '.join(cmd)}")
300
+ return True, f"Would run: {' '.join(cmd)}"
301
+
302
+ try:
303
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=400)
304
+
305
+ if result.returncode == 0:
306
+ return True, f"Installed to {self._get_executable_path(install_dir)}"
307
+ else:
308
+ return False, f"Installation failed: {result.stderr}"
309
+
310
+ except subprocess.TimeoutExpired:
311
+ return False, "Installation timed out"
312
+ except Exception as e:
313
+ return False, f"Installation error: {str(e)}"
314
+
315
+ def _install_unix(
316
+ self, install_dir: Path, exe_path: Path, dry_run: bool
317
+ ) -> tuple[bool, str]:
318
+ """Install micromamba on Unix (Linux/macOS).
319
+
320
+ Args:
321
+ install_dir: Installation directory
322
+ exe_path: Expected executable path
323
+ dry_run: If True, only print command without executing
324
+
325
+ Returns:
326
+ Tuple of (success, message)
327
+ """
328
+ logger.debug("[MicromambaInstaller] Installing on Unix")
329
+
330
+ # Detect platform and architecture
331
+ platform_name, arch = self._detect_platform()
332
+
333
+ # Build download URL
334
+ base_url = f"https://github.com/mamba-org/micromamba-releases/releases/latest/download/micromamba-{platform_name}-{arch}"
335
+
336
+ # Get URLs with mirror fallback
337
+ download_urls = self.mirror_selector.get_micromamba_urls(base_url, self.region)
338
+
339
+ logger.debug(
340
+ f"[MicromambaInstaller] Platform: {platform_name}-{arch}, URLs: {download_urls}"
341
+ )
342
+
343
+ # Create bin directory
344
+ bin_dir = install_dir / "bin"
345
+ bin_dir.mkdir(parents=True, exist_ok=True)
346
+
347
+ # Try each URL
348
+ for download_url in download_urls:
349
+ logger.debug(f"[MicromambaInstaller] Trying URL: {download_url}")
350
+
351
+ download_cmd = ["curl", "-fsSL", download_url, "-o", str(exe_path)]
352
+ chmod_cmd = ["chmod", "+x", str(exe_path)]
353
+
354
+ if dry_run:
355
+ logger.info(
356
+ f"[MicromambaInstaller] Would run: {' '.join(download_cmd)}"
357
+ )
358
+ return True, f"Would download from {download_url}"
359
+
360
+ try:
361
+ # Download
362
+ logger.info("[MicromambaInstaller] Downloading micromamba...")
363
+ download_result = subprocess.run(
364
+ download_cmd, capture_output=True, text=True, timeout=120
365
+ )
366
+
367
+ if download_result.returncode != 0:
368
+ logger.warning(
369
+ f"[MicromambaInstaller] Download failed: {download_result.stderr}"
370
+ )
371
+ continue
372
+
373
+ # Make executable
374
+ subprocess.run(chmod_cmd, capture_output=True, text=True, timeout=10)
375
+
376
+ # Configure conda-forge channels
377
+ self._configure_channels(exe_path)
378
+
379
+ logger.info(f"[MicromambaInstaller] Successfully installed: {exe_path}")
380
+ return True, f"Installed to {exe_path}"
381
+
382
+ except subprocess.TimeoutExpired:
383
+ logger.warning("[MicromambaInstaller] Download timed out")
384
+ continue
385
+ except Exception as e:
386
+ logger.warning(
387
+ f"[MicromambaInstaller] Install error: {type(e).__name__}: {e}"
388
+ )
389
+ continue
390
+
391
+ return False, "Failed to download from all sources"
392
+
393
+ def _detect_platform(self) -> tuple[str, str]:
394
+ """Detect platform and architecture for micromamba download.
395
+
396
+ Returns:
397
+ Tuple of (platform_name, arch)
398
+ - platform_name: "linux" or "osx"
399
+ - arch: "64", "aarch64", "arm64", or "ppc64le"
400
+
401
+ Raises:
402
+ Exception: If platform detection fails
403
+ """
404
+ try:
405
+ # Get OS platform
406
+ uname_result = subprocess.run(
407
+ ["uname", "-s"], capture_output=True, text=True, timeout=5
408
+ )
409
+ uname_s = uname_result.stdout.strip()
410
+
411
+ if uname_s == "Linux":
412
+ platform_name = "linux"
413
+ elif uname_s == "Darwin":
414
+ platform_name = "osx"
415
+ else:
416
+ raise Exception(f"Unsupported platform: {uname_s}")
417
+
418
+ # Get architecture
419
+ uname_result = subprocess.run(
420
+ ["uname", "-m"], capture_output=True, text=True, timeout=5
421
+ )
422
+ uname_m = uname_result.stdout.strip()
423
+
424
+ # Map architecture names (same logic as install.sh)
425
+ if uname_m in ("aarch64", "ppc64le", "arm64"):
426
+ arch = uname_m
427
+ else:
428
+ arch = "64"
429
+
430
+ logger.debug(f"[MicromambaInstaller] Detected: {platform_name}-{arch}")
431
+ return platform_name, arch
432
+
433
+ except Exception as e:
434
+ logger.error(f"[MicromambaInstaller] Platform detection failed: {e}")
435
+ raise Exception(f"Failed to detect platform: {e}")
436
+
437
+ def _configure_channels(self, exe_path: Path) -> None:
438
+ """Configure conda-forge channels for micromamba.
439
+
440
+ Args:
441
+ exe_path: Path to micromamba executable
442
+ """
443
+ logger.debug("[MicromambaInstaller] Configuring conda-forge channels")
444
+
445
+ config_commands = [
446
+ [str(exe_path), "config", "append", "channels", "conda-forge"],
447
+ [str(exe_path), "config", "append", "channels", "nodefaults"],
448
+ [str(exe_path), "config", "set", "channel_priority", "strict"],
449
+ ]
450
+
451
+ for cmd in config_commands:
452
+ try:
453
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
454
+ if result.returncode != 0:
455
+ logger.warning(
456
+ f"[MicromambaInstaller] Config failed: {result.stderr}"
457
+ )
458
+ except Exception as e:
459
+ logger.warning(f"[MicromambaInstaller] Config error: {e}")