dependence 0.3.0__tar.gz → 0.3.1__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.1
2
2
  Name: dependence
3
- Version: 0.3.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
@@ -1,12 +1,32 @@
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, Hashable, Iterable, List, Set
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
+
10
30
  def iter_distinct(items: Iterable[Hashable]) -> Iterable:
11
31
  """
12
32
  Yield distinct elements, preserving order
@@ -221,7 +221,6 @@ def _iter_frozen_requirements(
221
221
  )
222
222
  ),
223
223
  )
224
-
225
224
  requirements = map(get_requirement_string, requirements)
226
225
  return requirements
227
226
 
@@ -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,
@@ -37,6 +38,7 @@ from packaging.utils import canonicalize_name
37
38
 
38
39
  from ._utilities import (
39
40
  append_exception_text,
41
+ check_output,
40
42
  get_exception_text,
41
43
  iter_distinct,
42
44
  )
@@ -336,9 +338,28 @@ def _iter_setup_cfg_requirement_strings(path: str) -> Iterable[str]:
336
338
  return iter_distinct(requirement_strings)
337
339
 
338
340
 
339
- def _iter_tox_ini_requirement_strings(path: str) -> Iterable[str]:
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.
348
+
349
+ Parameters:
350
+
351
+ - path (str|Path) = "": The path to a tox.ini file
352
+ - string (str) = "": The contents of a tox.ini file
353
+ """
340
354
  parser: ConfigParser = ConfigParser()
341
- 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)
342
363
 
343
364
  def get_section_option_requirements(
344
365
  section_name: str, option_name: str
@@ -366,27 +387,112 @@ def _iter_tox_ini_requirement_strings(path: str) -> Iterable[str]:
366
387
  )
367
388
 
368
389
 
369
- 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
+ )
370
424
  pyproject_io: IO[str]
371
425
  with open(path) as pyproject_io:
372
426
  pyproject: Dict[str, Any] = tomli.loads(pyproject_io.read())
373
- if ("build-system" in pyproject) and (
374
- "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
375
432
  ):
376
433
  yield from pyproject["build-system"]["requires"]
377
- if ("project" in pyproject) and (
378
- "dependencies" in pyproject["project"]
379
- ):
380
- yield from pyproject["project"]["dependencies"]
381
- if "project.optional-dependencies" in pyproject:
382
- values: Iterable[str]
383
- for values in pyproject["project.optional-dependencies"].values():
384
- 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
+ )
385
460
 
386
461
 
387
- 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]:
388
472
  """
389
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
390
496
  """
391
497
  configuration_file_type: ConfigurationFileType = (
392
498
  get_configuration_file_type(path)
@@ -394,9 +500,22 @@ def iter_configuration_file_requirement_strings(path: str) -> Iterable[str]:
394
500
  if configuration_file_type == ConfigurationFileType.SETUP_CFG:
395
501
  return _iter_setup_cfg_requirement_strings(path)
396
502
  elif configuration_file_type == ConfigurationFileType.PYPROJECT_TOML:
397
- 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
+ )
398
517
  elif configuration_file_type == ConfigurationFileType.TOX_INI:
399
- return _iter_tox_ini_requirement_strings(path)
518
+ return _iter_tox_ini_requirement_strings(path=path)
400
519
  else:
401
520
  assert (
402
521
  configuration_file_type == ConfigurationFileType.REQUIREMENTS_TXT
@@ -451,13 +570,7 @@ def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
451
570
  if os.path.isfile(path):
452
571
  command: Tuple[str, ...] = (sys.executable, path) + args
453
572
  try:
454
- value = (
455
- check_output(
456
- command, encoding="utf-8", universal_newlines=True
457
- )
458
- .strip()
459
- .split("\n")[-1]
460
- )
573
+ value = check_output(command).strip().split("\n")[-1]
461
574
  except CalledProcessError:
462
575
  warn(
463
576
  f"A package name could not be found in {path}, "
@@ -467,13 +580,7 @@ def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
467
580
  # re-write egg info and attempt to get the name again
468
581
  setup_egg_info(directory)
469
582
  try:
470
- value = (
471
- check_output(
472
- command, encoding="utf-8", universal_newlines=True
473
- )
474
- .strip()
475
- .split("\n")[-1]
476
- )
583
+ value = check_output(command).strip().split("\n")[-1]
477
584
  except Exception:
478
585
  warn(
479
586
  f"A package name could not be found in {path}"
@@ -707,7 +814,7 @@ def _install_requirement_string(
707
814
  if editable:
708
815
  flags += ("-e",)
709
816
  try:
710
- check_call(
817
+ check_output(
711
818
  (
712
819
  sys.executable,
713
820
  "-m",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dependence
3
- Version: 0.3.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
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = dependence
3
- version = 0.3.0
3
+ version = 0.3.1
4
4
  author_email = david@belais.me
5
5
  description = Requirement (dependency) management for python projects
6
6
  long_description = file: README.md
@@ -21,6 +21,9 @@ TEST_PROJECT_A: Path = (
21
21
  TEST_PROJECT_B: Path = (
22
22
  Path(__file__).absolute().parent.joinpath("test_projects/test_project_b/")
23
23
  )
24
+ TEST_PROJECT_C: Path = (
25
+ Path(__file__).absolute().parent.joinpath("test_projects/test_project_c/")
26
+ )
24
27
 
25
28
 
26
29
  def is_nonzero(value: int) -> bool:
File without changes
File without changes
File without changes