dependence 1.1.1__tar.gz → 1.1.3__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dependence
3
- Version: 1.1.1
3
+ Version: 1.1.3
4
4
  Summary: A dependency management tool for python projects
5
5
  Project-URL: Documentation, https://dependence.enorganic.org
6
6
  Project-URL: Repository, https://github.com/enorganic/dependence
@@ -95,6 +95,44 @@ def _undefined() -> Undefined:
95
95
  return UNDEFINED
96
96
 
97
97
 
98
+ cache: Any
99
+ try:
100
+ from functools import cache # type: ignore
101
+ except ImportError:
102
+ from functools import lru_cache
103
+
104
+ cache = lru_cache(maxsize=None)
105
+
106
+
107
+ def as_tuple(
108
+ user_function: Callable[..., Iterable[Any]],
109
+ ) -> Callable[..., tuple[Any, ...]]:
110
+ """
111
+ This is a decorator which will return an iterable as a tuple.
112
+ """
113
+
114
+ def wrapper(*args: Any, **kwargs: Any) -> tuple[Any, ...]:
115
+ return tuple(user_function(*args, **kwargs) or ())
116
+
117
+ return functools.update_wrapper(wrapper, user_function)
118
+
119
+
120
+ def as_cached_tuple(
121
+ maxsize: int | None = None, *, typed: bool = False
122
+ ) -> Callable[[Callable[..., Iterable[Any]]], Callable[..., tuple[Any, ...]]]:
123
+ """
124
+ This is a decorator which will return an iterable as a tuple,
125
+ and cache that tuple.
126
+
127
+ Parameters:
128
+
129
+ - maxsize (int|None) = None: The maximum number of items to cache.
130
+ - typed (bool) = False: For class methods, should the cache be distinct for
131
+ sub-classes?
132
+ """
133
+ return functools.lru_cache(maxsize=maxsize, typed=typed)(as_tuple)
134
+
135
+
98
136
  def iter_distinct(items: Iterable[Hashable]) -> Iterable:
99
137
  """
100
138
  Yield distinct elements, preserving order
@@ -418,14 +456,8 @@ def iter_configuration_files(path: str | Path) -> Iterable[Path | str]:
418
456
  yield path
419
457
 
420
458
 
421
- class _EditablePackageMetadata(TypedDict):
422
- name: str
423
- version: str
424
- editable_project_location: str
425
-
426
-
427
459
  def _iter_editable_distribution_locations() -> Iterable[tuple[str, str]]:
428
- metadata: _EditablePackageMetadata
460
+ metadata: _PackageMetadata
429
461
  for metadata in json.loads(
430
462
  check_output(
431
463
  (
@@ -453,10 +485,38 @@ def get_editable_distributions_locations() -> dict[str, str]:
453
485
  return dict(_iter_editable_distribution_locations())
454
486
 
455
487
 
488
+ class _PackageMetadata(TypedDict, total=False):
489
+ name: str
490
+ version: str
491
+ editable_project_location: str
492
+
493
+
494
+ @cache
495
+ def map_pip_list() -> dict[str, _PackageMetadata]:
496
+ names_package_metadata: dict[str, _PackageMetadata] = {}
497
+ package_metadata: _PackageMetadata
498
+ for package_metadata in json.loads(
499
+ check_output(
500
+ (
501
+ sys.executable,
502
+ "-m",
503
+ "pip",
504
+ "list",
505
+ "--format=json",
506
+ )
507
+ )
508
+ ):
509
+ names_package_metadata[normalize_name(package_metadata["name"])] = (
510
+ package_metadata
511
+ )
512
+ return names_package_metadata
513
+
514
+
456
515
  def cache_clear() -> None:
457
516
  """
458
517
  Clear distribution metadata caches
459
518
  """
519
+ map_pip_list.cache_clear()
460
520
  get_installed_distributions.cache_clear()
461
521
  get_editable_distributions_locations.cache_clear()
462
522
  is_editable.cache_clear()
@@ -482,7 +542,13 @@ def get_installed_distributions() -> dict[str, Distribution]:
482
542
  refresh_editable_distributions()
483
543
  installed: dict[str, Distribution] = {}
484
544
  for distribution in _get_distributions():
485
- installed[normalize_name(distribution.metadata["Name"])] = distribution
545
+ name: str = distribution.metadata["Name"]
546
+ if distribution.version is None:
547
+ # If no version can be found, use pip to find the version
548
+ distribution.metadata["Version"] = (
549
+ map_pip_list().get(name, {}).get("version")
550
+ )
551
+ installed[normalize_name(name)] = distribution
486
552
  return installed
487
553
 
488
554
 
@@ -975,6 +1041,10 @@ def _get_requirement_name(requirement: Requirement) -> str:
975
1041
  return normalize_name(requirement.name)
976
1042
 
977
1043
 
1044
+ @deprecated(
1045
+ "dependence._utilities.install_requirement is deprecated and will be "
1046
+ "removed in a future release."
1047
+ )
978
1048
  def install_requirement(requirement: str | Requirement) -> None:
979
1049
  """
980
1050
  Install a requirement
@@ -1079,31 +1149,15 @@ def _install_requirement(
1079
1149
  cache_clear()
1080
1150
 
1081
1151
 
1082
- def _get_requirement_distribution(
1083
- requirement: Requirement,
1152
+ def _get_installed_distribution(
1084
1153
  name: str,
1085
- *,
1086
- reinstall: bool = True,
1087
- echo: bool = False,
1088
1154
  ) -> Distribution | None:
1089
1155
  if name in _BUILTIN_DISTRIBUTION_NAMES:
1090
1156
  return None
1091
1157
  try:
1092
1158
  return get_installed_distributions()[name]
1093
1159
  except KeyError:
1094
- if not reinstall:
1095
- raise
1096
- if echo:
1097
- warn(
1098
- f'The required distribution "{name}" was not installed, '
1099
- "attempting to install it now...",
1100
- stacklevel=2,
1101
- )
1102
- # Attempt to install the requirement...
1103
- install_requirement(requirement)
1104
- return _get_requirement_distribution(
1105
- requirement, name, reinstall=False, echo=echo
1106
- )
1160
+ return None
1107
1161
 
1108
1162
 
1109
1163
  def _iter_distribution_requirements(
@@ -1140,9 +1194,7 @@ def _iter_requirement_names(
1140
1194
  # Ensure we don't follow the same requirement again, causing cyclic
1141
1195
  # recursion
1142
1196
  exclude.add(name)
1143
- distribution: Distribution | None = _get_requirement_distribution(
1144
- requirement, name, echo=echo
1145
- )
1197
+ distribution: Distribution | None = _get_installed_distribution(name)
1146
1198
  if distribution is None:
1147
1199
  return ()
1148
1200
  requirements: tuple[Requirement, ...] = tuple(
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ import os
4
5
  from collections.abc import Iterable, MutableSet
5
6
  from fnmatch import fnmatch
6
7
  from functools import partial
7
- from importlib.metadata import Distribution
8
- from importlib.metadata import distribution as _get_distribution
9
8
  from itertools import chain
10
9
  from pathlib import Path
11
10
  from typing import TYPE_CHECKING, cast
@@ -14,7 +13,6 @@ from dependence._utilities import (
14
13
  get_distribution,
15
14
  get_required_distribution_names,
16
15
  get_requirement_string_distribution_name,
17
- install_requirement,
18
16
  iter_configuration_file_requirement_strings,
19
17
  iter_configuration_files,
20
18
  iter_distinct,
@@ -22,6 +20,9 @@ from dependence._utilities import (
22
20
  normalize_name,
23
21
  )
24
22
 
23
+ if TYPE_CHECKING:
24
+ from importlib.metadata import Distribution
25
+
25
26
  _DO_NOT_PIN_DISTRIBUTION_NAMES: MutableSet[str] = {
26
27
  "importlib-metadata",
27
28
  "importlib-resources",
@@ -119,7 +120,7 @@ def get_frozen_requirements( # noqa: C901
119
120
  elif not isinstance(no_version, tuple):
120
121
  no_version = tuple(no_version)
121
122
  # Separate requirement strings from requirement files
122
- configuration_files: MutableSet[str] = set()
123
+ configuration_files: dict[str, dict[str, tuple[str, ...]]] = {}
123
124
  requirement_strings: MutableSet[str] = set()
124
125
  requirement: str | Path
125
126
  for requirement in requirements:
@@ -129,21 +130,38 @@ def get_frozen_requirements( # noqa: C901
129
130
  iter_configuration_files(requirement)
130
131
  )
131
132
  if requirement_configuration_files:
132
- configuration_files |= requirement_configuration_files
133
+ is_directory: bool = os.path.isdir(requirement)
134
+ for (
135
+ requirement_configuration_file
136
+ ) in requirement_configuration_files:
137
+ configuration_files[requirement_configuration_file] = (
138
+ {"include_pointers": ("/project",)}
139
+ if (
140
+ is_directory
141
+ and os.path.basename(
142
+ requirement_configuration_file
143
+ ).lower()
144
+ == "pyproject.toml"
145
+ )
146
+ else {
147
+ "include_pointers": include_pointers,
148
+ "exclude_pointers": exclude_pointers,
149
+ }
150
+ )
133
151
  else:
134
152
  if requirement.startswith("setup.py"):
135
153
  raise ValueError(requirement)
136
154
  requirement_strings.add(requirement)
155
+ configuration_file: str
156
+ kwargs: dict[str, tuple[str, ...]]
137
157
  frozen_requirements: Iterable[str] = iter_distinct(
138
158
  chain(
139
159
  requirement_strings,
140
- *map(
141
- partial(
142
- iter_configuration_file_requirement_strings,
143
- include_pointers=include_pointers,
144
- exclude_pointers=exclude_pointers,
145
- ),
146
- configuration_files,
160
+ *(
161
+ iter_configuration_file_requirement_strings(
162
+ configuration_file, **kwargs
163
+ )
164
+ for configuration_file, kwargs in configuration_files.items()
147
165
  ),
148
166
  )
149
167
  )
@@ -200,7 +218,7 @@ def _iter_frozen_requirements(
200
218
  no_version: Iterable[str] = (),
201
219
  depth: int | None = None,
202
220
  ) -> Iterable[str]:
203
- def get_requirement_string(distribution_name: str) -> str:
221
+ def get_requirement_string(distribution_name: str) -> str | None:
204
222
  def distribution_name_matches_pattern(pattern: str) -> bool:
205
223
  return fnmatch(distribution_name, pattern)
206
224
 
@@ -212,9 +230,11 @@ def _iter_frozen_requirements(
212
230
  try:
213
231
  distribution = get_distribution(distribution_name)
214
232
  except KeyError:
233
+ # If the distribution is not installed, skip it
234
+ return None
215
235
  # If the distribution is missing, install it
216
- install_requirement(distribution_name)
217
- distribution = _get_distribution(distribution_name)
236
+ # install_requirement(distribution_name)
237
+ # distribution = _get_distribution(distribution_name)
218
238
  return f"{distribution.metadata['Name']}=={distribution.version}"
219
239
 
220
240
  def get_required_distribution_names_(
@@ -249,7 +269,7 @@ def _iter_frozen_requirements(
249
269
  )
250
270
  ),
251
271
  )
252
- return map(get_requirement_string, requirements)
272
+ return filter(None, map(get_requirement_string, requirements))
253
273
 
254
274
 
255
275
  def freeze(
@@ -133,10 +133,18 @@ def _get_updated_requirement_string(
133
133
  return requirement_string
134
134
  try:
135
135
  distribution: Distribution = get_installed_distributions()[name]
136
+ if distribution.version is None:
137
+ return requirement_string
136
138
  _update_requirement_specifiers(requirement, distribution.version)
137
139
  except KeyError:
138
140
  # If the requirement isn't installed, we can't update the version
139
141
  pass
142
+ except TypeError as error:
143
+ message: str = (
144
+ f"Unable to determine installed version for {requirement_string}: "
145
+ f"{distribution!r}"
146
+ )
147
+ raise ValueError(message) from error
140
148
  return str(requirement)
141
149
 
142
150
 
@@ -1,23 +1,16 @@
1
1
  [build-system]
2
- requires = [
3
- "hatchling",
4
- ]
2
+ requires = ["hatchling"]
5
3
  build-backend = "hatchling.build"
6
4
 
7
5
  [project]
8
6
  name = "dependence"
9
- version = "1.1.1"
7
+ version = "1.1.3"
10
8
  description = "A dependency management tool for python projects"
11
9
  readme = "README.md"
12
10
  license = "MIT"
13
11
  requires-python = "~=3.9"
14
- authors = [
15
- { email = "david@belais.me" },
16
- ]
17
- keywords = [
18
- "dependencies",
19
- "requirements",
20
- ]
12
+ authors = [{ email = "david@belais.me" }]
13
+ keywords = ["dependencies", "requirements"]
21
14
  dependencies = [
22
15
  "packaging",
23
16
  "pip",
@@ -35,31 +28,17 @@ Documentation = "https://dependence.enorganic.org"
35
28
  Repository = "https://github.com/enorganic/dependence"
36
29
 
37
30
  [tool.hatch.build.targets.sdist]
38
- packages = [
39
- "src/dependence",
40
- ]
41
- sources = [
42
- "src",
43
- ]
31
+ packages = ["src/dependence"]
32
+ sources = ["src"]
44
33
 
45
34
  [tool.hatch.build.targets.wheel]
46
- packages = [
47
- "src/dependence",
48
- ]
49
- sources = [
50
- "src",
51
- ]
35
+ packages = ["src/dependence"]
36
+ sources = ["src"]
52
37
 
53
38
  [tool.hatch.envs.default]
54
39
  python = "3.9"
55
- dependencies = [
56
- "mypy",
57
- "pytest",
58
- "ruff",
59
- ]
60
- pre-install-commands = [
61
- "pip install --upgrade pip setuptools",
62
- ]
40
+ dependencies = ["mypy", "pytest", "ruff"]
41
+ pre-install-commands = ["pip install --upgrade pip setuptools"]
63
42
  post-install-commands = [
64
43
  "hatch run mypy --install-types --non-interactive || echo",
65
44
  ]
@@ -70,11 +49,7 @@ lint = "ruff check . && ruff format --check . && mypy"
70
49
  [tool.hatch.envs.docs]
71
50
  template = "docs"
72
51
  python = "3.13"
73
- dependencies = [
74
- "mkdocs-material",
75
- "mkdocstrings[python]",
76
- "black",
77
- ]
52
+ dependencies = ["mkdocs-material", "mkdocstrings[python]", "black"]
78
53
 
79
54
  [tool.hatch.envs.hatch-static-analysis]
80
55
  skip-install = false
@@ -82,37 +57,17 @@ skip-install = false
82
57
  [tool.hatch.envs.hatch-test]
83
58
  template = "hatch-test"
84
59
  extra-dependencies = []
85
- extra-arguments = [
86
- "--cache-clear",
87
- ]
60
+ extra-arguments = ["--cache-clear"]
88
61
 
89
62
  [[tool.hatch.envs.hatch-test.matrix]]
90
- python = [
91
- "3.9",
92
- "3.10",
93
- "3.11",
94
- "3.12",
95
- "3.13",
96
- ]
63
+ python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
97
64
 
98
65
  [tool.ruff]
99
66
  line-length = 79
100
67
 
101
68
  [tool.ruff.lint]
102
- ignore = [
103
- "F842",
104
- "INP001",
105
- ]
106
- extend-select = [
107
- "E",
108
- "F",
109
- "UP",
110
- "B",
111
- "SIM",
112
- "I",
113
- "C",
114
- "N",
115
- ]
69
+ ignore = ["F842", "INP001"]
70
+ extend-select = ["E", "F", "UP", "B", "SIM", "I", "C", "N"]
116
71
 
117
72
  [tool.ruff.lint.mccabe]
118
73
  max-complexity = 10
@@ -123,38 +78,21 @@ docstring-code-line-length = 20
123
78
 
124
79
  [tool.black]
125
80
  line-length = 79
126
- target-version = [
127
- "py39",
128
- "py310",
129
- "py311",
130
- "py312",
131
- "py313",
132
- ]
81
+ target-version = ["py39", "py310", "py311", "py312", "py313"]
133
82
 
134
83
  [tool.mypy]
135
84
  python_version = "3.9"
136
- files = [
137
- "src",
138
- "tests",
139
- ]
140
- exclude = [
141
- "tests/test_projects",
142
- ]
85
+ files = ["src", "tests"]
86
+ exclude = ["tests/test_projects"]
143
87
  disallow_untyped_defs = true
144
88
  disallow_incomplete_defs = true
145
89
 
146
90
  [tool.coverage.run]
147
- include = [
148
- "src/**/*.py",
149
- ]
150
- omit = [
151
- "src/**/_*.py",
152
- ]
91
+ include = ["src/**/*.py"]
92
+ omit = ["src/**/_*.py"]
153
93
 
154
94
  [tool.coverage.paths]
155
- source = [
156
- "src/**",
157
- ]
95
+ source = ["src/**"]
158
96
 
159
97
  [tool.coverage.report]
160
98
  fail_under = 70
File without changes
File without changes