dependence 1.1.2__tar.gz → 1.2.0__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.2
3
+ Version: 1.2.0
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
@@ -4,6 +4,7 @@ import functools
4
4
  import json
5
5
  import os
6
6
  import re
7
+ import shutil
7
8
  import sys
8
9
  from collections import deque
9
10
  from collections.abc import Container, Hashable, Iterable, MutableSet
@@ -95,6 +96,44 @@ def _undefined() -> Undefined:
95
96
  return UNDEFINED
96
97
 
97
98
 
99
+ cache: Any
100
+ try:
101
+ from functools import cache # type: ignore
102
+ except ImportError:
103
+ from functools import lru_cache
104
+
105
+ cache = lru_cache(maxsize=None)
106
+
107
+
108
+ def as_tuple(
109
+ user_function: Callable[..., Iterable[Any]],
110
+ ) -> Callable[..., tuple[Any, ...]]:
111
+ """
112
+ This is a decorator which will return an iterable as a tuple.
113
+ """
114
+
115
+ def wrapper(*args: Any, **kwargs: Any) -> tuple[Any, ...]:
116
+ return tuple(user_function(*args, **kwargs) or ())
117
+
118
+ return functools.update_wrapper(wrapper, user_function)
119
+
120
+
121
+ def as_cached_tuple(
122
+ maxsize: int | None = None, *, typed: bool = False
123
+ ) -> Callable[[Callable[..., Iterable[Any]]], Callable[..., tuple[Any, ...]]]:
124
+ """
125
+ This is a decorator which will return an iterable as a tuple,
126
+ and cache that tuple.
127
+
128
+ Parameters:
129
+
130
+ - maxsize (int|None) = None: The maximum number of items to cache.
131
+ - typed (bool) = False: For class methods, should the cache be distinct for
132
+ sub-classes?
133
+ """
134
+ return functools.lru_cache(maxsize=maxsize, typed=typed)(as_tuple)
135
+
136
+
98
137
  def iter_distinct(items: Iterable[Hashable]) -> Iterable:
99
138
  """
100
139
  Yield distinct elements, preserving order
@@ -418,47 +457,75 @@ def iter_configuration_files(path: str | Path) -> Iterable[Path | str]:
418
457
  yield path
419
458
 
420
459
 
421
- class _EditablePackageMetadata(TypedDict):
460
+ def _iter_editable_project_locations() -> Iterable[tuple[str, str]]:
461
+ metadata: PackageMetadata
462
+ for name, metadata in map_pip_list().items():
463
+ editable_project_location: str | None = metadata.get(
464
+ "editable_project_location"
465
+ )
466
+ if editable_project_location:
467
+ yield (
468
+ name,
469
+ editable_project_location,
470
+ )
471
+
472
+
473
+ @functools.lru_cache
474
+ def map_editable_project_locations() -> dict[str, str]:
475
+ """
476
+ Get a mapping of (normalized) editable distribution names to their
477
+ locations.
478
+ """
479
+ return dict(_iter_editable_project_locations())
480
+
481
+
482
+ class PackageMetadata(TypedDict, total=False):
422
483
  name: str
423
484
  version: str
424
485
  editable_project_location: str
425
486
 
426
487
 
427
- def _iter_editable_distribution_locations() -> Iterable[tuple[str, str]]:
428
- metadata: _EditablePackageMetadata
429
- for metadata in json.loads(
430
- check_output(
431
- (
432
- sys.executable,
433
- "-m",
434
- "pip",
435
- "list",
436
- "--editable",
437
- "--format=json",
438
- )
488
+ def _iter_pip_list() -> Iterable[tuple[str, PackageMetadata]]:
489
+ uv: str | None = shutil.which("uv")
490
+ command: tuple[str, ...]
491
+ if uv:
492
+ command = (
493
+ uv,
494
+ "pip",
495
+ "list",
496
+ "--python",
497
+ sys.executable,
498
+ "--format=json",
439
499
  )
440
- ):
500
+ else:
501
+ # If `uv` is not available, use `pip`
502
+ command = (
503
+ sys.executable,
504
+ "-m",
505
+ "pip",
506
+ "list",
507
+ "--format=json",
508
+ )
509
+ metadata: PackageMetadata
510
+ for metadata in json.loads(check_output(command)):
441
511
  yield (
442
512
  normalize_name(metadata["name"]),
443
- metadata["editable_project_location"],
513
+ metadata,
444
514
  )
445
515
 
446
516
 
447
- @functools.lru_cache
448
- def get_editable_distributions_locations() -> dict[str, str]:
449
- """
450
- Get a mapping of (normalized) editable distribution names to their
451
- locations.
452
- """
453
- return dict(_iter_editable_distribution_locations())
517
+ @cache
518
+ def map_pip_list() -> dict[str, PackageMetadata]:
519
+ return dict(_iter_pip_list())
454
520
 
455
521
 
456
522
  def cache_clear() -> None:
457
523
  """
458
524
  Clear distribution metadata caches
459
525
  """
526
+ map_pip_list.cache_clear()
460
527
  get_installed_distributions.cache_clear()
461
- get_editable_distributions_locations.cache_clear()
528
+ map_editable_project_locations.cache_clear()
462
529
  is_editable.cache_clear()
463
530
  is_installed.cache_clear()
464
531
  get_requirement_string_distribution_name.cache_clear()
@@ -470,7 +537,7 @@ def refresh_editable_distributions() -> None:
470
537
  """
471
538
  name: str
472
539
  location: str
473
- for name, location in get_editable_distributions_locations().items():
540
+ for name, location in map_editable_project_locations().items():
474
541
  _install_requirement_string(location, name=name, editable=True)
475
542
 
476
543
 
@@ -482,7 +549,13 @@ def get_installed_distributions() -> dict[str, Distribution]:
482
549
  refresh_editable_distributions()
483
550
  installed: dict[str, Distribution] = {}
484
551
  for distribution in _get_distributions():
485
- installed[normalize_name(distribution.metadata["Name"])] = distribution
552
+ name: str = distribution.metadata["Name"]
553
+ if distribution.version is None:
554
+ # If no version can be found, use pip to find the version
555
+ distribution.metadata["Version"] = (
556
+ map_pip_list().get(name, {}).get("version")
557
+ )
558
+ installed[normalize_name(name)] = distribution
486
559
  return installed
487
560
 
488
561
 
@@ -751,7 +824,7 @@ def is_editable(name: str) -> bool:
751
824
  """
752
825
  Return `True` if the indicated distribution is an editable installation.
753
826
  """
754
- return bool(normalize_name(name) in get_editable_distributions_locations())
827
+ return bool(normalize_name(name) in map_editable_project_locations())
755
828
 
756
829
 
757
830
  def _get_setup_cfg_metadata(path: str, key: str) -> str:
@@ -880,7 +953,7 @@ def _setup_location(
880
953
 
881
954
 
882
955
  def get_editable_distribution_location(name: str) -> str:
883
- return get_editable_distributions_locations().get(normalize_name(name), "")
956
+ return map_editable_project_locations().get(normalize_name(name), "")
884
957
 
885
958
 
886
959
  def setup_egg_info(directory: str | Path, egg_base: str = "") -> None:
@@ -975,6 +1048,10 @@ def _get_requirement_name(requirement: Requirement) -> str:
975
1048
  return normalize_name(requirement.name)
976
1049
 
977
1050
 
1051
+ @deprecated(
1052
+ "dependence._utilities.install_requirement is deprecated and will be "
1053
+ "removed in a future release."
1054
+ )
978
1055
  def install_requirement(requirement: str | Requirement) -> None:
979
1056
  """
980
1057
  Install a requirement
@@ -1000,21 +1077,44 @@ def _install_requirement_string(
1000
1077
  Install a requirement string with no dependencies, compilation, build
1001
1078
  isolation, etc.
1002
1079
  """
1003
- command: tuple[str, ...] = (
1004
- sys.executable,
1005
- "-m",
1006
- "pip",
1007
- "install",
1008
- "--no-deps",
1009
- "--no-compile",
1010
- )
1011
- if editable:
1012
- command += (
1013
- "-e",
1014
- requirement_string,
1080
+ command: tuple[str, ...]
1081
+ uv: str | None = shutil.which("uv")
1082
+ if uv:
1083
+ command = (
1084
+ uv,
1085
+ "pip",
1086
+ "install",
1087
+ "--python",
1088
+ sys.executable,
1089
+ "--no-deps",
1090
+ "--no-compile",
1091
+ *(
1092
+ (
1093
+ "-e",
1094
+ requirement_string,
1095
+ )
1096
+ if editable
1097
+ else (requirement_string,)
1098
+ ),
1015
1099
  )
1016
1100
  else:
1017
- command += (requirement_string,)
1101
+ # If `uv` is not available, use `pip`
1102
+ command = (
1103
+ sys.executable,
1104
+ "-m",
1105
+ "pip",
1106
+ "install",
1107
+ "--no-deps",
1108
+ "--no-compile",
1109
+ *(
1110
+ (
1111
+ "-e",
1112
+ requirement_string,
1113
+ )
1114
+ if editable
1115
+ else (requirement_string,)
1116
+ ),
1117
+ )
1018
1118
  try:
1019
1119
  check_output(command)
1020
1120
  except CalledProcessError as error:
@@ -1079,31 +1179,15 @@ def _install_requirement(
1079
1179
  cache_clear()
1080
1180
 
1081
1181
 
1082
- def _get_requirement_distribution(
1083
- requirement: Requirement,
1182
+ def _get_installed_distribution(
1084
1183
  name: str,
1085
- *,
1086
- reinstall: bool = True,
1087
- echo: bool = False,
1088
1184
  ) -> Distribution | None:
1089
1185
  if name in _BUILTIN_DISTRIBUTION_NAMES:
1090
1186
  return None
1091
1187
  try:
1092
1188
  return get_installed_distributions()[name]
1093
1189
  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
- )
1190
+ return None
1107
1191
 
1108
1192
 
1109
1193
  def _iter_distribution_requirements(
@@ -1140,9 +1224,7 @@ def _iter_requirement_names(
1140
1224
  # Ensure we don't follow the same requirement again, causing cyclic
1141
1225
  # recursion
1142
1226
  exclude.add(name)
1143
- distribution: Distribution | None = _get_requirement_distribution(
1144
- requirement, name, echo=echo
1145
- )
1227
+ distribution: Distribution | None = _get_installed_distribution(name)
1146
1228
  if distribution is None:
1147
1229
  return ()
1148
1230
  requirements: tuple[Requirement, ...] = tuple(
@@ -5,8 +5,6 @@ import os
5
5
  from collections.abc import Iterable, MutableSet
6
6
  from fnmatch import fnmatch
7
7
  from functools import partial
8
- from importlib.metadata import Distribution
9
- from importlib.metadata import distribution as _get_distribution
10
8
  from itertools import chain
11
9
  from pathlib import Path
12
10
  from typing import TYPE_CHECKING, cast
@@ -15,7 +13,6 @@ from dependence._utilities import (
15
13
  get_distribution,
16
14
  get_required_distribution_names,
17
15
  get_requirement_string_distribution_name,
18
- install_requirement,
19
16
  iter_configuration_file_requirement_strings,
20
17
  iter_configuration_files,
21
18
  iter_distinct,
@@ -23,6 +20,9 @@ from dependence._utilities import (
23
20
  normalize_name,
24
21
  )
25
22
 
23
+ if TYPE_CHECKING:
24
+ from importlib.metadata import Distribution
25
+
26
26
  _DO_NOT_PIN_DISTRIBUTION_NAMES: MutableSet[str] = {
27
27
  "importlib-metadata",
28
28
  "importlib-resources",
@@ -218,7 +218,7 @@ def _iter_frozen_requirements(
218
218
  no_version: Iterable[str] = (),
219
219
  depth: int | None = None,
220
220
  ) -> Iterable[str]:
221
- def get_requirement_string(distribution_name: str) -> str:
221
+ def get_requirement_string(distribution_name: str) -> str | None:
222
222
  def distribution_name_matches_pattern(pattern: str) -> bool:
223
223
  return fnmatch(distribution_name, pattern)
224
224
 
@@ -230,9 +230,11 @@ def _iter_frozen_requirements(
230
230
  try:
231
231
  distribution = get_distribution(distribution_name)
232
232
  except KeyError:
233
+ # If the distribution is not installed, skip it
234
+ return None
233
235
  # If the distribution is missing, install it
234
- install_requirement(distribution_name)
235
- distribution = _get_distribution(distribution_name)
236
+ # install_requirement(distribution_name)
237
+ # distribution = _get_distribution(distribution_name)
236
238
  return f"{distribution.metadata['Name']}=={distribution.version}"
237
239
 
238
240
  def get_required_distribution_names_(
@@ -267,7 +269,7 @@ def _iter_frozen_requirements(
267
269
  )
268
270
  ),
269
271
  )
270
- return map(get_requirement_string, requirements)
272
+ return filter(None, map(get_requirement_string, requirements))
271
273
 
272
274
 
273
275
  def freeze(
@@ -133,12 +133,17 @@ 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
140
142
  except TypeError as error:
141
- message = f"Unable to determine installed version for {requirement}"
143
+ message: str = (
144
+ f"Unable to determine installed version for {requirement_string}: "
145
+ f"{distribution!r}"
146
+ )
142
147
  raise ValueError(message) from error
143
148
  return str(requirement)
144
149
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dependence"
7
- version = "1.1.2"
7
+ version = "1.2.0"
8
8
  description = "A dependency management tool for python projects"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -29,11 +29,15 @@ Repository = "https://github.com/enorganic/dependence"
29
29
 
30
30
  [tool.hatch.build.targets.sdist]
31
31
  packages = ["src/dependence"]
32
- sources = ["src"]
32
+
33
+ [tool.hatch.build.targets.sdist.sources]
34
+ src = ""
33
35
 
34
36
  [tool.hatch.build.targets.wheel]
35
37
  packages = ["src/dependence"]
36
- sources = ["src"]
38
+
39
+ [tool.hatch.build.targets.wheel.sources]
40
+ src = ""
37
41
 
38
42
  [tool.hatch.envs.default]
39
43
  python = "3.9"
@@ -56,7 +60,7 @@ skip-install = false
56
60
 
57
61
  [tool.hatch.envs.hatch-test]
58
62
  template = "hatch-test"
59
- extra-dependencies = []
63
+ extra-dependencies = ["ruff", "mypy", "flake8", "tox", "black"]
60
64
  extra-arguments = ["--cache-clear"]
61
65
 
62
66
  [[tool.hatch.envs.hatch-test.matrix]]
File without changes
File without changes