winipedia-utils 0.4.43__tar.gz → 0.4.48__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.

Potentially problematic release.


This version of winipedia-utils might be problematic. Click here for more details.

Files changed (94) hide show
  1. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/PKG-INFO +4 -2
  2. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/README.md +2 -1
  3. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/pyproject.toml +4 -1
  4. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/pre_commit/hooks.py +1 -1
  5. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/modules/module.py +22 -0
  6. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/modules/package.py +92 -0
  7. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/projects/poetry/config.py +4 -1
  8. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/text/config.py +9 -4
  9. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/LICENSE +0 -0
  10. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/__init__.py +0 -0
  11. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/concurrent/__init__.py +0 -0
  12. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/concurrent/concurrent.py +0 -0
  13. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/concurrent/multiprocessing.py +0 -0
  14. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/concurrent/multithreading.py +0 -0
  15. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/data/__init__.py +0 -0
  16. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/data/dataframe/__init__.py +0 -0
  17. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/data/dataframe/cleaning.py +0 -0
  18. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/data/structures/__init__.py +0 -0
  19. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/data/structures/dicts.py +0 -0
  20. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/__init__.py +0 -0
  21. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/__init__.py +0 -0
  22. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/github.py +0 -0
  23. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/repo/__init__.py +0 -0
  24. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/repo/protect.py +0 -0
  25. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/repo/repo.py +0 -0
  26. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/workflows/__init__.py +0 -0
  27. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/workflows/base/__init__.py +0 -0
  28. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/workflows/base/base.py +0 -0
  29. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/workflows/health_check.py +0 -0
  30. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/workflows/publish.py +0 -0
  31. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/github/workflows/release.py +0 -0
  32. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/gitignore/__init__.py +0 -0
  33. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/gitignore/config.py +0 -0
  34. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/gitignore/gitignore.py +0 -0
  35. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/pre_commit/__init__.py +0 -0
  36. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/pre_commit/config.py +0 -0
  37. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/git/pre_commit/run_hooks.py +0 -0
  38. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/iterating/__init__.py +0 -0
  39. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/iterating/iterate.py +0 -0
  40. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/logging/__init__.py +0 -0
  41. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/logging/ansi.py +0 -0
  42. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/logging/config.py +0 -0
  43. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/logging/logger.py +0 -0
  44. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/modules/__init__.py +0 -0
  45. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/modules/class_.py +0 -0
  46. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/modules/function.py +0 -0
  47. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/oop/__init__.py +0 -0
  48. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/oop/mixins/__init__.py +0 -0
  49. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/oop/mixins/meta.py +0 -0
  50. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/oop/mixins/mixin.py +0 -0
  51. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/os/__init__.py +0 -0
  52. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/os/os.py +0 -0
  53. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/projects/__init__.py +0 -0
  54. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/projects/poetry/__init__.py +0 -0
  55. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/projects/poetry/poetry.py +0 -0
  56. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/projects/project.py +0 -0
  57. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/py.typed +0 -0
  58. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/__init__.py +0 -0
  59. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/__init__.py +0 -0
  60. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/delete_garbage_can.svg +0 -0
  61. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/download_arrow.svg +0 -0
  62. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/exit_fullscreen_icon.svg +0 -0
  63. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/fullscreen_icon.svg +0 -0
  64. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/menu_icon.svg +0 -0
  65. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/pause_icon.svg +0 -0
  66. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/play_icon.svg +0 -0
  67. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/plus_icon.svg +0 -0
  68. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/resources/svgs/svg.py +0 -0
  69. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/security/__init__.py +0 -0
  70. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/security/cryptography.py +0 -0
  71. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/security/keyring.py +0 -0
  72. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/setup.py +0 -0
  73. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/__init__.py +0 -0
  74. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/assertions.py +0 -0
  75. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/config.py +0 -0
  76. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/convention.py +0 -0
  77. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/create_tests.py +0 -0
  78. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/fixtures.py +0 -0
  79. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/skip.py +0 -0
  80. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/__init__.py +0 -0
  81. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/__init__.py +0 -0
  82. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/__init__.py +0 -0
  83. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/fixture.py +0 -0
  84. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/scopes/__init__.py +0 -0
  85. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +0 -0
  86. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/scopes/function.py +0 -0
  87. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/scopes/module.py +0 -0
  88. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/scopes/package.py +0 -0
  89. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/fixtures/scopes/session.py +0 -0
  90. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/utils/__init__.py +0 -0
  91. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/base/utils/utils.py +0 -0
  92. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/testing/tests/conftest.py +0 -0
  93. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/text/__init__.py +0 -0
  94. {winipedia_utils-0.4.43 → winipedia_utils-0.4.48}/winipedia_utils/text/string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winipedia-utils
3
- Version: 0.4.43
3
+ Version: 0.4.48
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
@@ -78,7 +79,8 @@ git clone https://github.com/owner/repo.git
78
79
  poetry init # or poetry new
79
80
  # 4: Poetry will ask you some stuff when you run poetry init.
80
81
  # First author name must be equal to the GitHub repository owner (username).
81
- # The repository name must be equal to the package/project name.
82
+ # The repository name must be equal to the package/project name.
83
+ # (- instead of _ is fine, but only as the project name in pyproject.toml, folder names should all be _)
82
84
 
83
85
  # 5: Add winipedia-utils to your project
84
86
  poetry add winipedia-utils
@@ -52,7 +52,8 @@ git clone https://github.com/owner/repo.git
52
52
  poetry init # or poetry new
53
53
  # 4: Poetry will ask you some stuff when you run poetry init.
54
54
  # First author name must be equal to the GitHub repository owner (username).
55
- # The repository name must be equal to the package/project name.
55
+ # The repository name must be equal to the package/project name.
56
+ # (- instead of _ is fine, but only as the project name in pyproject.toml, folder names should all be _)
56
57
 
57
58
  # 5: Add winipedia-utils to your project
58
59
  poetry add winipedia-utils
@@ -1,7 +1,7 @@
1
1
  # Project section
2
2
  [project]
3
3
  name = "winipedia-utils"
4
- version = "0.4.43"
4
+ version = "0.4.48"
5
5
  description = "A package with many utility functions"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.12"
@@ -32,6 +32,7 @@ cryptography = "*"
32
32
  polars = "*"
33
33
  pygithub = "*"
34
34
  dotenv = "*"
35
+ networkx = "*"
35
36
 
36
37
  [tool.poetry.group.dev.dependencies]
37
38
  ruff = "*"
@@ -44,6 +45,7 @@ types-tqdm = "*"
44
45
  types-defusedxml = "*"
45
46
  types-pyyaml = "*"
46
47
  pytest-mock = "*"
48
+ types-networkx = "*"
47
49
 
48
50
  [tool.ruff]
49
51
  exclude = [ ".*", "**/migrations/*.py",]
@@ -64,3 +66,4 @@ files = "."
64
66
  testpaths = [ "tests",]
65
67
 
66
68
  [tool.bandit]
69
+ exclude_dirs = ["experiment.py"]
@@ -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,89 @@ 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 = self.parse_distname_from_metadata(dist)
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_distname_from_metadata(dist: importlib.metadata.Distribution) -> str:
461
+ """Extract the distribution name from its metadata."""
462
+ # replace - with _ to handle packages like winipedia-utils
463
+ name: str = dist.metadata["Name"]
464
+ return DependencyGraph.normalize_package_name(name)
465
+
466
+ @staticmethod
467
+ def normalize_package_name(name: str) -> str:
468
+ """Normalize a package name."""
469
+ return name.lower().replace("-", "_").strip()
470
+
471
+ @staticmethod
472
+ def parse_pkg_name_from_req(req: str) -> str | None:
473
+ """Extract the bare dependency name from a requirement string."""
474
+ # split on the first non alphanumeric character like >, <, =, etc.
475
+ dep = re.split(r"[^a-zA-Z0-9]", req.strip())[0].strip()
476
+ return DependencyGraph.normalize_package_name(dep) if dep else None
477
+
478
+ def get_all_depending_on(
479
+ self, package: ModuleType, *, include_self: bool = False
480
+ ) -> set[ModuleType]:
481
+ """Return all packages that directly or indirectly depend on the given package.
482
+
483
+ Args:
484
+ package: The module whose dependents should be found.
485
+ include_self: Whether to include the package itself in the result.
486
+
487
+ Returns:
488
+ A set of imported module objects representing dependents.
489
+ """
490
+ # replace - with _ to handle packages like winipedia-utils
491
+ target = package.__name__.lower()
492
+ if target not in self:
493
+ msg = f"Package '{target}' not found in dependency graph"
494
+ raise ValueError(msg)
495
+
496
+ dependents = nx.ancestors(self, target)
497
+ if include_self:
498
+ dependents.add(target)
499
+
500
+ return self.import_packages(dependents)
501
+
502
+ @staticmethod
503
+ def import_packages(names: set[str]) -> set[ModuleType]:
504
+ """Attempt to import all module names that can be resolved."""
505
+ modules: set[ModuleType] = set()
506
+ for name in names:
507
+ spec = importlib.util.find_spec(name)
508
+ if spec is not None:
509
+ modules.add(importlib.import_module(name))
510
+ return modules
511
+
512
+ def get_all_depending_on_winipedia_utils(
513
+ self, *, include_winipedia_utils: bool = False
514
+ ) -> set[ModuleType]:
515
+ """Return all packages that directly or indirectly depend on winipedia_utils."""
516
+ if get_src_package() == winipedia_utils:
517
+ deps: set[ModuleType] = set()
518
+ else:
519
+ deps = self.get_all_depending_on(winipedia_utils, include_self=False)
520
+ if include_winipedia_utils:
521
+ deps.add(winipedia_utils)
522
+ 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: