winipedia-utils 0.4.43__py3-none-any.whl → 0.4.46__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.
@@ -127,7 +127,7 @@ def check_static_types() -> list[str | Path]:
127
127
 
128
128
  This function returns the input for subprocess.run() to check the static types.
129
129
  """
130
- return ["mypy"]
130
+ return ["mypy", "--exclude-gitignore"]
131
131
 
132
132
 
133
133
  def check_security() -> list[str | Path]:
@@ -379,3 +379,25 @@ def get_unwrapped_obj(obj: Any) -> Any:
379
379
  if isinstance(obj, property):
380
380
  obj = obj.fget # get the getter function of the property
381
381
  return inspect.unwrap(obj)
382
+
383
+
384
+ def get_executing_module() -> ModuleType:
385
+ """Get the module where execution has started.
386
+
387
+ The executing module is the module that contains the __main__ attribute as __name__
388
+ E.g. if you run `python -m winipedia_utils.setup` from the command line,
389
+ then the executing module is winipedia_utils.modules.setup
390
+
391
+ Returns:
392
+ The module where execution has started
393
+
394
+ Raises:
395
+ ValueError: If no __main__ module is found or if the executing module
396
+ cannot be determined
397
+
398
+ """
399
+ main = sys.modules.get("__main__")
400
+ if main is None:
401
+ msg = "No __main__ module found"
402
+ raise ValueError(msg)
403
+ return main
@@ -9,17 +9,23 @@ The utilities support both static package analysis and dynamic package manipulat
9
9
  making them suitable for code generation, testing frameworks, and package management.
10
10
  """
11
11
 
12
+ import importlib.metadata
13
+ import importlib.util
12
14
  import os
13
15
  import pkgutil
16
+ import re
14
17
  import sys
15
18
  from collections.abc import Generator, Iterable
16
19
  from importlib import import_module
17
20
  from pathlib import Path
18
21
  from types import ModuleType
22
+ from typing import Any
19
23
 
24
+ import networkx as nx
20
25
  from setuptools import find_namespace_packages as _find_namespace_packages
21
26
  from setuptools import find_packages as _find_packages
22
27
 
28
+ import winipedia_utils
23
29
  from winipedia_utils.logging.logger import get_logger
24
30
 
25
31
  logger = get_logger(__name__)
@@ -428,3 +434,78 @@ def make_name_from_package(
428
434
  if capitalize:
429
435
  parts = [part.capitalize() for part in parts]
430
436
  return join_on.join(parts)
437
+
438
+
439
+ class DependencyGraph(nx.DiGraph): # type: ignore [type-arg]
440
+ """A directed graph representing Python package dependencies."""
441
+
442
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
443
+ """Initialize the dependency graph and build it immediately."""
444
+ super().__init__(*args, **kwargs)
445
+ self.build()
446
+
447
+ def build(self) -> None:
448
+ """Build the graph from installed Python distributions."""
449
+ for dist in importlib.metadata.distributions():
450
+ name = dist.metadata["Name"].lower()
451
+ self.add_node(name)
452
+
453
+ requires = dist.requires or []
454
+ for req in requires:
455
+ dep = self.parse_pkg_name_from_req(req)
456
+ if dep:
457
+ self.add_edge(name, dep) # package → dependency
458
+
459
+ @staticmethod
460
+ def parse_pkg_name_from_req(req: str) -> str | None:
461
+ """Extract the bare dependency name from a requirement string."""
462
+ if not req:
463
+ return None
464
+ # split on the first non alphanumeric character like >, <, =, etc.
465
+ dep = re.split(r"[^a-zA-Z0-9]", req.strip())[0].strip()
466
+ return dep.lower().strip() if dep else None
467
+
468
+ def get_all_depending_on(
469
+ self, package: ModuleType, *, include_self: bool = False
470
+ ) -> set[ModuleType]:
471
+ """Return all packages that directly or indirectly depend on the given package.
472
+
473
+ Args:
474
+ package: The module whose dependents should be found.
475
+ include_self: Whether to include the package itself in the result.
476
+
477
+ Returns:
478
+ A set of imported module objects representing dependents.
479
+ """
480
+ target = package.__name__.lower()
481
+ if target not in self:
482
+ msg = f"Package '{target}' not found in dependency graph"
483
+ raise ValueError(msg)
484
+
485
+ dependents = nx.ancestors(self, target)
486
+ if include_self:
487
+ dependents.add(target)
488
+
489
+ return self.import_packages(dependents)
490
+
491
+ @staticmethod
492
+ def import_packages(names: set[str]) -> set[ModuleType]:
493
+ """Attempt to import all module names that can be resolved."""
494
+ modules: set[ModuleType] = set()
495
+ for name in names:
496
+ spec = importlib.util.find_spec(name)
497
+ if spec is not None:
498
+ modules.add(importlib.import_module(name))
499
+ return modules
500
+
501
+ def get_all_depending_on_winipedia_utils(
502
+ self, *, include_winipedia_utils: bool = False
503
+ ) -> set[ModuleType]:
504
+ """Return all packages that directly or indirectly depend on winipedia_utils."""
505
+ if get_src_package() == winipedia_utils:
506
+ deps: set[ModuleType] = set()
507
+ else:
508
+ deps = self.get_all_depending_on(winipedia_utils, include_self=False)
509
+ if include_winipedia_utils:
510
+ deps.add(winipedia_utils)
511
+ return deps
@@ -5,6 +5,7 @@ from typing import Any, cast
5
5
 
6
6
  from winipedia_utils.modules.package import get_src_package, make_name_from_package
7
7
  from winipedia_utils.projects.poetry.poetry import VersionConstraint
8
+ from winipedia_utils.testing.config import ExperimentConfigFile
8
9
  from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
9
10
  from winipedia_utils.text.config import ConfigFile, TomlConfigFile
10
11
 
@@ -62,7 +63,9 @@ class PyprojectConfigFile(TomlConfigFile):
62
63
  "files": ".",
63
64
  },
64
65
  "pytest": {"ini_options": {"testpaths": [TESTS_PACKAGE_NAME]}},
65
- "bandit": {},
66
+ "bandit": {
67
+ "exclude_dirs": [ExperimentConfigFile.get_path().as_posix()],
68
+ },
66
69
  },
67
70
  }
68
71
 
@@ -9,10 +9,9 @@ import tomlkit
9
9
  import yaml
10
10
  from dotenv import dotenv_values
11
11
 
12
- import winipedia_utils
13
12
  from winipedia_utils.iterating.iterate import nested_structure_is_subset
14
13
  from winipedia_utils.modules.class_ import init_all_nonabstract_subclasses
15
- from winipedia_utils.modules.package import get_src_package
14
+ from winipedia_utils.modules.package import DependencyGraph, get_src_package
16
15
  from winipedia_utils.projects.poetry.poetry import (
17
16
  get_python_module_script,
18
17
  )
@@ -149,8 +148,14 @@ class ConfigFile(ABC):
149
148
  @classmethod
150
149
  def init_config_files(cls) -> None:
151
150
  """Initialize all subclasses."""
152
- init_all_nonabstract_subclasses(cls, load_package_before=winipedia_utils)
153
- init_all_nonabstract_subclasses(cls, load_package_before=get_src_package())
151
+ pkgs_depending_on_winipedia_utils = (
152
+ DependencyGraph().get_all_depending_on_winipedia_utils(
153
+ include_winipedia_utils=True
154
+ )
155
+ )
156
+ pkgs_depending_on_winipedia_utils.add(get_src_package())
157
+ for pkg in pkgs_depending_on_winipedia_utils:
158
+ init_all_nonabstract_subclasses(cls, load_package_before=pkg)
154
159
 
155
160
  @staticmethod
156
161
  def get_python_setup_script() -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winipedia-utils
3
- Version: 0.4.43
3
+ Version: 0.4.46
4
4
  Summary: A package with many utility functions
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -15,6 +15,7 @@ Requires-Dist: cryptography
15
15
  Requires-Dist: defusedxml
16
16
  Requires-Dist: dotenv
17
17
  Requires-Dist: keyring
18
+ Requires-Dist: networkx
18
19
  Requires-Dist: pathspec
19
20
  Requires-Dist: polars
20
21
  Requires-Dist: pygithub
@@ -25,7 +25,7 @@ winipedia_utils/git/gitignore/config.py,sha256=Oi1gAf2mbR7vxMi0zsAFpCGzDaLNDd5S2
25
25
  winipedia_utils/git/gitignore/gitignore.py,sha256=uE2MdynWgQuTG-y2YLR0FU5_giSE7s_TqSVQ6vnNOf8,2419
26
26
  winipedia_utils/git/pre_commit/__init__.py,sha256=gFLVGQRmS6abgy5MfPQy_GZiF1_hGxuXtcOHX95WL-A,58
27
27
  winipedia_utils/git/pre_commit/config.py,sha256=UagrtEp_TSb0THjmXZpvtl7agekEVOWnMmRPdWJ4kf4,1830
28
- winipedia_utils/git/pre_commit/hooks.py,sha256=jMGSRtesVBB6pCQNSQ-FubCoUql7gXn0xZeKn7x6Wbs,4241
28
+ winipedia_utils/git/pre_commit/hooks.py,sha256=HP1byMnHk8DhaHnPjp3ioKpXIAo4_9FbrehHSg9_xfs,4264
29
29
  winipedia_utils/git/pre_commit/run_hooks.py,sha256=UIz1k3Hx5sB6LcdIJaPNWL-YDZzbkWd7NHCsC41nczQ,1495
30
30
  winipedia_utils/iterating/__init__.py,sha256=rlF9hzxbowq5yOfcXvOKOQdB-EQmfrislQpf659Zeu4,53
31
31
  winipedia_utils/iterating/iterate.py,sha256=k4U6qrnE4zij-GJhI5X0wM3pveSi8wtEsA1i8xQrfG0,3522
@@ -36,8 +36,8 @@ winipedia_utils/logging/logger.py,sha256=NluvfRpY4SfJi6URjfV52l3cxeUYFMeCAJULK_P
36
36
  winipedia_utils/modules/__init__.py,sha256=e3CFaC3FhK4ibknFOv1bqOZxA7XeVwmLqWX7oajUm78,51
37
37
  winipedia_utils/modules/class_.py,sha256=908MgZZzcJBPmlVmMPZCFssHnRzbQYOQlrF3GQhpWm4,5411
38
38
  winipedia_utils/modules/function.py,sha256=cjD6dXkZzhtCClUs4uiOLaDOURVASp64iwTwXmI3ICo,3217
39
- winipedia_utils/modules/module.py,sha256=K4t3AHBySAsHt9CCYlO7NlKtJq8rD5dikNN0mTPa8IQ,13093
40
- winipedia_utils/modules/package.py,sha256=wytjRvzGj81FvHDK0qKJdIfmf90ivF0MX2nF8xI8eyU,14440
39
+ winipedia_utils/modules/module.py,sha256=s48epfMHKjEgEHRO7rGICIX0JaXppOfst2uT-jLQM6M,13766
40
+ winipedia_utils/modules/package.py,sha256=ynGRQlhOYb6CX_waE5TkYW8Y4kGzoFU6oLbhFwaN6Sg,17428
41
41
  winipedia_utils/oop/__init__.py,sha256=wGjsVwLbTVEQWOfDJvN9nlvC-3NmAi8Doc2xIrm6e78,47
42
42
  winipedia_utils/oop/mixins/__init__.py,sha256=PDK-cJcdRUfDUCz36qQ5pmMW07G133WtN49OpmILGNI,54
43
43
  winipedia_utils/oop/mixins/meta.py,sha256=0G4CzzzCoeP1Eas3vWe-uxvB5n5ncyw7Wc-sI9zmEBc,11150
@@ -46,7 +46,7 @@ winipedia_utils/os/__init__.py,sha256=cBRq8hWhaWvYeC3cSBYL6Y70kM9COQWHj8vVxxSadI
46
46
  winipedia_utils/os/os.py,sha256=K_5FD1sC1h5aSdtqXAG0uq90sSweLYLkgkRPQS0Jfxg,1768
47
47
  winipedia_utils/projects/__init__.py,sha256=_iYHzUcTPmutpsExPDcMF9OQDgnz-kTSuWens9iP9bI,52
48
48
  winipedia_utils/projects/poetry/__init__.py,sha256=tbvV3wYd3H39hjjlKbF84Irj4hYgv1A7KWyXdCQzFro,59
49
- winipedia_utils/projects/poetry/config.py,sha256=aqMBG36hWB5XK9Z1484JEFRUsM5HkPgMdJ8EoKtZ3p8,7762
49
+ winipedia_utils/projects/poetry/config.py,sha256=SFFWtY5JkKDpe1VZNmZYKIj4RvrlAZCmVnC9wz0464I,7925
50
50
  winipedia_utils/projects/poetry/poetry.py,sha256=CM-MKuYSITRIS95qngtCpDpjQS6tCe6joZOM03yEFdU,4033
51
51
  winipedia_utils/projects/project.py,sha256=rirg4xCIOTI6w7cLWufAVHAix7FGicvaCd9OnZQP8dA,521
52
52
  winipedia_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
@@ -86,9 +86,9 @@ winipedia_utils/testing/tests/base/utils/__init__.py,sha256=mC-8dCkp8xarqkQu2QQL
86
86
  winipedia_utils/testing/tests/base/utils/utils.py,sha256=D7N-PW4N8853nJ2m4eYjO3jBMByYB9oh1GK4Hl5Tbwg,2598
87
87
  winipedia_utils/testing/tests/conftest.py,sha256=BLgUJtLecOwuEsIyJ__0buqovd5AhiGvbMNk8CHgSQs,888
88
88
  winipedia_utils/text/__init__.py,sha256=j2bwtK6kyeHI6SnoBjpRju0C1W2n2paXBDlNjNtaUxA,48
89
- winipedia_utils/text/config.py,sha256=63AyQUNgjYZb5hlBIsmbAsFk38BYeaIo6XuVredkfkk,7578
89
+ winipedia_utils/text/config.py,sha256=BPpuD4ywzzzt6Jz2M5Eln5d1NLlDp4r7CPwQOgHkxcg,7782
90
90
  winipedia_utils/text/string.py,sha256=yXmwOab5hXyVQG1NwlWDpy2prj0U7Vb2F5HKLT2Y77Q,3382
91
- winipedia_utils-0.4.43.dist-info/METADATA,sha256=dsxBGDal_FJpJmnAbQXKhs8t4ijlbiupOuEujUKE9T4,14564
92
- winipedia_utils-0.4.43.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
93
- winipedia_utils-0.4.43.dist-info/licenses/LICENSE,sha256=o316mE2gGzd__JT69p7S_zlOmKiHh8YjpImCCcWyTvM,1066
94
- winipedia_utils-0.4.43.dist-info/RECORD,,
91
+ winipedia_utils-0.4.46.dist-info/METADATA,sha256=HbApkj3FfKSRnBKjXOvSSpWrmHmX97TNjpuDK7FcSeM,14588
92
+ winipedia_utils-0.4.46.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
93
+ winipedia_utils-0.4.46.dist-info/licenses/LICENSE,sha256=o316mE2gGzd__JT69p7S_zlOmKiHh8YjpImCCcWyTvM,1066
94
+ winipedia_utils-0.4.46.dist-info/RECORD,,