astToolkit 0.1.2__tar.gz → 0.1.3__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.
- asttoolkit-0.1.3/PKG-INFO +221 -0
- asttoolkit-0.1.3/README.md +178 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/__init__.py +5 -7
- asttoolkit-0.1.3/astToolkit/_toolkitAST.py +113 -0
- asttoolkit-0.1.2/astToolkit/_toolboxContainers.py → asttoolkit-0.1.3/astToolkit/_toolkitContainers.py +8 -9
- asttoolkit-0.1.3/astToolkit/_toolkitNodeVisitor.py +69 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/transformationTools.py +41 -42
- asttoolkit-0.1.3/astToolkit.egg-info/PKG-INFO +221 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit.egg-info/SOURCES.txt +3 -3
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit.egg-info/requires.txt +1 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/pyproject.toml +10 -6
- asttoolkit-0.1.2/PKG-INFO +0 -90
- asttoolkit-0.1.2/README.md +0 -51
- asttoolkit-0.1.2/astToolkit/_toolboxAST.py +0 -57
- asttoolkit-0.1.2/astToolkit/_toolboxPython.py +0 -188
- asttoolkit-0.1.2/astToolkit.egg-info/PKG-INFO +0 -90
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/LICENSE +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_astTypes.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_toolBe.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_toolDOT.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_toolGrab.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_toolIfThis.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_toolMake.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_toolThen.py +1 -1
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_types.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/_typesSpecial.py +1 -1
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/py.typed +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit/theSSOT.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit.egg-info/dependency_links.txt +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/astToolkit.egg-info/top_level.txt +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/setup.cfg +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/tests/test_basic.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/Z0Z_hardcoded.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/Z0Z_makeAstTools.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/__init__.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/astFactory.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/astFactory_annex.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/docstrings.py +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/py.typed +0 -0
- {asttoolkit-0.1.2 → asttoolkit-0.1.3}/toolFactory/startHere.py +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: astToolkit
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: AST manipulation and transformation tools for Python code
|
|
5
|
+
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
|
+
License: CC-BY-NC-4.0
|
|
7
|
+
Project-URL: Donate, https://www.patreon.com/integrated
|
|
8
|
+
Project-URL: Homepage, https://github.com/hunterhogan/astToolkit
|
|
9
|
+
Project-URL: Issues, https://github.com/hunterhogan/astToolkit/issues
|
|
10
|
+
Project-URL: Repository, https://github.com/hunterhogan/astToolkit.git
|
|
11
|
+
Keywords: ast,abstract syntax tree,code generation,code transformation
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: autoflake
|
|
32
|
+
Requires-Dist: python_minifier
|
|
33
|
+
Requires-Dist: tomli
|
|
34
|
+
Requires-Dist: Z0Z_tools
|
|
35
|
+
Provides-Extra: testing
|
|
36
|
+
Requires-Dist: mypy; extra == "testing"
|
|
37
|
+
Requires-Dist: pytest; extra == "testing"
|
|
38
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
|
39
|
+
Requires-Dist: pytest-xdist; extra == "testing"
|
|
40
|
+
Requires-Dist: pyupgrade; extra == "testing"
|
|
41
|
+
Requires-Dist: trove_classifiers; extra == "testing"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# astToolkit
|
|
45
|
+
|
|
46
|
+
## Do You Want This Package?
|
|
47
|
+
|
|
48
|
+
astToolkit provides a powerfully composable system for manipulating Python Abstract Syntax Trees. Use it when:
|
|
49
|
+
|
|
50
|
+
- You need to programmatically analyze, transform, or generate Python code.
|
|
51
|
+
- You want type-safe operations that help prevent AST manipulation errors.
|
|
52
|
+
- You prefer working with a consistent, fluent API rather than raw AST nodes.
|
|
53
|
+
- You desire the ability to compose complex AST transformations from simple, reusable parts.
|
|
54
|
+
|
|
55
|
+
Don't use it for simple text-based code manipulation—use regex or string operations instead.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### Core Atomic Operations
|
|
60
|
+
|
|
61
|
+
astToolkit provides four core classes that perform "atomic" operations on every AST class and attribute:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from astToolkit import Be, DOT, Grab, Make
|
|
65
|
+
import ast
|
|
66
|
+
|
|
67
|
+
# Create an AST node
|
|
68
|
+
node = Make.Name("example")
|
|
69
|
+
|
|
70
|
+
print(f"{ast.dump(node) = }\n")
|
|
71
|
+
|
|
72
|
+
# Access node properties safely with type checking
|
|
73
|
+
myNameDOTidIs = DOT.id(node)
|
|
74
|
+
|
|
75
|
+
print(f"{myNameDOTidIs = }\n")
|
|
76
|
+
|
|
77
|
+
# Check node type with type guard functions
|
|
78
|
+
CallNode = Make.Call(node)
|
|
79
|
+
print(f"{ast.dump(CallNode) = }\n")
|
|
80
|
+
|
|
81
|
+
print(f"{Be.Name(CallNode) = }")
|
|
82
|
+
print(f"{Be.Name(node) = }")
|
|
83
|
+
|
|
84
|
+
print(f"{Be.Call(CallNode) = }\n{Be.Name(DOT.func(CallNode)) = }\n")
|
|
85
|
+
|
|
86
|
+
# Transform node attributes while preserving structure
|
|
87
|
+
action = lambda idValue: f"modified_{idValue}"
|
|
88
|
+
doThis = Grab.idAttribute(action)
|
|
89
|
+
nodeAfterAction = doThis(node)
|
|
90
|
+
print(f"{ast.dump(node) = }")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### AST Traversal and Transformation
|
|
94
|
+
|
|
95
|
+
The real power lies in composable traversal and transformation using `NodeTourist` and `NodeChanger`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
The AI assistant's example was stupid and wrong. I'm too sick right
|
|
101
|
+
now to make a better one.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Extending `IfThis` and `Then` Classes
|
|
107
|
+
|
|
108
|
+
Extend these classes to create custom predicate and action functions:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from astToolkit import ast_Identifier, Be, IfThis as astToolkit_IfThis
|
|
112
|
+
from collections.abc import Callable
|
|
113
|
+
from typing import Any, TypeGuard
|
|
114
|
+
import ast
|
|
115
|
+
|
|
116
|
+
class IfThis(astToolkit_IfThis):
|
|
117
|
+
@staticmethod
|
|
118
|
+
def isAttributeNamespace_IdentifierGreaterThan0(
|
|
119
|
+
namespace: ast_Identifier,
|
|
120
|
+
identifier: ast_Identifier
|
|
121
|
+
) -> Callable[[ast.AST], TypeGuard[ast.Compare] | bool]:
|
|
122
|
+
|
|
123
|
+
return lambda node: (
|
|
124
|
+
Be.Compare(node)
|
|
125
|
+
and IfThis.isAttributeNamespace_Identifier(namespace, identifier)(node.left)
|
|
126
|
+
and Be.Gt(node.ops[0])
|
|
127
|
+
and IfThis.isConstant_value(0)(node.comparators[0]))
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def isIfAttributeNamespace_IdentifierGreaterThan0(
|
|
131
|
+
namespace: ast_Identifier,
|
|
132
|
+
identifier: ast_Identifier
|
|
133
|
+
) -> Callable[[ast.AST], TypeGuard[ast.If] | bool]:
|
|
134
|
+
|
|
135
|
+
return lambda node: (
|
|
136
|
+
Be.If(node)
|
|
137
|
+
and IfThis.isAttributeNamespace_IdentifierGreaterThan0(namespace, identifier)(node.test))
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def isWhileAttributeNamespace_IdentifierGreaterThan0(
|
|
141
|
+
namespace: ast_Identifier,
|
|
142
|
+
identifier: ast_Identifier
|
|
143
|
+
) -> Callable[[ast.AST], TypeGuard[ast.While] | bool]:
|
|
144
|
+
|
|
145
|
+
return lambda node: (
|
|
146
|
+
Be.While(node)
|
|
147
|
+
and IfThis.isAttributeNamespace_IdentifierGreaterThan0(namespace, identifier)(node.test))
|
|
148
|
+
|
|
149
|
+
# Or, make the comparison value a parameter
|
|
150
|
+
@staticmethod
|
|
151
|
+
def isAttributeNamespace_IdentifierGreaterThanThis(
|
|
152
|
+
namespace: ast_Identifier,
|
|
153
|
+
identifier: ast_Identifier,
|
|
154
|
+
value: Any
|
|
155
|
+
) -> Callable[[ast.AST], TypeGuard[ast.Compare] | bool]:
|
|
156
|
+
|
|
157
|
+
return lambda node: (
|
|
158
|
+
Be.Compare(node)
|
|
159
|
+
and IfThis.isAttributeNamespace_Identifier(namespace, identifier)(node.left)
|
|
160
|
+
and Be.Gt(node.ops[0])
|
|
161
|
+
and IfThis.isConstant_value(value)(node.comparators[0]))
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Easy-to-use Tools for Annoying Tasks
|
|
165
|
+
|
|
166
|
+
- extractClassDef
|
|
167
|
+
- extractFunctionDef
|
|
168
|
+
- parseLogicalPath2astModule
|
|
169
|
+
- parsePathFilename2astModule
|
|
170
|
+
|
|
171
|
+
### Easy-to-use Tools for More Complicated Tasks
|
|
172
|
+
|
|
173
|
+
- removeUnusedParameters
|
|
174
|
+
- write_astModule
|
|
175
|
+
|
|
176
|
+
### The `toolFactory`
|
|
177
|
+
|
|
178
|
+
Hypothetically, you could customize every aspect of the classes `Be`, `DOT`, `GRAB`, and `Make` and more than 100 `TypeAlias` in the toolFactory directory/package.
|
|
179
|
+
|
|
180
|
+
## Installation
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pip install astToolkit
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Technical Details
|
|
187
|
+
|
|
188
|
+
### Architecture
|
|
189
|
+
|
|
190
|
+
astToolkit implements a layered architecture designed for composability and type safety:
|
|
191
|
+
|
|
192
|
+
1. **Core "Atomic" Classes** - The foundation of the system:
|
|
193
|
+
- `Be`: Type guards that return `TypeGuard[ast.NodeType]` for safe type narrowing.
|
|
194
|
+
- `DOT`: Read-only accessors that retrieve node attributes with proper typing.
|
|
195
|
+
- `Grab`: Transformation functions that modify specific attributes while preserving node structure.
|
|
196
|
+
- `Make`: Factory methods that create properly configured AST nodes with consistent interfaces.
|
|
197
|
+
|
|
198
|
+
2. **Traversal and Transformation** - Built on the visitor pattern:
|
|
199
|
+
- `NodeTourist`: Extends `ast.NodeVisitor` to extract information from nodes that match predicates.
|
|
200
|
+
- `NodeChanger`: Extends `ast.NodeTransformer` to selectively transform nodes that match predicates.
|
|
201
|
+
|
|
202
|
+
3. **Composable APIs** - The predicate-action pattern:
|
|
203
|
+
- `IfThis`: Generates predicate functions that identify nodes based on structure, content, or relationships.
|
|
204
|
+
- `Then`: Creates action functions that specify what to do with matched nodes (extract, replace, modify).
|
|
205
|
+
|
|
206
|
+
4. **Higher-level Tools** - Built from the core components:
|
|
207
|
+
- `_toolkitAST.py`: Functions for common operations like extracting function definitions or importing modules.
|
|
208
|
+
- `transformationTools.py`: Advanced utilities like function inlining and code generation.
|
|
209
|
+
- `IngredientsFunction` and `IngredientsModule`: Containers for holding AST components and their dependencies.
|
|
210
|
+
|
|
211
|
+
5. **Type System** - Over 120 specialized types for AST components:
|
|
212
|
+
- Custom type annotations for AST node attributes.
|
|
213
|
+
- Union types that accurately model Python's AST structure.
|
|
214
|
+
- Type guards that enable static type checkers to understand dynamic type narrowing.
|
|
215
|
+
|
|
216
|
+
## My Recovery
|
|
217
|
+
|
|
218
|
+
[](https://HunterThinks.com/support)
|
|
219
|
+
[](https://www.youtube.com/@HunterHogan)
|
|
220
|
+
|
|
221
|
+
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# astToolkit
|
|
2
|
+
|
|
3
|
+
## Do You Want This Package?
|
|
4
|
+
|
|
5
|
+
astToolkit provides a powerfully composable system for manipulating Python Abstract Syntax Trees. Use it when:
|
|
6
|
+
|
|
7
|
+
- You need to programmatically analyze, transform, or generate Python code.
|
|
8
|
+
- You want type-safe operations that help prevent AST manipulation errors.
|
|
9
|
+
- You prefer working with a consistent, fluent API rather than raw AST nodes.
|
|
10
|
+
- You desire the ability to compose complex AST transformations from simple, reusable parts.
|
|
11
|
+
|
|
12
|
+
Don't use it for simple text-based code manipulation—use regex or string operations instead.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
### Core Atomic Operations
|
|
17
|
+
|
|
18
|
+
astToolkit provides four core classes that perform "atomic" operations on every AST class and attribute:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from astToolkit import Be, DOT, Grab, Make
|
|
22
|
+
import ast
|
|
23
|
+
|
|
24
|
+
# Create an AST node
|
|
25
|
+
node = Make.Name("example")
|
|
26
|
+
|
|
27
|
+
print(f"{ast.dump(node) = }\n")
|
|
28
|
+
|
|
29
|
+
# Access node properties safely with type checking
|
|
30
|
+
myNameDOTidIs = DOT.id(node)
|
|
31
|
+
|
|
32
|
+
print(f"{myNameDOTidIs = }\n")
|
|
33
|
+
|
|
34
|
+
# Check node type with type guard functions
|
|
35
|
+
CallNode = Make.Call(node)
|
|
36
|
+
print(f"{ast.dump(CallNode) = }\n")
|
|
37
|
+
|
|
38
|
+
print(f"{Be.Name(CallNode) = }")
|
|
39
|
+
print(f"{Be.Name(node) = }")
|
|
40
|
+
|
|
41
|
+
print(f"{Be.Call(CallNode) = }\n{Be.Name(DOT.func(CallNode)) = }\n")
|
|
42
|
+
|
|
43
|
+
# Transform node attributes while preserving structure
|
|
44
|
+
action = lambda idValue: f"modified_{idValue}"
|
|
45
|
+
doThis = Grab.idAttribute(action)
|
|
46
|
+
nodeAfterAction = doThis(node)
|
|
47
|
+
print(f"{ast.dump(node) = }")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### AST Traversal and Transformation
|
|
51
|
+
|
|
52
|
+
The real power lies in composable traversal and transformation using `NodeTourist` and `NodeChanger`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
The AI assistant's example was stupid and wrong. I'm too sick right
|
|
58
|
+
now to make a better one.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Extending `IfThis` and `Then` Classes
|
|
64
|
+
|
|
65
|
+
Extend these classes to create custom predicate and action functions:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from astToolkit import ast_Identifier, Be, IfThis as astToolkit_IfThis
|
|
69
|
+
from collections.abc import Callable
|
|
70
|
+
from typing import Any, TypeGuard
|
|
71
|
+
import ast
|
|
72
|
+
|
|
73
|
+
class IfThis(astToolkit_IfThis):
|
|
74
|
+
@staticmethod
|
|
75
|
+
def isAttributeNamespace_IdentifierGreaterThan0(
|
|
76
|
+
namespace: ast_Identifier,
|
|
77
|
+
identifier: ast_Identifier
|
|
78
|
+
) -> Callable[[ast.AST], TypeGuard[ast.Compare] | bool]:
|
|
79
|
+
|
|
80
|
+
return lambda node: (
|
|
81
|
+
Be.Compare(node)
|
|
82
|
+
and IfThis.isAttributeNamespace_Identifier(namespace, identifier)(node.left)
|
|
83
|
+
and Be.Gt(node.ops[0])
|
|
84
|
+
and IfThis.isConstant_value(0)(node.comparators[0]))
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def isIfAttributeNamespace_IdentifierGreaterThan0(
|
|
88
|
+
namespace: ast_Identifier,
|
|
89
|
+
identifier: ast_Identifier
|
|
90
|
+
) -> Callable[[ast.AST], TypeGuard[ast.If] | bool]:
|
|
91
|
+
|
|
92
|
+
return lambda node: (
|
|
93
|
+
Be.If(node)
|
|
94
|
+
and IfThis.isAttributeNamespace_IdentifierGreaterThan0(namespace, identifier)(node.test))
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def isWhileAttributeNamespace_IdentifierGreaterThan0(
|
|
98
|
+
namespace: ast_Identifier,
|
|
99
|
+
identifier: ast_Identifier
|
|
100
|
+
) -> Callable[[ast.AST], TypeGuard[ast.While] | bool]:
|
|
101
|
+
|
|
102
|
+
return lambda node: (
|
|
103
|
+
Be.While(node)
|
|
104
|
+
and IfThis.isAttributeNamespace_IdentifierGreaterThan0(namespace, identifier)(node.test))
|
|
105
|
+
|
|
106
|
+
# Or, make the comparison value a parameter
|
|
107
|
+
@staticmethod
|
|
108
|
+
def isAttributeNamespace_IdentifierGreaterThanThis(
|
|
109
|
+
namespace: ast_Identifier,
|
|
110
|
+
identifier: ast_Identifier,
|
|
111
|
+
value: Any
|
|
112
|
+
) -> Callable[[ast.AST], TypeGuard[ast.Compare] | bool]:
|
|
113
|
+
|
|
114
|
+
return lambda node: (
|
|
115
|
+
Be.Compare(node)
|
|
116
|
+
and IfThis.isAttributeNamespace_Identifier(namespace, identifier)(node.left)
|
|
117
|
+
and Be.Gt(node.ops[0])
|
|
118
|
+
and IfThis.isConstant_value(value)(node.comparators[0]))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Easy-to-use Tools for Annoying Tasks
|
|
122
|
+
|
|
123
|
+
- extractClassDef
|
|
124
|
+
- extractFunctionDef
|
|
125
|
+
- parseLogicalPath2astModule
|
|
126
|
+
- parsePathFilename2astModule
|
|
127
|
+
|
|
128
|
+
### Easy-to-use Tools for More Complicated Tasks
|
|
129
|
+
|
|
130
|
+
- removeUnusedParameters
|
|
131
|
+
- write_astModule
|
|
132
|
+
|
|
133
|
+
### The `toolFactory`
|
|
134
|
+
|
|
135
|
+
Hypothetically, you could customize every aspect of the classes `Be`, `DOT`, `GRAB`, and `Make` and more than 100 `TypeAlias` in the toolFactory directory/package.
|
|
136
|
+
|
|
137
|
+
## Installation
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pip install astToolkit
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Technical Details
|
|
144
|
+
|
|
145
|
+
### Architecture
|
|
146
|
+
|
|
147
|
+
astToolkit implements a layered architecture designed for composability and type safety:
|
|
148
|
+
|
|
149
|
+
1. **Core "Atomic" Classes** - The foundation of the system:
|
|
150
|
+
- `Be`: Type guards that return `TypeGuard[ast.NodeType]` for safe type narrowing.
|
|
151
|
+
- `DOT`: Read-only accessors that retrieve node attributes with proper typing.
|
|
152
|
+
- `Grab`: Transformation functions that modify specific attributes while preserving node structure.
|
|
153
|
+
- `Make`: Factory methods that create properly configured AST nodes with consistent interfaces.
|
|
154
|
+
|
|
155
|
+
2. **Traversal and Transformation** - Built on the visitor pattern:
|
|
156
|
+
- `NodeTourist`: Extends `ast.NodeVisitor` to extract information from nodes that match predicates.
|
|
157
|
+
- `NodeChanger`: Extends `ast.NodeTransformer` to selectively transform nodes that match predicates.
|
|
158
|
+
|
|
159
|
+
3. **Composable APIs** - The predicate-action pattern:
|
|
160
|
+
- `IfThis`: Generates predicate functions that identify nodes based on structure, content, or relationships.
|
|
161
|
+
- `Then`: Creates action functions that specify what to do with matched nodes (extract, replace, modify).
|
|
162
|
+
|
|
163
|
+
4. **Higher-level Tools** - Built from the core components:
|
|
164
|
+
- `_toolkitAST.py`: Functions for common operations like extracting function definitions or importing modules.
|
|
165
|
+
- `transformationTools.py`: Advanced utilities like function inlining and code generation.
|
|
166
|
+
- `IngredientsFunction` and `IngredientsModule`: Containers for holding AST components and their dependencies.
|
|
167
|
+
|
|
168
|
+
5. **Type System** - Over 120 specialized types for AST components:
|
|
169
|
+
- Custom type annotations for AST node attributes.
|
|
170
|
+
- Union types that accurately model Python's AST structure.
|
|
171
|
+
- Type guards that enable static type checkers to understand dynamic type narrowing.
|
|
172
|
+
|
|
173
|
+
## My Recovery
|
|
174
|
+
|
|
175
|
+
[](https://HunterThinks.com/support)
|
|
176
|
+
[](https://www.youtube.com/@HunterHogan)
|
|
177
|
+
|
|
178
|
+
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -21,13 +21,9 @@ from astToolkit._types import (
|
|
|
21
21
|
|
|
22
22
|
from astToolkit._astTypes import * # noqa: F403
|
|
23
23
|
|
|
24
|
-
from astToolkit.
|
|
25
|
-
importLogicalPath2Callable as importLogicalPath2Callable,
|
|
26
|
-
importPathFilename2Callable as importPathFilename2Callable,
|
|
24
|
+
from astToolkit._toolkitNodeVisitor import (
|
|
27
25
|
NodeChanger as NodeChanger,
|
|
28
26
|
NodeTourist as NodeTourist,
|
|
29
|
-
parseLogicalPath2astModule as parseLogicalPath2astModule,
|
|
30
|
-
parsePathFilename2astModule as parsePathFilename2astModule,
|
|
31
27
|
)
|
|
32
28
|
|
|
33
29
|
from astToolkit._toolBe import Be as Be
|
|
@@ -38,14 +34,16 @@ from astToolkit._toolMake import Make as Make
|
|
|
38
34
|
from astToolkit._toolIfThis import IfThis as IfThis
|
|
39
35
|
from astToolkit._toolThen import Then as Then
|
|
40
36
|
|
|
41
|
-
from astToolkit.
|
|
37
|
+
from astToolkit._toolkitContainers import (
|
|
42
38
|
IngredientsFunction as IngredientsFunction,
|
|
43
39
|
IngredientsModule as IngredientsModule,
|
|
44
40
|
LedgerOfImports as LedgerOfImports,
|
|
45
41
|
)
|
|
46
42
|
|
|
47
|
-
from astToolkit.
|
|
43
|
+
from astToolkit._toolkitAST import (
|
|
48
44
|
astModuleToIngredientsFunction as astModuleToIngredientsFunction,
|
|
49
45
|
extractClassDef as extractClassDef,
|
|
50
46
|
extractFunctionDef as extractFunctionDef,
|
|
47
|
+
parseLogicalPath2astModule as parseLogicalPath2astModule,
|
|
48
|
+
parsePathFilename2astModule as parsePathFilename2astModule,
|
|
51
49
|
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from inspect import getsource as inspect_getsource
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from pathlib import Path, PurePath
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
from astToolkit import ast_Identifier, IfThis, IngredientsFunction, LedgerOfImports, NodeTourist, Then, str_nameDOTname
|
|
8
|
+
from astToolkit import FREAKOUT
|
|
9
|
+
import ast
|
|
10
|
+
|
|
11
|
+
def astModuleToIngredientsFunction(astModule: ast.AST, identifierFunctionDef: ast_Identifier) -> IngredientsFunction:
|
|
12
|
+
"""
|
|
13
|
+
Extract a function definition from an AST module and create an `IngredientsFunction`.
|
|
14
|
+
|
|
15
|
+
This function finds a function definition with the specified identifier in the given AST module, extracts it, and
|
|
16
|
+
stores all module imports in the `LedgerOfImports`.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
astModule: The AST module containing the function definition.
|
|
20
|
+
identifierFunctionDef: The name of the function to extract.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
ingredientsFunction: `IngredientsFunction` object containing the `ast.FunctionDef` and _all_ imports from the
|
|
24
|
+
source module.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
FREAKOUT: If the function definition is not found.
|
|
28
|
+
"""
|
|
29
|
+
astFunctionDef = extractFunctionDef(astModule, identifierFunctionDef)
|
|
30
|
+
if not astFunctionDef: raise FREAKOUT
|
|
31
|
+
return IngredientsFunction(astFunctionDef, LedgerOfImports(astModule))
|
|
32
|
+
|
|
33
|
+
def extractClassDef(module: ast.AST, identifier: ast_Identifier) -> ast.ClassDef | None:
|
|
34
|
+
"""
|
|
35
|
+
Extract a class definition with a specific name from an AST module.
|
|
36
|
+
|
|
37
|
+
This function searches through an AST module for a class definition that matches the provided identifier and returns
|
|
38
|
+
it if found.
|
|
39
|
+
|
|
40
|
+
Parameters:
|
|
41
|
+
module: The AST module to search within.
|
|
42
|
+
identifier: The name of the class to find.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
astClassDef|None: The matching class definition AST node, or `None` if not found.
|
|
46
|
+
"""
|
|
47
|
+
return NodeTourist(IfThis.isClassDef_Identifier(identifier), Then.extractIt).captureLastMatch(module)
|
|
48
|
+
|
|
49
|
+
def extractFunctionDef(module: ast.AST, identifier: ast_Identifier) -> ast.FunctionDef | None:
|
|
50
|
+
"""
|
|
51
|
+
Extract a function definition with a specific name from an AST module.
|
|
52
|
+
|
|
53
|
+
This function searches through an AST module for a function definition that matches the provided identifier and
|
|
54
|
+
returns it if found.
|
|
55
|
+
|
|
56
|
+
Parameters:
|
|
57
|
+
module: The AST module to search within.
|
|
58
|
+
identifier: The name of the function to find.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
astFunctionDef|None: The matching function definition AST node, or `None` if not found.
|
|
62
|
+
"""
|
|
63
|
+
return NodeTourist(IfThis.isFunctionDef_Identifier(identifier), Then.extractIt).captureLastMatch(module)
|
|
64
|
+
|
|
65
|
+
def parseLogicalPath2astModule(logicalPathModule: str_nameDOTname, packageIdentifierIfRelative: ast_Identifier | None = None, mode: Literal['exec'] = 'exec') -> ast.Module:
|
|
66
|
+
"""
|
|
67
|
+
Parse a logical Python module path into an `ast.Module`.
|
|
68
|
+
|
|
69
|
+
This function imports a module using its logical path (e.g., 'package.subpackage.module') and converts its source
|
|
70
|
+
code into an Abstract Syntax Tree (AST) Module object.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
logicalPathModule
|
|
75
|
+
The logical path to the module using dot notation (e.g., 'package.module').
|
|
76
|
+
packageIdentifierIfRelative : None
|
|
77
|
+
The package identifier to use if the module path is relative, defaults to None.
|
|
78
|
+
mode : Literal['exec']
|
|
79
|
+
The mode parameter for `ast.parse`. Default is `Literal['exec']`. Options are `Literal['exec']`, `"exec"` (which
|
|
80
|
+
is _not_ the same as `Literal['exec']`), `Literal['eval']`, `Literal['func_type']`, `Literal['single']`. See
|
|
81
|
+
`ast.parse` documentation for some details and much confusion.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
astModule
|
|
86
|
+
An AST Module object representing the parsed source code of the imported module.
|
|
87
|
+
"""
|
|
88
|
+
moduleImported: ModuleType = importlib.import_module(logicalPathModule, packageIdentifierIfRelative)
|
|
89
|
+
sourcePython: str = inspect_getsource(moduleImported)
|
|
90
|
+
return ast.parse(sourcePython, mode)
|
|
91
|
+
|
|
92
|
+
def parsePathFilename2astModule(pathFilename: PathLike[Any] | PurePath, mode: Literal['exec'] = 'exec') -> ast.Module:
|
|
93
|
+
"""
|
|
94
|
+
Parse a file from a given path into an `ast.Module`.
|
|
95
|
+
|
|
96
|
+
This function reads the content of a file specified by `pathFilename` and parses it into an Abstract Syntax Tree
|
|
97
|
+
(AST) Module using Python's ast module.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
pathFilename
|
|
102
|
+
The path to the file to be parsed. Can be a string path, PathLike object, or PurePath object.
|
|
103
|
+
mode : Literal['exec']
|
|
104
|
+
The mode parameter for `ast.parse`. Default is `Literal['exec']`. Options are `Literal['exec']`, `"exec"` (which
|
|
105
|
+
is _not_ the same as `Literal['exec']`), `Literal['eval']`, `Literal['func_type']`, `Literal['single']`. See
|
|
106
|
+
`ast.parse` documentation for some details and much confusion.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
astModule
|
|
111
|
+
The parsed abstract syntax tree module.
|
|
112
|
+
"""
|
|
113
|
+
return ast.parse(Path(pathFilename).read_text(), mode)
|
|
@@ -160,7 +160,7 @@ class IngredientsFunction:
|
|
|
160
160
|
"""
|
|
161
161
|
astFunctionDef: ast.FunctionDef
|
|
162
162
|
imports: LedgerOfImports = dataclasses.field(default_factory=LedgerOfImports)
|
|
163
|
-
type_ignores: list[ast.TypeIgnore] = dataclasses.field(default_factory=list)
|
|
163
|
+
type_ignores: list[ast.TypeIgnore] = dataclasses.field(default_factory=lambda: list[ast.TypeIgnore]())
|
|
164
164
|
|
|
165
165
|
@dataclasses.dataclass
|
|
166
166
|
class IngredientsModule:
|
|
@@ -198,16 +198,16 @@ class IngredientsModule:
|
|
|
198
198
|
"""
|
|
199
199
|
imports: LedgerOfImports = dataclasses.field(default_factory=LedgerOfImports)
|
|
200
200
|
"""Modify this field using the methods in `LedgerOfImports`."""
|
|
201
|
-
prologue: ast.Module = Make.Module([]
|
|
202
|
-
"""Statements after the imports and before the functions in listIngredientsFunctions
|
|
203
|
-
listIngredientsFunctions: list[IngredientsFunction] = dataclasses.field(default_factory=list)
|
|
204
|
-
epilogue: ast.Module = Make.Module([]
|
|
205
|
-
"""Statements after the functions in listIngredientsFunctions and before `launcher`."""
|
|
206
|
-
launcher: ast.Module = Make.Module([]
|
|
201
|
+
prologue: ast.Module = Make.Module([])
|
|
202
|
+
"""Statements after the imports and before the functions in `listIngredientsFunctions`."""
|
|
203
|
+
listIngredientsFunctions: list[IngredientsFunction] = dataclasses.field(default_factory=lambda: list[IngredientsFunction]())
|
|
204
|
+
epilogue: ast.Module = Make.Module([])
|
|
205
|
+
"""Statements after the functions in `listIngredientsFunctions` and before `launcher`."""
|
|
206
|
+
launcher: ast.Module = Make.Module([])
|
|
207
207
|
"""`if __name__ == '__main__':`"""
|
|
208
208
|
|
|
209
209
|
# `ast.TypeIgnore` statements to supplement those in other fields; `type_ignores` is a parameter for `ast.Module` constructor
|
|
210
|
-
supplemental_type_ignores: list[ast.TypeIgnore] = dataclasses.field(default_factory=list)
|
|
210
|
+
supplemental_type_ignores: list[ast.TypeIgnore] = dataclasses.field(default_factory=lambda: list[ast.TypeIgnore]())
|
|
211
211
|
|
|
212
212
|
def __post_init__(self, ingredientsFunction: Sequence[IngredientsFunction] | IngredientsFunction | None = None) -> None:
|
|
213
213
|
if ingredientsFunction is not None:
|
|
@@ -217,7 +217,6 @@ class IngredientsModule:
|
|
|
217
217
|
self.appendIngredientsFunction(*ingredientsFunction)
|
|
218
218
|
|
|
219
219
|
def _append_astModule(self, self_astModule: ast.Module, astModule: ast.Module | None, statement: Sequence[ast.stmt] | ast.stmt | None, type_ignores: list[ast.TypeIgnore] | None) -> None:
|
|
220
|
-
"""Append one or more statements to `prologue`."""
|
|
221
220
|
list_body: list[ast.stmt] = []
|
|
222
221
|
listTypeIgnore: list[ast.TypeIgnore] = []
|
|
223
222
|
if astModule is not None and isinstance(astModule, ast.Module):
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core AST Traversal and Transformation Utilities for Python Code Manipulation
|
|
3
|
+
|
|
4
|
+
This module provides the foundation for traversing and modifying Python Abstract Syntax Trees (ASTs). It contains two
|
|
5
|
+
primary classes:
|
|
6
|
+
|
|
7
|
+
1. NodeTourist: Implements the visitor pattern to traverse an AST and extract information from nodes that match specific
|
|
8
|
+
predicates without modifying the AST.
|
|
9
|
+
|
|
10
|
+
2. NodeChanger: Extends ast.NodeTransformer to selectively transform AST nodes that match specific predicates, enabling
|
|
11
|
+
targeted code modifications.
|
|
12
|
+
|
|
13
|
+
The module also provides utilities for importing modules, loading callables from files, and parsing Python code into AST
|
|
14
|
+
structures, creating a complete workflow for code analysis and transformation.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from typing import Any
|
|
19
|
+
import ast
|
|
20
|
+
|
|
21
|
+
# TODO Identify the logic that narrows the type and can help the user during static type checking.
|
|
22
|
+
|
|
23
|
+
class NodeTourist(ast.NodeVisitor):
|
|
24
|
+
"""
|
|
25
|
+
Visit and extract information from AST nodes that match a predicate.
|
|
26
|
+
|
|
27
|
+
NodeTourist implements the visitor pattern to traverse an AST, applying a predicate function to each node and
|
|
28
|
+
capturing nodes or their attributes when they match. Unlike NodeChanger, it doesn't modify the AST but collects
|
|
29
|
+
information during traversal.
|
|
30
|
+
|
|
31
|
+
This class is particularly useful for analyzing AST structures, extracting specific nodes or node properties, and
|
|
32
|
+
gathering information about code patterns.
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, findThis: Callable[..., Any], doThat: Callable[..., Any]) -> None:
|
|
35
|
+
self.findThis = findThis
|
|
36
|
+
self.doThat = doThat
|
|
37
|
+
self.nodeCaptured: Any | None = None
|
|
38
|
+
|
|
39
|
+
def visit(self, node: ast.AST) -> None:
|
|
40
|
+
if self.findThis(node):
|
|
41
|
+
nodeActionReturn = self.doThat(node)
|
|
42
|
+
if nodeActionReturn is not None:
|
|
43
|
+
self.nodeCaptured = nodeActionReturn
|
|
44
|
+
self.generic_visit(node)
|
|
45
|
+
|
|
46
|
+
def captureLastMatch(self, node: ast.AST) -> Any | None:
|
|
47
|
+
self.nodeCaptured = None
|
|
48
|
+
self.visit(node)
|
|
49
|
+
return self.nodeCaptured
|
|
50
|
+
|
|
51
|
+
class NodeChanger(ast.NodeTransformer):
|
|
52
|
+
"""
|
|
53
|
+
Transform AST nodes that match a predicate by applying a transformation function.
|
|
54
|
+
|
|
55
|
+
NodeChanger is an AST node transformer that applies a targeted transformation to nodes matching a specific
|
|
56
|
+
predicate. It traverses the AST and only modifies nodes that satisfy the predicate condition, leaving other nodes
|
|
57
|
+
unchanged.
|
|
58
|
+
|
|
59
|
+
This class extends ast.NodeTransformer and implements the visitor pattern to systematically process and transform an
|
|
60
|
+
AST tree.
|
|
61
|
+
"""
|
|
62
|
+
def __init__(self, findThis: Callable[..., Any], doThat: Callable[..., Any]) -> None:
|
|
63
|
+
self.findThis = findThis
|
|
64
|
+
self.doThat = doThat
|
|
65
|
+
|
|
66
|
+
def visit(self, node: ast.AST) -> ast.AST:
|
|
67
|
+
if self.findThis(node):
|
|
68
|
+
return self.doThat(node)
|
|
69
|
+
return super().visit(node)
|