pythagoras 0.24.4__tar.gz → 0.24.6__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 (73) hide show
  1. {pythagoras-0.24.4 → pythagoras-0.24.6}/PKG-INFO +1 -1
  2. {pythagoras-0.24.4 → pythagoras-0.24.6}/pyproject.toml +1 -1
  3. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_060_autonomous_code_portals/autonomous_decorators.py +31 -4
  4. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_060_autonomous_code_portals/autonomous_portal_core_classes.py +94 -14
  5. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_060_autonomous_code_portals/names_usage_analyzer.py +133 -4
  6. pythagoras-0.24.6/src/pythagoras/_070_protected_code_portals/list_flattener.py +50 -0
  7. pythagoras-0.24.6/src/pythagoras/_070_protected_code_portals/package_manager.py +150 -0
  8. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_070_protected_code_portals/protected_portal_core_classes.py +70 -0
  9. pythagoras-0.24.6/src/pythagoras/_070_protected_code_portals/system_utils.py +153 -0
  10. pythagoras-0.24.6/src/pythagoras/_070_protected_code_portals/validation_succesful_const.py +16 -0
  11. pythagoras-0.24.6/src/pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +83 -0
  12. pythagoras-0.24.6/src/pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +26 -0
  13. pythagoras-0.24.6/src/pythagoras/_800_signatures_and_converters/hash_signatures.py +69 -0
  14. pythagoras-0.24.6/src/pythagoras/_800_signatures_and_converters/node_signature.py +35 -0
  15. pythagoras-0.24.6/src/pythagoras/_800_signatures_and_converters/random_signatures.py +22 -0
  16. pythagoras-0.24.4/src/pythagoras/_070_protected_code_portals/list_flattener.py +0 -12
  17. pythagoras-0.24.4/src/pythagoras/_070_protected_code_portals/package_manager.py +0 -75
  18. pythagoras-0.24.4/src/pythagoras/_070_protected_code_portals/system_utils.py +0 -80
  19. pythagoras-0.24.4/src/pythagoras/_070_protected_code_portals/validation_succesful_const.py +0 -11
  20. pythagoras-0.24.4/src/pythagoras/_800_signatures_and_converters/base_16_32_convertors.py +0 -48
  21. pythagoras-0.24.4/src/pythagoras/_800_signatures_and_converters/current_date_gmt_str.py +0 -11
  22. pythagoras-0.24.4/src/pythagoras/_800_signatures_and_converters/hash_signatures.py +0 -33
  23. pythagoras-0.24.4/src/pythagoras/_800_signatures_and_converters/node_signature.py +0 -20
  24. pythagoras-0.24.4/src/pythagoras/_800_signatures_and_converters/random_signatures.py +0 -11
  25. {pythagoras-0.24.4 → pythagoras-0.24.6}/README.md +0 -0
  26. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/.DS_Store +0 -0
  27. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_010_basic_portals/__init__.py +0 -0
  28. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_010_basic_portals/basic_portal_core_classes.py +0 -0
  29. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_010_basic_portals/exceptions.py +0 -0
  30. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_010_basic_portals/long_infoname.py +0 -0
  31. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_010_basic_portals/portal_tester.py +0 -0
  32. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_010_basic_portals/post_init_metaclass.py +0 -0
  33. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_020_ordinary_code_portals/__init__.py +0 -0
  34. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_020_ordinary_code_portals/code_normalizer.py +0 -0
  35. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_020_ordinary_code_portals/function_processing.py +0 -0
  36. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_020_ordinary_code_portals/ordinary_decorator.py +0 -0
  37. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_020_ordinary_code_portals/ordinary_portal_core_classes.py +0 -0
  38. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_030_data_portals/__init__.py +0 -0
  39. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_030_data_portals/data_portal_core_classes.py +0 -0
  40. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_030_data_portals/ready_and_get.py +0 -0
  41. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_030_data_portals/storable_decorator.py +0 -0
  42. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/__init__.py +0 -0
  43. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/exception_processing_tracking.py +0 -0
  44. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/execution_environment_summary.py +0 -0
  45. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/kw_args.py +0 -0
  46. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/logging_decorator.py +0 -0
  47. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/logging_portal_core_classes.py +0 -0
  48. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/notebook_checker.py +0 -0
  49. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/output_capturer.py +0 -0
  50. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_040_logging_code_portals/uncaught_exceptions.py +0 -0
  51. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_050_safe_code_portals/__init__.py +0 -0
  52. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_050_safe_code_portals/safe_decorator.py +0 -0
  53. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_050_safe_code_portals/safe_portal_core_classes.py +0 -0
  54. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_060_autonomous_code_portals/__init__.py +0 -0
  55. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_070_protected_code_portals/__init__.py +0 -0
  56. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_070_protected_code_portals/basic_pre_validators.py +0 -0
  57. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_070_protected_code_portals/fn_arg_names_checker.py +0 -0
  58. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_070_protected_code_portals/protected_decorators.py +0 -0
  59. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_080_pure_code_portals/__init__.py +0 -0
  60. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_080_pure_code_portals/pure_core_classes.py +0 -0
  61. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_080_pure_code_portals/pure_decorator.py +0 -0
  62. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_080_pure_code_portals/recursion_pre_validator.py +0 -0
  63. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_090_swarming_portals/__init__.py +0 -0
  64. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_090_swarming_portals/output_suppressor.py +0 -0
  65. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_090_swarming_portals/swarming_portals.py +0 -0
  66. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_100_top_level_API/__init__.py +0 -0
  67. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_100_top_level_API/default_local_portal.py +0 -0
  68. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_100_top_level_API/top_level_API.py +0 -0
  69. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_800_signatures_and_converters/__init__.py +0 -0
  70. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_900_project_stats_collector/__init__.py +0 -0
  71. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/_900_project_stats_collector/project_analyzer.py +0 -0
  72. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/__init__.py +0 -0
  73. {pythagoras-0.24.4 → pythagoras-0.24.6}/src/pythagoras/core/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythagoras
3
- Version: 0.24.4
3
+ Version: 0.24.6
4
4
  Summary: Planet-scale distributed computing in Python.
5
5
  Keywords: cloud,ML,AI,serverless,distributed,parallel,machine-learning,deep-learning,pythagoras
6
6
  Author: Volodymyr (Vlad) Pavlov
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "pythagoras"
7
- version = "0.24.4"
7
+ version = "0.24.6"
8
8
  authors = [
9
9
  {name = "Volodymyr (Vlad) Pavlov", email = "vlpavlov@ieee.org"},
10
10
  ]
@@ -43,11 +43,16 @@ from persidict import Joker, KEEP_CURRENT
43
43
 
44
44
 
45
45
  class autonomous(safe):
46
- """Decorator for enforcing autonomicity requirements for functions.
46
+ """Decorator that turns a regular function into an autonomous one.
47
47
 
48
- An autonomous function is only allowed to use the built-in objects
49
- (functions, types, variables), as well as global objects,
50
- accessible via import statements inside the function body.
48
+ An autonomous function is a self-contained function: it
49
+ can only use built-ins and any names it imports inside its own body. This
50
+ decorator wraps the target callable into an AutonomousFn and enforces both
51
+ static and runtime autonomy checks via the selected portal.
52
+
53
+ Notes:
54
+ - Only regular (non-async) functions are supported.
55
+ - Methods, closures, lambdas, and coroutines are not considered autonomous.
51
56
  """
52
57
  _fixed_args: dict|None
53
58
 
@@ -56,6 +61,19 @@ class autonomous(safe):
56
61
  , excessive_logging: bool|Joker = KEEP_CURRENT
57
62
  , portal: AutonomousCodePortal | None = None
58
63
  ):
64
+ """Initialize the decorator.
65
+
66
+ Args:
67
+ fixed_kwargs: Keyword arguments to pre-bind (partially apply) to the
68
+ decorated function. These will be merged into every call.
69
+ excessive_logging: If True, enables verbose logging within the
70
+ selected portal. KEEP_CURRENT leaves the portal's setting as-is.
71
+ portal: Portal instance to use for autonomy and safety checks.
72
+
73
+ Raises:
74
+ AssertionError: If portal is not an AutonomousCodePortal or None, or
75
+ if fixed_kwargs is not a dict or None.
76
+ """
59
77
  assert isinstance(portal, AutonomousCodePortal) or portal is None
60
78
  assert isinstance(fixed_kwargs, dict) or fixed_kwargs is None
61
79
  safe.__init__(self=self
@@ -65,6 +83,15 @@ class autonomous(safe):
65
83
 
66
84
 
67
85
  def __call__(self, fn: Callable|str) -> AutonomousFn:
86
+ """Wrap the function with autonomy enforcement.
87
+
88
+ Args:
89
+ fn: The function object to decorate.
90
+
91
+ Returns:
92
+ AutonomousFn: A wrapper that enforces autonomy at decoration and at
93
+ execution time, with any fixed keyword arguments pre-applied.
94
+ """
68
95
  wrapper = AutonomousFn(fn
69
96
  ,portal=self._portal
70
97
  ,fixed_kwargs=self._fixed_kwargs
@@ -1,14 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import builtins
4
- from typing import Callable, Any
5
-
6
- from persidict import PersiDict, Joker, KEEP_CURRENT
7
-
8
- from .._010_basic_portals import PortalAwareClass
9
- from .._010_basic_portals.basic_portal_core_classes import _visit_portal
10
4
  from .._020_ordinary_code_portals.code_normalizer import _pythagoras_decorator_names
11
- from .._030_data_portals import DataPortal
12
5
  from .._040_logging_code_portals import KwArgs
13
6
 
14
7
  from .._060_autonomous_code_portals.names_usage_analyzer import (
@@ -17,12 +10,27 @@ from .._060_autonomous_code_portals.names_usage_analyzer import (
17
10
  from .._050_safe_code_portals.safe_portal_core_classes import *
18
11
 
19
12
  class AutonomousCodePortal(SafeCodePortal):
20
-
13
+ """Portal configured for enforcing autonomy constraints.
14
+
15
+ This portal behaves like SafeCodePortal but is specialized for autonomous
16
+ functions. It controls logging and consistency checks for operations related
17
+ to AutonomousFn instances.
18
+ """
21
19
  def __init__(self
22
20
  , root_dict: PersiDict | str | None = None
23
21
  , p_consistency_checks: float | Joker = KEEP_CURRENT
24
22
  , excessive_logging: bool|Joker = KEEP_CURRENT
25
23
  ):
24
+ """Create an autonomous code portal.
25
+
26
+ Args:
27
+ root_dict: Persistence root backing the portal state. Can be a
28
+ PersiDict instance, a path string, or None for defaults.
29
+ p_consistency_checks: Probability [0..1] to run extra consistency
30
+ checks on operations. KEEP_CURRENT uses the existing setting.
31
+ excessive_logging: Whether to enable verbose logging. KEEP_CURRENT
32
+ preserves the existing portal setting.
33
+ """
26
34
  SafeCodePortal.__init__(self
27
35
  , root_dict=root_dict
28
36
  , p_consistency_checks=p_consistency_checks
@@ -30,10 +38,19 @@ class AutonomousCodePortal(SafeCodePortal):
30
38
 
31
39
 
32
40
  class AutonomousFnCallSignature(SafeFnCallSignature):
33
- """A signature of a call to an autonomous function"""
41
+ """A signature of a call to an autonomous function.
42
+
43
+ This extends SafeFnCallSignature to reference AutonomousFn instances.
44
+ """
34
45
  _fn_cache: AutonomousFn | None
35
46
 
36
47
  def __init__(self, fn: AutonomousFn, arguments: dict):
48
+ """Create a call signature for an autonomous function.
49
+
50
+ Args:
51
+ fn: The autonomous function being called.
52
+ arguments: The call-time arguments mapping (already validated).
53
+ """
37
54
  assert isinstance(fn, AutonomousFn)
38
55
  assert isinstance(arguments, dict)
39
56
  super().__init__(fn, arguments)
@@ -45,7 +62,13 @@ class AutonomousFnCallSignature(SafeFnCallSignature):
45
62
 
46
63
 
47
64
  class AutonomousFn(SafeFn):
65
+ """A SafeFn wrapper that enforces function autonomy rules.
48
66
 
67
+ AutonomousFn performs static validation at construction time to ensure that
68
+ the wrapped function uses only built-ins or names imported inside its body,
69
+ has no yield statements, and does not reference nonlocal variables.
70
+ It also supports partial application via fixed keyword arguments.
71
+ """
49
72
  _fixed_kwargs_cache: KwArgs | None
50
73
  _fixed_kwargs_packed: KwArgs | None
51
74
 
@@ -53,6 +76,20 @@ class AutonomousFn(SafeFn):
53
76
  , fixed_kwargs: dict[str,Any]|None = None
54
77
  , excessive_logging: bool|Joker = KEEP_CURRENT
55
78
  , portal: AutonomousCodePortal|None = None):
79
+ """Construct an AutonomousFn and validate autonomy constraints.
80
+
81
+ Args:
82
+ fn: The function object, a string with the function's source code,
83
+ or an existing SafeFn to wrap. If an AutonomousFn is provided,
84
+ fixed_kwargs are merged.
85
+ fixed_kwargs: Keyword arguments to pre-bind (partially apply).
86
+ excessive_logging: Verbose logging flag or KEEP_CURRENT.
87
+ portal: AutonomousCodePortal to use; may be None to defer.
88
+
89
+ Raises:
90
+ AssertionError: If static analysis detects violations of autonomy
91
+ (nonlocal/global unbound names, missing imports, or yield usage).
92
+ """
56
93
  super().__init__(fn=fn
57
94
  , portal = portal
58
95
  , excessive_logging = excessive_logging)
@@ -105,6 +142,11 @@ class AutonomousFn(SafeFn):
105
142
 
106
143
  @property
107
144
  def fixed_kwargs(self) -> KwArgs:
145
+ """KwArgs of pre-bound keyword arguments for this function.
146
+
147
+ Returns:
148
+ KwArgs: The fixed keyword arguments.
149
+ """
108
150
  if not hasattr(self, "_fixed_kwargs_cache"):
109
151
  with self.portal:
110
152
  self._fixed_kwargs_cache = self._fixed_kwargs_packed.unpack()
@@ -112,6 +154,19 @@ class AutonomousFn(SafeFn):
112
154
 
113
155
 
114
156
  def execute(self, **kwargs) -> Any:
157
+ """Execute the function within the portal, applying fixed kwargs.
158
+
159
+ Any kwargs provided here must not overlap with pre-bound fixed kwargs.
160
+
161
+ Args:
162
+ **kwargs: Call-time keyword arguments.
163
+
164
+ Returns:
165
+ Any: Result of the wrapped function call.
166
+
167
+ Raises:
168
+ AssertionError: If provided kwargs overlap with fixed kwargs.
169
+ """
115
170
  with self.portal:
116
171
  overlapping_keys = set(kwargs.keys()) & set(self.fixed_kwargs.keys())
117
172
  assert len(overlapping_keys) == 0
@@ -120,16 +175,33 @@ class AutonomousFn(SafeFn):
120
175
 
121
176
 
122
177
  def get_signature(self, arguments:dict) -> AutonomousFnCallSignature:
178
+ """Build a call signature object for this function.
179
+
180
+ Args:
181
+ arguments: Mapping of argument names to values for this call.
182
+
183
+ Returns:
184
+ AutonomousFnCallSignature: The signature representing this call.
185
+ """
123
186
  return AutonomousFnCallSignature(fn=self, arguments=arguments)
124
187
 
125
188
 
126
189
  def fix_kwargs(self, **kwargs) -> AutonomousFn:
127
- """Create a new function by pre-filling some arguments.
190
+ """Create a new autonomous function with some kwargs pre-filled.
128
191
 
129
- This is called a partial application in functional programming
130
- It allows creating specialized functions from general ones by
131
- transforming a function with multiple parameters
132
- into another function with fewer parameters by fixing some arguments.
192
+ This is partial application: it creates a function with fewer parameters
193
+ by fixing a subset of keyword arguments.
194
+
195
+ Args:
196
+ **kwargs: Keyword arguments to fix for the new function.
197
+
198
+ Returns:
199
+ AutonomousFn: A new wrapper that will always apply the provided
200
+ keyword arguments in addition to already fixed ones.
201
+
202
+ Raises:
203
+ AssertionError: If any of the provided kwargs overlap with already
204
+ fixed kwargs.
133
205
  """
134
206
 
135
207
  overlapping_keys = set(kwargs.keys()) & set(self.fixed_kwargs.keys())
@@ -140,6 +212,13 @@ class AutonomousFn(SafeFn):
140
212
 
141
213
 
142
214
  def _first_visit_to_portal(self, portal: DataPortal) -> None:
215
+ """Hook called on the first visit to a data portal.
216
+
217
+ Ensures that fixed kwargs are materialized (packed) within the portal.
218
+
219
+ Args:
220
+ portal: The data portal being visited for the first time.
221
+ """
143
222
  super()._first_visit_to_portal(portal)
144
223
  with portal:
145
224
  _ = self.fixed_kwargs.pack()
@@ -160,6 +239,7 @@ class AutonomousFn(SafeFn):
160
239
 
161
240
  @property
162
241
  def portal(self) -> AutonomousCodePortal:
242
+ """Return the autonomous portal associated with this function."""
163
243
  return super().portal
164
244
 
165
245
 
@@ -4,7 +4,23 @@ from typing import Callable, Union
4
4
  from .._020_ordinary_code_portals import get_normalized_function_source
5
5
 
6
6
  class NamesUsedInFunction:
7
+ """Container for name usage sets discovered in a function.
8
+
9
+ Attributes:
10
+ function: Name of the top-level function being analyzed.
11
+ explicitly_global_unbound_deep: Names explicitly marked as global in the
12
+ function or its nested functions, which are not locally bound.
13
+ explicitly_nonlocal_unbound_deep: Names explicitly marked as nonlocal in
14
+ the function or its nested functions, which are not locally bound.
15
+ local: Names bound locally in the top-level function (including args).
16
+ imported: Names explicitly imported within the function body.
17
+ unclassified_deep: Names used in the function and/or nested functions
18
+ that are neither imported nor explicitly marked global/nonlocal.
19
+ accessible: All names currently considered accessible within function
20
+ scope during analysis; a union built as nodes are visited.
21
+ """
7
22
  def __init__(self):
23
+ """Initialize all name sets to empty defaults."""
8
24
  self.function = None # name of the function
9
25
  self.explicitly_global_unbound_deep = set() # names, explicitly marked as global inside the function and/or called subfunctions, yet not bound to any object
10
26
  self.explicitly_nonlocal_unbound_deep = set() # names, explicitly marked as nonlocal inside the function and/or called subfunctions, yet not bound to any object
@@ -21,12 +37,23 @@ class NamesUsageAnalyzer(ast.NodeVisitor):
21
37
  """
22
38
  # TODO: add support for structural pattern matching
23
39
  def __init__(self):
40
+ """Initialize the analyzer state and counters."""
24
41
  self.names = NamesUsedInFunction()
25
42
  self.imported_packages_deep = set()
26
43
  self.func_nesting_level = 0
27
44
  self.n_yelds = 0
28
45
 
29
46
  def visit_FunctionDef(self, node):
47
+ """Handle a function definition.
48
+
49
+ - For the top-level function: record its name, parameters as locals,
50
+ and traverse its body.
51
+ - For nested functions: analyze them with a fresh analyzer and merge
52
+ relevant sets into the current analyzer, adjusting for accessibility.
53
+
54
+ Args:
55
+ node: The ast.FunctionDef node.
56
+ """
30
57
  if self.func_nesting_level == 0:
31
58
  self.names.function = node.name
32
59
  self.func_nesting_level += 1
@@ -54,6 +81,15 @@ class NamesUsageAnalyzer(ast.NodeVisitor):
54
81
  # self.n_yelds is not changing
55
82
 
56
83
  def visit_Name(self, node):
84
+ """Track variable usage and binding for a Name node.
85
+
86
+ - On load: if the name is not accessible, mark it unclassified and
87
+ accessible.
88
+ - On store: register it as a local and accessible.
89
+
90
+ Args:
91
+ node: The ast.Name node.
92
+ """
57
93
  if isinstance(node.ctx, ast.Load):
58
94
  if node.id not in self.names.accessible:
59
95
  self.names.unclassified_deep |= {node.id}
@@ -65,23 +101,58 @@ class NamesUsageAnalyzer(ast.NodeVisitor):
65
101
  self.generic_visit(node)
66
102
 
67
103
  def visit_Attribute(self, node):
104
+ """Visit an attribute access expression.
105
+
106
+ Currently no special handling is required; traversal continues.
107
+
108
+ Args:
109
+ node: The ast.Attribute node.
110
+ """
68
111
  self.generic_visit(node)
69
112
 
70
113
  def visit_Yield(self, node):
114
+ """Record usage of a yield expression.
115
+
116
+ Increments the number of yields found, which disqualifies autonomy.
117
+
118
+ Args:
119
+ node: The ast.Yield node.
120
+ """
71
121
  self.n_yelds += 1
72
122
  self.generic_visit(node)
73
123
 
74
124
  def visit_YieldFrom(self, node):
125
+ """Record usage of a 'yield from' expression.
126
+
127
+ Increments the number of yields found, which disqualifies autonomy.
128
+
129
+ Args:
130
+ node: The ast.YieldFrom node.
131
+ """
75
132
  self.n_yelds += 1
76
133
  self.generic_visit(node)
77
134
 
78
135
  def visit_Try(self, node):
136
+ """Track names bound in exception handlers within try/except.
137
+
138
+ Exception handler names become local and accessible.
139
+
140
+ Args:
141
+ node: The ast.Try node.
142
+ """
79
143
  for handler in node.handlers:
80
144
  self.names.local |= {handler.name}
81
145
  self.names.accessible |= {handler.name}
82
146
  self.generic_visit(node)
83
147
 
84
148
  def visit_comprehension(self, node):
149
+ """Handle variable binding within a comprehension clause.
150
+
151
+ Targets in comprehension generators become local and accessible.
152
+
153
+ Args:
154
+ node: The ast.comprehension node or a loop node with a similar API.
155
+ """
85
156
  if isinstance(node.target, (ast.Tuple, ast.List)):
86
157
  all_targets =node.target.elts
87
158
  else:
@@ -94,29 +165,59 @@ class NamesUsageAnalyzer(ast.NodeVisitor):
94
165
  self.generic_visit(node)
95
166
 
96
167
  def visit_For(self, node):
168
+ """Handle a for-loop comprehension-like binding.
169
+
170
+ Args:
171
+ node: The ast.For node.
172
+ """
97
173
  self.visit_comprehension(node)
98
174
 
99
175
  def visit_ListComp(self, node):
176
+ """Handle bindings within a list comprehension.
177
+
178
+ Args:
179
+ node: The ast.ListComp node.
180
+ """
100
181
  for gen in node.generators:
101
182
  self.visit_comprehension(gen)
102
183
  self.generic_visit(node)
103
184
 
104
185
  def visit_SetComp(self, node):
186
+ """Handle bindings within a set comprehension.
187
+
188
+ Args:
189
+ node: The ast.SetComp node.
190
+ """
105
191
  for gen in node.generators:
106
192
  self.visit_comprehension(gen)
107
193
  self.generic_visit(node)
108
194
 
109
195
  def visit_DictComp(self, node):
196
+ """Handle bindings within a dict comprehension.
197
+
198
+ Args:
199
+ node: The ast.DictComp node.
200
+ """
110
201
  for gen in node.generators:
111
202
  self.visit_comprehension(gen)
112
203
  self.generic_visit(node)
113
204
 
114
205
  def visit_GeneratorExp(self, node):
206
+ """Handle bindings within a generator expression.
207
+
208
+ Args:
209
+ node: The ast.GeneratorExp node.
210
+ """
115
211
  for gen in node.generators:
116
212
  self.visit_comprehension(gen)
117
213
  self.generic_visit(node)
118
214
 
119
215
  def visit_Import(self, node):
216
+ """Register imported names and top-level package usage.
217
+
218
+ Args:
219
+ node: The ast.Import node.
220
+ """
120
221
  for alias in node.names:
121
222
  name = alias.asname if alias.asname else alias.name
122
223
  self.names.imported |= {name}
@@ -125,6 +226,11 @@ class NamesUsageAnalyzer(ast.NodeVisitor):
125
226
  self.generic_visit(node)
126
227
 
127
228
  def visit_ImportFrom(self, node):
229
+ """Register names imported from a module and the module itself.
230
+
231
+ Args:
232
+ node: The ast.ImportFrom node.
233
+ """
128
234
  self.imported_packages_deep |= {node.module.split('.')[-1]}
129
235
  for alias in node.names:
130
236
  name = alias.asname if alias.asname else alias.name
@@ -133,12 +239,22 @@ class NamesUsageAnalyzer(ast.NodeVisitor):
133
239
  self.generic_visit(node)
134
240
 
135
241
  def visit_Nonlocal(self, node):
242
+ """Record names declared as nonlocal within the function.
243
+
244
+ Args:
245
+ node: The ast.Nonlocal node.
246
+ """
136
247
  nonlocals = set(node.names)
137
248
  self.names.explicitly_nonlocal_unbound_deep |= nonlocals
138
249
  self.names.accessible |= nonlocals
139
250
  self.generic_visit(node)
140
251
 
141
252
  def visit_Global(self, node):
253
+ """Record names declared as global within the function.
254
+
255
+ Args:
256
+ node: The ast.Global node.
257
+ """
142
258
  globals = set(node.names)
143
259
  self.names.explicitly_global_unbound_deep |= globals
144
260
  self.names.accessible |= globals
@@ -147,11 +263,24 @@ class NamesUsageAnalyzer(ast.NodeVisitor):
147
263
  def analyze_names_in_function(
148
264
  a_func: Union[Callable,str]
149
265
  ):
150
- """Analyze names used in a function.
266
+ """Analyze names used in a single conventional function.
267
+
268
+ The function source is normalized, decorators are skipped, and an AST is
269
+ parsed. Assertions ensure that exactly one top-level regular function
270
+ definition is present. The tree is visited with NamesUsageAnalyzer.
271
+
272
+ Args:
273
+ a_func: A function object or its source string to analyze.
274
+
275
+ Returns:
276
+ dict: A mapping with keys:
277
+ - tree (ast.Module): The parsed AST module with a single function.
278
+ - analyzer (NamesUsageAnalyzer): The populated analyzer instance.
279
+ - normalized_source (str): The normalized source code.
151
280
 
152
- It returns an instance of NamesUsageAnalyzer class,
153
- which contains all the data needed to analyze
154
- names, used by the function.
281
+ Raises:
282
+ AssertionError: If the input is not a single regular function (e.g., a
283
+ lambda, async function, callable class, or multiple definitions).
155
284
  """
156
285
 
157
286
  normalized_source = get_normalized_function_source(a_func)
@@ -0,0 +1,50 @@
1
+ from typing import List, Any
2
+
3
+
4
+ def flatten_list(nested_list: List[Any]) -> List[Any]:
5
+ """Flatten a nested list into a single-level list.
6
+
7
+ This function flattens lists of arbitrary depth using an iterative
8
+ (non-recursive) algorithm. Only values of type ``list`` are treated as
9
+ containers to be expanded. Other iterable types (e.g., tuples, sets,
10
+ strings, generators) are considered atomic values and are not traversed.
11
+
12
+ Parameters:
13
+ - nested_list: list
14
+ A possibly nested Python list. Must be an instance of ``list``.
15
+
16
+ Returns:
17
+ - list
18
+ A new list containing all elements from ``nested_list`` in their
19
+ original left-to-right order, but with one level of nesting (flat).
20
+
21
+ Raises:
22
+ - TypeError: If ``nested_list`` is not a ``list`` instance.
23
+
24
+ Notes:
25
+ - Preserves the order of elements.
26
+ - Supports unlimited nesting depth without recursion.
27
+ - Cyclic references (e.g., a list that contains itself, directly or
28
+ indirectly) will lead to an infinite loop.
29
+
30
+ Examples:
31
+ >>> flatten_list([1, [2, 3, [4]], 5])
32
+ [1, 2, 3, 4, 5]
33
+ >>> flatten_list([["a", ["b"]], "c"])
34
+ ['a', 'b', 'c']
35
+ >>> flatten_list([(1, 2), [3, 4]])
36
+ [(1, 2), 3, 4]
37
+ """
38
+ if not isinstance(nested_list, list):
39
+ raise TypeError(f"Expected list, got {type(nested_list).__name__}")
40
+ flattened = []
41
+ stack = [nested_list]
42
+
43
+ while stack:
44
+ current = stack.pop()
45
+ if isinstance(current, list):
46
+ stack.extend(reversed(current))
47
+ else:
48
+ flattened.append(current)
49
+
50
+ return flattened