depfix 0.1.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.
- depfix/__init__.py +196 -0
- depfix/__main__.py +3 -0
- depfix/_version.py +1 -0
- depfix/aliases.py +189 -0
- depfix/cache.py +385 -0
- depfix/cli.py +595 -0
- depfix/config.py +57 -0
- depfix/errors.py +214 -0
- depfix/handles.py +143 -0
- depfix/manager.py +258 -0
- depfix/manifest.py +456 -0
- depfix/models.py +113 -0
- depfix/project.py +585 -0
- depfix/py.typed +1 -0
- depfix/resolver.py +1000 -0
- depfix/runtime.py +708 -0
- depfix/scanner.py +271 -0
- depfix/schemas/depfix-manifest-v1.schema.json +165 -0
- depfix/settings.py +266 -0
- depfix/sources.py +306 -0
- depfix/specifiers.py +6 -0
- depfix/sync.py +123 -0
- depfix/uv_backend.py +334 -0
- depfix/wheel.py +406 -0
- depfix-0.1.0.dist-info/METADATA +223 -0
- depfix-0.1.0.dist-info/RECORD +29 -0
- depfix-0.1.0.dist-info/WHEEL +5 -0
- depfix-0.1.0.dist-info/entry_points.txt +2 -0
- depfix-0.1.0.dist-info/top_level.txt +1 -0
depfix/__init__.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Depfix: version-resolved imports with per-package dependency realms.
|
|
2
|
+
|
|
3
|
+
Importing this package performs no network, resolver, or subprocess work. Cold
|
|
4
|
+
preparation begins only when a load API is called.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
|
|
13
|
+
from ._version import __version__
|
|
14
|
+
from .errors import (
|
|
15
|
+
ArtifactError,
|
|
16
|
+
BundleError,
|
|
17
|
+
DepfixError,
|
|
18
|
+
FrozenManifestError,
|
|
19
|
+
HashMismatchError,
|
|
20
|
+
ManifestError,
|
|
21
|
+
ManifestMismatchError,
|
|
22
|
+
ManifestNotFoundError,
|
|
23
|
+
ModuleDiscoveryError,
|
|
24
|
+
ModuleNotProvidedError,
|
|
25
|
+
MultipleImportModulesError,
|
|
26
|
+
NativeIsolationRequired,
|
|
27
|
+
NoImportModulesError,
|
|
28
|
+
OfflineArtifactMissingError,
|
|
29
|
+
ResolutionError,
|
|
30
|
+
SourceError,
|
|
31
|
+
SpecifierError,
|
|
32
|
+
UnsupportedManifestVersionError,
|
|
33
|
+
UnsupportedUvVersionError,
|
|
34
|
+
UvBackendError,
|
|
35
|
+
UvBootstrapError,
|
|
36
|
+
UvNotFoundError,
|
|
37
|
+
)
|
|
38
|
+
from .handles import PackageHandle
|
|
39
|
+
from .manager import activate_manifest, prepare_request
|
|
40
|
+
from .settings import Settings, configure, resolve_settings
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def import_module(
|
|
44
|
+
specifier: str,
|
|
45
|
+
*,
|
|
46
|
+
module: str | None = None,
|
|
47
|
+
refresh: bool = False,
|
|
48
|
+
manifest: str | os.PathLike[str] | None = None,
|
|
49
|
+
frozen: bool | None = None,
|
|
50
|
+
offline: bool | None = None,
|
|
51
|
+
isolation: str | None = None,
|
|
52
|
+
) -> ModuleType:
|
|
53
|
+
"""Return exactly one canonical module or raise a typed discovery error."""
|
|
54
|
+
settings = resolve_settings(manifest=manifest, frozen=frozen, offline=offline)
|
|
55
|
+
runtime, request = prepare_request(
|
|
56
|
+
specifier,
|
|
57
|
+
module=module,
|
|
58
|
+
api="import_module",
|
|
59
|
+
refresh=refresh,
|
|
60
|
+
isolation=isolation,
|
|
61
|
+
settings=settings,
|
|
62
|
+
)
|
|
63
|
+
if not request.module:
|
|
64
|
+
raise NoImportModulesError("The resolved request has no selected import module", request=specifier)
|
|
65
|
+
return runtime.import_for_node(request.node, request.module)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def load_package(
|
|
69
|
+
specifier: str,
|
|
70
|
+
*,
|
|
71
|
+
refresh: bool = False,
|
|
72
|
+
manifest: str | os.PathLike[str] | None = None,
|
|
73
|
+
frozen: bool | None = None,
|
|
74
|
+
offline: bool | None = None,
|
|
75
|
+
isolation: str | None = None,
|
|
76
|
+
) -> PackageHandle:
|
|
77
|
+
"""Return one package handle without eagerly importing its root modules."""
|
|
78
|
+
settings = resolve_settings(manifest=manifest, frozen=frozen, offline=offline)
|
|
79
|
+
runtime, request = prepare_request(
|
|
80
|
+
specifier,
|
|
81
|
+
module=None,
|
|
82
|
+
api="load_package",
|
|
83
|
+
refresh=refresh,
|
|
84
|
+
isolation=isolation,
|
|
85
|
+
settings=settings,
|
|
86
|
+
)
|
|
87
|
+
return PackageHandle(runtime, request)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def import_module_async(
|
|
91
|
+
specifier: str,
|
|
92
|
+
*,
|
|
93
|
+
module: str | None = None,
|
|
94
|
+
refresh: bool = False,
|
|
95
|
+
manifest: str | os.PathLike[str] | None = None,
|
|
96
|
+
frozen: bool | None = None,
|
|
97
|
+
offline: bool | None = None,
|
|
98
|
+
isolation: str | None = None,
|
|
99
|
+
) -> ModuleType:
|
|
100
|
+
"""Async-friendly wrapper sharing canonical identity with `import_module`."""
|
|
101
|
+
return await asyncio.to_thread(
|
|
102
|
+
import_module,
|
|
103
|
+
specifier,
|
|
104
|
+
module=module,
|
|
105
|
+
refresh=refresh,
|
|
106
|
+
manifest=manifest,
|
|
107
|
+
frozen=frozen,
|
|
108
|
+
offline=offline,
|
|
109
|
+
isolation=isolation,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def load_package_async(
|
|
114
|
+
specifier: str,
|
|
115
|
+
*,
|
|
116
|
+
refresh: bool = False,
|
|
117
|
+
manifest: str | os.PathLike[str] | None = None,
|
|
118
|
+
frozen: bool | None = None,
|
|
119
|
+
offline: bool | None = None,
|
|
120
|
+
isolation: str | None = None,
|
|
121
|
+
) -> PackageHandle:
|
|
122
|
+
return await asyncio.to_thread(
|
|
123
|
+
load_package,
|
|
124
|
+
specifier,
|
|
125
|
+
refresh=refresh,
|
|
126
|
+
manifest=manifest,
|
|
127
|
+
frozen=frozen,
|
|
128
|
+
offline=offline,
|
|
129
|
+
isolation=isolation,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def activate(
|
|
134
|
+
manifest: str | os.PathLike[str] = ".depfix/imports.lock",
|
|
135
|
+
*,
|
|
136
|
+
cache_dir: str | os.PathLike[str] | None = None,
|
|
137
|
+
) -> object:
|
|
138
|
+
"""Activate an already installed manifest for notebooks or embedding."""
|
|
139
|
+
settings = resolve_settings(manifest=manifest, cache_dir=cache_dir, frozen=True, discover=False)
|
|
140
|
+
assert settings.manifest is not None
|
|
141
|
+
return activate_manifest(settings.manifest, settings)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _load_alias(name: str, identity: tuple[str, str, str, str]) -> ModuleType:
|
|
145
|
+
from .manager import load_generated_alias
|
|
146
|
+
|
|
147
|
+
return load_generated_alias(name, identity)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _load_package_alias(name: str, identity: tuple[str, str, str, str]) -> PackageHandle:
|
|
151
|
+
from .manager import load_generated_package
|
|
152
|
+
|
|
153
|
+
return load_generated_package(name, identity)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def multiprocessing_initializer(
|
|
157
|
+
manifest: str | os.PathLike[str],
|
|
158
|
+
cache_dir: str | os.PathLike[str] | None = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
activate(manifest, cache_dir=cache_dir)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
__all__ = [
|
|
164
|
+
"ArtifactError",
|
|
165
|
+
"BundleError",
|
|
166
|
+
"DepfixError",
|
|
167
|
+
"FrozenManifestError",
|
|
168
|
+
"HashMismatchError",
|
|
169
|
+
"ManifestError",
|
|
170
|
+
"ManifestMismatchError",
|
|
171
|
+
"ManifestNotFoundError",
|
|
172
|
+
"ModuleDiscoveryError",
|
|
173
|
+
"ModuleNotProvidedError",
|
|
174
|
+
"MultipleImportModulesError",
|
|
175
|
+
"NativeIsolationRequired",
|
|
176
|
+
"NoImportModulesError",
|
|
177
|
+
"OfflineArtifactMissingError",
|
|
178
|
+
"PackageHandle",
|
|
179
|
+
"ResolutionError",
|
|
180
|
+
"Settings",
|
|
181
|
+
"SourceError",
|
|
182
|
+
"SpecifierError",
|
|
183
|
+
"UnsupportedManifestVersionError",
|
|
184
|
+
"UnsupportedUvVersionError",
|
|
185
|
+
"UvBackendError",
|
|
186
|
+
"UvBootstrapError",
|
|
187
|
+
"UvNotFoundError",
|
|
188
|
+
"activate",
|
|
189
|
+
"configure",
|
|
190
|
+
"import_module",
|
|
191
|
+
"import_module_async",
|
|
192
|
+
"load_package",
|
|
193
|
+
"load_package_async",
|
|
194
|
+
"multiprocessing_initializer",
|
|
195
|
+
"__version__",
|
|
196
|
+
]
|
depfix/__main__.py
ADDED
depfix/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
depfix/aliases.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Generate the physical alias namespace used by editors and runtime code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .cache import Cache
|
|
11
|
+
from .models import LockedGraph, Node
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def generate_aliases(graph: LockedGraph, cache: Cache, output_root: Path) -> Path:
|
|
15
|
+
package = output_root / "depfix_imports"
|
|
16
|
+
package.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
alias_rows = {
|
|
18
|
+
alias.name: (graph.graph_id, alias.node, alias.module, alias.specifier)
|
|
19
|
+
for alias in sorted(graph.aliases, key=lambda value: value.name)
|
|
20
|
+
}
|
|
21
|
+
init = (
|
|
22
|
+
'"""Generated by Depfix; do not edit."""\n'
|
|
23
|
+
"from depfix import _load_alias as _depfix_load_alias\n"
|
|
24
|
+
"from depfix import _load_package_alias as _depfix_load_package_alias\n\n"
|
|
25
|
+
f"_ALIASES = {alias_rows!r}\n\n"
|
|
26
|
+
f"_PACKAGE_ALIASES = {sorted(alias.name for alias in graph.aliases if alias.api == 'load_package')!r}\n\n"
|
|
27
|
+
"def __getattr__(name):\n"
|
|
28
|
+
" if name not in _ALIASES:\n"
|
|
29
|
+
" raise AttributeError(name)\n"
|
|
30
|
+
" loader = _depfix_load_package_alias if name in _PACKAGE_ALIASES else _depfix_load_alias\n"
|
|
31
|
+
" value = loader(name, _ALIASES[name])\n"
|
|
32
|
+
" globals()[name] = value\n"
|
|
33
|
+
" return value\n\n"
|
|
34
|
+
f"__all__ = {sorted(alias_rows)!r}\n"
|
|
35
|
+
)
|
|
36
|
+
(package / "__init__.py").write_text(init, encoding="utf-8")
|
|
37
|
+
root_stub = "".join(f"from . import {name} as {name}\n" for name in sorted(alias_rows))
|
|
38
|
+
(package / "__init__.pyi").write_text(root_stub, encoding="utf-8")
|
|
39
|
+
(package / "py.typed").write_text("\n", encoding="utf-8")
|
|
40
|
+
manifest = {
|
|
41
|
+
"format_version": 1,
|
|
42
|
+
"graph_id": graph.graph_id,
|
|
43
|
+
"aliases": [
|
|
44
|
+
{"name": alias.name, "node": alias.node, "module": alias.module, "specifier": alias.specifier}
|
|
45
|
+
for alias in sorted(graph.aliases, key=lambda value: value.name)
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
(package / "aliases.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
49
|
+
source_map = {
|
|
50
|
+
"format_version": 1,
|
|
51
|
+
"manifest_id": graph.graph_id,
|
|
52
|
+
"aliases": {
|
|
53
|
+
alias.name: {
|
|
54
|
+
"source_file": alias.source_file,
|
|
55
|
+
"line": alias.source_line,
|
|
56
|
+
"column": alias.source_column,
|
|
57
|
+
"specifier": alias.specifier,
|
|
58
|
+
"logical_module": alias.module,
|
|
59
|
+
"node": alias.node,
|
|
60
|
+
}
|
|
61
|
+
for alias in sorted(graph.aliases, key=lambda value: value.name)
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
(package / "source-map.json").write_text(
|
|
65
|
+
json.dumps(source_map, indent=2, sort_keys=True) + "\n",
|
|
66
|
+
encoding="utf-8",
|
|
67
|
+
)
|
|
68
|
+
nodes = graph.node_index
|
|
69
|
+
for alias in graph.aliases:
|
|
70
|
+
if alias.api == "load_package":
|
|
71
|
+
_generate_package_stub_tree(package, alias.name, nodes[alias.node], cache, alias_rows[alias.name])
|
|
72
|
+
else:
|
|
73
|
+
_generate_stub_tree(package, alias.name, alias.module, nodes[alias.node], cache)
|
|
74
|
+
_write_editor_snippets(output_root)
|
|
75
|
+
return package
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _generate_stub_tree(package: Path, alias_name: str, logical_module: str, node: Node, cache: Cache) -> None:
|
|
79
|
+
alias_root = package / alias_name
|
|
80
|
+
if alias_root.exists():
|
|
81
|
+
shutil.rmtree(alias_root)
|
|
82
|
+
alias_root.mkdir(parents=True)
|
|
83
|
+
artifact_root = cache.unpacked_path(node.artifact) / "purelib"
|
|
84
|
+
source_root = artifact_root.joinpath(*logical_module.split("."))
|
|
85
|
+
if source_root.is_dir():
|
|
86
|
+
typed = (source_root / "py.typed").exists()
|
|
87
|
+
for source in source_root.rglob("*"):
|
|
88
|
+
if not source.is_file():
|
|
89
|
+
continue
|
|
90
|
+
relative = source.relative_to(source_root)
|
|
91
|
+
if source.suffix == ".pyi":
|
|
92
|
+
target = alias_root / relative
|
|
93
|
+
elif typed and source.suffix == ".py":
|
|
94
|
+
target = (alias_root / relative).with_suffix(".pyi")
|
|
95
|
+
else:
|
|
96
|
+
continue
|
|
97
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
_copy_stub(source, target, logical_module, f"depfix_imports.{alias_name}")
|
|
99
|
+
else:
|
|
100
|
+
source_file = source_root.with_suffix(".py")
|
|
101
|
+
stub_file = source_root.with_suffix(".pyi")
|
|
102
|
+
selected = stub_file if stub_file.is_file() else source_file if source_file.is_file() else None
|
|
103
|
+
if selected is not None:
|
|
104
|
+
_copy_stub(selected, alias_root / "__init__.pyi", logical_module, f"depfix_imports.{alias_name}")
|
|
105
|
+
fallback = alias_root / "__init__.pyi"
|
|
106
|
+
if not fallback.exists():
|
|
107
|
+
fallback.write_text("# Untyped artifact: API is intentionally unknown to static analyzers.\n", encoding="utf-8")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _generate_package_stub_tree(
|
|
111
|
+
package: Path,
|
|
112
|
+
alias_name: str,
|
|
113
|
+
node: Node,
|
|
114
|
+
cache: Cache,
|
|
115
|
+
identity: tuple[str, str, str, str],
|
|
116
|
+
) -> None:
|
|
117
|
+
alias_root = package / alias_name
|
|
118
|
+
if alias_root.exists():
|
|
119
|
+
shutil.rmtree(alias_root)
|
|
120
|
+
alias_root.mkdir(parents=True)
|
|
121
|
+
runtime = (
|
|
122
|
+
'"""Generated Depfix package-module collection."""\n'
|
|
123
|
+
"from depfix import _load_package_alias\n"
|
|
124
|
+
f"_PACKAGE = _load_package_alias({alias_name!r}, {identity!r})\n"
|
|
125
|
+
"def __getattr__(name):\n"
|
|
126
|
+
" try:\n"
|
|
127
|
+
" return _PACKAGE.modules[name]\n"
|
|
128
|
+
" except Exception as exc:\n"
|
|
129
|
+
" raise AttributeError(name) from exc\n"
|
|
130
|
+
f"__all__ = {list(node.public_modules)!r}\n"
|
|
131
|
+
)
|
|
132
|
+
(alias_root / "__init__.py").write_text(runtime, encoding="utf-8")
|
|
133
|
+
imports: list[str] = []
|
|
134
|
+
artifact_root = cache.unpacked_path(node.artifact) / "purelib"
|
|
135
|
+
for logical in node.public_modules:
|
|
136
|
+
target_root = alias_root.joinpath(*logical.split("."))
|
|
137
|
+
source_root = artifact_root.joinpath(*logical.split("."))
|
|
138
|
+
if source_root.is_dir():
|
|
139
|
+
target_root.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
typed = (source_root / "py.typed").exists()
|
|
141
|
+
for source in source_root.rglob("*"):
|
|
142
|
+
if not source.is_file() or (source.suffix != ".pyi" and not (typed and source.suffix == ".py")):
|
|
143
|
+
continue
|
|
144
|
+
relative = source.relative_to(source_root)
|
|
145
|
+
target = target_root / relative
|
|
146
|
+
if target.suffix == ".py":
|
|
147
|
+
target = target.with_suffix(".pyi")
|
|
148
|
+
_copy_stub(
|
|
149
|
+
source, target, logical.split(".", 1)[0], f"depfix_imports.{alias_name}.{logical.split('.', 1)[0]}"
|
|
150
|
+
)
|
|
151
|
+
(target_root / "__init__.pyi").touch(exist_ok=True)
|
|
152
|
+
else:
|
|
153
|
+
selected = source_root.with_suffix(".pyi")
|
|
154
|
+
if not selected.is_file():
|
|
155
|
+
selected = source_root.with_suffix(".py")
|
|
156
|
+
target_root.parent.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
target = target_root.with_suffix(".pyi")
|
|
158
|
+
if selected.is_file():
|
|
159
|
+
_copy_stub(
|
|
160
|
+
selected,
|
|
161
|
+
target,
|
|
162
|
+
logical.split(".", 1)[0],
|
|
163
|
+
f"depfix_imports.{alias_name}.{logical.split('.', 1)[0]}",
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
target.write_text("# Untyped module.\n", encoding="utf-8")
|
|
167
|
+
top = logical.split(".", 1)[0]
|
|
168
|
+
imports.append(f"from . import {top} as {top}\n")
|
|
169
|
+
(alias_root / "__init__.pyi").write_text("".join(dict.fromkeys(imports)), encoding="utf-8")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _copy_stub(source: Path, target: Path, logical_root: str, alias_root: str) -> None:
|
|
173
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
174
|
+
text = source.read_text(encoding="utf-8")
|
|
175
|
+
escaped = re.escape(logical_root)
|
|
176
|
+
text = re.sub(rf"(?m)^(\s*from\s+){escaped}(?=\.|\s+import\b)", rf"\1{alias_root}", text)
|
|
177
|
+
text = re.sub(rf"(?m)^(\s*import\s+){escaped}(?=\.|\s|$)", rf"\1{alias_root}", text)
|
|
178
|
+
target.write_text(text, encoding="utf-8")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _write_editor_snippets(output_root: Path) -> None:
|
|
182
|
+
generated = str(output_root.resolve())
|
|
183
|
+
(output_root / "pyrightconfig.depfix.json").write_text(
|
|
184
|
+
json.dumps({"extraPaths": [generated], "stubPath": generated}, indent=2) + "\n", encoding="utf-8"
|
|
185
|
+
)
|
|
186
|
+
(output_root / "mypy.depfix.ini").write_text(f"[mypy]\nmypy_path = {generated}\n", encoding="utf-8")
|
|
187
|
+
(output_root / "PYCHARM.md").write_text(
|
|
188
|
+
"Mark this generated directory as a Sources Root in Settings > Project Structure.\n", encoding="utf-8"
|
|
189
|
+
)
|