dependence 1.0.4__py3-none-any.whl → 1.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.
dependence/freeze.py CHANGED
@@ -1,18 +1,22 @@
1
+ from __future__ import annotations
2
+
1
3
  import argparse
4
+ from collections.abc import Iterable, MutableSet
2
5
  from fnmatch import fnmatch
3
6
  from functools import partial
4
7
  from importlib.metadata import Distribution
5
8
  from importlib.metadata import distribution as _get_distribution
6
9
  from itertools import chain
7
- from typing import Dict, Iterable, MutableSet, Optional, Tuple, cast
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, cast
8
12
 
9
- from ._utilities import (
13
+ from dependence._utilities import (
10
14
  get_distribution,
11
15
  get_required_distribution_names,
12
16
  get_requirement_string_distribution_name,
13
17
  install_requirement,
14
- is_configuration_file,
15
18
  iter_configuration_file_requirement_strings,
19
+ iter_configuration_files,
16
20
  iter_distinct,
17
21
  iter_parse_delimited_values,
18
22
  normalize_name,
@@ -30,18 +34,18 @@ def _iter_sort_dependents_last(requirements: Iterable[str]) -> Iterable[str]:
30
34
  """
31
35
  requirements = list(requirements)
32
36
  distribution_name: str
33
- distribution_requirement: Dict[str, str] = {
37
+ distribution_requirement: dict[str, str] = {
34
38
  get_requirement_string_distribution_name(requirement): requirement
35
39
  for requirement in requirements
36
40
  }
37
- dependent_dependencies: Dict[str, MutableSet[str]] = {
41
+ dependent_dependencies: dict[str, MutableSet[str]] = {
38
42
  distribution_name: get_required_distribution_names(requirement)
39
43
  for distribution_name, requirement in distribution_requirement.items()
40
44
  }
41
45
  while dependent_dependencies:
42
46
  dependent: str
43
47
  dependencies: MutableSet[str]
44
- item: Tuple[str, MutableSet[str]]
48
+ item: tuple[str, MutableSet[str]]
45
49
  for dependent, dependencies in sorted( # noqa: C414
46
50
  tuple(dependent_dependencies.items()),
47
51
  key=lambda item: item[0].lower(),
@@ -71,17 +75,18 @@ def _iter_sort_dependents_last(requirements: Iterable[str]) -> Iterable[str]:
71
75
  del dependent_dependencies[dependent]
72
76
 
73
77
 
74
- def get_frozen_requirements(
75
- requirements: Iterable[str] = (),
78
+ def get_frozen_requirements( # noqa: C901
79
+ requirements: Iterable[str | Path] = (),
80
+ *,
76
81
  exclude: Iterable[str] = (),
77
82
  exclude_recursive: Iterable[str] = (),
78
83
  no_version: Iterable[str] = (),
79
84
  dependency_order: bool = False,
80
85
  reverse: bool = False,
81
- depth: Optional[int] = None,
82
- include_pointers: Tuple[str, ...] = (),
83
- exclude_pointers: Tuple[str, ...] = (),
84
- ) -> Tuple[str, ...]:
86
+ depth: int | None = None,
87
+ include_pointers: tuple[str, ...] = (),
88
+ exclude_pointers: tuple[str, ...] = (),
89
+ ) -> tuple[str, ...]:
85
90
  """
86
91
  Get the (frozen) requirements for one or more specified distributions or
87
92
  configuration files.
@@ -105,21 +110,30 @@ def get_frozen_requirements(
105
110
  exclude_pointers: A tuple of JSON pointers indicating elements to
106
111
  exclude (defaults to no exclusions). Only applies to TOML files.
107
112
  """
108
- # Separate requirement strings from requirement files
109
- if isinstance(requirements, str):
110
- requirements = {requirements}
113
+ if isinstance(requirements, (str, Path)):
114
+ requirements = {str(requirements)}
111
115
  else:
112
- requirements = set(requirements)
116
+ requirements = set(map(str, requirements))
113
117
  if isinstance(no_version, str):
114
118
  no_version = (no_version,)
115
119
  elif not isinstance(no_version, tuple):
116
120
  no_version = tuple(no_version)
117
- requirement_files: MutableSet[str] = set(
118
- filter(is_configuration_file, requirements)
119
- )
120
- requirement_strings: MutableSet[str] = cast(
121
- MutableSet[str], requirements - requirement_files
122
- )
121
+ # Separate requirement strings from requirement files
122
+ configuration_files: MutableSet[str] = set()
123
+ requirement_strings: MutableSet[str] = set()
124
+ requirement: str | Path
125
+ for requirement in requirements:
126
+ if TYPE_CHECKING:
127
+ assert isinstance(requirement, str)
128
+ requirement_configuration_files: set[str] = set(
129
+ iter_configuration_files(requirement)
130
+ )
131
+ if requirement_configuration_files:
132
+ configuration_files |= requirement_configuration_files
133
+ else:
134
+ if requirement.startswith("setup.py"):
135
+ raise ValueError(requirement)
136
+ requirement_strings.add(requirement)
123
137
  frozen_requirements: Iterable[str] = iter_distinct(
124
138
  chain(
125
139
  requirement_strings,
@@ -129,10 +143,11 @@ def get_frozen_requirements(
129
143
  include_pointers=include_pointers,
130
144
  exclude_pointers=exclude_pointers,
131
145
  ),
132
- requirement_files,
146
+ configuration_files,
133
147
  ),
134
148
  )
135
149
  )
150
+ frozen_requirements = tuple(frozen_requirements)
136
151
  if depth is not None:
137
152
  depth -= 1
138
153
  if (depth is None) or depth >= 0:
@@ -183,7 +198,7 @@ def _iter_frozen_requirements(
183
198
  exclude: MutableSet[str],
184
199
  exclude_recursive: MutableSet[str],
185
200
  no_version: Iterable[str] = (),
186
- depth: Optional[int] = None,
201
+ depth: int | None = None,
187
202
  ) -> Iterable[str]:
188
203
  def get_requirement_string(distribution_name: str) -> str:
189
204
  def distribution_name_matches_pattern(pattern: str) -> bool:
@@ -198,13 +213,13 @@ def _iter_frozen_requirements(
198
213
  distribution = get_distribution(distribution_name)
199
214
  except KeyError:
200
215
  # If the distribution is missing, install it
201
- install_requirement(distribution_name, echo=False)
216
+ install_requirement(distribution_name)
202
217
  distribution = _get_distribution(distribution_name)
203
218
  return f"{distribution.metadata['Name']}=={distribution.version}"
204
219
 
205
220
  def get_required_distribution_names_(
206
221
  requirement_string: str,
207
- depth_: Optional[int] = None,
222
+ depth_: int | None = None,
208
223
  ) -> MutableSet[str]:
209
224
  name: str = get_requirement_string_distribution_name(
210
225
  requirement_string
@@ -234,20 +249,20 @@ def _iter_frozen_requirements(
234
249
  )
235
250
  ),
236
251
  )
237
- requirements = map(get_requirement_string, requirements)
238
- return requirements
252
+ return map(get_requirement_string, requirements)
239
253
 
240
254
 
241
255
  def freeze(
242
- requirements: Iterable[str] = (),
256
+ requirements: Iterable[str | Path] = (),
257
+ *,
243
258
  exclude: Iterable[str] = (),
244
259
  exclude_recursive: Iterable[str] = (),
245
260
  no_version: Iterable[str] = (),
246
261
  dependency_order: bool = False,
247
262
  reverse: bool = False,
248
- depth: Optional[int] = None,
249
- include_pointers: Tuple[str, ...] = (),
250
- exclude_pointers: Tuple[str, ...] = (),
263
+ depth: int | None = None,
264
+ include_pointers: tuple[str, ...] = (),
265
+ exclude_pointers: tuple[str, ...] = (),
251
266
  ) -> None:
252
267
  """
253
268
  Print the (frozen) requirements for one or more specified requirements or
@@ -259,9 +274,10 @@ def freeze(
259
274
  paths to a setup.py, setup.cfg, pyproject.toml, tox.ini or
260
275
  requirements.txt file
261
276
  exclude: One or more distributions to exclude/ignore
262
- exclude_recursive: One or more distributions to exclude/ignore.
263
- Note: Excluding a distribution here halts recursive
264
- discovery of requirements.
277
+ exclude_recursive: One or more distributions to exclude.
278
+ exclude_recursive: One or more distributions to exclude. Recursive
279
+ dependency discovery is also halted for these distributions,
280
+ unlike those passed to `exclude`.
265
281
  no_version: Exclude version numbers from the output
266
282
  (only print distribution names) for package names matching any of
267
283
  these patterns
@@ -273,7 +289,7 @@ def freeze(
273
289
  exclude_pointers: If not empty, these TOML tables will *not* be
274
290
  inspected (for pyproject.toml files)
275
291
  """
276
- print(
292
+ print( # noqa: T201
277
293
  "\n".join(
278
294
  get_frozen_requirements(
279
295
  requirements=requirements,
dependence/update.py CHANGED
@@ -1,23 +1,19 @@
1
+ from __future__ import annotations
2
+
1
3
  import argparse
2
4
  import re
3
5
  from collections import deque
4
6
  from configparser import ConfigParser, SectionProxy
5
7
  from copy import deepcopy
6
8
  from dataclasses import dataclass
7
- from importlib.metadata import Distribution
8
9
  from io import StringIO
9
10
  from itertools import chain
11
+ from pathlib import Path
10
12
  from typing import (
11
13
  IO,
14
+ TYPE_CHECKING,
12
15
  Any,
13
16
  Callable,
14
- Dict,
15
- Iterable,
16
- List,
17
- Optional,
18
- Set,
19
- Tuple,
20
- Union,
21
17
  )
22
18
 
23
19
  import tomli
@@ -27,7 +23,7 @@ from packaging.specifiers import Specifier, SpecifierSet
27
23
  from packaging.version import Version
28
24
  from packaging.version import parse as parse_version
29
25
 
30
- from ._utilities import (
26
+ from dependence._utilities import (
31
27
  ConfigurationFileType,
32
28
  get_configuration_file_type,
33
29
  get_installed_distributions,
@@ -38,6 +34,10 @@ from ._utilities import (
38
34
  normalize_name,
39
35
  )
40
36
 
37
+ if TYPE_CHECKING:
38
+ from collections.abc import Iterable
39
+ from importlib.metadata import Distribution
40
+
41
41
 
42
42
  @dataclass
43
43
  class _Version:
@@ -48,7 +48,7 @@ class _Version:
48
48
  """
49
49
 
50
50
  epoch: int
51
- release: Tuple[int, ...]
51
+ release: tuple[int, ...]
52
52
  pre: Any
53
53
  post: Any
54
54
  dev: Any
@@ -64,13 +64,14 @@ def _update_requirement_specifiers(
64
64
  """
65
65
  installed_version: Version = parse_version(installed_version_string)
66
66
  specifier: Specifier
67
- updated_specifier_strings: List[str] = []
67
+ updated_specifier_strings: list[str] = []
68
68
  for specifier in requirement.specifier: # type: ignore
69
69
  # Only update requirement to match our installed version
70
70
  # if the requirement is *inclusive*
71
71
  if ("=" in specifier.operator) and ("!" not in specifier.operator):
72
72
  specifier_version: Version = parse_version(specifier.version)
73
- assert installed_version.release is not None
73
+ if installed_version.release is None:
74
+ raise ValueError(installed_version)
74
75
  if specifier_version.release is None:
75
76
  updated_specifier_strings.append(f"{specifier.operator}")
76
77
  else:
@@ -117,7 +118,7 @@ def _update_requirement_specifiers(
117
118
 
118
119
 
119
120
  def _get_updated_requirement_string(
120
- requirement_string: str, ignore: Set[str]
121
+ requirement_string: str, ignore: set[str]
121
122
  ) -> str:
122
123
  """
123
124
  This function updates version numbers in a requirement string to match
@@ -139,8 +140,8 @@ def _get_updated_requirement_string(
139
140
  return str(requirement)
140
141
 
141
142
 
142
- def _normalize_ignore_argument(ignore: Iterable[str]) -> Set[str]:
143
- ignore_set: Set[str]
143
+ def _normalize_ignore_argument(ignore: Iterable[str]) -> set[str]:
144
+ ignore_set: set[str]
144
145
  # Normalize/harmonize excluded project names
145
146
  if isinstance(ignore, str):
146
147
  ignore = (ignore,)
@@ -161,7 +162,7 @@ def _get_updated_requirements_txt(
161
162
  - data (str): The contents of a *requirements.txt* file
162
163
  - ignore ([str]): One or more project names to leave as-is
163
164
  """
164
- ignore_set: Set[str] = _normalize_ignore_argument(ignore)
165
+ ignore_set: set[str] = _normalize_ignore_argument(ignore)
165
166
 
166
167
  def get_updated_requirement_string(requirement: str) -> str:
167
168
  return _get_updated_requirement_string(requirement, ignore=ignore_set)
@@ -184,7 +185,7 @@ def _get_updated_setup_cfg(
184
185
  - all_extra_name (str): An (optional) extra name which will
185
186
  consolidate requirements from all other extras
186
187
  """
187
- ignore_set: Set[str] = _normalize_ignore_argument(ignore)
188
+ ignore_set: set[str] = _normalize_ignore_argument(ignore)
188
189
 
189
190
  def get_updated_requirement_string(requirement: str) -> str:
190
191
  return _get_updated_requirement_string(requirement, ignore=ignore_set)
@@ -202,10 +203,10 @@ def _get_updated_setup_cfg(
202
203
  )
203
204
  if "options.extras_require" in parser:
204
205
  extras_require: SectionProxy = parser["options.extras_require"]
205
- all_extra_requirements: List[str] = []
206
+ all_extra_requirements: list[str] = []
206
207
  extra_name: str
207
208
  extra_requirements_string: str
208
- extra_requirements: List[str]
209
+ extra_requirements: list[str]
209
210
  for extra_name, extra_requirements_string in extras_require.items():
210
211
  if extra_name != all_extra_name:
211
212
  extra_requirements = list(
@@ -223,7 +224,7 @@ def _get_updated_setup_cfg(
223
224
  # We pre-pend an empty requirement string in order to]
224
225
  # force new-line creation at the beginning of the extra
225
226
  extras_require[all_extra_name] = "\n".join(
226
- iter_distinct([""] + all_extra_requirements)
227
+ iter_distinct(["", *all_extra_requirements])
227
228
  )
228
229
  # Return as a string
229
230
  setup_cfg: str
@@ -246,10 +247,10 @@ def _get_updated_tox_ini(data: str, ignore: Iterable[str] = ()) -> str:
246
247
  - data (str): The contents of a **tox.ini** file
247
248
  - ignore ([str]): One or more project names to leave as-is
248
249
  """
249
- ignore_set: Set[str] = _normalize_ignore_argument(ignore)
250
+ ignore_set: set[str] = _normalize_ignore_argument(ignore)
250
251
 
251
252
  def get_updated_requirement_string(requirement: str) -> str:
252
- prefix: Optional[str] = None
253
+ prefix: str | None = None
253
254
  if ":" in requirement:
254
255
  prefix, requirement = requirement.split(":", maxsplit=1)
255
256
  requirement = _get_updated_requirement_string(
@@ -294,18 +295,18 @@ def _get_updated_tox_ini(data: str, ignore: Iterable[str] = ()) -> str:
294
295
 
295
296
 
296
297
  def _update_document_requirements(
297
- document: Dict[str, Any],
298
+ document: dict[str, Any],
298
299
  ignore: Iterable[str] = (),
299
- include_pointers: Tuple[str, ...] = (),
300
- exclude_pointers: Tuple[str, ...] = (),
300
+ include_pointers: tuple[str, ...] = (),
301
+ exclude_pointers: tuple[str, ...] = (),
301
302
  ) -> None:
302
- ignore_set: Set[str] = _normalize_ignore_argument(ignore)
303
+ ignore_set: set[str] = _normalize_ignore_argument(ignore)
303
304
 
304
305
  def get_updated_requirement_string(requirement: str) -> str:
305
306
  return _get_updated_requirement_string(requirement, ignore=ignore_set)
306
307
 
307
308
  # Find and update requirements
308
- requirements_list: List[str]
309
+ requirements_list: list[str]
309
310
  for requirements_list in iter_find_requirements_lists(
310
311
  document,
311
312
  include_pointers=include_pointers,
@@ -323,8 +324,8 @@ def _get_updated_pyproject_toml(
323
324
  data: str,
324
325
  ignore: Iterable[str] = (),
325
326
  all_extra_name: str = "",
326
- include_pointers: Tuple[str, ...] = (),
327
- exclude_pointers: Tuple[str, ...] = (),
327
+ include_pointers: tuple[str, ...] = (),
328
+ exclude_pointers: tuple[str, ...] = (),
328
329
  ) -> str:
329
330
  """
330
331
  Return the contents of a *pyproject.toml* file, updated to reflect the
@@ -345,8 +346,8 @@ def _get_updated_pyproject_toml(
345
346
  The contents of the updated pyproject.toml file.
346
347
  """
347
348
  # Parse pyproject.toml
348
- original_pyproject: Dict[str, Any] = tomli.loads(data)
349
- updated_pyproject: Dict[str, Any] = deepcopy(original_pyproject)
349
+ original_pyproject: dict[str, Any] = tomli.loads(data)
350
+ updated_pyproject: dict[str, Any] = deepcopy(original_pyproject)
350
351
  # Find and update requirements
351
352
  _update_document_requirements(
352
353
  updated_pyproject,
@@ -355,13 +356,13 @@ def _get_updated_pyproject_toml(
355
356
  exclude_pointers=exclude_pointers,
356
357
  )
357
358
  # Update consolidated optional requirements
358
- project_optional_dependencies: Dict[str, List[str]] = (
359
+ project_optional_dependencies: dict[str, list[str]] = (
359
360
  updated_pyproject.get("project", {}).get("optional-dependencies", {})
360
361
  )
361
362
  # Update an extra indicated to encompass all other extras
362
363
  if project_optional_dependencies and all_extra_name:
363
364
  key: str
364
- dependencies: List[str]
365
+ dependencies: list[str]
365
366
  project_optional_dependencies[all_extra_name] = list(
366
367
  iter_distinct(
367
368
  chain(
@@ -384,8 +385,8 @@ def _get_updated_pyproject_toml(
384
385
  def _get_updated_toml(
385
386
  data: str,
386
387
  ignore: Iterable[str] = (),
387
- include_pointers: Tuple[str, ...] = (),
388
- exclude_pointers: Tuple[str, ...] = (),
388
+ include_pointers: tuple[str, ...] = (),
389
+ exclude_pointers: tuple[str, ...] = (),
389
390
  ) -> str:
390
391
  """
391
392
  Return the contents of a TOML file, updated to reflect the
@@ -407,8 +408,8 @@ def _get_updated_toml(
407
408
  The contents of the updated TOML file.
408
409
  """
409
410
  # Parse pyproject.toml
410
- original_pyproject: Dict[str, Any] = tomli.loads(data)
411
- updated_pyproject: Dict[str, Any] = deepcopy(original_pyproject)
411
+ original_pyproject: dict[str, Any] = tomli.loads(data)
412
+ updated_pyproject: dict[str, Any] = deepcopy(original_pyproject)
412
413
  # Find and update requirements
413
414
  _update_document_requirements(
414
415
  updated_pyproject,
@@ -423,15 +424,16 @@ def _get_updated_toml(
423
424
 
424
425
 
425
426
  def _update(
426
- path: str,
427
+ path: str | Path,
427
428
  ignore: Iterable[str] = (),
428
429
  all_extra_name: str = "",
429
- include_pointers: Tuple[str, ...] = (),
430
- exclude_pointers: Tuple[str, ...] = (),
430
+ include_pointers: tuple[str, ...] = (),
431
+ exclude_pointers: tuple[str, ...] = (),
431
432
  ) -> None:
433
+ message: str
432
434
  data: str
433
435
  update_function: Callable[[str], str]
434
- kwargs: Dict[str, Union[str, Iterable[str]]] = {}
436
+ kwargs: dict[str, str | Iterable[str]] = {}
435
437
  configuration_file_type: ConfigurationFileType = (
436
438
  get_configuration_file_type(path)
437
439
  )
@@ -457,49 +459,58 @@ def _update(
457
459
  elif configuration_file_type == ConfigurationFileType.REQUIREMENTS_TXT:
458
460
  update_function = _get_updated_requirements_txt
459
461
  else:
460
- raise NotImplementedError(
461
- f"Updating requirements for {path} is not supported"
462
- )
462
+ message = f"Updating requirements for {path!s} is not supported"
463
+ raise NotImplementedError(message)
463
464
  kwargs["ignore"] = ignore
464
465
  file_io: IO[str]
465
466
  with open(path) as file_io:
466
467
  data = file_io.read()
467
468
  updated_data: str = update_function(data, **kwargs)
468
469
  if updated_data == data:
469
- print(f"All requirements were already up-to-date in {path}")
470
+ print( # noqa: T201
471
+ f"All requirements were already up-to-date in {path!s}"
472
+ )
470
473
  else:
471
- print(f"Updating requirements in {path}")
474
+ print( # noqa: T201
475
+ f"Updating requirements in {path!s}"
476
+ )
472
477
  with open(path, "w") as file_io:
473
478
  file_io.write(updated_data)
474
479
 
475
480
 
476
481
  def update(
477
- paths: Iterable[str],
482
+ paths: Iterable[str | Path],
483
+ *,
478
484
  ignore: Iterable[str] = (),
479
485
  all_extra_name: str = "",
480
- include_pointers: Tuple[str, ...] = (),
481
- exclude_pointers: Tuple[str, ...] = (),
486
+ include_pointers: tuple[str, ...] = (),
487
+ exclude_pointers: tuple[str, ...] = (),
482
488
  ) -> None:
483
489
  """
484
490
  Update requirement versions in the specified files.
485
491
 
486
492
  Parameters:
487
- path: One or more local paths to a setup.cfg,
493
+ paths: One or more local paths to a pyproject.toml,
488
494
  setup.cfg, and/or requirements.txt files
489
- ignore: One or more project names to ignore (leave as-is)
495
+ ignore: One or more project/package names to ignore (leave
496
+ as-is) when updating dependency requirement specifiers.
490
497
  all_extra_name: If provided, an extra which consolidates
491
498
  the requirements for all other extras will be added/updated to
492
- setup.cfg or setup.cfg (this argument is ignored for
499
+ pyproject.toml or setup.cfg (this argument is ignored for
493
500
  requirements.txt files)
494
501
  include_pointers: A tuple of JSON pointers indicating elements to
495
- include (defaults to all elements).
502
+ include (defaults to all elements). This applies only to TOML
503
+ files (including pyproject.toml), and is ignored for all other
504
+ file types.
496
505
  exclude_pointers: A tuple of JSON pointers indicating elements to
497
- exclude (defaults to no exclusions).
506
+ exclude (defaults to no exclusions). This applies only to TOML
507
+ files (including pyproject.toml), and is ignored for all other
508
+ file types.
498
509
  """
499
- if isinstance(paths, str):
510
+ if isinstance(paths, (str, Path)):
500
511
  paths = (paths,)
501
512
 
502
- def update_(path: str) -> None:
513
+ def update_(path: str | Path) -> None:
503
514
  _update(
504
515
  path,
505
516
  ignore=ignore,