winipedia-utils 0.4.43__py3-none-any.whl → 0.7.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.

Potentially problematic release.


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

Files changed (120) hide show
  1. winipedia_utils/artifacts/build.py +27 -0
  2. winipedia_utils/dev/artifacts/build.py +62 -0
  3. winipedia_utils/{text → dev/configs/base}/config.py +16 -11
  4. winipedia_utils/{git/gitignore/config.py → dev/configs/gitignore.py} +2 -2
  5. winipedia_utils/{git/pre_commit/config.py → dev/configs/pre_commit.py} +6 -6
  6. winipedia_utils/{projects/poetry/config.py → dev/configs/pyproject.py} +120 -32
  7. winipedia_utils/{testing/config.py → dev/configs/testing.py} +7 -4
  8. winipedia_utils/dev/configs/workflows/base/base.py +907 -0
  9. winipedia_utils/dev/configs/workflows/health_check.py +69 -0
  10. winipedia_utils/dev/configs/workflows/publish.py +51 -0
  11. winipedia_utils/dev/configs/workflows/release.py +91 -0
  12. winipedia_utils/dev/git/github/repo/__init__.py +1 -0
  13. winipedia_utils/{git → dev/git}/github/repo/protect.py +5 -5
  14. winipedia_utils/dev/git/pre_commit/hooks.py +85 -0
  15. winipedia_utils/{git → dev/git}/pre_commit/run_hooks.py +23 -13
  16. winipedia_utils/dev/projects/poetry/dev_deps.py +21 -0
  17. winipedia_utils/dev/projects/poetry/poetry.py +248 -0
  18. winipedia_utils/{projects → dev/projects}/project.py +6 -7
  19. winipedia_utils/dev/testing/__init__.py +1 -0
  20. winipedia_utils/{testing → dev/testing}/convention.py +1 -1
  21. winipedia_utils/{testing → dev/testing}/create_tests.py +14 -14
  22. winipedia_utils/dev/testing/tests/__init__.py +1 -0
  23. winipedia_utils/dev/testing/tests/base/__init__.py +1 -0
  24. winipedia_utils/dev/testing/tests/base/fixtures/__init__.py +1 -0
  25. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/fixture.py +1 -1
  26. winipedia_utils/dev/testing/tests/base/fixtures/scopes/__init__.py +1 -0
  27. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/class_.py +2 -2
  28. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/module.py +2 -2
  29. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/session.py +10 -10
  30. winipedia_utils/dev/testing/tests/base/utils/__init__.py +1 -0
  31. winipedia_utils/dev/testing/tests/base/utils/utils.py +1 -0
  32. winipedia_utils/{testing → dev/testing}/tests/conftest.py +2 -2
  33. winipedia_utils/{testing/tests/base/utils → dev/testing}/utils.py +7 -24
  34. winipedia_utils/setup.py +9 -5
  35. winipedia_utils/utils/__init__.py +1 -0
  36. winipedia_utils/utils/data/dataframe/__init__.py +1 -0
  37. winipedia_utils/{data → utils/data}/dataframe/cleaning.py +1 -1
  38. winipedia_utils/utils/data/structures/__init__.py +1 -0
  39. winipedia_utils/{text → utils/data/structures/text}/string.py +36 -3
  40. winipedia_utils/utils/git/__init__.py +1 -0
  41. winipedia_utils/utils/git/github/__init__.py +1 -0
  42. winipedia_utils/{git → utils/git}/github/github.py +1 -1
  43. winipedia_utils/utils/git/github/repo/__init__.py +1 -0
  44. winipedia_utils/{git → utils/git}/github/repo/repo.py +1 -1
  45. winipedia_utils/{git → utils/git}/gitignore/gitignore.py +2 -2
  46. winipedia_utils/{concurrent → utils/iterating/concurrent}/concurrent.py +4 -4
  47. winipedia_utils/{concurrent → utils/iterating/concurrent}/multiprocessing.py +2 -2
  48. winipedia_utils/{concurrent → utils/iterating/concurrent}/multithreading.py +1 -1
  49. winipedia_utils/{logging → utils/logging}/logger.py +1 -1
  50. winipedia_utils/{modules → utils/modules}/class_.py +15 -8
  51. winipedia_utils/{modules → utils/modules}/function.py +10 -4
  52. winipedia_utils/utils/modules/inspection.py +56 -0
  53. winipedia_utils/{modules → utils/modules}/module.py +27 -32
  54. winipedia_utils/{modules → utils/modules}/package.py +100 -34
  55. winipedia_utils/{oop → utils/oop}/mixins/meta.py +4 -4
  56. winipedia_utils/{oop → utils/oop}/mixins/mixin.py +2 -2
  57. winipedia_utils/{os → utils/os}/os.py +2 -2
  58. winipedia_utils/utils/resources/__init__.py +1 -0
  59. winipedia_utils/utils/resources/svgs/__init__.py +1 -0
  60. winipedia_utils/{resources → utils/resources}/svgs/svg.py +1 -1
  61. winipedia_utils/utils/testing/__init__.py +1 -0
  62. winipedia_utils/{testing → utils/testing}/assertions.py +18 -0
  63. winipedia_utils/{testing → utils/testing}/skip.py +1 -1
  64. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/METADATA +71 -36
  65. winipedia_utils-0.7.1.dist-info/RECORD +109 -0
  66. winipedia_utils/git/github/workflows/base/base.py +0 -294
  67. winipedia_utils/git/github/workflows/health_check.py +0 -57
  68. winipedia_utils/git/github/workflows/publish.py +0 -49
  69. winipedia_utils/git/github/workflows/release.py +0 -45
  70. winipedia_utils/git/pre_commit/hooks.py +0 -147
  71. winipedia_utils/projects/poetry/poetry.py +0 -129
  72. winipedia_utils/testing/__init__.py +0 -1
  73. winipedia_utils/testing/tests/__init__.py +0 -1
  74. winipedia_utils/testing/tests/base/__init__.py +0 -1
  75. winipedia_utils/testing/tests/base/fixtures/__init__.py +0 -1
  76. winipedia_utils/testing/tests/base/fixtures/scopes/__init__.py +0 -1
  77. winipedia_utils/testing/tests/base/utils/__init__.py +0 -1
  78. winipedia_utils-0.4.43.dist-info/RECORD +0 -94
  79. /winipedia_utils/{data/dataframe → artifacts}/__init__.py +0 -0
  80. /winipedia_utils/{data/structures → dev}/__init__.py +0 -0
  81. /winipedia_utils/{git/github → dev/artifacts}/__init__.py +0 -0
  82. /winipedia_utils/{git/github/repo → dev/configs}/__init__.py +0 -0
  83. /winipedia_utils/{git/github/workflows → dev/configs}/base/__init__.py +0 -0
  84. /winipedia_utils/{git/github → dev/configs}/workflows/__init__.py +0 -0
  85. /winipedia_utils/{resources → dev/configs/workflows/base}/__init__.py +0 -0
  86. /winipedia_utils/{git → dev/git}/__init__.py +0 -0
  87. /winipedia_utils/{resources/svgs → dev/git/github}/__init__.py +0 -0
  88. /winipedia_utils/{git → dev/git}/pre_commit/__init__.py +0 -0
  89. /winipedia_utils/{projects → dev/projects}/__init__.py +0 -0
  90. /winipedia_utils/{projects → dev/projects}/poetry/__init__.py +0 -0
  91. /winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/function.py +0 -0
  92. /winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/package.py +0 -0
  93. /winipedia_utils/{data → utils/data}/__init__.py +0 -0
  94. /winipedia_utils/{data → utils/data}/structures/dicts.py +0 -0
  95. /winipedia_utils/{text → utils/data/structures/text}/__init__.py +0 -0
  96. /winipedia_utils/{git → utils/git}/gitignore/__init__.py +0 -0
  97. /winipedia_utils/{iterating → utils/iterating}/__init__.py +0 -0
  98. /winipedia_utils/{concurrent → utils/iterating/concurrent}/__init__.py +0 -0
  99. /winipedia_utils/{iterating → utils/iterating}/iterate.py +0 -0
  100. /winipedia_utils/{logging → utils/logging}/__init__.py +0 -0
  101. /winipedia_utils/{logging → utils/logging}/ansi.py +0 -0
  102. /winipedia_utils/{logging → utils/logging}/config.py +0 -0
  103. /winipedia_utils/{modules → utils/modules}/__init__.py +0 -0
  104. /winipedia_utils/{oop → utils/oop}/__init__.py +0 -0
  105. /winipedia_utils/{oop → utils/oop}/mixins/__init__.py +0 -0
  106. /winipedia_utils/{os → utils/os}/__init__.py +0 -0
  107. /winipedia_utils/{resources → utils/resources}/svgs/delete_garbage_can.svg +0 -0
  108. /winipedia_utils/{resources → utils/resources}/svgs/download_arrow.svg +0 -0
  109. /winipedia_utils/{resources → utils/resources}/svgs/exit_fullscreen_icon.svg +0 -0
  110. /winipedia_utils/{resources → utils/resources}/svgs/fullscreen_icon.svg +0 -0
  111. /winipedia_utils/{resources → utils/resources}/svgs/menu_icon.svg +0 -0
  112. /winipedia_utils/{resources → utils/resources}/svgs/pause_icon.svg +0 -0
  113. /winipedia_utils/{resources → utils/resources}/svgs/play_icon.svg +0 -0
  114. /winipedia_utils/{resources → utils/resources}/svgs/plus_icon.svg +0 -0
  115. /winipedia_utils/{security → utils/security}/__init__.py +0 -0
  116. /winipedia_utils/{security → utils/security}/cryptography.py +0 -0
  117. /winipedia_utils/{security → utils/security}/keyring.py +0 -0
  118. /winipedia_utils/{testing → utils/testing}/fixtures.py +0 -0
  119. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/WHEEL +0 -0
  120. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -17,15 +17,19 @@ from collections.abc import Callable, Sequence
17
17
  from importlib import import_module
18
18
  from pathlib import Path
19
19
  from types import ModuleType
20
- from typing import Any, cast
20
+ from typing import Any
21
21
 
22
- from winipedia_utils.logging.logger import get_logger
23
- from winipedia_utils.modules.class_ import (
22
+ from winipedia_utils.utils.logging.logger import get_logger
23
+ from winipedia_utils.utils.modules.class_ import (
24
24
  get_all_cls_from_module,
25
25
  get_all_methods_from_cls,
26
26
  )
27
- from winipedia_utils.modules.function import get_all_functions_from_module
28
- from winipedia_utils.modules.package import (
27
+ from winipedia_utils.utils.modules.function import get_all_functions_from_module
28
+ from winipedia_utils.utils.modules.inspection import (
29
+ get_qualname_of_obj,
30
+ get_unwrapped_obj,
31
+ )
32
+ from winipedia_utils.utils.modules.package import (
29
33
  get_modules_and_packages_from_package,
30
34
  make_dir_with_init_file,
31
35
  module_is_package,
@@ -329,24 +333,6 @@ def get_default_module_content() -> str:
329
333
  return '''"""module."""'''
330
334
 
331
335
 
332
- def inside_frozen_bundle() -> bool:
333
- """Return True if the code is running inside a frozen bundle."""
334
- return getattr(sys, "frozen", False)
335
-
336
-
337
- def get_def_line(obj: Any) -> int:
338
- """Return the line number where a method-like object is defined."""
339
- if isinstance(obj, property):
340
- obj = obj.fget
341
- unwrapped = inspect.unwrap(obj)
342
- if hasattr(unwrapped, "__code__"):
343
- return int(unwrapped.__code__.co_firstlineno)
344
- # getsourcelines does not work if in a pyinstaller bundle or something
345
- if inside_frozen_bundle():
346
- return 0
347
- return inspect.getsourcelines(unwrapped)[1]
348
-
349
-
350
336
  def get_module_of_obj(obj: Any, default: ModuleType | None = None) -> ModuleType:
351
337
  """Return the module name where a method-like object is defined.
352
338
 
@@ -368,14 +354,23 @@ def get_module_of_obj(obj: Any, default: ModuleType | None = None) -> ModuleType
368
354
  return module
369
355
 
370
356
 
371
- def get_qualname_of_obj(obj: Callable[..., Any] | type) -> str:
372
- """Return the name of a method-like object."""
373
- unwrapped = get_unwrapped_obj(obj)
374
- return cast("str", unwrapped.__qualname__)
357
+ def get_executing_module() -> ModuleType:
358
+ """Get the module where execution has started.
375
359
 
360
+ The executing module is the module that contains the __main__ attribute as __name__
361
+ E.g. if you run `python -m winipedia_utils.setup` from the command line,
362
+ then the executing module is winipedia_utils.modules.setup
376
363
 
377
- def get_unwrapped_obj(obj: Any) -> Any:
378
- """Return the unwrapped version of a method-like object."""
379
- if isinstance(obj, property):
380
- obj = obj.fget # get the getter function of the property
381
- return inspect.unwrap(obj)
364
+ Returns:
365
+ The module where execution has started
366
+
367
+ Raises:
368
+ ValueError: If no __main__ module is found or if the executing module
369
+ cannot be determined
370
+
371
+ """
372
+ main = sys.modules.get("__main__")
373
+ if main is None:
374
+ msg = "No __main__ module found"
375
+ raise ValueError(msg)
376
+ return main
@@ -9,18 +9,24 @@ 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
 
23
- from winipedia_utils.logging.logger import get_logger
28
+ import winipedia_utils
29
+ from winipedia_utils.utils.logging.logger import get_logger
24
30
 
25
31
  logger = get_logger(__name__)
26
32
 
@@ -40,11 +46,11 @@ def get_src_package() -> ModuleType:
40
46
  if only the test package exists
41
47
 
42
48
  """
43
- from winipedia_utils.testing.convention import ( # noqa: PLC0415 # avoid circular import
49
+ from winipedia_utils.dev.testing.convention import ( # noqa: PLC0415 # avoid circular import
44
50
  TESTS_PACKAGE_NAME,
45
51
  )
46
52
 
47
- packages = find_packages_as_modules(depth=0)
53
+ packages = find_packages_as_modules(depth=0, include_namespace_packages=True)
48
54
  return next(p for p in packages if p.__name__ != TESTS_PACKAGE_NAME)
49
55
 
50
56
 
@@ -178,7 +184,7 @@ def find_packages(
178
184
  find_packages(depth=1) might return ["package1", "package2"]
179
185
 
180
186
  """
181
- from winipedia_utils.git.gitignore.config import ( # noqa: PLC0415
187
+ from winipedia_utils.dev.configs.gitignore import ( # noqa: PLC0415
182
188
  GitIgnoreConfigFile, # avoid circular import
183
189
  )
184
190
 
@@ -283,10 +289,10 @@ def make_init_modules_for_package(path: str | Path | ModuleType) -> None:
283
289
  from get_default_init_module_content.
284
290
 
285
291
  """
286
- from winipedia_utils.git.gitignore.gitignore import ( # noqa: PLC0415
292
+ from winipedia_utils.utils.git.gitignore.gitignore import ( # noqa: PLC0415
287
293
  walk_os_skipping_gitignore_patterns, # avoid circular import
288
294
  )
289
- from winipedia_utils.modules.module import ( # noqa: PLC0415
295
+ from winipedia_utils.utils.modules.module import ( # noqa: PLC0415
290
296
  to_path, # avoid circular import
291
297
  )
292
298
 
@@ -313,7 +319,7 @@ def make_init_module(path: str | Path) -> None:
313
319
  Creates parent directories if they don't exist.
314
320
 
315
321
  """
316
- from winipedia_utils.modules.module import ( # noqa: PLC0415 # avoid circular import
322
+ from winipedia_utils.utils.modules.module import ( # noqa: PLC0415 # avoid circular import
317
323
  get_default_init_module_content,
318
324
  to_path,
319
325
  )
@@ -349,7 +355,7 @@ def copy_package(
349
355
  with_file_content (bool, optional): copies the content of the files.
350
356
 
351
357
  """
352
- from winipedia_utils.modules.module import ( # noqa: PLC0415 # avoid circular import
358
+ from winipedia_utils.utils.modules.module import ( # noqa: PLC0415 # avoid circular import
353
359
  create_module,
354
360
  get_isolated_obj_name,
355
361
  get_module_content_as_str,
@@ -379,7 +385,7 @@ def get_main_package() -> ModuleType:
379
385
 
380
386
  Even when this package is installed as a module.
381
387
  """
382
- from winipedia_utils.modules.module import ( # noqa: PLC0415 # avoid circular import
388
+ from winipedia_utils.utils.modules.module import ( # noqa: PLC0415 # avoid circular import
383
389
  to_module_name,
384
390
  )
385
391
 
@@ -403,28 +409,88 @@ def get_main_package() -> ModuleType:
403
409
  raise ValueError(msg)
404
410
 
405
411
 
406
- def make_name_from_package(
407
- package: ModuleType,
408
- split_on: str = "_",
409
- join_on: str = "-",
410
- *,
411
- capitalize: bool = True,
412
- ) -> str:
413
- """Make a name from a package.
414
-
415
- takes a package and makes a name from it that is readable by humans.
416
-
417
- Args:
418
- package (ModuleType): The package to make a name from
419
- split_on (str, optional): what to split the package name on. Defaults to "_".
420
- join_on (str, optional): what to join the package name with. Defaults to "-".
421
- capitalize (bool, optional): Whether to capitalize each part. Defaults to True.
422
-
423
- Returns:
424
- str: _description_
425
- """
426
- package_name = package.__name__.split(".")[-1]
427
- parts = package_name.split(split_on)
428
- if capitalize:
429
- parts = [part.capitalize() for part in parts]
430
- return join_on.join(parts)
412
+ class DependencyGraph(nx.DiGraph): # type: ignore [type-arg]
413
+ """A directed graph representing Python package dependencies."""
414
+
415
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
416
+ """Initialize the dependency graph and build it immediately."""
417
+ super().__init__(*args, **kwargs)
418
+ self.build()
419
+
420
+ def build(self) -> None:
421
+ """Build the graph from installed Python distributions."""
422
+ for dist in importlib.metadata.distributions():
423
+ name = self.parse_distname_from_metadata(dist)
424
+ self.add_node(name)
425
+
426
+ requires = dist.requires or []
427
+ for req in requires:
428
+ dep = self.parse_pkg_name_from_req(req)
429
+ if dep:
430
+ self.add_edge(name, dep) # package → dependency
431
+
432
+ @staticmethod
433
+ def parse_distname_from_metadata(dist: importlib.metadata.Distribution) -> str:
434
+ """Extract the distribution name from its metadata."""
435
+ # replace - with _ to handle packages like winipedia-utils
436
+ name: str = dist.metadata["Name"]
437
+ return DependencyGraph.normalize_package_name(name)
438
+
439
+ @staticmethod
440
+ def normalize_package_name(name: str) -> str:
441
+ """Normalize a package name."""
442
+ return name.lower().replace("-", "_").strip()
443
+
444
+ @staticmethod
445
+ def parse_pkg_name_from_req(req: str) -> str | None:
446
+ """Extract the bare dependency name from a requirement string."""
447
+ # split on the first non alphanumeric character like >, <, =, etc.
448
+ # keep - and _ for names like winipedia-utils or winipedia_utils
449
+ dep = re.split(r"[^a-zA-Z0-9_-]", req.strip())[0].strip()
450
+ return DependencyGraph.normalize_package_name(dep) if dep else None
451
+
452
+ def get_all_depending_on(
453
+ self, package: ModuleType, *, include_self: bool = False
454
+ ) -> set[ModuleType]:
455
+ """Return all packages that directly or indirectly depend on the given package.
456
+
457
+ Args:
458
+ package: The module whose dependents should be found.
459
+ include_self: Whether to include the package itself in the result.
460
+
461
+ Returns:
462
+ A set of imported module objects representing dependents.
463
+ """
464
+ # replace - with _ to handle packages like winipedia-utils
465
+ target = package.__name__.lower()
466
+ if target not in self:
467
+ msg = f"Package '{target}' not found in dependency graph"
468
+ raise ValueError(msg)
469
+
470
+ dependents = nx.ancestors(self, target)
471
+ if include_self:
472
+ dependents.add(target)
473
+
474
+ return self.import_packages(dependents)
475
+
476
+ @staticmethod
477
+ def import_packages(names: set[str]) -> set[ModuleType]:
478
+ """Attempt to import all module names that can be resolved."""
479
+ modules: set[ModuleType] = set()
480
+ for name in names:
481
+ spec = importlib.util.find_spec(name)
482
+ if spec is not None:
483
+ modules.add(importlib.import_module(name))
484
+ return modules
485
+
486
+ def get_all_depending_on_winipedia_utils(
487
+ self, *, include_winipedia_utils: bool = False
488
+ ) -> set[ModuleType]:
489
+ """Return all packages that directly or indirectly depend on winipedia_utils."""
490
+ if get_src_package() == winipedia_utils:
491
+ deps: set[ModuleType] = set()
492
+ else:
493
+ deps = self.get_all_depending_on(winipedia_utils, include_self=False)
494
+ if include_winipedia_utils:
495
+ deps.add(winipedia_utils)
496
+ return deps
@@ -13,10 +13,10 @@ from collections.abc import Callable
13
13
  from functools import wraps
14
14
  from typing import Any, final
15
15
 
16
- from winipedia_utils.logging.logger import get_logger
17
- from winipedia_utils.modules.class_ import get_all_methods_from_cls
18
- from winipedia_utils.modules.function import is_func, unwrap_method
19
- from winipedia_utils.text.string import value_to_truncated_string
16
+ from winipedia_utils.utils.data.structures.text.string import value_to_truncated_string
17
+ from winipedia_utils.utils.logging.logger import get_logger
18
+ from winipedia_utils.utils.modules.class_ import get_all_methods_from_cls
19
+ from winipedia_utils.utils.modules.function import is_func, unwrap_method
20
20
 
21
21
  logger = get_logger(__name__)
22
22
 
@@ -10,8 +10,8 @@ These utilities help create robust class hierarchies with proper implementation
10
10
  enforcement and built-in logging capabilities.
11
11
  """
12
12
 
13
- from winipedia_utils.logging.logger import get_logger
14
- from winipedia_utils.oop.mixins.meta import ABCLoggingMeta, StrictABCLoggingMeta
13
+ from winipedia_utils.utils.logging.logger import get_logger
14
+ from winipedia_utils.utils.oop.mixins.meta import ABCLoggingMeta, StrictABCLoggingMeta
15
15
 
16
16
  logger = get_logger(__name__)
17
17
 
@@ -7,7 +7,7 @@ These utilities help with system-level operations and configuration.
7
7
 
8
8
  import shutil
9
9
  import subprocess # nosec: B404
10
- from pathlib import Path
10
+ from collections.abc import Sequence
11
11
  from typing import Any
12
12
 
13
13
 
@@ -34,7 +34,7 @@ def which_with_raise(cmd: str, *, raise_error: bool = True) -> str | None:
34
34
 
35
35
 
36
36
  def run_subprocess(
37
- args: list[str | Path],
37
+ args: Sequence[str],
38
38
  *,
39
39
  input_: str | bytes | None = None,
40
40
  capture_output: bool = True,
@@ -0,0 +1 @@
1
+ """__init__ module."""
@@ -0,0 +1 @@
1
+ """__init__ module."""
@@ -4,7 +4,7 @@ from importlib.resources import as_file, files
4
4
  from pathlib import Path
5
5
  from types import ModuleType
6
6
 
7
- from winipedia_utils.resources import svgs
7
+ from winipedia_utils.utils.resources import svgs
8
8
 
9
9
 
10
10
  def get_svg_path(svg_name: str, package: ModuleType | None = None) -> Path:
@@ -0,0 +1 @@
1
+ """__init__ module."""
@@ -7,6 +7,8 @@ specialized validation logic for common testing scenarios.
7
7
 
8
8
  from typing import Any
9
9
 
10
+ from winipedia_utils.utils.modules.function import is_abstractmethod
11
+
10
12
 
11
13
  def assert_with_msg(expr: bool, msg: str) -> None: # noqa: FBT001
12
14
  """Assert that an expression is true with a custom error message.
@@ -46,3 +48,19 @@ Actual: {actual}
46
48
  {msg}
47
49
  """
48
50
  assert_with_msg(expr, msg)
51
+
52
+
53
+ def assert_isabstrct_method(method: Any) -> None:
54
+ """Assert that a method is an abstract method.
55
+
56
+ Args:
57
+ method: The method to check
58
+
59
+ Raises:
60
+ AssertionError: If the method is not an abstract method
61
+
62
+ """
63
+ assert_with_msg(
64
+ is_abstractmethod(method),
65
+ f"Expected {method} to be abstract method",
66
+ )
@@ -4,7 +4,7 @@ import functools
4
4
 
5
5
  import pytest
6
6
 
7
- from winipedia_utils.git.github.github import running_in_github_actions
7
+ from winipedia_utils.utils.git.github.github import running_in_github_actions
8
8
 
9
9
  skip_fixture_test: pytest.MarkDecorator = functools.partial(
10
10
  pytest.mark.skip,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winipedia-utils
3
- Version: 0.4.43
3
+ Version: 0.7.1
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
@@ -103,7 +105,7 @@ The setup creates the following configuration files:
103
105
  - `.pre-commit-config.yaml` - Pre-commit hook configuration
104
106
  - `.gitignore` - Git ignore rules (assumes you added one on GitHub before.)
105
107
  - `pyproject.toml` - Project configuration with Poetry settings
106
- - `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request, all workfows run on the latest possible python version in pyproject.toml)
108
+ - `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request using a matrix strategy to test across multiple operating systems and Python versions)
107
109
  - `.github/workflows/release.yaml` - Release workflow (Creates a release on GitHub when the same actions as in health check pass and commits are pushed to main)
108
110
  - `.github/workflows/publish.yaml` - Publishing workflow (Publishes to PyPI when a release is created by the release workflow, if you use this workflow, you need to add a PYPI_TOKEN (named PYPI_TOKEN) to your GitHub secrets that has write access to the package on PyPI.)
109
111
  - `py.typed` - PEP 561 marker for type hints
@@ -112,27 +114,60 @@ The setup creates the following configuration files:
112
114
  - `conftest.py` - Pytest configuration file
113
115
  - `.python-version` - Python version file for pyenv (if you use pyenv, puts in the lowest supported python version in pyproject.toml opposed to the latest possible python version in workflows)
114
116
 
117
+ ### GitHub Workflows and Matrix Strategy
118
+
119
+ The project uses GitHub Actions workflows with a **matrix strategy** to ensure cross-platform compatibility:
120
+
121
+ #### Matrix Configuration
122
+
123
+ The health check and release workflows test your code across:
124
+ - **Operating Systems**: Ubuntu (latest), Windows (latest), macOS (latest)
125
+ - **Python Versions**: All versions specified in your `pyproject.toml` (e.g., 3.12, 3.13, 3.14)
126
+
127
+ This matrix strategy ensures your code works reliably across different environments before merging or releasing.
128
+
129
+ #### Workflow Structure
130
+
131
+ The health check workflow consists of two jobs:
132
+
133
+ 1. **Matrix Job** (`health_check_matrix`) - Runs all checks in parallel across the matrix of OS and Python versions:
134
+ - Checkout repository
135
+ - Setup Git, Python, and Poetry
136
+ - Add Poetry to PATH (Windows-specific step)
137
+ - Install dependencies
138
+ - Setup CI keyring
139
+ - Protect repository (applies branch protection rules and repository settings)
140
+ - Run pre-commit hooks (linting, formatting, type checking, security, tests)
141
+
142
+ 2. **Aggregation Job** (`health_check`) - Aggregates matrix results into a single status check:
143
+ - Required for branch protection compatibility
144
+ - Only runs after all matrix jobs complete successfully
145
+ - Provides a single status check that can be marked as required in branch protection rules
146
+
147
+ The release workflow extends the health check workflow and adds a release job that runs after all health checks pass.
148
+ A build job is added before the release job if a script src/artifacts/build.py exists. This script is created by the setup command and can be modified to create build artifacts for your project. This script then just needs to create artifacts in a folder called artifacts and those will be uploaded as artifacts to the release.
149
+
115
150
  ### Pre-commit Hook Workflow
116
151
 
117
152
  When you commit code using `git commit`, the following checks run automatically:
118
153
 
119
154
  Info: If git commit fails bc of ModuleNotFoundError or smth similar, you need to run `poetry run git commit` instead.
120
155
  winipedia_utils hook is a python script that depends on winipedia_utils being installed. Poetry is needed to install winipedia_utils.
121
- Usually VSCode or other IDEs activates the venv automatically when opening the terminal but if not you need to activate it manually or run `poetry run git commit` instead.
122
-
123
- 1. Patch version (poetry version patch)
124
- 2. Add version patch to git (git add pyproject.toml)
125
- 3. Update package manager (poetry self update)
126
- 4. Install packages (poetry install --with dev)
127
- 5. Update packages (poetry update --with dev (winipedia_utils forces all dependencies with * to be updated to latest compatible version))
128
- 6. Lock dependencies (poetry lock)
129
- 7. Check package manager configs (poetry check --strict)
130
- 8. Create tests (python -m winipedia_utils.testing.create_tests)
131
- 9. Lint code (ruff check --fix)
132
- 10. Format code (ruff format)
133
- 11. Check static types (mypy)
134
- 12. Check security (bandit -c pyproject.toml -r .)
135
- 13. Run tests (pytest (uses pyproject.toml as config))
156
+ Usually VSCode or other IDEs activates the venv automatically when opening the terminal but if not you need to activate it manually or run `poetry run git commit` instead. It fails fast, so if one hook in winipedia_utils hook fails, the others don't run bc sys.exit(1) is called.
157
+
158
+ Hooks run in the following order:
159
+
160
+ - Update package manager (poetry self update)
161
+ - Install packages (poetry install --with dev)
162
+ - Update packages (poetry update --with dev (winipedia_utils forces all dependencies with * to be updated to latest compatible version))
163
+ - Lock dependencies (poetry lock)
164
+ - Check package manager configs (poetry check --strict)
165
+ - Create tests (python -m winipedia_utils.dev.testing.create_tests)
166
+ - Lint code (ruff check --fix)
167
+ - Format code (ruff format)
168
+ - Check static types (mypy)
169
+ - Check security (bandit -c pyproject.toml -r .)
170
+ - Run tests (pytest (uses pyproject.toml as config))
136
171
 
137
172
  ### Auto-generated Test Structure
138
173
 
@@ -168,7 +203,7 @@ Configuration files are managed automatically by the setup system:
168
203
 
169
204
  ## Branch Protection
170
205
 
171
- As soon as you push to `main` on GitHub (provided the `REPO_TOKEN` secret is set up correctly), the `health_check.yaml` workflow will run and execute `winipedia_utils.git.github.repo.protect`, which uses PyGithub to protect the repository.
206
+ As soon as you push to `main` on GitHub (provided the `REPO_TOKEN` secret is set up correctly), the `health_check.yaml` workflow will run and execute `winipedia_utils.dev.git.github.repo.protect`, which uses PyGithub to protect the repository.
172
207
 
173
208
  ### Repository Settings
174
209
 
@@ -201,8 +236,8 @@ A ruleset named `main protection` is created for the `main` branch with the foll
201
236
  - Requires review thread resolution (all comments in reviews must be resolved before merge)
202
237
  - Allowed merge methods: `squash` and `rebase` (no merge commits, keeps history clean)
203
238
  - **Required Status Checks:**
204
- - Strict mode enabled (all status checks must pass on the latest commit, not older ones (sets the health check as required status check))
205
- - Health check workflow must pass (the CI/CD pipeline must complete successfully)
239
+ - Strict mode enabled (all status checks must pass on the latest commit, not older ones)
240
+ - Health check workflow must pass (the aggregated `health_check` job ensures all matrix combinations passed successfully)
206
241
  - **Bypass Actors** - Repository admins can bypass all rules (for emergency situations)
207
242
 
208
243
  ## Utilities
@@ -214,8 +249,8 @@ Winipedia Utils provides comprehensive utility modules for common development ta
214
249
  Unified interface for multiprocessing and multithreading:
215
250
 
216
251
  ```python
217
- from winipedia_utils.concurrent.multiprocessing import multiprocess_loop
218
- from winipedia_utils.concurrent.multithreading import multithread_loop
252
+ from winipedia_utils.utils.iterating.concurrent.multiprocessing import multiprocess_loop
253
+ from winipedia_utils.utils.iterating.concurrent.multithreading import multithread_loop
219
254
  ```
220
255
 
221
256
  ### Data Cleaning & Handling
@@ -223,7 +258,7 @@ from winipedia_utils.concurrent.multithreading import multithread_loop
223
258
  Build data cleaning pipelines using Polars:
224
259
 
225
260
  ```python
226
- from winipedia_utils.data.dataframe.cleaning import CleaningDF
261
+ from winipedia_utils.utils.data.dataframe.cleaning import CleaningDF
227
262
  import polars as pl
228
263
  ```
229
264
 
@@ -232,7 +267,7 @@ import polars as pl
232
267
  Simple, standardized logging setup with automatic method instrumentation:
233
268
 
234
269
  ```python
235
- from winipedia_utils.logging.logger import get_logger
270
+ from winipedia_utils.utils.logging.logger import get_logger
236
271
 
237
272
  logger = get_logger(__name__)
238
273
  logger.info("Application started")
@@ -250,7 +285,7 @@ logger.error("An error occurred")
250
285
  Advanced metaclasses and mixins for class composition and behavior extension:
251
286
 
252
287
  ```python
253
- from winipedia_utils.oop.mixins.mixin import ABCLoggingMixin, StrictABCLoggingMixin
288
+ from winipedia_utils.utils.oop.mixins.mixin import ABCLoggingMixin, StrictABCLoggingMixin
254
289
  ```
255
290
 
256
291
  ### Security Utilities
@@ -258,7 +293,7 @@ from winipedia_utils.oop.mixins.mixin import ABCLoggingMixin, StrictABCLoggingMi
258
293
  Encryption and secure credential storage using keyring:
259
294
 
260
295
  ```python
261
- from winipedia_utils.security.keyring import (
296
+ from winipedia_utils.utils.security.keyring import (
262
297
  get_or_create_fernet,
263
298
  get_or_create_aes_gcm
264
299
  )
@@ -269,8 +304,8 @@ from winipedia_utils.security.keyring import (
269
304
  Comprehensive testing framework with automatic test generation:
270
305
 
271
306
  ```python
272
- from winipedia_utils.testing.assertions import assert_with_msg
273
- from winipedia_utils.testing.convention import (
307
+ from winipedia_utils.utils.testing.assertions import assert_with_msg
308
+ from winipedia_utils.dev.testing.convention import (
274
309
  make_test_obj_name,
275
310
  get_test_obj_from_obj,
276
311
  make_test_obj_importpath_from_obj
@@ -300,10 +335,10 @@ test_path = make_test_obj_importpath_from_obj(my_function)
300
335
  Tools for working with Python modules, packages, classes, and functions:
301
336
 
302
337
  ```python
303
- from winipedia_utils.modules.package import find_packages, walk_package
304
- from winipedia_utils.modules.module import create_module, import_obj_from_importpath
305
- from winipedia_utils.modules.class_ import get_all_cls_from_module, get_all_methods_from_cls
306
- from winipedia_utils.modules.function import get_all_functions_from_module
338
+ from winipedia_utils.utils.modules.package import find_packages, walk_package
339
+ from winipedia_utils.utils.modules.module import create_module, import_obj_from_importpath
340
+ from winipedia_utils.utils.modules.class_ import get_all_cls_from_module, get_all_methods_from_cls
341
+ from winipedia_utils.utils.modules.function import get_all_functions_from_module
307
342
  ```
308
343
 
309
344
  ### Text and String Utilities
@@ -311,7 +346,7 @@ from winipedia_utils.modules.function import get_all_functions_from_module
311
346
  String manipulation and configuration file handling:
312
347
 
313
348
  ```python
314
- from winipedia_utils.text.string import value_to_truncated_string
349
+ from winipedia_utils.utils.data.structures.text.string import value_to_truncated_string
315
350
  ```
316
351
 
317
352
  ### OS and System Utilities
@@ -319,7 +354,7 @@ from winipedia_utils.text.string import value_to_truncated_string
319
354
  Operating system and subprocess utilities:
320
355
 
321
356
  ```python
322
- from winipedia_utils.os.os import run_subprocess
357
+ from winipedia_utils.utils.os.os import run_subprocess
323
358
  ```
324
359
 
325
360
  ### Iteration Utilities
@@ -327,7 +362,7 @@ from winipedia_utils.os.os import run_subprocess
327
362
  Utilities for working with iterables and nested structures:
328
363
 
329
364
  ```python
330
- from winipedia_utils.iterating.iterate import get_len_with_default, nested_structure_is_subset
365
+ from winipedia_utils.utils.iterating.iterate import get_len_with_default, nested_structure_is_subset
331
366
  ```
332
367
 
333
368
  ### Philosophy