winipedia-utils 0.2.10__py3-none-any.whl → 0.2.17__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 (69) hide show
  1. winipedia_utils/concurrent/concurrent.py +245 -245
  2. winipedia_utils/concurrent/multiprocessing.py +130 -130
  3. winipedia_utils/concurrent/multithreading.py +93 -93
  4. winipedia_utils/consts.py +21 -21
  5. winipedia_utils/data/__init__.py +1 -1
  6. winipedia_utils/data/dataframe/__init__.py +1 -1
  7. winipedia_utils/data/dataframe/cleaning.py +378 -378
  8. winipedia_utils/data/structures/__init__.py +1 -1
  9. winipedia_utils/data/structures/dicts.py +16 -16
  10. winipedia_utils/git/__init__.py +1 -1
  11. winipedia_utils/git/gitignore/__init__.py +1 -1
  12. winipedia_utils/git/gitignore/gitignore.py +136 -136
  13. winipedia_utils/git/pre_commit/__init__.py +1 -1
  14. winipedia_utils/git/pre_commit/config.py +70 -70
  15. winipedia_utils/git/pre_commit/hooks.py +127 -109
  16. winipedia_utils/git/pre_commit/run_hooks.py +49 -49
  17. winipedia_utils/iterating/__init__.py +1 -1
  18. winipedia_utils/iterating/iterate.py +29 -29
  19. winipedia_utils/logging/ansi.py +6 -6
  20. winipedia_utils/logging/config.py +64 -64
  21. winipedia_utils/logging/logger.py +26 -26
  22. winipedia_utils/modules/class_.py +119 -119
  23. winipedia_utils/modules/function.py +101 -101
  24. winipedia_utils/modules/module.py +379 -379
  25. winipedia_utils/modules/package.py +390 -390
  26. winipedia_utils/oop/mixins/meta.py +333 -333
  27. winipedia_utils/oop/mixins/mixin.py +37 -37
  28. winipedia_utils/os/__init__.py +1 -1
  29. winipedia_utils/os/os.py +63 -63
  30. winipedia_utils/projects/__init__.py +1 -1
  31. winipedia_utils/projects/poetry/__init__.py +1 -1
  32. winipedia_utils/projects/poetry/config.py +117 -117
  33. winipedia_utils/projects/poetry/poetry.py +31 -31
  34. winipedia_utils/projects/project.py +48 -48
  35. winipedia_utils/resources/__init__.py +1 -1
  36. winipedia_utils/resources/svgs/__init__.py +1 -1
  37. winipedia_utils/resources/svgs/download_arrow.svg +2 -2
  38. winipedia_utils/resources/svgs/exit_fullscreen_icon.svg +5 -5
  39. winipedia_utils/resources/svgs/fullscreen_icon.svg +2 -2
  40. winipedia_utils/resources/svgs/menu_icon.svg +3 -3
  41. winipedia_utils/resources/svgs/pause_icon.svg +3 -3
  42. winipedia_utils/resources/svgs/play_icon.svg +16 -16
  43. winipedia_utils/resources/svgs/plus_icon.svg +23 -23
  44. winipedia_utils/resources/svgs/svg.py +15 -15
  45. winipedia_utils/security/__init__.py +1 -1
  46. winipedia_utils/security/cryptography.py +29 -29
  47. winipedia_utils/security/keyring.py +70 -70
  48. winipedia_utils/setup.py +47 -47
  49. winipedia_utils/testing/assertions.py +23 -23
  50. winipedia_utils/testing/convention.py +177 -177
  51. winipedia_utils/testing/create_tests.py +297 -297
  52. winipedia_utils/testing/fixtures.py +28 -28
  53. winipedia_utils/testing/tests/base/fixtures/__init__.py +1 -1
  54. winipedia_utils/testing/tests/base/fixtures/fixture.py +6 -6
  55. winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +33 -33
  56. winipedia_utils/testing/tests/base/fixtures/scopes/function.py +7 -7
  57. winipedia_utils/testing/tests/base/fixtures/scopes/module.py +33 -33
  58. winipedia_utils/testing/tests/base/fixtures/scopes/package.py +7 -7
  59. winipedia_utils/testing/tests/base/fixtures/scopes/session.py +296 -296
  60. winipedia_utils/testing/tests/base/utils/utils.py +111 -111
  61. winipedia_utils/testing/tests/conftest.py +32 -32
  62. winipedia_utils/text/string.py +126 -126
  63. winipedia_utils-0.2.17.dist-info/METADATA +716 -0
  64. winipedia_utils-0.2.17.dist-info/RECORD +80 -0
  65. {winipedia_utils-0.2.10.dist-info → winipedia_utils-0.2.17.dist-info}/licenses/LICENSE +21 -21
  66. winipedia_utils/testing/tests/test_0.py +0 -8
  67. winipedia_utils-0.2.10.dist-info/METADATA +0 -355
  68. winipedia_utils-0.2.10.dist-info/RECORD +0 -81
  69. {winipedia_utils-0.2.10.dist-info → winipedia_utils-0.2.17.dist-info}/WHEEL +0 -0
@@ -1,390 +1,390 @@
1
- """Package utilities for introspection and manipulation.
2
-
3
- This module provides comprehensive utility functions for working with Python packages,
4
- including package discovery, creation, traversal, and module extraction. It handles
5
- both regular packages and namespace packages, offering tools for filesystem operations
6
- and module imports related to package structures.
7
-
8
- The utilities support both static package analysis and dynamic package manipulation,
9
- making them suitable for code generation, testing frameworks, and package management.
10
- """
11
-
12
- import os
13
- import pkgutil
14
- import sys
15
- from collections.abc import Generator, Iterable
16
- from importlib import import_module
17
- from pathlib import Path
18
- from types import ModuleType
19
-
20
- from setuptools import find_namespace_packages as _find_namespace_packages
21
- from setuptools import find_packages as _find_packages
22
-
23
- from winipedia_utils.git.gitignore.gitignore import (
24
- load_gitignore,
25
- walk_os_skipping_gitignore_patterns,
26
- )
27
- from winipedia_utils.logging.logger import get_logger
28
-
29
- logger = get_logger(__name__)
30
-
31
-
32
- def get_src_package() -> ModuleType:
33
- """Identify and return the main source package of the project.
34
-
35
- Discovers the main source package by finding all top-level packages
36
- and filtering out the test package. This is useful for automatically
37
- determining the package that contains the actual implementation code.
38
-
39
- Returns:
40
- The main source package as a module object
41
-
42
- Raises:
43
- StopIteration: If no source package can be found or
44
- if only the test package exists
45
-
46
- """
47
- from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
48
-
49
- packages = find_packages_as_modules(depth=0)
50
- return next(p for p in packages if p.__name__ != TESTS_PACKAGE_NAME)
51
-
52
-
53
- def make_dir_with_init_file(path: str | Path) -> None:
54
- """Create a directory and initialize it as a Python package.
55
-
56
- Creates the specified directory (including any necessary parent directories)
57
- and adds __init__.py files to make it a proper Python package. Optionally
58
- writes custom content to the __init__.py file.
59
-
60
- Args:
61
- path: The directory path to create and initialize as a package
62
-
63
- Note:
64
- If the directory already exists, it will not be modified, but __init__.py
65
- files will still be added if missing.
66
-
67
- """
68
- path = Path(path)
69
- path.mkdir(parents=True, exist_ok=True)
70
- make_init_modules_for_package(path)
71
-
72
-
73
- def module_is_package(obj: ModuleType) -> bool:
74
- """Determine if a module object represents a package.
75
-
76
- Checks if the given module object is a package by looking for the __path__
77
- attribute, which is only present in package modules.
78
-
79
- Args:
80
- obj: The module object to check
81
-
82
- Returns:
83
- True if the module is a package, False otherwise
84
-
85
- Note:
86
- This works for both regular packages and namespace packages.
87
-
88
- """
89
- return hasattr(obj, "__path__")
90
-
91
-
92
- def package_name_to_path(package_name: str | Path | ModuleType) -> Path:
93
- """Convert a Python package import name to its filesystem path.
94
-
95
- Transforms a Python package name (with dots) into the corresponding
96
- directory path by replacing dots with the appropriate directory separator
97
- for the current operating system.
98
-
99
- Args:
100
- package_name: A Python package name to convert
101
- or a Path object or a ModuleType object
102
-
103
- Returns:
104
- A Path object representing the filesystem path to the package
105
-
106
- Example:
107
- package_name_to_path("package.subpackage") -> Path("package/subpackage")
108
-
109
- """
110
- if isinstance(package_name, ModuleType):
111
- package_name = package_name.__name__
112
- elif isinstance(package_name, Path):
113
- package_name = package_name.as_posix()
114
- return Path(package_name.replace(".", os.sep))
115
-
116
-
117
- def get_modules_and_packages_from_package(
118
- package: ModuleType,
119
- ) -> tuple[list[ModuleType], list[ModuleType]]:
120
- """Extract all direct subpackages and modules from a package.
121
-
122
- Discovers and imports all direct child modules and subpackages within
123
- the given package. Returns them as separate lists.
124
-
125
- Args:
126
- package: The package module to extract subpackages and modules from
127
-
128
- Returns:
129
- A tuple containing (list of subpackages, list of modules)
130
-
131
- Note:
132
- Only includes direct children, not recursive descendants.
133
- All discovered modules and packages are imported during this process.
134
-
135
- """
136
- packages: list[ModuleType] = []
137
- modules: list[ModuleType] = []
138
- for _, name, is_pkg in pkgutil.iter_modules(
139
- package.__path__, prefix=package.__name__ + "."
140
- ):
141
- mod = import_module(name)
142
- if is_pkg:
143
- packages.append(mod)
144
- else:
145
- modules.append(mod)
146
-
147
- # make consistent order
148
- packages.sort(key=lambda p: p.__name__)
149
- modules.sort(key=lambda m: m.__name__)
150
-
151
- return packages, modules
152
-
153
-
154
- def find_packages(
155
- *,
156
- depth: int | None = None,
157
- include_namespace_packages: bool = False,
158
- where: str = ".",
159
- exclude: Iterable[str] | None = None,
160
- include: Iterable[str] = ("*",),
161
- ) -> list[str]:
162
- """Discover Python packages in the specified directory.
163
-
164
- Finds all Python packages in the given directory, with options to filter
165
- by depth, include/exclude patterns, and namespace packages. This is a wrapper
166
- around setuptools' find_packages and find_namespace_packages functions with
167
- additional filtering capabilities.
168
-
169
- Args:
170
- depth: Optional maximum depth of package nesting to include (None for unlimited)
171
- include_namespace_packages: Whether to include namespace packages
172
- where: Directory to search for packages (default: current directory)
173
- exclude: Patterns of package names to exclude
174
- include: Patterns of package names to include
175
-
176
- Returns:
177
- A list of package names as strings
178
-
179
- Example:
180
- find_packages(depth=1) might return ["package1", "package2"]
181
-
182
- """
183
- if exclude is None:
184
- exclude = load_gitignore()
185
- exclude = [
186
- p.replace("/", ".").removesuffix(".") for p in exclude if p.endswith("/")
187
- ]
188
- if include_namespace_packages:
189
- package_names = _find_namespace_packages(
190
- where=where, exclude=exclude, include=include
191
- )
192
- else:
193
- package_names = _find_packages(where=where, exclude=exclude, include=include)
194
-
195
- # Convert to list of strings explicitly
196
- package_names_list: list[str] = list(map(str, package_names))
197
-
198
- if depth is not None:
199
- package_names_list = [p for p in package_names_list if p.count(".") <= depth]
200
-
201
- return package_names_list
202
-
203
-
204
- def find_packages_as_modules(
205
- *,
206
- depth: int | None = None,
207
- include_namespace_packages: bool = False,
208
- where: str = ".",
209
- exclude: Iterable[str] | None = None,
210
- include: Iterable[str] = ("*",),
211
- ) -> list[ModuleType]:
212
- """Discover and import Python packages in the specified directory.
213
-
214
- Similar to find_packages, but imports and returns the actual module objects
215
- instead of just the package names.
216
-
217
- Args:
218
- depth: Optional maximum depth of package nesting to include (None for unlimited)
219
- include_namespace_packages: Whether to include namespace packages
220
- where: Directory to search for packages (default: current directory)
221
- exclude: Patterns of package names to exclude
222
- include: Patterns of package names to include
223
-
224
- Returns:
225
- A list of imported package module objects
226
-
227
- Note:
228
- All discovered packages are imported during this process.
229
-
230
- """
231
- package_names = find_packages(
232
- depth=depth,
233
- include_namespace_packages=include_namespace_packages,
234
- where=where,
235
- exclude=exclude,
236
- include=include,
237
- )
238
- return [import_module(package_name) for package_name in package_names]
239
-
240
-
241
- def walk_package(
242
- package: ModuleType,
243
- ) -> Generator[tuple[ModuleType, list[ModuleType]], None, None]:
244
- """Recursively walk through a package and all its subpackages.
245
-
246
- Performs a depth-first traversal of the package hierarchy, yielding each
247
- package along with its direct module children.
248
-
249
- Args:
250
- package: The root package module to start walking from
251
-
252
- Yields:
253
- Tuples of (package, list of modules in package)
254
-
255
- Note:
256
- All packages and modules are imported during this process.
257
- The traversal is depth-first, so subpackages are fully processed
258
- before moving to siblings.
259
-
260
- """
261
- subpackages, submodules = get_modules_and_packages_from_package(package)
262
- yield package, submodules
263
- for subpackage in subpackages:
264
- yield from walk_package(subpackage)
265
-
266
-
267
- def make_init_modules_for_package(path: str | Path | ModuleType) -> None:
268
- """Create __init__.py files in all subdirectories of a package.
269
-
270
- Ensures that all subdirectories of the given package have __init__.py files,
271
- effectively converting them into proper Python packages. Skips directories
272
- that match patterns in .gitignore.
273
-
274
- Args:
275
- path: The package path or module object to process
276
-
277
- Note:
278
- Does not modify directories that already have __init__.py files.
279
- Uses the default content for __init__.py files
280
- from get_default_init_module_content.
281
-
282
- """
283
- from winipedia_utils.modules.module import to_path
284
-
285
- path = to_path(path, is_package=True)
286
-
287
- for root, _dirs, files in walk_os_skipping_gitignore_patterns(path):
288
- if "__init__.py" in files:
289
- continue
290
- make_init_module(root)
291
-
292
-
293
- def make_init_module(path: str | Path) -> None:
294
- """Create an __init__.py file in the specified directory.
295
-
296
- Creates an __init__.py file with default content in the given directory,
297
- making it a proper Python package.
298
-
299
- Args:
300
- path: The directory path where the __init__.py file should be created
301
-
302
- Note:
303
- If the path already points to an __init__.py file, that file will be
304
- overwritten with the default content.
305
- Creates parent directories if they don't exist.
306
-
307
- """
308
- from winipedia_utils.modules.module import get_default_init_module_content, to_path
309
-
310
- path = to_path(path, is_package=True)
311
-
312
- # if __init__.py not in path add it
313
- if path.name != "__init__.py":
314
- path = path / "__init__.py"
315
-
316
- content = get_default_init_module_content()
317
-
318
- path.parent.mkdir(parents=True, exist_ok=True)
319
- path.write_text(content)
320
-
321
-
322
- def copy_package(
323
- src_package: ModuleType,
324
- dst: str | Path | ModuleType,
325
- *,
326
- with_file_content: bool = True,
327
- ) -> None:
328
- """Copy a package to a different destination.
329
-
330
- Takes a ModuleType of package and a destination package name and then copies
331
- the package to the destination. If with_file_content is True, it copies the
332
- content of the files, otherwise it just creates the files.
333
-
334
- Args:
335
- src_package (ModuleType): The package to copy
336
- dst (str | Path): destination package name as a
337
- Path with / or as a str with dots
338
- with_file_content (bool, optional): copies the content of the files.
339
-
340
- """
341
- from winipedia_utils.modules.module import (
342
- create_module,
343
- get_isolated_obj_name,
344
- get_module_content_as_str,
345
- to_path,
346
- )
347
-
348
- src_path = to_path(src_package, is_package=True)
349
- dst_path = to_path(dst, is_package=True) / get_isolated_obj_name(src_package)
350
- for package, modules in walk_package(src_package):
351
- # we need to make right path from the package to the dst
352
- # so that if we have a package package.package2.package3
353
- # and dst is a path like package4/package5/package6
354
- # we get the right path which is package4/package5/package6/package3
355
- package_path = to_path(package, is_package=True)
356
- dst_package_path = dst_path / package_path.relative_to(src_path)
357
- create_module(dst_package_path, is_package=True)
358
- for module in modules:
359
- module_name = get_isolated_obj_name(module)
360
- module_path = dst_package_path / module_name
361
- create_module(module_path, is_package=False)
362
- if with_file_content:
363
- module_path.write_text(get_module_content_as_str(module))
364
-
365
-
366
- def get_main_package() -> ModuleType:
367
- """Gets the main package of the executing code.
368
-
369
- Even when this package is installed as a module.
370
- """
371
- from winipedia_utils.modules.module import to_module_name
372
-
373
- main = sys.modules.get("__main__")
374
- if main is None:
375
- msg = "No __main__ module found"
376
- raise ValueError(msg)
377
-
378
- package_name = getattr(main, "__package__", None)
379
- if package_name:
380
- package_name = package_name.split(".")[0]
381
- return import_module(package_name)
382
-
383
- file_name = getattr(main, "__file__", None)
384
- if file_name:
385
- package_name = to_module_name(file_name)
386
- package_name = package_name.split(".")[0]
387
- return import_module(package_name)
388
-
389
- msg = "Not able to determine the main package"
390
- raise ValueError(msg)
1
+ """Package utilities for introspection and manipulation.
2
+
3
+ This module provides comprehensive utility functions for working with Python packages,
4
+ including package discovery, creation, traversal, and module extraction. It handles
5
+ both regular packages and namespace packages, offering tools for filesystem operations
6
+ and module imports related to package structures.
7
+
8
+ The utilities support both static package analysis and dynamic package manipulation,
9
+ making them suitable for code generation, testing frameworks, and package management.
10
+ """
11
+
12
+ import os
13
+ import pkgutil
14
+ import sys
15
+ from collections.abc import Generator, Iterable
16
+ from importlib import import_module
17
+ from pathlib import Path
18
+ from types import ModuleType
19
+
20
+ from setuptools import find_namespace_packages as _find_namespace_packages
21
+ from setuptools import find_packages as _find_packages
22
+
23
+ from winipedia_utils.git.gitignore.gitignore import (
24
+ load_gitignore,
25
+ walk_os_skipping_gitignore_patterns,
26
+ )
27
+ from winipedia_utils.logging.logger import get_logger
28
+
29
+ logger = get_logger(__name__)
30
+
31
+
32
+ def get_src_package() -> ModuleType:
33
+ """Identify and return the main source package of the project.
34
+
35
+ Discovers the main source package by finding all top-level packages
36
+ and filtering out the test package. This is useful for automatically
37
+ determining the package that contains the actual implementation code.
38
+
39
+ Returns:
40
+ The main source package as a module object
41
+
42
+ Raises:
43
+ StopIteration: If no source package can be found or
44
+ if only the test package exists
45
+
46
+ """
47
+ from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
48
+
49
+ packages = find_packages_as_modules(depth=0)
50
+ return next(p for p in packages if p.__name__ != TESTS_PACKAGE_NAME)
51
+
52
+
53
+ def make_dir_with_init_file(path: str | Path) -> None:
54
+ """Create a directory and initialize it as a Python package.
55
+
56
+ Creates the specified directory (including any necessary parent directories)
57
+ and adds __init__.py files to make it a proper Python package. Optionally
58
+ writes custom content to the __init__.py file.
59
+
60
+ Args:
61
+ path: The directory path to create and initialize as a package
62
+
63
+ Note:
64
+ If the directory already exists, it will not be modified, but __init__.py
65
+ files will still be added if missing.
66
+
67
+ """
68
+ path = Path(path)
69
+ path.mkdir(parents=True, exist_ok=True)
70
+ make_init_modules_for_package(path)
71
+
72
+
73
+ def module_is_package(obj: ModuleType) -> bool:
74
+ """Determine if a module object represents a package.
75
+
76
+ Checks if the given module object is a package by looking for the __path__
77
+ attribute, which is only present in package modules.
78
+
79
+ Args:
80
+ obj: The module object to check
81
+
82
+ Returns:
83
+ True if the module is a package, False otherwise
84
+
85
+ Note:
86
+ This works for both regular packages and namespace packages.
87
+
88
+ """
89
+ return hasattr(obj, "__path__")
90
+
91
+
92
+ def package_name_to_path(package_name: str | Path | ModuleType) -> Path:
93
+ """Convert a Python package import name to its filesystem path.
94
+
95
+ Transforms a Python package name (with dots) into the corresponding
96
+ directory path by replacing dots with the appropriate directory separator
97
+ for the current operating system.
98
+
99
+ Args:
100
+ package_name: A Python package name to convert
101
+ or a Path object or a ModuleType object
102
+
103
+ Returns:
104
+ A Path object representing the filesystem path to the package
105
+
106
+ Example:
107
+ package_name_to_path("package.subpackage") -> Path("package/subpackage")
108
+
109
+ """
110
+ if isinstance(package_name, ModuleType):
111
+ package_name = package_name.__name__
112
+ elif isinstance(package_name, Path):
113
+ package_name = package_name.as_posix()
114
+ return Path(package_name.replace(".", os.sep))
115
+
116
+
117
+ def get_modules_and_packages_from_package(
118
+ package: ModuleType,
119
+ ) -> tuple[list[ModuleType], list[ModuleType]]:
120
+ """Extract all direct subpackages and modules from a package.
121
+
122
+ Discovers and imports all direct child modules and subpackages within
123
+ the given package. Returns them as separate lists.
124
+
125
+ Args:
126
+ package: The package module to extract subpackages and modules from
127
+
128
+ Returns:
129
+ A tuple containing (list of subpackages, list of modules)
130
+
131
+ Note:
132
+ Only includes direct children, not recursive descendants.
133
+ All discovered modules and packages are imported during this process.
134
+
135
+ """
136
+ packages: list[ModuleType] = []
137
+ modules: list[ModuleType] = []
138
+ for _, name, is_pkg in pkgutil.iter_modules(
139
+ package.__path__, prefix=package.__name__ + "."
140
+ ):
141
+ mod = import_module(name)
142
+ if is_pkg:
143
+ packages.append(mod)
144
+ else:
145
+ modules.append(mod)
146
+
147
+ # make consistent order
148
+ packages.sort(key=lambda p: p.__name__)
149
+ modules.sort(key=lambda m: m.__name__)
150
+
151
+ return packages, modules
152
+
153
+
154
+ def find_packages(
155
+ *,
156
+ depth: int | None = None,
157
+ include_namespace_packages: bool = False,
158
+ where: str = ".",
159
+ exclude: Iterable[str] | None = None,
160
+ include: Iterable[str] = ("*",),
161
+ ) -> list[str]:
162
+ """Discover Python packages in the specified directory.
163
+
164
+ Finds all Python packages in the given directory, with options to filter
165
+ by depth, include/exclude patterns, and namespace packages. This is a wrapper
166
+ around setuptools' find_packages and find_namespace_packages functions with
167
+ additional filtering capabilities.
168
+
169
+ Args:
170
+ depth: Optional maximum depth of package nesting to include (None for unlimited)
171
+ include_namespace_packages: Whether to include namespace packages
172
+ where: Directory to search for packages (default: current directory)
173
+ exclude: Patterns of package names to exclude
174
+ include: Patterns of package names to include
175
+
176
+ Returns:
177
+ A list of package names as strings
178
+
179
+ Example:
180
+ find_packages(depth=1) might return ["package1", "package2"]
181
+
182
+ """
183
+ if exclude is None:
184
+ exclude = load_gitignore()
185
+ exclude = [
186
+ p.replace("/", ".").removesuffix(".") for p in exclude if p.endswith("/")
187
+ ]
188
+ if include_namespace_packages:
189
+ package_names = _find_namespace_packages(
190
+ where=where, exclude=exclude, include=include
191
+ )
192
+ else:
193
+ package_names = _find_packages(where=where, exclude=exclude, include=include)
194
+
195
+ # Convert to list of strings explicitly
196
+ package_names_list: list[str] = list(map(str, package_names))
197
+
198
+ if depth is not None:
199
+ package_names_list = [p for p in package_names_list if p.count(".") <= depth]
200
+
201
+ return package_names_list
202
+
203
+
204
+ def find_packages_as_modules(
205
+ *,
206
+ depth: int | None = None,
207
+ include_namespace_packages: bool = False,
208
+ where: str = ".",
209
+ exclude: Iterable[str] | None = None,
210
+ include: Iterable[str] = ("*",),
211
+ ) -> list[ModuleType]:
212
+ """Discover and import Python packages in the specified directory.
213
+
214
+ Similar to find_packages, but imports and returns the actual module objects
215
+ instead of just the package names.
216
+
217
+ Args:
218
+ depth: Optional maximum depth of package nesting to include (None for unlimited)
219
+ include_namespace_packages: Whether to include namespace packages
220
+ where: Directory to search for packages (default: current directory)
221
+ exclude: Patterns of package names to exclude
222
+ include: Patterns of package names to include
223
+
224
+ Returns:
225
+ A list of imported package module objects
226
+
227
+ Note:
228
+ All discovered packages are imported during this process.
229
+
230
+ """
231
+ package_names = find_packages(
232
+ depth=depth,
233
+ include_namespace_packages=include_namespace_packages,
234
+ where=where,
235
+ exclude=exclude,
236
+ include=include,
237
+ )
238
+ return [import_module(package_name) for package_name in package_names]
239
+
240
+
241
+ def walk_package(
242
+ package: ModuleType,
243
+ ) -> Generator[tuple[ModuleType, list[ModuleType]], None, None]:
244
+ """Recursively walk through a package and all its subpackages.
245
+
246
+ Performs a depth-first traversal of the package hierarchy, yielding each
247
+ package along with its direct module children.
248
+
249
+ Args:
250
+ package: The root package module to start walking from
251
+
252
+ Yields:
253
+ Tuples of (package, list of modules in package)
254
+
255
+ Note:
256
+ All packages and modules are imported during this process.
257
+ The traversal is depth-first, so subpackages are fully processed
258
+ before moving to siblings.
259
+
260
+ """
261
+ subpackages, submodules = get_modules_and_packages_from_package(package)
262
+ yield package, submodules
263
+ for subpackage in subpackages:
264
+ yield from walk_package(subpackage)
265
+
266
+
267
+ def make_init_modules_for_package(path: str | Path | ModuleType) -> None:
268
+ """Create __init__.py files in all subdirectories of a package.
269
+
270
+ Ensures that all subdirectories of the given package have __init__.py files,
271
+ effectively converting them into proper Python packages. Skips directories
272
+ that match patterns in .gitignore.
273
+
274
+ Args:
275
+ path: The package path or module object to process
276
+
277
+ Note:
278
+ Does not modify directories that already have __init__.py files.
279
+ Uses the default content for __init__.py files
280
+ from get_default_init_module_content.
281
+
282
+ """
283
+ from winipedia_utils.modules.module import to_path
284
+
285
+ path = to_path(path, is_package=True)
286
+
287
+ for root, _dirs, files in walk_os_skipping_gitignore_patterns(path):
288
+ if "__init__.py" in files:
289
+ continue
290
+ make_init_module(root)
291
+
292
+
293
+ def make_init_module(path: str | Path) -> None:
294
+ """Create an __init__.py file in the specified directory.
295
+
296
+ Creates an __init__.py file with default content in the given directory,
297
+ making it a proper Python package.
298
+
299
+ Args:
300
+ path: The directory path where the __init__.py file should be created
301
+
302
+ Note:
303
+ If the path already points to an __init__.py file, that file will be
304
+ overwritten with the default content.
305
+ Creates parent directories if they don't exist.
306
+
307
+ """
308
+ from winipedia_utils.modules.module import get_default_init_module_content, to_path
309
+
310
+ path = to_path(path, is_package=True)
311
+
312
+ # if __init__.py not in path add it
313
+ if path.name != "__init__.py":
314
+ path = path / "__init__.py"
315
+
316
+ content = get_default_init_module_content()
317
+
318
+ path.parent.mkdir(parents=True, exist_ok=True)
319
+ path.write_text(content)
320
+
321
+
322
+ def copy_package(
323
+ src_package: ModuleType,
324
+ dst: str | Path | ModuleType,
325
+ *,
326
+ with_file_content: bool = True,
327
+ ) -> None:
328
+ """Copy a package to a different destination.
329
+
330
+ Takes a ModuleType of package and a destination package name and then copies
331
+ the package to the destination. If with_file_content is True, it copies the
332
+ content of the files, otherwise it just creates the files.
333
+
334
+ Args:
335
+ src_package (ModuleType): The package to copy
336
+ dst (str | Path): destination package name as a
337
+ Path with / or as a str with dots
338
+ with_file_content (bool, optional): copies the content of the files.
339
+
340
+ """
341
+ from winipedia_utils.modules.module import (
342
+ create_module,
343
+ get_isolated_obj_name,
344
+ get_module_content_as_str,
345
+ to_path,
346
+ )
347
+
348
+ src_path = to_path(src_package, is_package=True)
349
+ dst_path = to_path(dst, is_package=True) / get_isolated_obj_name(src_package)
350
+ for package, modules in walk_package(src_package):
351
+ # we need to make right path from the package to the dst
352
+ # so that if we have a package package.package2.package3
353
+ # and dst is a path like package4/package5/package6
354
+ # we get the right path which is package4/package5/package6/package3
355
+ package_path = to_path(package, is_package=True)
356
+ dst_package_path = dst_path / package_path.relative_to(src_path)
357
+ create_module(dst_package_path, is_package=True)
358
+ for module in modules:
359
+ module_name = get_isolated_obj_name(module)
360
+ module_path = dst_package_path / module_name
361
+ create_module(module_path, is_package=False)
362
+ if with_file_content:
363
+ module_path.write_text(get_module_content_as_str(module))
364
+
365
+
366
+ def get_main_package() -> ModuleType:
367
+ """Gets the main package of the executing code.
368
+
369
+ Even when this package is installed as a module.
370
+ """
371
+ from winipedia_utils.modules.module import to_module_name
372
+
373
+ main = sys.modules.get("__main__")
374
+ if main is None:
375
+ msg = "No __main__ module found"
376
+ raise ValueError(msg)
377
+
378
+ package_name = getattr(main, "__package__", None)
379
+ if package_name:
380
+ package_name = package_name.split(".")[0]
381
+ return import_module(package_name)
382
+
383
+ file_name = getattr(main, "__file__", None)
384
+ if file_name:
385
+ package_name = to_module_name(file_name)
386
+ package_name = package_name.split(".")[0]
387
+ return import_module(package_name)
388
+
389
+ msg = "Not able to determine the main package"
390
+ raise ValueError(msg)