mapFolding 0.8.3__py3-none-any.whl → 0.8.5__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.
Files changed (38) hide show
  1. mapFolding/__init__.py +6 -3
  2. mapFolding/basecamp.py +13 -7
  3. mapFolding/beDRY.py +241 -68
  4. mapFolding/oeis.py +4 -4
  5. mapFolding/reference/hunterNumba.py +1 -1
  6. mapFolding/someAssemblyRequired/__init__.py +40 -20
  7. mapFolding/someAssemblyRequired/_theTypes.py +53 -0
  8. mapFolding/someAssemblyRequired/_tool_Make.py +99 -0
  9. mapFolding/someAssemblyRequired/_tool_Then.py +72 -0
  10. mapFolding/someAssemblyRequired/_toolboxAntecedents.py +358 -0
  11. mapFolding/someAssemblyRequired/_toolboxContainers.py +334 -0
  12. mapFolding/someAssemblyRequired/_toolboxPython.py +62 -0
  13. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +2 -2
  14. mapFolding/someAssemblyRequired/newInliner.py +22 -0
  15. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +158 -0
  16. mapFolding/someAssemblyRequired/toolboxNumba.py +358 -0
  17. mapFolding/someAssemblyRequired/transformationTools.py +289 -698
  18. mapFolding/syntheticModules/numbaCount_doTheNeedful.py +36 -33
  19. mapFolding/theDao.py +13 -11
  20. mapFolding/theSSOT.py +83 -128
  21. mapFolding/toolboxFilesystem.py +219 -0
  22. {mapfolding-0.8.3.dist-info → mapfolding-0.8.5.dist-info}/METADATA +4 -2
  23. mapfolding-0.8.5.dist-info/RECORD +48 -0
  24. {mapfolding-0.8.3.dist-info → mapfolding-0.8.5.dist-info}/WHEEL +1 -1
  25. tests/conftest.py +56 -52
  26. tests/test_computations.py +42 -32
  27. tests/test_filesystem.py +4 -4
  28. tests/test_other.py +2 -2
  29. tests/test_tasks.py +2 -2
  30. mapFolding/filesystem.py +0 -129
  31. mapFolding/someAssemblyRequired/ingredientsNumba.py +0 -206
  32. mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +0 -211
  33. mapFolding/someAssemblyRequired/synthesizeNumbaJobVESTIGIAL.py +0 -413
  34. mapFolding/someAssemblyRequired/transformDataStructures.py +0 -168
  35. mapfolding-0.8.3.dist-info/RECORD +0 -43
  36. {mapfolding-0.8.3.dist-info → mapfolding-0.8.5.dist-info}/entry_points.txt +0 -0
  37. {mapfolding-0.8.3.dist-info → mapfolding-0.8.5.dist-info}/licenses/LICENSE +0 -0
  38. {mapfolding-0.8.3.dist-info → mapfolding-0.8.5.dist-info}/top_level.txt +0 -0
@@ -23,633 +23,301 @@ they are designed as general-purpose utilities applicable to a wide range of cod
23
23
  transformation scenarios beyond the scope of this package.
24
24
  """
25
25
  from autoflake import fix_code as autoflake_fix_code
26
- from collections import defaultdict
27
- from collections.abc import Callable, Container, Sequence
26
+ from collections.abc import Callable, Mapping
28
27
  from copy import deepcopy
29
- from importlib import import_module as importlib_import_module
30
- from inspect import getsource as inspect_getsource
31
- from mapFolding.filesystem import writeStringToHere
32
- from mapFolding.theSSOT import (
33
- raiseIfNoneGitHubIssueNumber3,
34
- The,
35
- theFormatStrModuleForCallableSynthetic,
36
- theFormatStrModuleSynthetic,
37
- theLogicalPathModuleDispatcherSynthetic,
38
- theModuleDispatcherSynthetic,
28
+ from mapFolding.beDRY import outfitCountFolds
29
+ from mapFolding.toolboxFilesystem import getPathFilenameFoldsTotal, writeStringToHere
30
+ from mapFolding.someAssemblyRequired import (
31
+ ast_Identifier,
32
+ be,
33
+ DOT,
34
+ ifThis,
35
+ ImaAnnotationType,
36
+ importLogicalPath2Callable,
37
+ IngredientsFunction,
38
+ IngredientsModule,
39
+ LedgerOfImports,
40
+ Make,
41
+ NodeChanger,
42
+ NodeTourist,
43
+ parseLogicalPath2astModule,
44
+ ShatteredDataclass,
45
+ str_nameDOTname,
46
+ Then,
47
+ TypeCertified,
48
+ 又,
39
49
  )
50
+ from mapFolding.theSSOT import ComputationState, The, raiseIfNoneGitHubIssueNumber3
40
51
  from os import PathLike
41
- from pathlib import Path, PurePath, PurePosixPath
42
- from types import ModuleType
43
- from typing import Any, cast, Generic, TypeAlias, TypeGuard, TypeVar
44
- from Z0Z_tools import updateExtendPolishDictionaryLists
52
+ from pathlib import Path, PurePath
53
+ from typing import Any, Literal, overload
45
54
  import ast
46
55
  import dataclasses
56
+ import pickle
57
+
58
+ def astModuleToIngredientsFunction(astModule: ast.AST, identifierFunctionDef: ast_Identifier) -> IngredientsFunction:
59
+ astFunctionDef = extractFunctionDef(astModule, identifierFunctionDef)
60
+ if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
61
+ return IngredientsFunction(astFunctionDef, LedgerOfImports(astModule))
62
+
63
+ def extractClassDef(module: ast.AST, identifier: ast_Identifier) -> ast.ClassDef | None:
64
+ return NodeTourist(ifThis.isClassDef_Identifier(identifier), Then.getIt).captureLastMatch(module)
65
+
66
+ def extractFunctionDef(module: ast.AST, identifier: ast_Identifier) -> ast.FunctionDef | None:
67
+ return NodeTourist(ifThis.isFunctionDef_Identifier(identifier), Then.getIt).captureLastMatch(module)
68
+
69
+ def makeDictionaryFunctionDef(module: ast.AST) -> dict[ast_Identifier, ast.FunctionDef]:
70
+ dictionaryIdentifier2FunctionDef: dict[ast_Identifier, ast.FunctionDef] = {}
71
+ NodeTourist(be.FunctionDef, Then.updateKeyValueIn(DOT.name, Then.getIt, dictionaryIdentifier2FunctionDef)).visit(module)
72
+ return dictionaryIdentifier2FunctionDef
73
+
74
+ def makeDictionary4InliningFunction(identifierToInline: ast_Identifier, dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef], FunctionDefToInline: ast.FunctionDef | None = None) -> dict[str, ast.FunctionDef]:
75
+ """
76
+ Creates a dictionary of function definitions required for inlining a target function.
77
+ This function analyzes a target function and recursively collects all function definitions
78
+ that are called within it (and any functions called by those functions), preparing them for inlining.
79
+ Parameters:
80
+ ----------
81
+ identifierToInline : ast_Identifier
82
+ The identifier of the function to be inlined.
83
+ dictionaryFunctionDef : dict[ast_Identifier, ast.FunctionDef]
84
+ A dictionary mapping function identifiers to their AST function definitions.
85
+ FunctionDefToInline : ast.FunctionDef | None, optional
86
+ The AST function definition to inline. If None, it will be retrieved from dictionaryFunctionDef using identifierToInline.
87
+ Returns:
88
+ -------
89
+ dict[str, ast.FunctionDef]
90
+ A dictionary mapping function names to their AST function definitions, containing all functions needed for inlining.
91
+ Raises:
92
+ ------
93
+ ValueError
94
+ If the function to inline is not found in the dictionary, or if recursion is detected during analysis.
95
+ Notes:
96
+ -----
97
+ The function performs a recursive analysis to find all dependent functions needed for inlining.
98
+ It detects and prevents recursive function calls that could cause infinite inlining.
99
+ """
100
+ if FunctionDefToInline is None:
101
+ try:
102
+ FunctionDefToInline = dictionaryFunctionDef[identifierToInline]
103
+ except KeyError as ERRORmessage:
104
+ raise ValueError(f"FunctionDefToInline not found in dictionaryIdentifier2FunctionDef: {identifierToInline = }") from ERRORmessage
105
+
106
+ listIdentifiersCalledFunctions: list[ast_Identifier] = []
107
+ findIdentifiersToInline = NodeTourist(ifThis.isCallToName, lambda node: Then.appendTo(listIdentifiersCalledFunctions)(DOT.id(DOT.func(node)))) # pyright: ignore[reportArgumentType]
108
+ findIdentifiersToInline.visit(FunctionDefToInline)
109
+
110
+ dictionary4Inlining: dict[ast_Identifier, ast.FunctionDef] = {}
111
+ for identifier in sorted(set(listIdentifiersCalledFunctions).intersection(dictionaryFunctionDef.keys())):
112
+ dictionary4Inlining[identifier] = dictionaryFunctionDef[identifier]
47
113
 
48
- """
49
- Semiotic notes:
50
- In the `ast` package, some things that look and feel like a "name" are not `ast.Name` type. The following semiotics are a balance between technical precision and practical usage.
51
-
52
- astName: always means `ast.Name`.
53
- Name: uppercase, _should_ be interchangeable with astName, even in camelCase.
54
- Hunter: ^^ did you do that ^^ ? Are you sure? You just fixed some that should have been "_name" because it confused you.
55
- name: lowercase, never means `ast.Name`. In camelCase, I _should_ avoid using it in such a way that it could be confused with "Name", uppercase.
56
- _Identifier: very strongly correlates with the private `ast._Identifier`, which is a TypeAlias for `str`.
57
- identifier: lowercase, a general term that includes the above and other Python identifiers.
58
- Identifier: uppercase, without the leading underscore should only appear in camelCase and means "identifier", lowercase.
59
- namespace: lowercase, in dotted-names, such as `pathlib.Path` or `collections.abc`, "namespace" is the part before the dot.
60
- Namespace: uppercase, should only appear in camelCase and means "namespace", lowercase.
61
- """
62
-
63
- # Would `LibCST` be better than `ast` in some cases? https://github.com/hunterhogan/mapFolding/issues/7
64
-
65
- ast_expr_Slice: TypeAlias = ast.expr
66
- ast_Identifier: TypeAlias = str
67
- astClassHasAttributeDOTname: TypeAlias = ast.FunctionDef | ast.ClassDef | ast.AsyncFunctionDef
68
- astMosDef = TypeVar('astMosDef', bound=astClassHasAttributeDOTname)
69
- list_ast_type_paramORintORNone: TypeAlias = Any
70
- nodeType = TypeVar('nodeType', bound=ast.AST)
71
- strDotStrCuzPyStoopid: TypeAlias = str
72
- strORintORNone: TypeAlias = Any
73
- strORlist_ast_type_paramORintORNone: TypeAlias = Any
74
-
75
- class NodeCollector(Generic[nodeType], ast.NodeVisitor):
76
- """A node visitor that collects data via one or more actions when a predicate is met."""
77
- def __init__(self, findThis: Callable[[ast.AST], TypeGuard[nodeType] | bool], doThat: list[Callable[[nodeType], Any]]) -> None:
78
- self.findThis = findThis
79
- self.doThat = doThat
80
-
81
- def visit(self, node: ast.AST) -> None:
82
- if self.findThis(node):
83
- for action in self.doThat:
84
- action(cast(nodeType, node))
85
- self.generic_visit(node)
86
-
87
- class NodeReplacer(Generic[nodeType], ast.NodeTransformer):
88
- """A node transformer that replaces or removes AST nodes based on a condition."""
89
- def __init__(self, findThis: Callable[[ast.AST], TypeGuard[nodeType] | bool], doThat: Callable[[nodeType], ast.AST | Sequence[ast.AST] | None]) -> None:
90
- self.findThis = findThis
91
- self.doThat = doThat
92
-
93
- def visit(self, node: ast.AST) -> ast.AST | Sequence[ast.AST] | None:
94
- if self.findThis(node):
95
- return self.doThat(cast(nodeType, node))
96
- return super().visit(node)
97
-
98
- class ifThis:
99
- @staticmethod
100
- def ast_IdentifierIsIn(container: Container[ast_Identifier]) -> Callable[[ast_Identifier], TypeGuard[ast_Identifier] | bool]:
101
- return lambda node: node in container
102
- @staticmethod
103
- def CallDoesNotCallItself(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
104
- """If `namespace` is not applicable to your case, then call with `namespace=""`."""
105
- return lambda node: ifThis.matchesMeButNotAnyDescendant(ifThis.CallReallyIs(namespace, identifier))(node)
106
- @staticmethod
107
- def CallDoesNotCallItselfAndNameDOTidIsIn(container: Container[ast_Identifier]) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
108
- return lambda node: ifThis.isCall(node) and ifThis.isName(node.func) and ifThis.ast_IdentifierIsIn(container)(node.func.id) and ifThis.CallDoesNotCallItself("", node.func.id)(node)
109
- @staticmethod
110
- def CallReallyIs(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
111
- return ifThis.isAnyOf(ifThis.isCall_Identifier(identifier), ifThis.isCallNamespace_Identifier(namespace, identifier))
112
- @staticmethod
113
- def is_keyword(node: ast.AST) -> TypeGuard[ast.keyword]:
114
- return isinstance(node, ast.keyword)
115
- @staticmethod
116
- def is_keywordAndValueIsConstant(node: ast.AST) -> TypeGuard[ast.keyword]:
117
- return ifThis.is_keyword(node) and ifThis.isConstant(node.value)
118
- @staticmethod
119
- def is_keyword_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.keyword] | bool]:
120
- def workhorse(node: ast.AST) -> TypeGuard[ast.keyword] | bool:
121
- return ifThis.is_keyword(node) and node.arg == identifier
122
- return workhorse
123
- @staticmethod
124
- def is_keyword_IdentifierEqualsConstantValue(identifier: ast_Identifier, ConstantValue: Any) -> Callable[[ast.AST], TypeGuard[ast.keyword] | bool]:
125
- return lambda node: ifThis.is_keyword_Identifier(identifier)(node) and ifThis.is_keywordAndValueIsConstant(node) and ifThis.isConstantEquals(ConstantValue)(node.value)
126
- @staticmethod
127
- def isAllOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
128
- return lambda node: all(predicate(node) for predicate in thesePredicates)
129
- @staticmethod
130
- def isAnnAssign(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
131
- return isinstance(node, ast.AnnAssign)
132
- @staticmethod
133
- def isAnnAssignAndAnnotationIsName(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
134
- return ifThis.isAnnAssign(node) and ifThis.isName(node.annotation)
135
- @staticmethod
136
- def isAnnAssignAndTargetIsName(node: ast.AST) -> TypeGuard[ast.AnnAssign]:
137
- return ifThis.isAnnAssign(node) and ifThis.isName(node.target)
138
- @staticmethod
139
- def isAnnAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AnnAssign] | bool]:
140
- return lambda node: ifThis.isAnnAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target)
141
- @staticmethod
142
- def isAnyAssignmentTo(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
143
- return ifThis.isAnyOf(ifThis.isAssignOnlyTo(identifier), ifThis.isAnnAssignTo(identifier), ifThis.isAugAssignTo(identifier))
144
- @staticmethod
145
- def isAnyCompare(node: ast.AST) -> TypeGuard[ast.Compare] | TypeGuard[ast.BoolOp]:
146
- return ifThis.isCompare(node) or ifThis.isBoolOp(node)
147
- @staticmethod
148
- def isAnyOf(*thesePredicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
149
- return lambda node: any(predicate(node) for predicate in thesePredicates)
150
- @staticmethod
151
- def isAssign(node: ast.AST) -> TypeGuard[ast.Assign]:
152
- return isinstance(node, ast.Assign)
153
- @staticmethod
154
- def isAssignAndValueIsCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
155
- return lambda node: ifThis.isAssign(node) and ifThis.isCall_Identifier(identifier)(node.value)
156
- @staticmethod
157
- def isAssignAndValueIsCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
158
- return ifThis.isAssignAndValueIs(ifThis.isCallNamespace_Identifier(namespace, identifier))
159
- @staticmethod
160
- def isAssignOnlyTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
161
- return lambda node: ifThis.isAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.targets[0])
162
- @staticmethod
163
- def isAssignAndTargets0Is(targets0Predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
164
- """node is Assign and node.targets[0] matches `targets0Predicate`."""
165
- return lambda node: ifThis.isAssign(node) and targets0Predicate(node.targets[0])
166
- @staticmethod
167
- def isAssignAndValueIs(valuePredicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], TypeGuard[ast.Assign] | bool]:
168
- """node is ast.Assign and node.value matches `valuePredicate`.
169
- Parameters:
170
- valuePredicate: Function that evaluates the value of the assignment
171
- Returns:
172
- predicate: matches assignments with values meeting the criteria
173
- """
174
- return lambda node: ifThis.isAssign(node) and valuePredicate(node.value)
175
- @staticmethod
176
- def isAttribute(node: ast.AST) -> TypeGuard[ast.Attribute]:
177
- return isinstance(node, ast.Attribute)
178
- @staticmethod
179
- def isAugAssign(node: ast.AST) -> TypeGuard[ast.AugAssign]:
180
- return isinstance(node, ast.AugAssign)
181
- @staticmethod
182
- def isAugAssignTo(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.AugAssign] | bool]:
183
- return lambda node: ifThis.isAugAssign(node) and ifThis.NameReallyIs_Identifier(identifier)(node.target)
184
- @staticmethod
185
- def isBoolOp(node: ast.AST) -> TypeGuard[ast.BoolOp]:
186
- return isinstance(node, ast.BoolOp)
187
- @staticmethod
188
- def isCall(node: ast.AST) -> TypeGuard[ast.Call]:
189
- return isinstance(node, ast.Call)
190
- @staticmethod
191
- def isCall_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
192
- def workhorse(node: ast.AST) -> TypeGuard[ast.Call] | bool: return ifThis.isCall(node) and ifThis.isName_Identifier(identifier)(node.func)
193
- return workhorse
194
- @staticmethod
195
- def isCallNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Call] | bool]:
196
- return lambda node: ifThis.isCall(node) and ifThis.is_nameDOTnameNamespace_Identifier(namespace, identifier)(node.func)
197
- @staticmethod
198
- def isCallToName(node: ast.AST) -> TypeGuard[ast.Call]:
199
- return ifThis.isCall(node) and ifThis.isName(node.func)
200
- @staticmethod
201
- def isClassDef(node: ast.AST) -> TypeGuard[ast.ClassDef]:
202
- return isinstance(node, ast.ClassDef)
203
- @staticmethod
204
- def isClassDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.ClassDef] | bool]:
205
- return lambda node: ifThis.isClassDef(node) and node.name == identifier
206
- @staticmethod
207
- def isCompare(node: ast.AST) -> TypeGuard[ast.Compare]:
208
- return isinstance(node, ast.Compare)
209
- @staticmethod
210
- def isConstant(node: ast.AST) -> TypeGuard[ast.Constant]:
211
- return isinstance(node, ast.Constant)
212
- @staticmethod
213
- def isConstantEquals(value: Any) -> Callable[[ast.AST], TypeGuard[ast.Constant] | bool]:
214
- return lambda node: ifThis.isConstant(node) and node.value == value
215
- @staticmethod
216
- def isExpr(node: ast.AST) -> TypeGuard[ast.Expr]:
217
- return isinstance(node, ast.Expr)
218
- @staticmethod
219
- def isFunctionDef(node: ast.AST) -> TypeGuard[ast.FunctionDef]: return isinstance(node, ast.FunctionDef)
220
- @staticmethod
221
- def isFunctionDef_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.FunctionDef] | bool]:
222
- return lambda node: ifThis.isFunctionDef(node) and node.name == identifier
223
- @staticmethod
224
- def isImport(node: ast.AST) -> TypeGuard[ast.Import]:
225
- return isinstance(node, ast.Import)
226
- @staticmethod
227
- def isName(node: ast.AST) -> TypeGuard[ast.Name]:
228
- """TODO
229
- ast.Name()
230
- ast.Attribute()
231
- ast.Subscript()
232
- ast.Starred()
233
- """
234
- return isinstance(node, ast.Name)
235
- @staticmethod
236
- def isName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Name] | bool]:
237
- return lambda node: ifThis.isName(node) and node.id == identifier
238
- @staticmethod
239
- def is_nameDOTname(node: ast.AST) -> TypeGuard[ast.Attribute]:
240
- return ifThis.isAttribute(node) and ifThis.isName(node.value)
241
- @staticmethod
242
- def is_nameDOTnameNamespace(namespace: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
243
- return lambda node: ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value)
244
- @staticmethod
245
- def is_nameDOTnameNamespace_Identifier(namespace: ast_Identifier, identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Attribute] | bool]:
246
- return lambda node: ifThis.is_nameDOTname(node) and ifThis.isName_Identifier(namespace)(node.value) and node.attr == identifier
247
- @staticmethod
248
- def NameReallyIs_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], bool]:
249
- # The following logic is incomplete.
250
- return ifThis.isAnyOf(ifThis.isName_Identifier(identifier), ifThis.isSubscriptIsName_Identifier(identifier))
251
- @staticmethod
252
- def isReturn(node: ast.AST) -> TypeGuard[ast.Return]:
253
- return isinstance(node, ast.Return)
254
- @staticmethod
255
- def isReturnAnyCompare(node: ast.AST) -> TypeGuard[ast.Return]:
256
- return ifThis.isReturn(node) and node.value is not None and ifThis.isAnyCompare(node.value)
257
- @staticmethod
258
- def isReturnUnaryOp(node: ast.AST) -> TypeGuard[ast.Return]:
259
- return ifThis.isReturn(node) and node.value is not None and ifThis.isUnaryOp(node.value)
260
- @staticmethod
261
- def isSubscript(node: ast.AST) -> TypeGuard[ast.Subscript]:
262
- return isinstance(node, ast.Subscript)
263
- @staticmethod
264
- def isSubscript_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript]]:
265
- """node is `ast.Subscript` and the top-level `ast.Name` is `identifier`
266
- Parameters:
267
- identifier: The identifier to look for in the value chain
268
- Returns:
269
- workhorse: function that checks if a node matches the criteria
270
- """
271
- def workhorse(node: ast.AST) -> TypeGuard[ast.Subscript]:
272
- if not ifThis.isSubscript(node):
273
- return False
274
- def checkNodeDOTvalue(nodeDOTvalue: ast.AST) -> bool:
275
- if ifThis.isName(nodeDOTvalue):
276
- if nodeDOTvalue.id == identifier:
277
- return True
278
- elif hasattr(nodeDOTvalue, "value"):
279
- return checkNodeDOTvalue(nodeDOTvalue.value) # type: ignore
280
- return False
281
- return checkNodeDOTvalue(node.value)
282
- return workhorse
283
- @staticmethod
284
- def isSubscriptIsName_Identifier(identifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript] | bool]:
285
- return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value)
286
- @staticmethod
287
- def isSubscript_Identifier_Identifier(identifier: ast_Identifier, sliceIdentifier: ast_Identifier) -> Callable[[ast.AST], TypeGuard[ast.Subscript] | bool]:
288
- return lambda node: ifThis.isSubscript(node) and ifThis.isName_Identifier(identifier)(node.value) and ifThis.isName_Identifier(sliceIdentifier)(node.slice)
289
- @staticmethod
290
- def isUnaryOp(node: ast.AST) -> TypeGuard[ast.UnaryOp]:
291
- return isinstance(node, ast.UnaryOp)
292
- # TODO Does this work?
293
- @staticmethod
294
- def matchesAtLeast1Descendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
295
- """Create a predicate that returns True if any descendant of the node matches the given predicate."""
296
- return lambda node: not ifThis.matchesNoDescendant(predicate)(node)
297
- # TODO Does this work?
298
- @staticmethod
299
- def matchesMeAndMyDescendantsExactlyNTimes(predicate: Callable[[ast.AST], bool], nTimes: int) -> Callable[[ast.AST], bool]:
300
- """Create a predicate that returns True if exactly 'count' nodes in the tree match the predicate."""
301
- def countMatchingNodes(node: ast.AST) -> bool:
302
- matches = sum(1 for descendant in ast.walk(node) if predicate(descendant))
303
- return matches == nTimes
304
- return countMatchingNodes
305
- @staticmethod
306
- def matchesMeButNotAnyDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
307
- """Create a predicate that returns True if the node matches but none of its descendants match the predicate."""
308
- return lambda node: predicate(node) and ifThis.matchesNoDescendant(predicate)(node)
309
- @staticmethod
310
- def matchesNoDescendant(predicate: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
311
- """Create a predicate that returns True if no descendant of the node matches the given predicate."""
312
- def workhorse(node: ast.AST) -> bool:
313
- for descendant in ast.walk(node):
314
- if descendant is not node and predicate(descendant):
315
- return False
316
- return True
317
- return workhorse
318
- @staticmethod
319
- def onlyReturnAnyCompare(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
320
- return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnAnyCompare(astFunctionDef.body[0])
321
- @staticmethod
322
- def onlyReturnUnaryOp(astFunctionDef: ast.AST) -> TypeGuard[ast.FunctionDef]:
323
- return ifThis.isFunctionDef(astFunctionDef) and len(astFunctionDef.body) == 1 and ifThis.isReturnUnaryOp(astFunctionDef.body[0])
324
-
325
- class Make:
326
- @staticmethod
327
- def ast_arg(identifier: ast_Identifier, annotation: ast.expr | None = None, **keywordArguments: strORintORNone) -> ast.arg:
328
- """keywordArguments: type_comment:str|None, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
329
- return ast.arg(identifier, annotation, **keywordArguments)
330
- @staticmethod
331
- def ast_keyword(keywordArgument: ast_Identifier, value: ast.expr, **keywordArguments: int) -> ast.keyword:
332
- return ast.keyword(arg=keywordArgument, value=value, **keywordArguments)
333
- @staticmethod
334
- def astAlias(name: ast_Identifier, asname: ast_Identifier | None = None) -> ast.alias:
335
- return ast.alias(name, asname)
336
- @staticmethod
337
- def astAnnAssign(target: ast.Name | ast.Attribute | ast.Subscript, annotation: ast.expr, value: ast.expr | None = None, **keywordArguments: int) -> ast.AnnAssign:
338
- """`simple: int`: uses a clever int-from-boolean to assign the correct value to the `simple` attribute. So, don't add it as a parameter."""
339
- return ast.AnnAssign(target, annotation, value, simple=int(isinstance(target, ast.Name)), **keywordArguments)
340
- @staticmethod
341
- def astAssign(listTargets: Any, value: ast.expr, **keywordArguments: strORintORNone) -> ast.Assign:
342
- """keywordArguments: type_comment:str|None, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
343
- return ast.Assign(targets=listTargets, value=value, **keywordArguments)
344
- @staticmethod
345
- def astArgumentsSpecification(posonlyargs: list[ast.arg]=[], args: list[ast.arg]=[], vararg: ast.arg|None=None, kwonlyargs: list[ast.arg]=[], kw_defaults: list[ast.expr|None]=[None], kwarg: ast.arg|None=None, defaults: list[ast.expr]=[]) -> ast.arguments:
346
- return ast.arguments(posonlyargs, args, vararg, kwonlyargs, kw_defaults, kwarg, defaults)
347
- @staticmethod
348
- def astAttribute(value: ast.expr, attribute: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Attribute:
349
- """
350
- Parameters:
351
- value: the part before the dot (hint `ast.Name` for nameDOTname)
352
- attribute: the `str` after the dot
353
- context (ast.Load()): Load/Store/Del"""
354
- return ast.Attribute(value, attribute, context, **keywordArguments)
355
- @staticmethod
356
- def astCall(caller: ast.Name | ast.Attribute, listArguments: Sequence[ast.expr] | None = None, list_astKeywords: Sequence[ast.keyword] | None = None) -> ast.Call:
357
- return ast.Call(func=caller, args=list(listArguments) if listArguments else [], keywords=list(list_astKeywords) if list_astKeywords else [])
358
- @staticmethod
359
- def astClassDef(name: ast_Identifier, listBases: list[ast.expr]=[], list_keyword: list[ast.keyword]=[], body: list[ast.stmt]=[], decorator_list: list[ast.expr]=[], **keywordArguments: list_ast_type_paramORintORNone) -> ast.ClassDef:
360
- """keywordArguments: type_params:list[ast.type_param], lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
361
- return ast.ClassDef(name=name, bases=listBases, keywords=list_keyword, body=body, decorator_list=decorator_list, **keywordArguments)
362
- @staticmethod
363
- def astConstant(value: Any, **keywordArguments: strORintORNone) -> ast.Constant:
364
- """value: str|int|float|bool|None|bytes|bytearray|memoryview|complex|list|tuple|dict|set, or any other type that can be represented as a constant in Python.
365
- keywordArguments: kind:str, lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
366
- return ast.Constant(value, **keywordArguments)
367
- @staticmethod
368
- def astFunctionDef(name: ast_Identifier, argumentsSpecification: ast.arguments=ast.arguments(), body: list[ast.stmt]=[], decorator_list: list[ast.expr]=[], returns: ast.expr|None=None, **keywordArguments: strORlist_ast_type_paramORintORNone) -> ast.FunctionDef:
369
- """keywordArguments: type_comment:str|None, type_params:list[ast.type_param], lineno:int, col_offset:int, end_lineno:int|None, end_col_offset:int|None"""
370
- return ast.FunctionDef(name=name, args=argumentsSpecification, body=body, decorator_list=decorator_list, returns=returns, **keywordArguments)
371
- @staticmethod
372
- def astImport(moduleName: ast_Identifier, asname: ast_Identifier | None = None, **keywordArguments: int) -> ast.Import:
373
- return ast.Import(names=[Make.astAlias(moduleName, asname)], **keywordArguments)
374
- @staticmethod
375
- def astImportFrom(moduleName: ast_Identifier, list_astAlias: list[ast.alias], **keywordArguments: int) -> ast.ImportFrom:
376
- return ast.ImportFrom(module=moduleName, names=list_astAlias, level=0, **keywordArguments)
377
- @staticmethod
378
- def astModule(body: list[ast.stmt], type_ignores: list[ast.TypeIgnore] = []) -> ast.Module:
379
- return ast.Module(body, type_ignores)
380
- @staticmethod
381
- def astName(identifier: ast_Identifier, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Name:
382
- return ast.Name(identifier, context, **keywordArguments)
383
- @staticmethod
384
- def itDOTname(nameChain: ast.Name | ast.Attribute, dotName: str) -> ast.Attribute:
385
- return ast.Attribute(value=nameChain, attr=dotName, ctx=ast.Load())
386
- @staticmethod
387
- # TODO rewrite with all parameters
388
- def nameDOTname(identifier: ast_Identifier, *dotName: str) -> ast.Name | ast.Attribute:
389
- nameDOTname: ast.Name | ast.Attribute = Make.astName(identifier)
390
- if not dotName:
391
- return nameDOTname
392
- for suffix in dotName:
393
- nameDOTname = Make.itDOTname(nameDOTname, suffix)
394
- return nameDOTname
395
- @staticmethod
396
- def astReturn(value: ast.expr | None = None, **keywordArguments: int) -> ast.Return:
397
- return ast.Return(value, **keywordArguments)
398
- @staticmethod
399
- def astSubscript(value: ast.expr, slice: ast_expr_Slice, context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Subscript:
400
- return ast.Subscript(value, slice, ctx=context, **keywordArguments)
401
- @staticmethod
402
- def astTuple(elements: Sequence[ast.expr], context: ast.expr_context = ast.Load(), **keywordArguments: int) -> ast.Tuple:
403
- """context: Load/Store/Del"""
404
- return ast.Tuple(elts=list(elements), ctx=context, **keywordArguments)
405
-
406
- class LedgerOfImports:
407
- # TODO When resolving the ledger of imports, remove self-referential imports
408
-
409
- def __init__(self, startWith: ast.AST | None = None) -> None:
410
- self.dictionaryImportFrom: dict[str, list[tuple[str, str | None]]] = defaultdict(list)
411
- self.listImport: list[str] = []
412
-
413
- if startWith:
414
- self.walkThis(startWith)
415
-
416
- def addAst(self, astImport_: ast.Import | ast.ImportFrom) -> None:
417
- assert isinstance(astImport_, (ast.Import, ast.ImportFrom)), f"Expected ast.Import or ast.ImportFrom, got {type(astImport_)}"
418
- if isinstance(astImport_, ast.Import):
419
- for alias in astImport_.names:
420
- self.listImport.append(alias.name)
421
- else:
422
- if astImport_.module is not None:
423
- for alias in astImport_.names:
424
- self.dictionaryImportFrom[astImport_.module].append((alias.name, alias.asname))
425
-
426
- def addImportStr(self, module: str) -> None:
427
- self.listImport.append(module)
428
-
429
- def addImportFromStr(self, module: str, name: str, asname: str | None = None) -> None:
430
- self.dictionaryImportFrom[module].append((name, asname))
431
-
432
- def exportListModuleNames(self) -> list[str]:
433
- listModuleNames: list[str] = list(self.dictionaryImportFrom.keys())
434
- listModuleNames.extend(self.listImport)
435
- return sorted(set(listModuleNames))
436
-
437
- def makeListAst(self) -> list[ast.ImportFrom | ast.Import]:
438
- listAstImportFrom: list[ast.ImportFrom] = []
439
-
440
- for module, listOfNameTuples in sorted(self.dictionaryImportFrom.items()):
441
- listOfNameTuples = sorted(list(set(listOfNameTuples)), key=lambda nameTuple: nameTuple[0])
442
- listAlias: list[ast.alias] = []
443
- for name, asname in listOfNameTuples:
444
- listAlias.append(Make.astAlias(name, asname))
445
- listAstImportFrom.append(Make.astImportFrom(module, listAlias))
446
-
447
- listAstImport: list[ast.Import] = [Make.astImport(name) for name in sorted(set(self.listImport))]
448
- return listAstImportFrom + listAstImport
449
-
450
- def update(self, *fromLedger: 'LedgerOfImports') -> None:
451
- """Update this ledger with imports from one or more other ledgers.
452
- Parameters:
453
- *fromLedger: One or more other `LedgerOfImports` objects from which to merge.
454
- """
455
- self.dictionaryImportFrom = updateExtendPolishDictionaryLists(self.dictionaryImportFrom, *(ledger.dictionaryImportFrom for ledger in fromLedger), destroyDuplicates=True, reorderLists=True)
456
-
457
- for ledger in fromLedger:
458
- self.listImport.extend(ledger.listImport)
459
-
460
- def walkThis(self, walkThis: ast.AST) -> None:
461
- for smurf in ast.walk(walkThis):
462
- if isinstance(smurf, (ast.Import, ast.ImportFrom)):
463
- self.addAst(smurf)
464
-
465
- class Then:
466
- @staticmethod
467
- def append_targetTo(listName: list[ast.AST]) -> Callable[[ast.AnnAssign], None]:
468
- return lambda node: listName.append(node.target)
469
- @staticmethod
470
- def appendTo(listOfAny: list[Any]) -> Callable[[ast.AST], None]:
471
- return lambda node: listOfAny.append(node)
472
- @staticmethod
473
- def insertThisAbove(list_astAST: Sequence[ast.AST]) -> Callable[[ast.AST], Sequence[ast.AST]]:
474
- return lambda aboveMe: [*list_astAST, aboveMe]
475
- @staticmethod
476
- def insertThisBelow(list_astAST: Sequence[ast.AST]) -> Callable[[ast.AST], Sequence[ast.AST]]:
477
- return lambda belowMe: [belowMe, *list_astAST]
478
- @staticmethod
479
- def removeThis(_node: ast.AST) -> None: return None
480
- @staticmethod
481
- def replaceWith(astAST: ast.AST) -> Callable[[ast.AST], ast.AST]: return lambda _replaceMe: astAST
482
- @staticmethod
483
- def replaceDOTfuncWith(ast_expr: ast.expr) -> Callable[[ast.Call], ast.Call]:
484
- def workhorse(node: ast.Call) -> ast.Call:
485
- node.func = ast_expr
486
- return node
487
- return workhorse
488
- @staticmethod
489
- def updateThis(dictionaryOf_astMosDef: dict[ast_Identifier, astMosDef]) -> Callable[[astMosDef], astMosDef]:
490
- return lambda node: dictionaryOf_astMosDef.setdefault(node.name, node)
491
- @staticmethod
492
- def Z0Z_ledger(logicalPath: strDotStrCuzPyStoopid, ledger: LedgerOfImports) -> Callable[[ast.AnnAssign], None]:
493
- return lambda node: ledger.addImportFromStr(logicalPath, node.annotation.id) # type: ignore
494
- @staticmethod
495
- def Z0Z_appendKeywordMirroredTo(list_keyword: list[ast.keyword]) -> Callable[[ast.AnnAssign], None]:
496
- return lambda node: list_keyword.append(Make.ast_keyword(node.target.id, node.target)) # type: ignore
497
- @staticmethod
498
- def Z0Z_appendAnnAssignOf_nameDOTnameTo(identifier: ast_Identifier, list_nameDOTname: list[ast.AnnAssign]) -> Callable[[ast.AnnAssign], None]:
499
- return lambda node: list_nameDOTname.append(Make.astAnnAssign(node.target, node.annotation, Make.nameDOTname(identifier, node.target.id))) # type: ignore
114
+ keepGoing = True
115
+ while keepGoing:
116
+ keepGoing = False
117
+ listIdentifiersCalledFunctions.clear()
118
+ findIdentifiersToInline.visit(Make.Module(list(dictionary4Inlining.values())))
119
+
120
+ # NOTE: This is simple not comprehensive recursion protection. # TODO think about why I dislike `ifThis.CallDoesNotCallItself`
121
+ if identifierToInline in listIdentifiersCalledFunctions: raise ValueError(f"Recursion found: {identifierToInline = }.")
122
+
123
+ listIdentifiersCalledFunctions = sorted((set(listIdentifiersCalledFunctions).difference(dictionary4Inlining.keys())).intersection(dictionaryFunctionDef.keys()))
124
+ if len(listIdentifiersCalledFunctions) > 0:
125
+ keepGoing = True
126
+ for identifier in listIdentifiersCalledFunctions:
127
+ if identifier in dictionaryFunctionDef:
128
+ dictionary4Inlining[identifier] = dictionaryFunctionDef[identifier]
129
+
130
+ return dictionary4Inlining
131
+
132
+ @overload
133
+ def makeInitializedComputationState(mapShape: tuple[int, ...], writeJob: Literal[True], *, pathFilename: PathLike[str] | PurePath | None = None, **keywordArguments: Any) -> Path: ...
134
+ @overload
135
+ def makeInitializedComputationState(mapShape: tuple[int, ...], writeJob: Literal[False] = False, **keywordArguments: Any) -> ComputationState: ...
136
+ def makeInitializedComputationState(mapShape: tuple[int, ...], writeJob: bool = False, *, pathFilename: PathLike[str] | PurePath | None = None, **keywordArguments: Any) -> ComputationState | Path:
137
+ """
138
+ Initializes a computation state and optionally saves it to disk.
139
+
140
+ This function initializes a computation state using the source algorithm.
141
+
142
+ Hint: If you want an uninitialized state, call `outfitCountFolds` directly.
143
+
144
+ Parameters:
145
+ mapShape: List of integers representing the dimensions of the map to be folded.
146
+ writeJob (False): Whether to save the state to disk.
147
+ pathFilename (getPathFilenameFoldsTotal.pkl): The path and filename to save the state. If None, uses a default path.
148
+ **keywordArguments: computationDivisions:int|str|None=None,concurrencyLimit:int=1.
149
+ Returns:
150
+ stateUniversal|pathFilenameJob: The computation state for the map folding calculations, or
151
+ the path to the saved state file if writeJob is True.
152
+ """
153
+ stateUniversal: ComputationState = outfitCountFolds(mapShape, **keywordArguments)
154
+
155
+ initializeState = importLogicalPath2Callable(The.logicalPathModuleSourceAlgorithm, The.sourceCallableInitialize)
156
+ stateUniversal = initializeState(stateUniversal)
157
+
158
+ if not writeJob:
159
+ return stateUniversal
160
+
161
+ if pathFilename:
162
+ pathFilenameJob = Path(pathFilename)
163
+ pathFilenameJob.parent.mkdir(parents=True, exist_ok=True)
164
+ else:
165
+ pathFilenameJob = getPathFilenameFoldsTotal(stateUniversal.mapShape).with_suffix('.pkl')
166
+
167
+ pathFilenameJob.write_bytes(pickle.dumps(stateUniversal))
168
+ return pathFilenameJob
500
169
 
501
170
  @dataclasses.dataclass
502
- class IngredientsFunction:
503
- """Everything necessary to integrate a function into a module should be here."""
504
- astFunctionDef: ast.FunctionDef # hint `Make.astFunctionDef`
505
- imports: LedgerOfImports = dataclasses.field(default_factory=LedgerOfImports)
171
+ class DeReConstructField2ast:
172
+ dataclassesDOTdataclassLogicalPathModule: dataclasses.InitVar[str_nameDOTname]
173
+ dataclassClassDef: dataclasses.InitVar[ast.ClassDef]
174
+ dataclassesDOTdataclassInstance_Identifier: dataclasses.InitVar[ast_Identifier]
175
+ field: dataclasses.InitVar[dataclasses.Field[Any]]
176
+
177
+ ledger: LedgerOfImports = dataclasses.field(default_factory=LedgerOfImports)
178
+
179
+ name: ast_Identifier = dataclasses.field(init=False)
180
+ typeBuffalo: type[Any] | str | Any = dataclasses.field(init=False)
181
+ default: Any | None = dataclasses.field(init=False)
182
+ default_factory: Callable[..., Any] | None = dataclasses.field(init=False)
183
+ repr: bool = dataclasses.field(init=False)
184
+ hash: bool | None = dataclasses.field(init=False)
185
+ init: bool = dataclasses.field(init=False)
186
+ compare: bool = dataclasses.field(init=False)
187
+ metadata: dict[Any, Any] = dataclasses.field(init=False)
188
+ kw_only: bool = dataclasses.field(init=False)
189
+
190
+ astName: ast.Name = dataclasses.field(init=False)
191
+ ast_keyword_field__field: ast.keyword = dataclasses.field(init=False)
192
+ ast_nameDOTname: ast.Attribute = dataclasses.field(init=False)
193
+ astAnnotation: ImaAnnotationType = dataclasses.field(init=False)
194
+ ast_argAnnotated: ast.arg = dataclasses.field(init=False)
195
+ astAnnAssignConstructor: ast.AnnAssign = dataclasses.field(init=False)
196
+ Z0Z_hack: tuple[ast.AnnAssign, str] = dataclasses.field(init=False)
197
+
198
+ def __post_init__(self, dataclassesDOTdataclassLogicalPathModule: str_nameDOTname, dataclassClassDef: ast.ClassDef, dataclassesDOTdataclassInstance_Identifier: ast_Identifier, field: dataclasses.Field[Any]) -> None:
199
+ self.compare = field.compare
200
+ self.default = field.default if field.default is not dataclasses.MISSING else None
201
+ self.default_factory = field.default_factory if field.default_factory is not dataclasses.MISSING else None
202
+ self.hash = field.hash
203
+ self.init = field.init
204
+ self.kw_only = field.kw_only if field.kw_only is not dataclasses.MISSING else False
205
+ self.metadata = dict(field.metadata)
206
+ self.name = field.name
207
+ self.repr = field.repr
208
+ self.typeBuffalo = field.type
209
+
210
+ self.astName = Make.Name(self.name)
211
+ self.ast_keyword_field__field = Make.keyword(self.name, self.astName)
212
+ self.ast_nameDOTname = Make.Attribute(Make.Name(dataclassesDOTdataclassInstance_Identifier), self.name)
213
+
214
+ sherpa = NodeTourist(ifThis.isAnnAssign_targetIs(ifThis.isName_Identifier(self.name)), 又.annotation(Then.getIt)).captureLastMatch(dataclassClassDef)
215
+ if sherpa is None: raise raiseIfNoneGitHubIssueNumber3
216
+ else: self.astAnnotation = sherpa
217
+
218
+ self.ast_argAnnotated = Make.arg(self.name, self.astAnnotation)
219
+
220
+ dtype = self.metadata.get('dtype', None)
221
+ if dtype:
222
+ constructor = 'array'
223
+ self.astAnnAssignConstructor = Make.AnnAssign(self.astName, self.astAnnotation, Make.Call(Make.Name(constructor), list_astKeywords=[Make.keyword('dtype', Make.Name(dtype.__name__))]))
224
+ self.ledger.addImportFrom_asStr('numpy', constructor)
225
+ self.ledger.addImportFrom_asStr('numpy', dtype.__name__)
226
+ self.Z0Z_hack = (self.astAnnAssignConstructor, 'array')
227
+ elif be.Name(self.astAnnotation):
228
+ self.astAnnAssignConstructor = Make.AnnAssign(self.astName, self.astAnnotation, Make.Call(self.astAnnotation, [Make.Constant(-1)]))
229
+ self.ledger.addImportFrom_asStr(dataclassesDOTdataclassLogicalPathModule, self.astAnnotation.id)
230
+ self.Z0Z_hack = (self.astAnnAssignConstructor, 'scalar')
231
+ elif be.Subscript(self.astAnnotation):
232
+ elementConstructor: ast_Identifier = self.metadata['elementConstructor']
233
+ self.ledger.addImportFrom_asStr(dataclassesDOTdataclassLogicalPathModule, elementConstructor)
234
+ takeTheTuple: ast.Tuple = deepcopy(self.astAnnotation.slice) # type: ignore
235
+ self.astAnnAssignConstructor = Make.AnnAssign(self.astName, self.astAnnotation, takeTheTuple)
236
+ self.Z0Z_hack = (self.astAnnAssignConstructor, elementConstructor)
237
+ if be.Name(self.astAnnotation):
238
+ self.ledger.addImportFrom_asStr(dataclassesDOTdataclassLogicalPathModule, self.astAnnotation.id) # pyright: ignore [reportUnknownArgumentType, reportUnknownMemberType, reportIJustCalledATypeGuardMethod_WTF]
239
+
240
+ def shatter_dataclassesDOTdataclass(logicalPathModule: str_nameDOTname, dataclass_Identifier: ast_Identifier, instance_Identifier: ast_Identifier) -> ShatteredDataclass:
241
+ """
242
+ Parameters:
243
+ logicalPathModule: gimme string cuz python is stoopid
244
+ dataclass_Identifier: The identifier of the dataclass to be dismantled.
245
+ instance_Identifier: In the synthesized module/function/scope, the identifier that will be used for the instance.
246
+ """
247
+ Official_fieldOrder: list[ast_Identifier] = []
248
+ dictionaryDeReConstruction: dict[ast_Identifier, DeReConstructField2ast] = {}
249
+
250
+ dataclassClassDef = extractClassDef(parseLogicalPath2astModule(logicalPathModule), dataclass_Identifier)
251
+ if not isinstance(dataclassClassDef, ast.ClassDef): raise ValueError(f"I could not find {dataclass_Identifier=} in {logicalPathModule=}.")
252
+
253
+ countingVariable = None
254
+ for aField in dataclasses.fields(importLogicalPath2Callable(logicalPathModule, dataclass_Identifier)): # pyright: ignore [reportArgumentType]
255
+ Official_fieldOrder.append(aField.name)
256
+ dictionaryDeReConstruction[aField.name] = DeReConstructField2ast(logicalPathModule, dataclassClassDef, instance_Identifier, aField)
257
+ if aField.metadata.get('theCountingIdentifier', False):
258
+ countingVariable = dictionaryDeReConstruction[aField.name].name
259
+
260
+ if countingVariable is None:
261
+ raise ValueError(f"I could not find the counting variable in {dataclass_Identifier=} in {logicalPathModule=}.")
262
+
263
+ shatteredDataclass = ShatteredDataclass(
264
+ countingVariableAnnotation=dictionaryDeReConstruction[countingVariable].astAnnotation,
265
+ countingVariableName=dictionaryDeReConstruction[countingVariable].astName,
266
+ field2AnnAssign={dictionaryDeReConstruction[field].name: dictionaryDeReConstruction[field].astAnnAssignConstructor for field in Official_fieldOrder},
267
+ Z0Z_field2AnnAssign={dictionaryDeReConstruction[field].name: dictionaryDeReConstruction[field].Z0Z_hack for field in Official_fieldOrder},
268
+ list_argAnnotated4ArgumentsSpecification=[dictionaryDeReConstruction[field].ast_argAnnotated for field in Official_fieldOrder],
269
+ list_keyword_field__field4init=[dictionaryDeReConstruction[field].ast_keyword_field__field for field in Official_fieldOrder if dictionaryDeReConstruction[field].init],
270
+ listAnnotations=[dictionaryDeReConstruction[field].astAnnotation for field in Official_fieldOrder],
271
+ listName4Parameters=[dictionaryDeReConstruction[field].astName for field in Official_fieldOrder],
272
+ listUnpack=[Make.AnnAssign(dictionaryDeReConstruction[field].astName, dictionaryDeReConstruction[field].astAnnotation, dictionaryDeReConstruction[field].ast_nameDOTname) for field in Official_fieldOrder],
273
+ map_stateDOTfield2Name={dictionaryDeReConstruction[field].ast_nameDOTname: dictionaryDeReConstruction[field].astName for field in Official_fieldOrder},
274
+ )
275
+ shatteredDataclass.fragments4AssignmentOrParameters = Make.Tuple(shatteredDataclass.listName4Parameters, ast.Store())
276
+ shatteredDataclass.repack = Make.Assign(listTargets=[Make.Name(instance_Identifier)], value=Make.Call(Make.Name(dataclass_Identifier), list_astKeywords=shatteredDataclass.list_keyword_field__field4init))
277
+ shatteredDataclass.signatureReturnAnnotation = Make.Subscript(Make.Name('tuple'), Make.Tuple(shatteredDataclass.listAnnotations))
278
+
279
+ shatteredDataclass.ledger.update(*(dictionaryDeReConstruction[field].ledger for field in Official_fieldOrder))
280
+ shatteredDataclass.ledger.addImportFrom_asStr(logicalPathModule, dataclass_Identifier)
281
+
282
+ return shatteredDataclass
283
+
284
+ def write_astModule(ingredients: IngredientsModule, pathFilename: PathLike[Any] | PurePath, packageName: ast_Identifier | None = None) -> None:
285
+ astModule = Make.Module(ingredients.body, ingredients.type_ignores)
286
+ ast.fix_missing_locations(astModule)
287
+ pythonSource: str = ast.unparse(astModule)
288
+ if not pythonSource: raise raiseIfNoneGitHubIssueNumber3
289
+ autoflake_additional_imports: list[str] = ingredients.imports.exportListModuleIdentifiers()
290
+ if packageName:
291
+ autoflake_additional_imports.append(packageName)
292
+ pythonSource = autoflake_fix_code(pythonSource, autoflake_additional_imports, expand_star_imports=False, remove_all_unused_imports=False, remove_duplicate_keys = False, remove_unused_variables = False)
293
+ writeStringToHere(pythonSource, pathFilename)
506
294
 
507
- @dataclasses.dataclass
508
- class IngredientsModule:
509
- """Everything necessary to create one _logical_ `ast.Module` should be here.
510
- Extrinsic qualities should _probably_ be handled externally."""
511
- ingredientsFunction: dataclasses.InitVar[Sequence[IngredientsFunction] | IngredientsFunction | None] = None
512
-
513
- # init var with an existing module? method to deconstruct an existing module?
514
-
515
- # `body` attribute of `ast.Module`
516
- imports: LedgerOfImports = dataclasses.field(default_factory=LedgerOfImports)
517
- prologue: list[ast.stmt] = dataclasses.field(default_factory=list)
518
- functions: list[ast.FunctionDef | ast.stmt] = dataclasses.field(default_factory=list)
519
- epilogue: list[ast.stmt] = dataclasses.field(default_factory=list)
520
- launcher: list[ast.stmt] = dataclasses.field(default_factory=list)
521
-
522
- # parameter for `ast.Module` constructor
523
- type_ignores: list[ast.TypeIgnore] = dataclasses.field(default_factory=list)
524
-
525
- def __post_init__(self, ingredientsFunction: Sequence[IngredientsFunction] | IngredientsFunction | None = None) -> None:
526
- if ingredientsFunction is not None:
527
- if isinstance(ingredientsFunction, IngredientsFunction):
528
- self.addIngredientsFunction(ingredientsFunction)
529
- else:
530
- self.addIngredientsFunction(*ingredientsFunction)
531
-
532
- def addIngredientsFunction(self, *ingredientsFunction: IngredientsFunction) -> None:
533
- """Add one or more `IngredientsFunction`."""
534
- listLedgers: list[LedgerOfImports] = []
535
- for definition in ingredientsFunction:
536
- self.functions.append(definition.astFunctionDef)
537
- listLedgers.append(definition.imports)
538
- self.imports.update(*listLedgers)
539
-
540
- def _makeModuleBody(self) -> list[ast.stmt]:
541
- body: list[ast.stmt] = []
542
- body.extend(self.imports.makeListAst())
543
- body.extend(self.prologue)
544
- body.extend(self.functions)
545
- body.extend(self.epilogue)
546
- body.extend(self.launcher)
547
- # TODO `launcher`, if it exists, must start with `if __name__ == '__main__':` and be indented
548
- return body
549
-
550
- def export(self) -> ast.Module:
551
- """Create a new `ast.Module` from the ingredients."""
552
- return Make.astModule(self._makeModuleBody(), self.type_ignores)
295
+ # END of acceptable classes and functions ======================================================
296
+ dictionaryEstimates: dict[tuple[int, ...], int] = {
297
+ (2,2,2,2,2,2,2,2): 362794844160000,
298
+ (2,21): 1493028892051200,
299
+ (3,15): 9842024675968800,
300
+ (3,3,3,3): 85109616000000000000000000000000,
301
+ (8,8): 129950723279272000,
302
+ }
303
+
304
+ # END of marginal classes and functions ======================================================
305
+ def Z0Z_lameFindReplace(astTree, mappingFindReplaceNodes: Mapping[ast.AST, ast.AST]):
306
+ keepGoing = True
307
+ newTree = deepcopy(astTree)
553
308
 
554
- @dataclasses.dataclass
555
- class RecipeSynthesizeFlow:
556
- """Settings for synthesizing flow."""
557
- # ========================================
558
- # Source
559
- sourceAlgorithm: ModuleType = importlib_import_module(The.logicalPathModuleSourceAlgorithm)
560
- sourcePython: str = inspect_getsource(sourceAlgorithm)
561
- source_astModule: ast.Module = ast.parse(sourcePython)
562
-
563
- # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
564
- sourceDispatcherCallable: str = The.dispatcherCallable
565
- sourceInitializeCallable: str = The.sourceInitializeCallable
566
- sourceParallelCallable: str = The.sourceParallelCallable
567
- sourceSequentialCallable: str = The.sourceSequentialCallable
568
-
569
- sourceDataclassIdentifier: str = The.dataclassIdentifier
570
- sourceDataclassInstance: str = The.dataclassInstance
571
- sourceDataclassInstanceTaskDistribution: str = The.dataclassInstanceTaskDistribution
572
- sourcePathModuleDataclass: str = The.logicalPathModuleDataclass
573
-
574
- sourceConcurrencyManagerNamespace = The.sourceConcurrencyManagerNamespace
575
- sourceConcurrencyManagerIdentifier = The.sourceConcurrencyManagerIdentifier
576
- # ========================================
577
- # Filesystem
578
- pathPackage: PurePosixPath | None = PurePosixPath(The.pathPackage)
579
- fileExtension: str = The.fileExtension
580
-
581
- # ========================================
582
- # Logical identifiers
583
- # meta
584
- formatStrModuleSynthetic: str = theFormatStrModuleSynthetic
585
- formatStrModuleForCallableSynthetic: str = theFormatStrModuleForCallableSynthetic
586
-
587
- # Package
588
- packageName: ast_Identifier | None = The.packageName
589
-
590
- # Module
591
- # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
592
- Z0Z_flowLogicalPathRoot: str | None = The.moduleOfSyntheticModules
593
- moduleDispatcher: str = theModuleDispatcherSynthetic
594
- logicalPathModuleDataclass: str = sourcePathModuleDataclass
595
- # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
596
- # `theLogicalPathModuleDispatcherSynthetic` is a problem. It is defined in theSSOT, but it can also be calculated.
597
- logicalPathModuleDispatcher: str = theLogicalPathModuleDispatcherSynthetic
598
-
599
- # Function
600
- dispatcherCallable: str = sourceDispatcherCallable
601
- initializeCallable: str = sourceInitializeCallable
602
- parallelCallable: str = sourceParallelCallable
603
- sequentialCallable: str = sourceSequentialCallable
604
- # initializeCallable: str = 'StartTheCommotion'
605
- # parallelCallable: str = sourceParallelCallable
606
- # sequentialCallable: str = sourceSequentialCallable
607
-
608
- dataclassIdentifier: str = sourceDataclassIdentifier
609
-
610
- # Variable
611
- dataclassInstance: str = sourceDataclassInstance
612
-
613
- def _makePathFilename(self, filenameStem: str,
614
- pathRoot: PurePosixPath | None = None,
615
- logicalPathINFIX: strDotStrCuzPyStoopid | None = None,
616
- fileExtension: str | None = None,
617
- ) -> PurePosixPath:
618
- """filenameStem: (hint: the name of the logical module)"""
619
- if pathRoot is None:
620
- pathRoot = self.pathPackage or PurePosixPath(Path.cwd())
621
- if logicalPathINFIX:
622
- whyIsThisStillAThing: list[str] = logicalPathINFIX.split('.')
623
- pathRoot = pathRoot.joinpath(*whyIsThisStillAThing)
624
- if fileExtension is None:
625
- fileExtension = self.fileExtension
626
- filename: str = filenameStem + fileExtension
627
- return pathRoot.joinpath(filename)
628
-
629
- @property
630
- def pathFilenameDispatcher(self) -> PurePosixPath:
631
- return self._makePathFilename(filenameStem=self.moduleDispatcher, logicalPathINFIX=self.Z0Z_flowLogicalPathRoot)
632
-
633
- def extractClassDef(module: ast.Module, identifier: ast_Identifier) -> ast.ClassDef | None:
634
- sherpa: list[ast.ClassDef] = []
635
- extractor = NodeCollector(ifThis.isClassDef_Identifier(identifier), [Then.appendTo(sherpa)])
636
- extractor.visit(module)
637
- astClassDef = sherpa[0] if sherpa else None
638
- return astClassDef
639
-
640
- def extractFunctionDef(module: ast.Module, identifier: ast_Identifier) -> ast.FunctionDef | None:
641
- sherpa: list[ast.FunctionDef] = []
642
- extractor = NodeCollector(ifThis.isFunctionDef_Identifier(identifier), [Then.appendTo(sherpa)])
643
- extractor.visit(module)
644
- astClassDef = sherpa[0] if sherpa else None
645
- return astClassDef
646
-
647
- def makeDictionaryFunctionDef(module: ast.Module) -> dict[ast_Identifier, ast.FunctionDef]:
648
- dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef] = {}
649
- NodeCollector(ifThis.isFunctionDef, [Then.updateThis(dictionaryFunctionDef)]).visit(module)
650
- return dictionaryFunctionDef
651
-
652
- def makeDictionaryReplacementStatements(module: ast.Module) -> dict[ast_Identifier, ast.stmt | list[ast.stmt]]:
309
+ while keepGoing:
310
+ for nodeFind, nodeReplace in mappingFindReplaceNodes.items():
311
+ NodeChanger(ifThis.Z0Z_unparseIs(nodeFind), Then.replaceWith(nodeReplace)).visit(newTree)
312
+
313
+ if ast.unparse(newTree) == ast.unparse(astTree):
314
+ keepGoing = False
315
+ else:
316
+ astTree = deepcopy(newTree)
317
+ return newTree
318
+
319
+ # Start of I HATE PROGRAMMING ==========================================================
320
+ def Z0Z_makeDictionaryReplacementStatements(module: ast.AST) -> dict[ast_Identifier, ast.stmt | list[ast.stmt]]:
653
321
  """Return a dictionary of function names and their replacement statements."""
654
322
  dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef] = makeDictionaryFunctionDef(module)
655
323
  dictionaryReplacementStatements: dict[ast_Identifier, ast.stmt | list[ast.stmt]] = {}
@@ -662,29 +330,7 @@ def makeDictionaryReplacementStatements(module: ast.Module) -> dict[ast_Identifi
662
330
  dictionaryReplacementStatements[name] = astFunctionDef.body[0:-1]
663
331
  return dictionaryReplacementStatements
664
332
 
665
- def Z0Z_descendantContainsMatchingNode(node: ast.AST, predicateFunction: Callable[[ast.AST], bool]) -> bool:
666
- """Return True if any descendant of the node (or the node itself) matches the predicateFunction."""
667
- matchFound = False
668
-
669
- class DescendantFinder(ast.NodeVisitor):
670
- def generic_visit(self, node: ast.AST) -> None:
671
- nonlocal matchFound
672
- if predicateFunction(node):
673
- matchFound = True
674
- else:
675
- super().generic_visit(node)
676
-
677
- DescendantFinder().visit(node)
678
- return matchFound
679
-
680
- def Z0Z_executeActionUnlessDescendantMatches(exclusionPredicate: Callable[[ast.AST], bool], actionFunction: Callable[[ast.AST], None]) -> Callable[[ast.AST], None]:
681
- """Return a new action that will execute actionFunction only if no descendant (or the node itself) matches exclusionPredicate."""
682
- def wrappedAction(node: ast.AST) -> None:
683
- if not Z0Z_descendantContainsMatchingNode(node, exclusionPredicate):
684
- actionFunction(node)
685
- return wrappedAction
686
-
687
- def inlineThisFunctionWithTheseValues(astFunctionDef: ast.FunctionDef, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> ast.FunctionDef:
333
+ def Z0Z_inlineThisFunctionWithTheseValues(astFunctionDef: ast.FunctionDef, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> ast.FunctionDef:
688
334
  class FunctionInliner(ast.NodeTransformer):
689
335
  def __init__(self, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> None:
690
336
  self.dictionaryReplacementStatements = dictionaryReplacementStatements
@@ -695,17 +341,17 @@ def inlineThisFunctionWithTheseValues(astFunctionDef: ast.FunctionDef, dictionar
695
341
 
696
342
  def visit_Expr(self, node: ast.Expr) -> ast.AST | list[ast.stmt]:
697
343
  if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
698
- return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
344
+ return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore
699
345
  return node
700
346
 
701
347
  def visit_Assign(self, node: ast.Assign) -> ast.AST | list[ast.stmt]:
702
348
  if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
703
- return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
349
+ return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore
704
350
  return node
705
351
 
706
352
  def visit_Call(self, node: ast.Call) -> ast.AST | list[ast.stmt]:
707
353
  if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node):
708
- replacement = self.dictionaryReplacementStatements[node.func.id] # type: ignore[attr-defined]
354
+ replacement = self.dictionaryReplacementStatements[node.func.id] # type: ignore
709
355
  if not isinstance(replacement, list):
710
356
  return replacement
711
357
  return node
@@ -721,58 +367,3 @@ def inlineThisFunctionWithTheseValues(astFunctionDef: ast.FunctionDef, dictionar
721
367
  astFunctionDef = deepcopy(ImaInlineFunction)
722
368
  ast.fix_missing_locations(astFunctionDef)
723
369
  return ImaInlineFunction
724
-
725
- def Z0Z_replaceMatchingASTnodes(astTree: ast.AST, mappingFindReplaceNodes: dict[ast.AST, ast.AST]) -> ast.AST:
726
- class TargetedNodeReplacer(ast.NodeTransformer):
727
- def __init__(self, mappingFindReplaceNodes: dict[ast.AST, ast.AST]) -> None:
728
- self.mappingFindReplaceNodes = mappingFindReplaceNodes
729
-
730
- def visit(self, node: ast.AST) -> ast.AST:
731
- for nodeFind, nodeReplace in self.mappingFindReplaceNodes.items():
732
- if self.nodesMatchStructurally(node, nodeFind):
733
- return nodeReplace
734
- return self.generic_visit(node)
735
-
736
- def nodesMatchStructurally(self, nodeSubject: ast.AST | list[Any] | Any, nodePattern: ast.AST | list[Any] | Any) -> bool:
737
- if nodeSubject is None or nodePattern is None:
738
- return nodeSubject is None and nodePattern is None
739
-
740
- if type(nodeSubject) != type(nodePattern):
741
- return False
742
-
743
- if isinstance(nodeSubject, ast.AST):
744
- for field, fieldValueSubject in ast.iter_fields(nodeSubject):
745
- if field in ('lineno', 'col_offset', 'end_lineno', 'end_col_offset', 'ctx'):
746
- continue
747
- attrPattern = getattr(nodePattern, field, None)
748
- if not self.nodesMatchStructurally(fieldValueSubject, attrPattern):
749
- return False
750
- return True
751
-
752
- if isinstance(nodeSubject, list) and isinstance(nodePattern, list):
753
- nodeSubjectList: list[Any] = nodeSubject
754
- nodePatternList: list[Any] = nodePattern
755
- return len(nodeSubjectList) == len(nodePatternList) and all(
756
- self.nodesMatchStructurally(elementSubject, elementPattern)
757
- for elementSubject, elementPattern in zip(nodeSubjectList, nodePatternList)
758
- )
759
-
760
- return nodeSubject == nodePattern
761
-
762
- astTreeCurrent, astTreePrevious = None, astTree
763
- while astTreeCurrent is None or ast.unparse(astTreeCurrent) != ast.unparse(astTreePrevious):
764
- astTreePrevious = astTreeCurrent if astTreeCurrent else astTree
765
- astTreeCurrent = TargetedNodeReplacer(mappingFindReplaceNodes).visit(astTreePrevious)
766
-
767
- return astTreeCurrent
768
-
769
- def write_astModule(ingredients: IngredientsModule, pathFilename: str | PathLike[Any] | PurePath, packageName: ast_Identifier | None = None) -> None:
770
- astModule = ingredients.export()
771
- ast.fix_missing_locations(astModule)
772
- pythonSource: str = ast.unparse(astModule)
773
- if not pythonSource: raise raiseIfNoneGitHubIssueNumber3
774
- autoflake_additional_imports: list[str] = ingredients.imports.exportListModuleNames()
775
- if packageName:
776
- autoflake_additional_imports.append(packageName)
777
- pythonSource = autoflake_fix_code(pythonSource, autoflake_additional_imports, expand_star_imports=False, remove_all_unused_imports=False, remove_duplicate_keys = False, remove_unused_variables = False)
778
- writeStringToHere(pythonSource, pathFilename)