nab-python 0.0.1__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 (71) hide show
  1. nab_python/__init__.py +1 -0
  2. nab_python/_build/__init__.py +1 -0
  3. nab_python/_build/env.py +364 -0
  4. nab_python/_build/errors.py +17 -0
  5. nab_python/_build/runner.py +254 -0
  6. nab_python/_lockfile/__init__.py +1 -0
  7. nab_python/_lockfile/builder.py +339 -0
  8. nab_python/_lockfile/disjointness.py +207 -0
  9. nab_python/_lockfile/pylock.py +323 -0
  10. nab_python/_lockfile/requirements.py +121 -0
  11. nab_python/_packaging_provider.py +98 -0
  12. nab_python/_provider/__init__.py +1 -0
  13. nab_python/_provider/build_remote.py +95 -0
  14. nab_python/_provider/extras.py +231 -0
  15. nab_python/_provider/listing.py +442 -0
  16. nab_python/_provider/lookahead.py +156 -0
  17. nab_python/_provider/metadata_resolver.py +450 -0
  18. nab_python/_provider/priority.py +174 -0
  19. nab_python/_provider/sources.py +215 -0
  20. nab_python/_testing/__init__.py +1 -0
  21. nab_python/_testing/coordinator_fake.py +240 -0
  22. nab_python/_vcs_admission.py +209 -0
  23. nab_python/_vendor/__init__.py +6 -0
  24. nab_python/_vendor/packaging/LICENSE +3 -0
  25. nab_python/_vendor/packaging/LICENSE.APACHE +177 -0
  26. nab_python/_vendor/packaging/LICENSE.BSD +23 -0
  27. nab_python/_vendor/packaging/PROVENANCE.md +73 -0
  28. nab_python/_vendor/packaging/__init__.py +15 -0
  29. nab_python/_vendor/packaging/_elffile.py +108 -0
  30. nab_python/_vendor/packaging/_manylinux.py +265 -0
  31. nab_python/_vendor/packaging/_musllinux.py +88 -0
  32. nab_python/_vendor/packaging/_parser.py +394 -0
  33. nab_python/_vendor/packaging/_structures.py +33 -0
  34. nab_python/_vendor/packaging/_tokenizer.py +196 -0
  35. nab_python/_vendor/packaging/dependency_groups.py +302 -0
  36. nab_python/_vendor/packaging/direct_url.py +325 -0
  37. nab_python/_vendor/packaging/errors.py +94 -0
  38. nab_python/_vendor/packaging/licenses/__init__.py +186 -0
  39. nab_python/_vendor/packaging/licenses/_spdx.py +799 -0
  40. nab_python/_vendor/packaging/markers.py +506 -0
  41. nab_python/_vendor/packaging/metadata.py +964 -0
  42. nab_python/_vendor/packaging/py.typed +0 -0
  43. nab_python/_vendor/packaging/pylock.py +910 -0
  44. nab_python/_vendor/packaging/ranges.py +1803 -0
  45. nab_python/_vendor/packaging/requirements.py +132 -0
  46. nab_python/_vendor/packaging/specifiers.py +1141 -0
  47. nab_python/_vendor/packaging/tags.py +929 -0
  48. nab_python/_vendor/packaging/utils.py +296 -0
  49. nab_python/_vendor/packaging/version.py +1230 -0
  50. nab_python/build_backend.py +184 -0
  51. nab_python/config.py +805 -0
  52. nab_python/download.py +170 -0
  53. nab_python/fetch.py +827 -0
  54. nab_python/lockfile.py +238 -0
  55. nab_python/metadata.py +145 -0
  56. nab_python/provider.py +1235 -0
  57. nab_python/py.typed +0 -0
  58. nab_python/requirements_file.py +180 -0
  59. nab_python/resolve.py +497 -0
  60. nab_python/universal/__init__.py +1 -0
  61. nab_python/universal/matrix.py +235 -0
  62. nab_python/universal/provider.py +214 -0
  63. nab_python/universal/reresolve.py +310 -0
  64. nab_python/universal/resolve.py +508 -0
  65. nab_python/universal/validate.py +439 -0
  66. nab_python/universal/wheel_selection.py +327 -0
  67. nab_python/workspace.py +214 -0
  68. nab_python-0.0.1.dist-info/METADATA +49 -0
  69. nab_python-0.0.1.dist-info/RECORD +71 -0
  70. nab_python-0.0.1.dist-info/WHEEL +4 -0
  71. nab_python-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,184 @@
1
+ """Metadata extraction for source trees.
2
+
3
+ Hand a source directory to :func:`extract_metadata` and get back a
4
+ :class:`WheelMetadata`. The static pyproject.toml reader runs first;
5
+ dynamic ``project.dependencies`` fall through to a PEP 517 backend
6
+ invocation inside :class:`~nab_python._build.env.NabBuildEnv`. The
7
+ dynamic path needs a :class:`NabProjectConfig`.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from functools import lru_cache
14
+ from typing import TYPE_CHECKING
15
+
16
+ from ._build.errors import (
17
+ BuildBackendError as BuildBackendError, # noqa: PLC0414 (public re-export)
18
+ )
19
+ from ._vendor.packaging.requirements import Requirement
20
+ from ._vendor.packaging.specifiers import SpecifierSet
21
+ from ._vendor.packaging.utils import canonicalize_name
22
+ from ._vendor.packaging.version import InvalidVersion, Version
23
+ from .metadata import WheelMetadata, load_static_project
24
+
25
+ if TYPE_CHECKING:
26
+ from pathlib import Path
27
+
28
+ from .config import NabProjectConfig
29
+
30
+ __all__ = [
31
+ "BuildBackendError",
32
+ "extract_metadata",
33
+ "extract_static_metadata",
34
+ ]
35
+
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ @lru_cache(maxsize=4096)
41
+ def extract_static_metadata(source_dir: Path) -> WheelMetadata | None:
42
+ """Build :class:`WheelMetadata` from a directory's static pyproject.toml.
43
+
44
+ Returns ``None`` when ``[project]`` is missing, malformed, or
45
+ ``project.dynamic`` includes ``dependencies`` /
46
+ ``optional-dependencies`` (the field cannot be trusted as static).
47
+ Returns a :class:`WheelMetadata` shape when the static fields are
48
+ authoritative, populating ``name``, ``version``,
49
+ ``requires_python``, ``requires_dist``, and ``provides_extra``.
50
+
51
+ The returned ``provides_extra`` includes both the lower-cased
52
+ keys of ``project.optional-dependencies`` and any extras declared
53
+ via PEP 685 markers in ``requires-dist``. PEP 685 normalisation
54
+ is applied to all extra names.
55
+
56
+ Cached per ``source_dir``: the static fields are deterministic
57
+ derivations of the on-disk ``pyproject.toml``, and a universal
58
+ resolve reads the same workspace member's file once per matrix
59
+ tuple. Callers must treat the returned :class:`WheelMetadata`
60
+ as read-only.
61
+ """
62
+ pyproject = source_dir / "pyproject.toml"
63
+ if not pyproject.is_file():
64
+ return None
65
+ try:
66
+ text = pyproject.read_text(encoding="utf-8")
67
+ except OSError:
68
+ # ``is_file`` is racy: the file may vanish or become unreadable
69
+ # between the check and the read. Treat it the same as missing.
70
+ return None
71
+ project = load_static_project(text)
72
+ if project is None:
73
+ return None
74
+ return _project_to_metadata(project)
75
+
76
+
77
+ def _project_to_metadata(project: dict) -> WheelMetadata | None:
78
+ """Convert a static ``[project]`` table to :class:`WheelMetadata`."""
79
+ name = project.get("name")
80
+ version_raw = project.get("version")
81
+ if not isinstance(name, str) or not isinstance(version_raw, str):
82
+ return None
83
+ try:
84
+ version = Version(version_raw)
85
+ except InvalidVersion:
86
+ return None
87
+ requires_python_raw = project.get("requires-python")
88
+ requires_python = (
89
+ SpecifierSet(requires_python_raw)
90
+ if isinstance(requires_python_raw, str)
91
+ else None
92
+ )
93
+ return WheelMetadata(
94
+ name=canonicalize_name(name),
95
+ version=version,
96
+ requires_python=requires_python,
97
+ requires_dist=_collect_requires_dist(project),
98
+ provides_extra=sorted(_collect_provides_extra(project)),
99
+ )
100
+
101
+
102
+ def _collect_requires_dist(project: dict) -> list[Requirement]:
103
+ """Concatenate PEP 631 ``dependencies`` + ``optional-dependencies``.
104
+
105
+ Optional-dependencies entries get an ``; extra == "name"`` marker
106
+ appended (combined with any existing marker via ``and``).
107
+ """
108
+ out: list[Requirement] = []
109
+ _extend_with_dep_strings(out, project.get("dependencies"))
110
+ optional = project.get("optional-dependencies", {})
111
+ if isinstance(optional, dict):
112
+ for raw_extra, deps in sorted(optional.items()):
113
+ extra = canonicalize_name(str(raw_extra))
114
+ _extend_with_dep_strings(out, deps, extra=extra)
115
+ return out
116
+
117
+
118
+ def _extend_with_dep_strings(
119
+ out: list[Requirement],
120
+ raw: object,
121
+ *,
122
+ extra: str | None = None,
123
+ ) -> None:
124
+ if not isinstance(raw, list):
125
+ return
126
+ # Late import: ``pypi`` imports this module at module load.
127
+ from .provider import _add_extra_marker # noqa: PLC0415
128
+
129
+ for dep in raw:
130
+ if not isinstance(dep, str):
131
+ continue
132
+ text = _add_extra_marker(dep, extra) if extra is not None else dep
133
+ try:
134
+ out.append(Requirement(text))
135
+ except (ValueError, TypeError):
136
+ logger.warning("skipping unparseable requirement: %s", dep)
137
+
138
+
139
+ def _collect_provides_extra(project: dict) -> set[str]:
140
+ optional = project.get("optional-dependencies", {})
141
+ if not isinstance(optional, dict):
142
+ return set()
143
+ return {canonicalize_name(str(extra)) for extra in optional}
144
+
145
+
146
+ def extract_metadata(
147
+ source_dir: Path,
148
+ *,
149
+ config: NabProjectConfig | None = None,
150
+ python_version: str | None = None,
151
+ ) -> WheelMetadata:
152
+ """Extract metadata for a source directory.
153
+
154
+ Tries the static path first (:func:`extract_static_metadata`).
155
+ When that returns ``None``, the dynamic-build path is taken:
156
+ the project's PEP 517 backend is invoked in an isolated venv
157
+ via :func:`nab_python._build.runner.run_build_backend`. That
158
+ needs a :class:`NabProjectConfig`; callers that cannot provide
159
+ one get a :class:`BuildBackendError` instead.
160
+
161
+ The build env owns its own HTTP transport (see
162
+ :class:`~nab_python._build.env.NabBuildEnv` for why); callers
163
+ do not pass one in.
164
+ """
165
+ static = extract_static_metadata(source_dir)
166
+ if static is not None:
167
+ return static
168
+ if config is None:
169
+ msg = (
170
+ "dynamic-metadata path requires a NabProjectConfig;"
171
+ f" the static reader returned None for {source_dir}."
172
+ " Pass one through ``extract_metadata`` or use a"
173
+ " build-policy that does not enter the dynamic path."
174
+ )
175
+ raise BuildBackendError(msg)
176
+ # Late import: ``_build.runner`` pulls in ``build`` and friends,
177
+ # which we should not pay for in static-only callers.
178
+ from ._build.runner import run_build_backend # noqa: PLC0415
179
+
180
+ return run_build_backend(
181
+ source_dir,
182
+ config=config,
183
+ python_version=python_version,
184
+ )