pyrig-runtime 2.1.2__tar.gz → 2.2.0__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.
Files changed (31) hide show
  1. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/PKG-INFO +1 -1
  2. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/pyproject.toml +1 -1
  3. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/dependencies/discovery.py +2 -5
  4. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/graph.py +10 -7
  5. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/introspection/functions.py +6 -3
  6. pyrig_runtime-2.2.0/src/pyrig_runtime/core/introspection/modules.py +197 -0
  7. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/strings.py +3 -0
  8. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/wrappers.py +0 -4
  9. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/cli/cli.py +8 -9
  10. pyrig_runtime-2.1.2/src/pyrig_runtime/core/introspection/modules.py +0 -126
  11. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/LICENSE +0 -0
  12. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/README.md +0 -0
  13. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/__init__.py +0 -0
  14. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/__init__.py +0 -0
  15. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/constants.py +0 -0
  16. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/dependencies/__init__.py +0 -0
  17. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/dependencies/graph.py +0 -0
  18. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/dependencies/subclass.py +0 -0
  19. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/introspection/__init__.py +0 -0
  20. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/introspection/classes.py +0 -0
  21. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/introspection/inspection.py +0 -0
  22. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/core/introspection/packages.py +0 -0
  23. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/py.typed +0 -0
  24. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/__init__.py +0 -0
  25. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/__init__.py +0 -0
  26. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/cli/__init__.py +0 -0
  27. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/commands/__init__.py +0 -0
  28. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/commands/version.py +0 -0
  29. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/main.py +0 -0
  30. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/shared_subcommands.py +0 -0
  31. {pyrig_runtime-2.1.2 → pyrig_runtime-2.2.0}/src/pyrig_runtime/rig/cli/subcommands.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrig-runtime
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: Runtime dependency and library for projects built with pyrig.
5
5
  Keywords: pyrig
6
6
  Author: Winipedia
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyrig-runtime"
3
- version = "2.1.2"
3
+ version = "2.2.0"
4
4
  description = "Runtime dependency and library for projects built with pyrig."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -9,8 +9,8 @@ from types import ModuleType
9
9
  from pyrig_runtime.core.dependencies.graph import DependencyGraph
10
10
  from pyrig_runtime.core.introspection.modules import (
11
11
  import_modules,
12
+ replace_root_module,
12
13
  root_module,
13
- safe_import_module,
14
14
  )
15
15
  from pyrig_runtime.core.introspection.packages import (
16
16
  discover_subclasses_across_package,
@@ -79,10 +79,7 @@ def discover_equivalent_modules_across_dependents(
79
79
  )
80
80
 
81
81
  for package in deps_depending_on_dep(dependency):
82
- package_module_name = module.__name__.replace(
83
- dependency.__name__, package.__name__, 1
84
- )
85
- package_module = safe_import_module(package_module_name, default=None)
82
+ package_module = replace_root_module(module, package.__name__, default=None)
86
83
  if package_module is not None:
87
84
  yield package_module
88
85
 
@@ -105,10 +105,10 @@ class DiGraph(ABC):
105
105
 
106
106
  Returns:
107
107
  List of ancestor node identifiers in topological order.
108
- Returns an empty list if the target has no ancestors or is not
109
- in the graph.
108
+ Returns an empty list if the target has no ancestors.
110
109
 
111
110
  Raises:
111
+ KeyError: If the target node is not in the graph.
112
112
  RuntimeError: If the ancestor subgraph contains a cycle, making
113
113
  topological sorting impossible.
114
114
  """
@@ -124,16 +124,19 @@ class DiGraph(ABC):
124
124
 
125
125
  Returns:
126
126
  Set of all nodes with a directed path to the target, excluding the
127
- target itself. Returns an empty set if the target is not in the graph.
127
+ target itself.
128
+
129
+ Raises:
130
+ KeyError: If the target node is not in the graph.
128
131
  """
129
132
  visited: set[str] = set()
130
- queue: deque[str] = deque(self.reverse_edges.get(target, set()))
133
+ queue: deque[str] = deque(self.reverse_edges[target])
131
134
 
132
135
  while queue:
133
136
  node = queue.popleft()
134
137
  if node not in visited:
135
138
  visited.add(node)
136
- for neighbor in self.reverse_edges.get(node, set()):
139
+ for neighbor in self.reverse_edges[node]:
137
140
  if neighbor not in visited:
138
141
  queue.append(neighbor)
139
142
 
@@ -164,7 +167,7 @@ class DiGraph(ABC):
164
167
  out_degree: dict[str, int] = dict.fromkeys(nodes, 0)
165
168
 
166
169
  for node in nodes:
167
- for dependency in self.edges.get(node, set()):
170
+ for dependency in self.edges[node]:
168
171
  if dependency in nodes:
169
172
  out_degree[node] += 1
170
173
 
@@ -176,7 +179,7 @@ class DiGraph(ABC):
176
179
  node = heapq.heappop(heap)
177
180
  result.append(node)
178
181
 
179
- for dependent in self.reverse_edges.get(node, set()):
182
+ for dependent in self.reverse_edges[node]:
180
183
  if dependent in nodes:
181
184
  out_degree[dependent] -= 1
182
185
  if out_degree[dependent] == 0:
@@ -24,7 +24,10 @@ def module_functions(
24
24
  Yields:
25
25
  Each function defined in `module`.
26
26
  """
27
- for _name, function in obj_members(module):
28
- func = unwrap_obj(function)
29
- if inspect.isfunction(func) and inspect.getmodule(func) is module:
27
+ for _name, func in obj_members(module):
28
+ unwrapped_func = unwrap_obj(func)
29
+ if (
30
+ inspect.isfunction(unwrapped_func)
31
+ and inspect.getmodule(unwrapped_func) is module
32
+ ):
30
33
  yield func
@@ -0,0 +1,197 @@
1
+ """Utilities for Python modules."""
2
+
3
+ import logging
4
+ from collections.abc import Iterable, Iterator
5
+ from importlib import import_module
6
+ from pkgutil import iter_modules as pkgutil_iter_modules
7
+ from types import ModuleType
8
+ from typing import Any, overload
9
+
10
+ from pyrig_runtime.core.constants import MISSING
11
+ from pyrig_runtime.core.wrappers import safe_call
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @overload
17
+ def replace_root_module(module: ModuleType, root: str) -> ModuleType: ...
18
+ @overload
19
+ def replace_root_module[T](
20
+ module: ModuleType, root: str, default: T
21
+ ) -> ModuleType | T: ...
22
+ def replace_root_module(
23
+ module: ModuleType, root: str, default: Any = MISSING
24
+ ) -> ModuleType | Any:
25
+ """Import the equivalent module under a different root package.
26
+
27
+ Replaces the first dotted segment of `module`'s name with `root` and
28
+ attempts to import the resulting module name. Later segments are left
29
+ untouched even if they happen to share the old root's name.
30
+
31
+ Args:
32
+ module: Module whose root segment should be swapped.
33
+ root: Root package name to substitute in.
34
+ default: Value to return if the import fails. If not provided,
35
+ the exception propagates unchanged.
36
+
37
+ Returns:
38
+ The imported module at the equivalent sub-path under `root`, or
39
+ `default` if the import fails and `default` was provided.
40
+
41
+ Example:
42
+ >>> from some_package.subpackage import module
43
+ >>> replace_root_module(module, "other_package")
44
+ <module 'other_package.subpackage.module'
45
+ from '/path/to/other_package/subpackage/module.py'>
46
+ """
47
+ return safe_import_module(replace_root_module_name(module, root), default=default)
48
+
49
+
50
+ def replace_root_module_name(module: ModuleType, root: str) -> str:
51
+ """Return the equivalent module name under a different root package.
52
+
53
+ Replaces the first dotted segment of `module.__name__` with `root`. Later
54
+ segments are left untouched even if they happen to share the old root's
55
+ name.
56
+
57
+ Args:
58
+ module: Module whose root segment should be swapped.
59
+ root: Root package name to substitute in.
60
+
61
+ Returns:
62
+ The equivalent dotted module name under `root`.
63
+
64
+ Example:
65
+ >>> replace_root_module_name(module, "other_package")
66
+ 'other_package.subpackage.module'
67
+ """
68
+ return module.__name__.replace(root_module_name(module), root, 1)
69
+
70
+
71
+ def root_module(module: ModuleType) -> ModuleType:
72
+ """Import and return the top-level package of the given module.
73
+
74
+ For a module named `"package.subpackage.module"`, the module corresponding
75
+ to `"package"` is returned. For a top-level module with no dots in its name,
76
+ the module for that same name is returned.
77
+
78
+ Args:
79
+ module: Module to resolve the root package for.
80
+
81
+ Returns:
82
+ The module corresponding to the first segment of the dotted name.
83
+
84
+ Example:
85
+ >>> from some_package.subpackage import module
86
+ >>> root_module(module)
87
+ <module 'some_package' from '/path/to/some_package/__init__.py'>
88
+ """
89
+ return import_module(root_module_name(module))
90
+
91
+
92
+ def root_module_name(module: ModuleType) -> str:
93
+ """Return the name of the top-level package of the given module.
94
+
95
+ For a module named `"package.subpackage.module"`, the string `"package"`
96
+ is returned. For a top-level module with no dots in its name, that same
97
+ name is returned.
98
+
99
+ Args:
100
+ module: Module to resolve the root package name for.
101
+
102
+ Returns:
103
+ The first segment of the dotted module name.
104
+
105
+ Example:
106
+ >>> from some_package.subpackage import module
107
+ >>> root_module_name(module)
108
+ 'some_package'
109
+ """
110
+ return module.__name__.split(".", 1)[0]
111
+
112
+
113
+ @overload
114
+ def safe_import_module(
115
+ module_name: str,
116
+ package: str | None = ...,
117
+ *,
118
+ exceptions: tuple[type[BaseException], ...] = ...,
119
+ ) -> ModuleType: ...
120
+ @overload
121
+ def safe_import_module[T](
122
+ module_name: str,
123
+ package: str | None = ...,
124
+ *,
125
+ default: T,
126
+ exceptions: tuple[type[BaseException], ...] = ...,
127
+ ) -> ModuleType | T: ...
128
+ def safe_import_module(
129
+ module_name: str,
130
+ package: str | None = None,
131
+ *,
132
+ default: Any = MISSING,
133
+ exceptions: tuple[type[BaseException], ...] = (Exception,),
134
+ ) -> ModuleType | Any:
135
+ """Import a module by name, with an optional fallback on failure.
136
+
137
+ Any `Exception` raised during import — not just `ImportError` — is
138
+ handled, so errors at module level (e.g., `ValueError` raised on
139
+ import) are also covered.
140
+
141
+ Args:
142
+ module_name: Dotted module name (e.g., `"package.subpackage.module"`).
143
+ package: Anchor package for relative imports, forwarded to `import_module`.
144
+ default: Value to return if the import raises. If not provided,
145
+ the exception propagates unchanged.
146
+ exceptions: Tuple of exception types to catch. Defaults to `(Exception,)`.
147
+
148
+ Returns:
149
+ The imported module, or `default` if an exception is raised and
150
+ `default` was provided.
151
+ """
152
+ return safe_call(
153
+ import_module,
154
+ module_name,
155
+ package,
156
+ default=default,
157
+ exceptions=exceptions,
158
+ )
159
+
160
+
161
+ def import_modules(module_names: Iterable[str]) -> Iterator[ModuleType]:
162
+ """Import multiple modules by name, lazily.
163
+
164
+ Modules are imported on demand as the result is iterated, not eagerly.
165
+
166
+ Args:
167
+ module_names: Dotted module names to import.
168
+
169
+ Yields:
170
+ Each imported module, in the order the names are iterated.
171
+ """
172
+ return (import_module(name) for name in module_names)
173
+
174
+
175
+ def iter_modules(package: ModuleType) -> Iterator[tuple[ModuleType, bool]]:
176
+ """Import and yield each direct child of a package, in discovery order.
177
+
178
+ Only the immediate children are visited; nested sub-packages are not
179
+ recursed into.
180
+
181
+ Args:
182
+ package: Package to iterate. Must have a `__path__` attribute
183
+ (i.e., must be a package, not a plain module).
184
+
185
+ Yields:
186
+ `(module, is_package)` pairs where `module` is the imported child and
187
+ `is_package` is `True` when the child is itself a sub-package.
188
+
189
+ Note:
190
+ Importing each child is a deliberate side effect — any module-level
191
+ code in those children executes on demand as the iterator is consumed.
192
+ """
193
+ for _finder, name, is_package in pkgutil_iter_modules(
194
+ package.__path__, prefix=package.__name__ + "."
195
+ ):
196
+ mod = import_module(name)
197
+ yield mod, is_package
@@ -68,6 +68,9 @@ def fully_qualified_name(obj: MethodType | FunctionType | type) -> str:
68
68
  def distribution_summary(name: str) -> str:
69
69
  """Return the summary recorded in an installed distribution's metadata.
70
70
 
71
+ This function assumes that the package is installed and its
72
+ metadata has a "Summary" field.
73
+
71
74
  Args:
72
75
  name: Name of an installed distribution (e.g. `"requests"`).
73
76
 
@@ -14,8 +14,6 @@ def safe_call[T, D](
14
14
  exceptions: tuple[type[BaseException], ...] = ...,
15
15
  **kwargs: Any,
16
16
  ) -> T | D: ...
17
-
18
-
19
17
  @overload
20
18
  def safe_call[T](
21
19
  func: Callable[..., T],
@@ -23,8 +21,6 @@ def safe_call[T](
23
21
  exceptions: tuple[type[BaseException], ...] = ...,
24
22
  **kwargs: Any,
25
23
  ) -> T: ...
26
-
27
-
28
24
  def safe_call(
29
25
  func: Callable[..., Any],
30
26
  *args: Any,
@@ -15,8 +15,7 @@ from pyrig_runtime.core.dependencies.discovery import (
15
15
  from pyrig_runtime.core.dependencies.subclass import DependencySubclass
16
16
  from pyrig_runtime.core.introspection.functions import module_functions
17
17
  from pyrig_runtime.core.introspection.modules import (
18
- replace_root_module_name,
19
- safe_import_module,
18
+ replace_root_module,
20
19
  )
21
20
  from pyrig_runtime.core.strings import (
22
21
  distribution_summary,
@@ -144,14 +143,15 @@ class CLI(DependencySubclass):
144
143
  level -= step * verbose
145
144
  level += step * quiet
146
145
 
147
- verbose_timestamps = 3
148
- verbose_modules = 2
146
+ verbose_names = 1
147
+ verbose_modules = verbose_names + 1
148
+ verbose_timestamps = verbose_modules + 1
149
149
 
150
150
  if verbose >= verbose_timestamps:
151
151
  fmt = "%(asctime)s %(levelname)s [%(name)s] %(message)s"
152
152
  elif verbose == verbose_modules:
153
153
  fmt = "%(levelname)s [%(name)s] %(message)s"
154
- elif verbose == 1:
154
+ elif verbose == verbose_names:
155
155
  fmt = "%(levelname)s: %(message)s"
156
156
  else:
157
157
  fmt = "%(message)s"
@@ -171,10 +171,9 @@ class CLI(DependencySubclass):
171
171
  If the invoking project's subcommands module cannot be imported,
172
172
  registration is silently skipped.
173
173
  """
174
- subcommands_module_name = replace_root_module_name(
175
- subcommands, root_module_name=self.package_name()
174
+ subcommands_module = replace_root_module(
175
+ subcommands, root=self.package_name(), default=None
176
176
  )
177
- subcommands_module = safe_import_module(subcommands_module_name, default=None)
178
177
 
179
178
  if subcommands_module is None:
180
179
  return
@@ -264,4 +263,4 @@ class CLI(DependencySubclass):
264
263
  (e.g. `uv run my-project`), `sys.argv[0]` is the path to that script, so
265
264
  its basename is the project name as it was registered.
266
265
  """
267
- return Path(sys.argv[0]).name
266
+ return Path(sys.argv[0]).stem
@@ -1,126 +0,0 @@
1
- """Utilities for Python modules."""
2
-
3
- import logging
4
- from collections.abc import Iterable, Iterator
5
- from importlib import import_module
6
- from pkgutil import iter_modules as pkgutil_iter_modules
7
- from types import ModuleType
8
- from typing import Any
9
-
10
- from pyrig_runtime.core.constants import MISSING
11
- from pyrig_runtime.core.wrappers import safe_call
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- def root_module(module: ModuleType) -> ModuleType:
17
- """Import and return the top-level package of the given module.
18
-
19
- For a module named `"package.subpackage.module"`, the module corresponding
20
- to `"package"` is returned. For a top-level module with no dots in its name,
21
- the module for that same name is returned.
22
-
23
- Args:
24
- module: Module to resolve the root package for.
25
-
26
- Returns:
27
- The module corresponding to the first segment of the dotted name.
28
- """
29
- return import_module(module.__name__.split(".")[0])
30
-
31
-
32
- def safe_import_module(
33
- module_name: str,
34
- *args: Any,
35
- default: Any = MISSING,
36
- exceptions: tuple[type[BaseException], ...] = (Exception,),
37
- **kwargs: Any,
38
- ) -> ModuleType | Any:
39
- """Import a module by name, with an optional fallback on failure.
40
-
41
- Any `Exception` raised during import — not just `ImportError` — is
42
- handled, so errors at module level (e.g., `ValueError` raised on
43
- import) are also covered.
44
-
45
- Args:
46
- module_name: Dotted module name (e.g., `"package.subpackage.module"`).
47
- *args: Positional arguments forwarded to `import_module`.
48
- default: Value to return if the import raises. If not provided,
49
- the exception propagates unchanged.
50
- exceptions: Tuple of exception types to catch. Defaults to `(Exception,)`.
51
- **kwargs: Keyword arguments forwarded to `import_module`.
52
-
53
- Returns:
54
- The imported module, or `default` if an exception is raised and
55
- `default` was provided.
56
- """
57
- return safe_call(
58
- import_module,
59
- module_name,
60
- *args,
61
- **kwargs,
62
- default=default,
63
- exceptions=exceptions,
64
- )
65
-
66
-
67
- def replace_root_module_name(module: ModuleType, root_module_name: str) -> str:
68
- """Replace the root package segment in a module's fully qualified name.
69
-
70
- Only the first segment is replaced; later segments are left untouched even
71
- if they happen to share the old root's name.
72
-
73
- Args:
74
- module: Module whose name to transform.
75
- root_module_name: Root package name to substitute in.
76
-
77
- Returns:
78
- The module name with its first dotted segment replaced.
79
-
80
- Example:
81
- >>> from types import ModuleType
82
- >>> mod = ModuleType("some_package.sub.module")
83
- >>> replace_root_module_name(mod, "my_project")
84
- 'my_project.sub.module'
85
- """
86
- module_current_start = module.__name__.split(".")[0]
87
- return module.__name__.replace(module_current_start, root_module_name, 1)
88
-
89
-
90
- def import_modules(module_names: Iterable[str]) -> Iterator[ModuleType]:
91
- """Import multiple modules by name, lazily.
92
-
93
- Modules are imported on demand as the result is iterated, not eagerly.
94
-
95
- Args:
96
- module_names: Dotted module names to import.
97
-
98
- Yields:
99
- Each imported module, in the order the names are iterated.
100
- """
101
- return (import_module(name) for name in module_names)
102
-
103
-
104
- def iter_modules(package: ModuleType) -> Iterator[tuple[ModuleType, bool]]:
105
- """Import and yield each direct child of a package, in discovery order.
106
-
107
- Only the immediate children are visited; nested sub-packages are not
108
- recursed into.
109
-
110
- Args:
111
- package: Package to iterate. Must have a `__path__` attribute
112
- (i.e., must be a package, not a plain module).
113
-
114
- Yields:
115
- `(module, is_package)` pairs where `module` is the imported child and
116
- `is_package` is `True` when the child is itself a sub-package.
117
-
118
- Note:
119
- Importing each child is a deliberate side effect — any module-level
120
- code in those children executes on demand as the iterator is consumed.
121
- """
122
- for _finder, name, is_package in pkgutil_iter_modules(
123
- package.__path__, prefix=package.__name__ + "."
124
- ):
125
- mod = import_module(name)
126
- yield mod, is_package
File without changes
File without changes