dependence 0.2.0__py3-none-any.whl → 0.3.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.
dependence/_utilities.py CHANGED
@@ -1,12 +1,44 @@
1
+ import os
1
2
  import sys
2
3
  from functools import lru_cache
3
4
  from itertools import chain
5
+ from subprocess import check_output as _check_output
6
+ from tempfile import mktemp
4
7
  from traceback import format_exception
5
- from typing import Any, Dict, Iterable, List
8
+ from typing import Any, Dict, Hashable, Iterable, List, Set, Tuple
6
9
 
7
10
  import tomli
8
11
 
9
12
 
13
+ def check_output(command: Tuple[str, ...]) -> str:
14
+ """
15
+ This function wraps `subprocess.check_output`, but redirects stderr
16
+ to a temporary file, then deletes that file (a platform-independent
17
+ means of redirecting output to DEVNULL).
18
+
19
+ Parameters:
20
+
21
+ - command (Tuple[str, ...]): The command to run
22
+ """
23
+ stderr_path: str = mktemp()
24
+ with open(stderr_path, "w") as stderr:
25
+ output = _check_output(command, stderr=stderr, text=True)
26
+ os.remove(stderr_path)
27
+ return output
28
+
29
+
30
+ def iter_distinct(items: Iterable[Hashable]) -> Iterable:
31
+ """
32
+ Yield distinct elements, preserving order
33
+ """
34
+ visited: Set[Hashable] = set()
35
+ item: Hashable
36
+ for item in items:
37
+ if item not in visited:
38
+ visited.add(item)
39
+ yield item
40
+
41
+
10
42
  @lru_cache()
11
43
  def pyproject_toml_defines_project(pyproject_toml_path: str) -> bool:
12
44
  pyproject: Dict[str, Any]
dependence/freeze.py CHANGED
@@ -5,9 +5,7 @@ from importlib.metadata import distribution as _get_distribution
5
5
  from itertools import chain
6
6
  from typing import Dict, Iterable, MutableSet, Optional, Tuple, cast
7
7
 
8
- from more_itertools import unique_everseen
9
-
10
- from ._utilities import iter_parse_delimited_values
8
+ from ._utilities import iter_distinct, iter_parse_delimited_values
11
9
  from .utilities import (
12
10
  get_distribution,
13
11
  get_required_distribution_names,
@@ -113,7 +111,7 @@ def get_frozen_requirements(
113
111
  requirement_strings: MutableSet[str] = cast(
114
112
  MutableSet[str], requirements - requirement_files
115
113
  )
116
- frozen_requirements: Iterable[str] = unique_everseen(
114
+ frozen_requirements: Iterable[str] = iter_distinct(
117
115
  chain(
118
116
  requirement_strings,
119
117
  *map(
@@ -213,7 +211,7 @@ def _iter_frozen_requirements(
213
211
  )
214
212
 
215
213
  distribution_names: MutableSet[str]
216
- requirements: Iterable[str] = unique_everseen(
214
+ requirements: Iterable[str] = iter_distinct(
217
215
  chain(
218
216
  *map(
219
217
  lambda distribution_names: get_required_distribution_names_(
@@ -223,7 +221,6 @@ def _iter_frozen_requirements(
223
221
  )
224
222
  ),
225
223
  )
226
-
227
224
  requirements = map(get_requirement_string, requirements)
228
225
  return requirements
229
226
 
dependence/update.py CHANGED
@@ -21,13 +21,12 @@ from typing import (
21
21
 
22
22
  import tomli
23
23
  import tomli_w
24
- from more_itertools import unique_everseen
25
24
  from packaging.requirements import Requirement
26
25
  from packaging.specifiers import Specifier, SpecifierSet
27
26
  from packaging.version import Version
28
27
  from packaging.version import parse as parse_version
29
28
 
30
- from ._utilities import iter_parse_delimited_values
29
+ from ._utilities import iter_distinct, iter_parse_delimited_values
31
30
  from .utilities import (
32
31
  get_installed_distributions,
33
32
  is_requirement_string,
@@ -230,7 +229,7 @@ def get_updated_setup_cfg(
230
229
  # We pre-pend an empty requirement string in order to]
231
230
  # force new-line creation at the beginning of the extra
232
231
  extras_require[all_extra_name] = "\n".join(
233
- unique_everseen([""] + all_extra_requirements)
232
+ iter_distinct([""] + all_extra_requirements)
234
233
  )
235
234
  # Return as a string
236
235
  setup_cfg: str
@@ -369,7 +368,7 @@ def get_updated_pyproject_toml(
369
368
  project_optional_dependencies[extra_name] = extra_requirements
370
369
  if all_extra_name:
371
370
  project_optional_dependencies[all_extra_name] = list(
372
- unique_everseen(all_extra_requirements)
371
+ iter_distinct(all_extra_requirements)
373
372
  )
374
373
  if (
375
374
  build_system_requires
dependence/utilities.py CHANGED
@@ -13,11 +13,12 @@ from itertools import chain
13
13
  from pathlib import Path
14
14
  from runpy import run_path
15
15
  from shutil import move, rmtree
16
- from subprocess import CalledProcessError, check_call, check_output
16
+ from subprocess import CalledProcessError
17
17
  from tempfile import mkdtemp
18
18
  from types import ModuleType
19
19
  from typing import (
20
20
  IO,
21
+ AbstractSet,
21
22
  Any,
22
23
  Container,
23
24
  Dict,
@@ -32,11 +33,15 @@ from typing import (
32
33
  from warnings import warn
33
34
 
34
35
  import tomli
35
- from more_itertools import unique_everseen
36
36
  from packaging.requirements import InvalidRequirement, Requirement
37
37
  from packaging.utils import canonicalize_name
38
38
 
39
- from ._utilities import append_exception_text, get_exception_text
39
+ from ._utilities import (
40
+ append_exception_text,
41
+ check_output,
42
+ get_exception_text,
43
+ iter_distinct,
44
+ )
40
45
 
41
46
  _BUILTIN_DISTRIBUTION_NAMES: Tuple[str] = ("distribute",)
42
47
 
@@ -330,12 +335,31 @@ def _iter_setup_cfg_requirement_strings(path: str) -> Iterable[str]:
330
335
  extra_requirements_string.split("\n"),
331
336
  ),
332
337
  )
333
- return unique_everseen(requirement_strings)
338
+ return iter_distinct(requirement_strings)
339
+
340
+
341
+ def _iter_tox_ini_requirement_strings(
342
+ path: Union[str, Path, ConfigParser] = "",
343
+ string: str = "",
344
+ ) -> Iterable[str]:
345
+ """
346
+ Parse a tox.ini file and yield the requirements found in the `deps`
347
+ options of each section.
334
348
 
349
+ Parameters:
335
350
 
336
- def _iter_tox_ini_requirement_strings(path: str) -> Iterable[str]:
351
+ - path (str|Path) = "": The path to a tox.ini file
352
+ - string (str) = "": The contents of a tox.ini file
353
+ """
337
354
  parser: ConfigParser = ConfigParser()
338
- parser.read(path)
355
+ if path:
356
+ assert (
357
+ not string
358
+ ), "Either `path` or `string` arguments may be provided, but not both"
359
+ parser.read(path)
360
+ else:
361
+ assert string, "Either a `path` or `string` argument must be provided"
362
+ parser.read_string(string)
339
363
 
340
364
  def get_section_option_requirements(
341
365
  section_name: str, option_name: str
@@ -358,32 +382,117 @@ def _iter_tox_ini_requirement_strings(path: str) -> Iterable[str]:
358
382
  )
359
383
  return requirements
360
384
 
361
- return unique_everseen(
385
+ return iter_distinct(
362
386
  chain(("tox",), *map(get_section_requirements, parser.sections()))
363
387
  )
364
388
 
365
389
 
366
- def _iter_pyproject_toml_requirement_strings(path: str) -> Iterable[str]:
390
+ def _iter_pyproject_toml_requirement_strings(
391
+ path: str,
392
+ exclude_build_system: bool = False,
393
+ exclude_project: bool = False,
394
+ exclude_project_dependencies: bool = False,
395
+ exclude_project_optional_dependencies: bool = False,
396
+ include_project_optional_dependencies: Iterable[str] = frozenset(),
397
+ exclude_tools: bool = False,
398
+ exclude_tox: bool = False,
399
+ ) -> Iterable[str]:
400
+ """
401
+ Read a pyproject.toml file and yield the requirements found.
402
+
403
+ - exclude_build_system (bool) = False: If `True`, build-system
404
+ requirements will not be included
405
+ - exclude_project (bool) = False: If `True`, build-system
406
+ requirements will not be included
407
+ - exclude_project_dependencies (bool) = False: If `True`, project
408
+ dependencies will not be included
409
+ - exclude_project_optional_dependencies (bool) = False: If `True`, project
410
+ optional dependencies will not be included
411
+ - include_project_optional_dependencies ({str}) = frozenset(): If a
412
+ non-empty set is provided, *only* dependencies for the specified extras
413
+ (options) will be included
414
+ - exclude_tools (bool) = False: If `True`, tool requirements will not be
415
+ included
416
+ - exclude_tox (bool) = False: If `True`, tool.tox dependencies will not be
417
+ included
418
+ """
419
+ include_project_optional_dependencies = (
420
+ include_project_optional_dependencies
421
+ if isinstance(include_project_optional_dependencies, set)
422
+ else frozenset(include_project_optional_dependencies)
423
+ )
367
424
  pyproject_io: IO[str]
368
425
  with open(path) as pyproject_io:
369
426
  pyproject: Dict[str, Any] = tomli.loads(pyproject_io.read())
370
- if ("build-system" in pyproject) and (
371
- "requires" in pyproject["build-system"]
427
+ # Build system requirements
428
+ if (
429
+ ("build-system" in pyproject)
430
+ and ("requires" in pyproject["build-system"])
431
+ and not exclude_build_system
372
432
  ):
373
433
  yield from pyproject["build-system"]["requires"]
374
- if ("project" in pyproject) and (
375
- "dependencies" in pyproject["project"]
376
- ):
377
- yield from pyproject["project"]["dependencies"]
378
- if "project.optional-dependencies" in pyproject:
379
- values: Iterable[str]
380
- for values in pyproject["project.optional-dependencies"].values():
381
- yield from values
434
+ # Project requirements
435
+ if ("project" in pyproject) and not exclude_project:
436
+ if (
437
+ "dependencies" in pyproject["project"]
438
+ ) and not exclude_project_dependencies:
439
+ yield from pyproject["project"]["dependencies"]
440
+ if (
441
+ "optional-dependencies" in pyproject["project"]
442
+ ) and not exclude_project_optional_dependencies:
443
+ key: str
444
+ values: Iterable[str]
445
+ for key, values in pyproject["project"][
446
+ "optional-dependencies"
447
+ ].items():
448
+ if (not include_project_optional_dependencies) or (
449
+ key in include_project_optional_dependencies
450
+ ):
451
+ yield from values
452
+ # Tool Requirements
453
+ if ("tool" in pyproject) and not exclude_tools:
454
+ # Tox
455
+ if ("tox" in pyproject["tool"]) and not exclude_tox:
456
+ if "legacy_tox_ini" in pyproject["tool"]["tox"]:
457
+ yield from _iter_tox_ini_requirement_strings(
458
+ string=pyproject["tool"]["tox"]["legacy_tox_ini"]
459
+ )
382
460
 
383
461
 
384
- def iter_configuration_file_requirement_strings(path: str) -> Iterable[str]:
462
+ def iter_configuration_file_requirement_strings(
463
+ path: str,
464
+ exclude_build_system: bool = False,
465
+ exclude_project: bool = False,
466
+ exclude_project_dependencies: bool = False,
467
+ exclude_project_optional_dependencies: bool = False,
468
+ include_project_optional_dependencies: AbstractSet[str] = frozenset(),
469
+ exclude_tools: bool = False,
470
+ exclude_tox: bool = False,
471
+ ) -> Iterable[str]:
385
472
  """
386
473
  Read a configuration file and yield the parsed requirements.
474
+
475
+ Parameters:
476
+
477
+ - path (str): The path to a configuration file
478
+
479
+ Parameters only applicable to `pyproject.toml` files:
480
+
481
+ - exclude_build_system (bool) = False: If `True`, build-system
482
+ requirements will not be included
483
+ - exclude_project (bool) = False: If `True`, build-system
484
+ requirements will not be included
485
+ - exclude_project_dependencies (bool) = False: If `True`, project
486
+ dependencies will not be included
487
+ - exclude_project_optional_dependencies (bool) = False: If `True`, project
488
+ optional dependencies will not be included
489
+ - include_project_optional_dependencies ({str}) = frozenset(): If a
490
+ non-empty set is provided, *only* dependencies for the specified extras
491
+ (options) will be included
492
+ - exclude_tools (bool) = False: If `True`, tool requirements will not be
493
+ included
494
+ - exclude_tox (bool) = False: If `True`, tool.tox dependencies will not be
495
+ included
387
496
  """
388
497
  configuration_file_type: ConfigurationFileType = (
389
498
  get_configuration_file_type(path)
@@ -391,9 +500,22 @@ def iter_configuration_file_requirement_strings(path: str) -> Iterable[str]:
391
500
  if configuration_file_type == ConfigurationFileType.SETUP_CFG:
392
501
  return _iter_setup_cfg_requirement_strings(path)
393
502
  elif configuration_file_type == ConfigurationFileType.PYPROJECT_TOML:
394
- return _iter_pyproject_toml_requirement_strings(path)
503
+ return _iter_pyproject_toml_requirement_strings(
504
+ path,
505
+ exclude_build_system=exclude_build_system,
506
+ exclude_project=exclude_project,
507
+ exclude_project_dependencies=exclude_project_dependencies,
508
+ exclude_project_optional_dependencies=(
509
+ exclude_project_optional_dependencies
510
+ ),
511
+ include_project_optional_dependencies=(
512
+ include_project_optional_dependencies
513
+ ),
514
+ exclude_tools=exclude_tools,
515
+ exclude_tox=exclude_tox,
516
+ )
395
517
  elif configuration_file_type == ConfigurationFileType.TOX_INI:
396
- return _iter_tox_ini_requirement_strings(path)
518
+ return _iter_tox_ini_requirement_strings(path=path)
397
519
  else:
398
520
  assert (
399
521
  configuration_file_type == ConfigurationFileType.REQUIREMENTS_TXT
@@ -448,13 +570,7 @@ def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
448
570
  if os.path.isfile(path):
449
571
  command: Tuple[str, ...] = (sys.executable, path) + args
450
572
  try:
451
- value = (
452
- check_output(
453
- command, encoding="utf-8", universal_newlines=True
454
- )
455
- .strip()
456
- .split("\n")[-1]
457
- )
573
+ value = check_output(command).strip().split("\n")[-1]
458
574
  except CalledProcessError:
459
575
  warn(
460
576
  f"A package name could not be found in {path}, "
@@ -464,13 +580,7 @@ def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
464
580
  # re-write egg info and attempt to get the name again
465
581
  setup_egg_info(directory)
466
582
  try:
467
- value = (
468
- check_output(
469
- command, encoding="utf-8", universal_newlines=True
470
- )
471
- .strip()
472
- .split("\n")[-1]
473
- )
583
+ value = check_output(command).strip().split("\n")[-1]
474
584
  except Exception:
475
585
  warn(
476
586
  f"A package name could not be found in {path}"
@@ -704,7 +814,7 @@ def _install_requirement_string(
704
814
  if editable:
705
815
  flags += ("-e",)
706
816
  try:
707
- check_call(
817
+ check_output(
708
818
  (
709
819
  sys.executable,
710
820
  "-m",
@@ -836,7 +946,7 @@ def _iter_requirement_names(
836
946
  if distribution is None:
837
947
  return ()
838
948
  requirements: Tuple[Requirement, ...] = tuple(
839
- unique_everseen(
949
+ iter_distinct(
840
950
  _iter_distribution_requirements(
841
951
  distribution,
842
952
  extras=extras,
@@ -918,7 +1028,7 @@ def _iter_requirement_strings_required_distribution_names(
918
1028
  pass
919
1029
  return set()
920
1030
 
921
- return unique_everseen(
1031
+ return iter_distinct(
922
1032
  chain(*map(get_required_distribution_names_, requirement_strings)),
923
1033
  )
924
1034
 
@@ -951,7 +1061,7 @@ def get_requirements_required_distribution_names(
951
1061
  name: str
952
1062
  return set(
953
1063
  _iter_requirement_strings_required_distribution_names(
954
- unique_everseen(
1064
+ iter_distinct(
955
1065
  chain(
956
1066
  requirement_strings,
957
1067
  *map(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dependence
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Requirement (dependency) management for python projects
5
5
  Home-page: https://github.com/enorganic/dependence
6
6
  Author-email: david@belais.me
@@ -12,7 +12,6 @@ Requires-Dist: packaging
12
12
  Requires-Dist: pip
13
13
  Requires-Dist: tomli ~=2.0
14
14
  Requires-Dist: tomli-w ~=1.0
15
- Requires-Dist: more-itertools >7
16
15
  Requires-Dist: setuptools >63
17
16
 
18
17
  # dependence
@@ -0,0 +1,12 @@
1
+ dependence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ dependence/__main__.py,sha256=myBIBZdez2jC3_dkVSE-mOLo39yKi2cF_0NuVXcXF1E,1528
3
+ dependence/_utilities.py,sha256=3fhceJ3yNRLp3HsDxvIEFZsjRlemEQ6oOF7Wx1jaRhQ,3296
4
+ dependence/freeze.py,sha256=6sP-z-BIwOzF7JgnUD134gu4711-LY-zaQ26yPdq5JE,13298
5
+ dependence/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ dependence/update.py,sha256=FRl0FsdAIfjWacw1cVS8Fh024ZN7hK3ZHJiQpsRinfQ,16571
7
+ dependence/utilities.py,sha256=DwYIFlS5h9B-wrycsntZsOtmuMped0Eyra9ZFmnCxwU,36201
8
+ dependence-0.3.1.dist-info/METADATA,sha256=V2-5Ce3KwhYzl31JGftABxvlRDV42Y3ahZbvgvfoqK4,5597
9
+ dependence-0.3.1.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
10
+ dependence-0.3.1.dist-info/entry_points.txt,sha256=NStO_B0D81ObVYr9zDs6LCy0whm0a8KCiiFHMmTwOVE,56
11
+ dependence-0.3.1.dist-info/top_level.txt,sha256=5rooMYWKlAUelE8hjGegbf4uuAIqNuRlhlpA7oc7Qro,11
12
+ dependence-0.3.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (71.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,12 +0,0 @@
1
- dependence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- dependence/__main__.py,sha256=myBIBZdez2jC3_dkVSE-mOLo39yKi2cF_0NuVXcXF1E,1528
3
- dependence/_utilities.py,sha256=pGK9JAMOAVGvamo4Espmk0NUftosW7UxZnwWXbNM47w,2385
4
- dependence/freeze.py,sha256=BCbqXNMReXX5N5S0FJK_bqSZCOHOIh2ti0m-4Klzpxc,13332
5
- dependence/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- dependence/update.py,sha256=9TIU4JC1M-RHZfXYxeAmtbXS_lAFy_Z9sy8Ce1PTz4g,16603
7
- dependence/utilities.py,sha256=Lo3guZSA7a2gARz3RzEqLenbG6VTWcA5-eucHB9S-58,31837
8
- dependence-0.2.0.dist-info/METADATA,sha256=H-olY5qur_SwQupzfKeK-OofgWYt2UKg8sEorub_0Q4,5630
9
- dependence-0.2.0.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
10
- dependence-0.2.0.dist-info/entry_points.txt,sha256=NStO_B0D81ObVYr9zDs6LCy0whm0a8KCiiFHMmTwOVE,56
11
- dependence-0.2.0.dist-info/top_level.txt,sha256=5rooMYWKlAUelE8hjGegbf4uuAIqNuRlhlpA7oc7Qro,11
12
- dependence-0.2.0.dist-info/RECORD,,