comfygit-core 0.2.0__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.
- comfygit_core/analyzers/custom_node_scanner.py +109 -0
- comfygit_core/analyzers/git_change_parser.py +156 -0
- comfygit_core/analyzers/model_scanner.py +318 -0
- comfygit_core/analyzers/node_classifier.py +58 -0
- comfygit_core/analyzers/node_git_analyzer.py +77 -0
- comfygit_core/analyzers/status_scanner.py +362 -0
- comfygit_core/analyzers/workflow_dependency_parser.py +143 -0
- comfygit_core/caching/__init__.py +16 -0
- comfygit_core/caching/api_cache.py +210 -0
- comfygit_core/caching/base.py +212 -0
- comfygit_core/caching/comfyui_cache.py +100 -0
- comfygit_core/caching/custom_node_cache.py +320 -0
- comfygit_core/caching/workflow_cache.py +797 -0
- comfygit_core/clients/__init__.py +4 -0
- comfygit_core/clients/civitai_client.py +412 -0
- comfygit_core/clients/github_client.py +349 -0
- comfygit_core/clients/registry_client.py +230 -0
- comfygit_core/configs/comfyui_builtin_nodes.py +1614 -0
- comfygit_core/configs/comfyui_models.py +62 -0
- comfygit_core/configs/model_config.py +151 -0
- comfygit_core/constants.py +82 -0
- comfygit_core/core/environment.py +1635 -0
- comfygit_core/core/workspace.py +898 -0
- comfygit_core/factories/environment_factory.py +419 -0
- comfygit_core/factories/uv_factory.py +61 -0
- comfygit_core/factories/workspace_factory.py +109 -0
- comfygit_core/infrastructure/sqlite_manager.py +156 -0
- comfygit_core/integrations/__init__.py +7 -0
- comfygit_core/integrations/uv_command.py +318 -0
- comfygit_core/logging/logging_config.py +15 -0
- comfygit_core/managers/environment_git_orchestrator.py +316 -0
- comfygit_core/managers/environment_model_manager.py +296 -0
- comfygit_core/managers/export_import_manager.py +116 -0
- comfygit_core/managers/git_manager.py +667 -0
- comfygit_core/managers/model_download_manager.py +252 -0
- comfygit_core/managers/model_symlink_manager.py +166 -0
- comfygit_core/managers/node_manager.py +1378 -0
- comfygit_core/managers/pyproject_manager.py +1321 -0
- comfygit_core/managers/user_content_symlink_manager.py +436 -0
- comfygit_core/managers/uv_project_manager.py +569 -0
- comfygit_core/managers/workflow_manager.py +1944 -0
- comfygit_core/models/civitai.py +432 -0
- comfygit_core/models/commit.py +18 -0
- comfygit_core/models/environment.py +293 -0
- comfygit_core/models/exceptions.py +378 -0
- comfygit_core/models/manifest.py +132 -0
- comfygit_core/models/node_mapping.py +201 -0
- comfygit_core/models/protocols.py +248 -0
- comfygit_core/models/registry.py +63 -0
- comfygit_core/models/shared.py +356 -0
- comfygit_core/models/sync.py +42 -0
- comfygit_core/models/system.py +204 -0
- comfygit_core/models/workflow.py +914 -0
- comfygit_core/models/workspace_config.py +71 -0
- comfygit_core/py.typed +0 -0
- comfygit_core/repositories/migrate_paths.py +49 -0
- comfygit_core/repositories/model_repository.py +958 -0
- comfygit_core/repositories/node_mappings_repository.py +246 -0
- comfygit_core/repositories/workflow_repository.py +57 -0
- comfygit_core/repositories/workspace_config_repository.py +121 -0
- comfygit_core/resolvers/global_node_resolver.py +459 -0
- comfygit_core/resolvers/model_resolver.py +250 -0
- comfygit_core/services/import_analyzer.py +218 -0
- comfygit_core/services/model_downloader.py +422 -0
- comfygit_core/services/node_lookup_service.py +251 -0
- comfygit_core/services/registry_data_manager.py +161 -0
- comfygit_core/strategies/__init__.py +4 -0
- comfygit_core/strategies/auto.py +72 -0
- comfygit_core/strategies/confirmation.py +69 -0
- comfygit_core/utils/comfyui_ops.py +125 -0
- comfygit_core/utils/common.py +164 -0
- comfygit_core/utils/conflict_parser.py +232 -0
- comfygit_core/utils/dependency_parser.py +231 -0
- comfygit_core/utils/download.py +216 -0
- comfygit_core/utils/environment_cleanup.py +111 -0
- comfygit_core/utils/filesystem.py +178 -0
- comfygit_core/utils/git.py +1184 -0
- comfygit_core/utils/input_signature.py +145 -0
- comfygit_core/utils/model_categories.py +52 -0
- comfygit_core/utils/pytorch.py +71 -0
- comfygit_core/utils/requirements.py +211 -0
- comfygit_core/utils/retry.py +242 -0
- comfygit_core/utils/symlink_utils.py +119 -0
- comfygit_core/utils/system_detector.py +258 -0
- comfygit_core/utils/uuid.py +28 -0
- comfygit_core/utils/uv_error_handler.py +158 -0
- comfygit_core/utils/version.py +73 -0
- comfygit_core/utils/workflow_hash.py +90 -0
- comfygit_core/validation/resolution_tester.py +297 -0
- comfygit_core-0.2.0.dist-info/METADATA +939 -0
- comfygit_core-0.2.0.dist-info/RECORD +93 -0
- comfygit_core-0.2.0.dist-info/WHEEL +4 -0
- comfygit_core-0.2.0.dist-info/licenses/LICENSE.txt +661 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Generic SQLite database operations utility."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from collections.abc import Generator
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ..logging.logging_config import get_logger
|
|
10
|
+
from ..models.exceptions import ComfyDockError
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SQLiteManager:
|
|
16
|
+
"""Generic SQLite database manager with connection management."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, db_path: Path):
|
|
19
|
+
"""Initialize SQLite manager.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
db_path: Path to SQLite database file
|
|
23
|
+
"""
|
|
24
|
+
self.db_path = db_path
|
|
25
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
|
|
27
|
+
@contextmanager
|
|
28
|
+
def get_connection(self) -> Generator[sqlite3.Connection, None, None]:
|
|
29
|
+
"""Get database connection with context management.
|
|
30
|
+
|
|
31
|
+
Yields:
|
|
32
|
+
SQLite connection with row factory enabled
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ComfyDockError: If database connection fails
|
|
36
|
+
"""
|
|
37
|
+
conn = None
|
|
38
|
+
try:
|
|
39
|
+
conn = sqlite3.connect(self.db_path)
|
|
40
|
+
conn.row_factory = sqlite3.Row
|
|
41
|
+
yield conn
|
|
42
|
+
except sqlite3.Error as e:
|
|
43
|
+
logger.error(f"Database error: {e}")
|
|
44
|
+
raise ComfyDockError(f"Database operation failed: {e}")
|
|
45
|
+
finally:
|
|
46
|
+
if conn:
|
|
47
|
+
conn.close()
|
|
48
|
+
|
|
49
|
+
def execute_query(self, query: str, params: tuple = ()) -> list[dict[str, Any]]:
|
|
50
|
+
"""Execute SELECT query and return results.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
query: SQL SELECT query
|
|
54
|
+
params: Query parameters
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List of dictionaries representing rows
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ComfyDockError: If query execution fails
|
|
61
|
+
"""
|
|
62
|
+
with self.get_connection() as conn:
|
|
63
|
+
try:
|
|
64
|
+
cursor = conn.cursor()
|
|
65
|
+
cursor.execute(query, params)
|
|
66
|
+
rows = cursor.fetchall()
|
|
67
|
+
return [dict(row) for row in rows]
|
|
68
|
+
except sqlite3.Error as e:
|
|
69
|
+
logger.error(f"Query execution failed: {query} with params {params}: {e}")
|
|
70
|
+
raise ComfyDockError(f"Query execution failed: {e}")
|
|
71
|
+
|
|
72
|
+
def execute_write(self, query: str, params: tuple = ()) -> int:
|
|
73
|
+
"""Execute INSERT/UPDATE/DELETE query.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
query: SQL write query
|
|
77
|
+
params: Query parameters
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Number of affected rows
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ComfyDockError: If write operation fails
|
|
84
|
+
"""
|
|
85
|
+
with self.get_connection() as conn:
|
|
86
|
+
try:
|
|
87
|
+
cursor = conn.cursor()
|
|
88
|
+
cursor.execute(query, params)
|
|
89
|
+
conn.commit()
|
|
90
|
+
return cursor.rowcount
|
|
91
|
+
except sqlite3.Error as e:
|
|
92
|
+
logger.error(f"Write operation failed: {query} with params {params}: {e}")
|
|
93
|
+
conn.rollback()
|
|
94
|
+
raise ComfyDockError(f"Write operation failed: {e}")
|
|
95
|
+
|
|
96
|
+
def create_table(self, schema: str) -> None:
|
|
97
|
+
"""Create table using schema SQL.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
schema: CREATE TABLE SQL statement
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ComfyDockError: If table creation fails
|
|
104
|
+
"""
|
|
105
|
+
with self.get_connection() as conn:
|
|
106
|
+
try:
|
|
107
|
+
cursor = conn.cursor()
|
|
108
|
+
cursor.execute(schema)
|
|
109
|
+
conn.commit()
|
|
110
|
+
logger.debug("Table schema ensured")
|
|
111
|
+
except sqlite3.Error as e:
|
|
112
|
+
logger.error(f"Table creation failed: {schema}: {e}")
|
|
113
|
+
raise ComfyDockError(f"Table creation failed: {e}")
|
|
114
|
+
|
|
115
|
+
def begin_transaction(self) -> sqlite3.Connection:
|
|
116
|
+
"""Begin a transaction and return connection for manual management.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
SQLite connection with transaction started
|
|
120
|
+
|
|
121
|
+
Note:
|
|
122
|
+
Caller is responsible for commit/rollback and closing connection
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
conn = sqlite3.connect(self.db_path)
|
|
126
|
+
conn.row_factory = sqlite3.Row
|
|
127
|
+
conn.execute("BEGIN")
|
|
128
|
+
return conn
|
|
129
|
+
except sqlite3.Error as e:
|
|
130
|
+
logger.error(f"Transaction start failed: {e}")
|
|
131
|
+
raise ComfyDockError(f"Transaction start failed: {e}")
|
|
132
|
+
|
|
133
|
+
def table_exists(self, table_name: str) -> bool:
|
|
134
|
+
"""Check if table exists in database.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
table_name: Name of table to check
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
True if table exists, False otherwise
|
|
141
|
+
"""
|
|
142
|
+
query = "SELECT name FROM sqlite_master WHERE type='table' AND name=?"
|
|
143
|
+
results = self.execute_query(query, (table_name,))
|
|
144
|
+
return len(results) > 0
|
|
145
|
+
|
|
146
|
+
def get_table_info(self, table_name: str) -> list[dict[str, Any]]:
|
|
147
|
+
"""Get table schema information.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
table_name: Name of table
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List of column information dictionaries
|
|
154
|
+
"""
|
|
155
|
+
query = f"PRAGMA table_info({table_name})"
|
|
156
|
+
return self.execute_query(query)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""Pure UV command wrapper with no business logic."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from ..logging.logging_config import get_logger
|
|
11
|
+
from ..models.exceptions import UVCommandError, UVNotInstalledError
|
|
12
|
+
from ..utils.common import run_command
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class CommandResult:
|
|
19
|
+
"""Result of a UV command execution."""
|
|
20
|
+
stdout: str
|
|
21
|
+
stderr: str
|
|
22
|
+
returncode: int
|
|
23
|
+
success: bool
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_completed_process(cls, result) -> "CommandResult":
|
|
27
|
+
return cls(
|
|
28
|
+
stdout=result.stdout,
|
|
29
|
+
stderr=result.stderr,
|
|
30
|
+
returncode=result.returncode,
|
|
31
|
+
success=result.returncode == 0
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class UVCommand:
|
|
36
|
+
"""Pure wrapper around UV CLI commands. No business logic or pyproject.toml manipulation."""
|
|
37
|
+
|
|
38
|
+
DEFAULT_TIMEOUT = None # no timeout
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
binary_path: Path | None = None,
|
|
43
|
+
project_env: Path | None = None,
|
|
44
|
+
cache_dir: Path | None = None,
|
|
45
|
+
python_install_dir: Path | None = None,
|
|
46
|
+
link_mode: str | None = "hardlink",
|
|
47
|
+
cwd: Path | None = None,
|
|
48
|
+
torch_backend: str | None = None,
|
|
49
|
+
):
|
|
50
|
+
self._binary = self._check_uv_installed(binary_path)
|
|
51
|
+
self.timeout = self.DEFAULT_TIMEOUT
|
|
52
|
+
self._project_env = project_env
|
|
53
|
+
self._cache_dir = cache_dir
|
|
54
|
+
self._python_install_dir = python_install_dir
|
|
55
|
+
self._link_mode = link_mode
|
|
56
|
+
self._cwd = cwd
|
|
57
|
+
self._torch_backend = torch_backend
|
|
58
|
+
self._base_env = self._setup_base_environment()
|
|
59
|
+
|
|
60
|
+
def _check_uv_installed(self, binary_path: Path | None) -> str:
|
|
61
|
+
# Explicit path takes priority
|
|
62
|
+
if binary_path and binary_path.is_file():
|
|
63
|
+
return str(binary_path)
|
|
64
|
+
|
|
65
|
+
# Try UV from Python package (installed with comfydock)
|
|
66
|
+
try:
|
|
67
|
+
from uv import find_uv_bin
|
|
68
|
+
binary = find_uv_bin()
|
|
69
|
+
logger.debug(f"Using UV from package: {binary}")
|
|
70
|
+
return binary
|
|
71
|
+
except ImportError:
|
|
72
|
+
logger.debug("UV package not found in current environment")
|
|
73
|
+
except FileNotFoundError as e:
|
|
74
|
+
logger.warning(f"UV package found but binary missing: {e}")
|
|
75
|
+
|
|
76
|
+
# Fallback to system UV
|
|
77
|
+
binary = shutil.which("uv")
|
|
78
|
+
if binary is None:
|
|
79
|
+
raise UVNotInstalledError(
|
|
80
|
+
"uv is not installed. Install comfydock with: pip install comfygit"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
logger.warning(
|
|
84
|
+
f"Using system UV from PATH: {binary}. "
|
|
85
|
+
f"This may cause version compatibility issues. "
|
|
86
|
+
f"Recommended: pip install --force-reinstall comfydock-cli"
|
|
87
|
+
)
|
|
88
|
+
return binary
|
|
89
|
+
|
|
90
|
+
def _setup_base_environment(self) -> dict[str, str]:
|
|
91
|
+
env = os.environ.copy()
|
|
92
|
+
env.update({
|
|
93
|
+
"UV_NO_PROGRESS": "1",
|
|
94
|
+
"NO_COLOR": "1",
|
|
95
|
+
"VIRTUAL_ENV": "",
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if self._project_env:
|
|
99
|
+
env["UV_PROJECT_ENVIRONMENT"] = str(self._project_env)
|
|
100
|
+
if self._cache_dir:
|
|
101
|
+
env["UV_CACHE_DIR"] = str(self._cache_dir)
|
|
102
|
+
if self._python_install_dir:
|
|
103
|
+
env["UV_PYTHON_INSTALL_DIR"] = str(self._python_install_dir)
|
|
104
|
+
if self._link_mode:
|
|
105
|
+
env["UV_LINK_MODE"] = self._link_mode
|
|
106
|
+
if self._torch_backend:
|
|
107
|
+
env["UV_TORCH_BACKEND"] = self._torch_backend
|
|
108
|
+
|
|
109
|
+
return env
|
|
110
|
+
|
|
111
|
+
def _build_command(self, base: list[str], **options) -> list[str]:
|
|
112
|
+
cmd = [self._binary] + base
|
|
113
|
+
|
|
114
|
+
flag_map = {
|
|
115
|
+
'python': '--python',
|
|
116
|
+
'index_url': '--index-url',
|
|
117
|
+
'frozen': '--frozen',
|
|
118
|
+
'dry_run': '--dry-run',
|
|
119
|
+
'no_sync': '--no-sync',
|
|
120
|
+
'name': '--name',
|
|
121
|
+
'no_workspace': '--no-workspace',
|
|
122
|
+
'bare': '--bare',
|
|
123
|
+
'raw': '--raw',
|
|
124
|
+
'dev': '--dev',
|
|
125
|
+
'group': '--group',
|
|
126
|
+
'editable': '--editable',
|
|
127
|
+
'bounds': '--bounds',
|
|
128
|
+
'prerelease': '--prerelease',
|
|
129
|
+
'all_groups': '--all-groups',
|
|
130
|
+
'no_default_groups': '--no-default-groups',
|
|
131
|
+
'seed': '--seed',
|
|
132
|
+
'upgrade': '--upgrade',
|
|
133
|
+
'no_install_project': '--no-install-project',
|
|
134
|
+
'no_deps': '--no-deps',
|
|
135
|
+
'compile_bytecode': '--compile-bytecode',
|
|
136
|
+
'quiet': '--quiet',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for key, value in options.items():
|
|
140
|
+
if value is None or value is False:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
flag = flag_map.get(key)
|
|
144
|
+
if flag:
|
|
145
|
+
if isinstance(value, bool):
|
|
146
|
+
cmd.append(flag)
|
|
147
|
+
elif isinstance(value, list):
|
|
148
|
+
# Handle list values that need multiple flags (e.g., --group x --group y)
|
|
149
|
+
for item in value:
|
|
150
|
+
cmd.extend([flag, str(item)])
|
|
151
|
+
else:
|
|
152
|
+
cmd.extend([flag, str(value)])
|
|
153
|
+
|
|
154
|
+
return cmd
|
|
155
|
+
|
|
156
|
+
def _execute(self, cmd: list[str], expect_failure: bool = False, verbose: bool = False) -> CommandResult:
|
|
157
|
+
try:
|
|
158
|
+
env = self._base_env.copy()
|
|
159
|
+
if verbose:
|
|
160
|
+
# Show full output with progress and summary
|
|
161
|
+
env.pop("UV_NO_PROGRESS", None)
|
|
162
|
+
env.pop("NO_COLOR", None)
|
|
163
|
+
result = run_command(cmd, cwd=self._cwd, timeout=self.timeout, env=env, capture_output=False)
|
|
164
|
+
else:
|
|
165
|
+
# Default: quiet mode (capture output, only show on error)
|
|
166
|
+
result = run_command(cmd, cwd=self._cwd, timeout=self.timeout, env=self._base_env, capture_output=True)
|
|
167
|
+
|
|
168
|
+
if result.returncode == 0 or expect_failure:
|
|
169
|
+
return CommandResult.from_completed_process(result)
|
|
170
|
+
else:
|
|
171
|
+
raise UVCommandError(
|
|
172
|
+
f"UV command failed with code {result.returncode}",
|
|
173
|
+
command=cmd,
|
|
174
|
+
stderr=result.stderr,
|
|
175
|
+
stdout=result.stdout,
|
|
176
|
+
returncode=result.returncode
|
|
177
|
+
)
|
|
178
|
+
except subprocess.TimeoutExpired:
|
|
179
|
+
raise TimeoutError(f"UV command timed out after {self.timeout}s: {' '.join(cmd)}")
|
|
180
|
+
except UVCommandError:
|
|
181
|
+
raise
|
|
182
|
+
except Exception as e:
|
|
183
|
+
raise UVCommandError(f"Failed to execute UV command: {e}", command=cmd) from e
|
|
184
|
+
|
|
185
|
+
# ===== Project Management Commands =====
|
|
186
|
+
|
|
187
|
+
def init(self, name: str | None = None, python: str | None = None, **flags) -> CommandResult:
|
|
188
|
+
cmd = self._build_command(["init"], name=name, python=python, **flags)
|
|
189
|
+
return self._execute(cmd)
|
|
190
|
+
|
|
191
|
+
def add(self, packages: list[str] | None = None, requirements_file: Path | None = None, **flags) -> CommandResult:
|
|
192
|
+
if requirements_file:
|
|
193
|
+
cmd = self._build_command(["add", "-r", str(requirements_file)], **flags)
|
|
194
|
+
else:
|
|
195
|
+
cmd = self._build_command(["add"] + (packages or []), **flags)
|
|
196
|
+
return self._execute(cmd)
|
|
197
|
+
|
|
198
|
+
def remove(self, packages: list[str], **flags) -> CommandResult:
|
|
199
|
+
cmd = self._build_command(["remove"] + packages, **flags)
|
|
200
|
+
return self._execute(cmd)
|
|
201
|
+
|
|
202
|
+
def sync(self, verbose: bool = False, **flags) -> CommandResult:
|
|
203
|
+
cmd = self._build_command(["sync"], **flags)
|
|
204
|
+
return self._execute(cmd, verbose=verbose)
|
|
205
|
+
|
|
206
|
+
def lock(self, **flags) -> CommandResult:
|
|
207
|
+
cmd = self._build_command(["lock"], **flags)
|
|
208
|
+
return self._execute(cmd, expect_failure=flags.get('dry_run', False))
|
|
209
|
+
|
|
210
|
+
def run(self, command: list[str], **flags) -> CommandResult:
|
|
211
|
+
cmd = self._build_command(["run"] + command, **flags)
|
|
212
|
+
return self._execute(cmd)
|
|
213
|
+
|
|
214
|
+
# ===== Virtual Environment Management =====
|
|
215
|
+
|
|
216
|
+
def venv(self, path: Path, **flags) -> CommandResult:
|
|
217
|
+
cmd = self._build_command(["venv", str(path)], **flags)
|
|
218
|
+
return self._execute(cmd)
|
|
219
|
+
|
|
220
|
+
# ===== Pip Compatibility =====
|
|
221
|
+
|
|
222
|
+
def pip_install(self, packages: list[str] | None = None, requirements_file: Path | None = None,
|
|
223
|
+
python: Path | None = None, torch_backend: str | None = None,
|
|
224
|
+
verbose: bool = False, **flags) -> CommandResult:
|
|
225
|
+
cmd = [self._binary, "pip", "install"]
|
|
226
|
+
|
|
227
|
+
if python:
|
|
228
|
+
cmd.extend(["--python", str(python)])
|
|
229
|
+
|
|
230
|
+
if torch_backend:
|
|
231
|
+
cmd.extend(["--torch-backend", torch_backend])
|
|
232
|
+
|
|
233
|
+
for key, value in flags.items():
|
|
234
|
+
if value is None or value is False:
|
|
235
|
+
continue
|
|
236
|
+
flag = f"--{key.replace('_', '-')}"
|
|
237
|
+
if isinstance(value, bool):
|
|
238
|
+
cmd.append(flag)
|
|
239
|
+
else:
|
|
240
|
+
cmd.extend([flag, str(value)])
|
|
241
|
+
|
|
242
|
+
if requirements_file:
|
|
243
|
+
cmd.extend(["-r", str(requirements_file)])
|
|
244
|
+
elif packages:
|
|
245
|
+
cmd.extend(packages)
|
|
246
|
+
|
|
247
|
+
return self._execute(cmd, verbose=verbose)
|
|
248
|
+
|
|
249
|
+
def pip_show(self, package: str, python: Path, **flags) -> CommandResult:
|
|
250
|
+
cmd = [self._binary, "pip", "show", "--python", str(python), package]
|
|
251
|
+
return self._execute(cmd)
|
|
252
|
+
|
|
253
|
+
def pip_list(self, python: Path, **flags) -> CommandResult:
|
|
254
|
+
cmd = [self._binary, "pip", "list", "--python", str(python)]
|
|
255
|
+
return self._execute(cmd)
|
|
256
|
+
|
|
257
|
+
def pip_freeze(self, python: Path, **flags) -> CommandResult:
|
|
258
|
+
cmd = [self._binary, "pip", "freeze", "--python", str(python)]
|
|
259
|
+
return self._execute(cmd)
|
|
260
|
+
|
|
261
|
+
def pip_compile(self, input_file: Path | None = None, output_file: Path | None = None, **flags) -> CommandResult:
|
|
262
|
+
cmd = [self._binary, "pip", "compile"]
|
|
263
|
+
|
|
264
|
+
if input_file:
|
|
265
|
+
cmd.append(str(input_file))
|
|
266
|
+
|
|
267
|
+
if output_file:
|
|
268
|
+
cmd.extend(["-o", str(output_file)])
|
|
269
|
+
|
|
270
|
+
for key, value in flags.items():
|
|
271
|
+
if value is None or value is False:
|
|
272
|
+
continue
|
|
273
|
+
flag = f"--{key.replace('_', '-')}"
|
|
274
|
+
if isinstance(value, bool):
|
|
275
|
+
cmd.append(flag)
|
|
276
|
+
else:
|
|
277
|
+
cmd.extend([flag, str(value)])
|
|
278
|
+
|
|
279
|
+
return self._execute(cmd)
|
|
280
|
+
|
|
281
|
+
# ===== Tool Management =====
|
|
282
|
+
|
|
283
|
+
def tool_run(self, tool: str, args: list[str] | None = None, **flags) -> CommandResult:
|
|
284
|
+
cmd = self._build_command(["tool", "run", tool] + (args or []), **flags)
|
|
285
|
+
return self._execute(cmd)
|
|
286
|
+
|
|
287
|
+
def tool_install(self, tool: str, **flags) -> CommandResult:
|
|
288
|
+
cmd = self._build_command(["tool", "install", tool], **flags)
|
|
289
|
+
return self._execute(cmd)
|
|
290
|
+
|
|
291
|
+
# ===== Python Management =====
|
|
292
|
+
|
|
293
|
+
def python_install(self, version: str, **flags) -> CommandResult:
|
|
294
|
+
cmd = self._build_command(["python", "install", version], **flags)
|
|
295
|
+
return self._execute(cmd)
|
|
296
|
+
|
|
297
|
+
def python_list(self, **flags) -> CommandResult:
|
|
298
|
+
cmd = self._build_command(["python", "list"], **flags)
|
|
299
|
+
return self._execute(cmd)
|
|
300
|
+
|
|
301
|
+
# ===== Utility =====
|
|
302
|
+
|
|
303
|
+
def version(self) -> str:
|
|
304
|
+
result = self._execute([self._binary, "--version"])
|
|
305
|
+
return result.stdout.strip().split()[-1]
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def binary(self) -> str:
|
|
309
|
+
return self._binary
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def python_executable(self) -> Path:
|
|
313
|
+
if not self._project_env:
|
|
314
|
+
raise ValueError("No project environment configured")
|
|
315
|
+
# TODO: Make this more robust and cross-platform
|
|
316
|
+
if sys.platform == "win32":
|
|
317
|
+
return self._project_env / "Scripts" / "python.exe"
|
|
318
|
+
return self._project_env / "bin" / "python"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Core logging utilities for ComfyDock."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_logger(name: str) -> logging.Logger:
|
|
7
|
+
"""Get a logger instance for the given module.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
name: Module name (typically __name__)
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Configured logger instance
|
|
14
|
+
"""
|
|
15
|
+
return logging.getLogger(name)
|