py-adtools 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of py-adtools might be problematic. Click here for more details.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rui Zhang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-adtools
3
+ Version: 0.1.0
4
+ Summary: Useful tools for parsing Python programs for algorithm design.
5
+ Home-page: https://github.com/RayZhhh/py-adtools
6
+ Author: Rui Zhang
7
+ Author-email: rzhang.cs@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: license-file
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # Useful tools for parsing Python programs for algorithm design
26
+
27
+ ------
28
+
29
+ > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
30
+ >
31
+ > More tools will be provided soon.
32
+
33
+ ------
34
+
35
+ The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
36
+
37
+ ![pycode](./assets/pycode.png)
38
+
39
+ ------
40
+
41
+ ## Installation
42
+
43
+ > [!TIP]
44
+ >
45
+ > It is recommended to use Python >= 3.10.
46
+
47
+ Run the following instructions to install adtools.
48
+
49
+ ```shell
50
+ pip install git+https://github.com/RayZhhh/adtool.git
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ Parse your code (in string) into Python code instances.
56
+
57
+ ```python
58
+ from adtools import PyProgram
59
+
60
+ code = r'''
61
+ import ast
62
+ import numpy as np
63
+
64
+ def func():
65
+ a = 5
66
+ return a + a
67
+
68
+ class A(B):
69
+ a=1
70
+
71
+ @yes()
72
+ @deco()
73
+ def __init__(self):
74
+ pass
75
+
76
+ def method(self):
77
+ pass
78
+
79
+ b=2
80
+ '''
81
+
82
+ p = PyProgram.from_text(code)
83
+ print(p)
84
+ print(f'-------------------------------------')
85
+ print(p.classes[0].functions[0].decorator)
86
+ print(f'-------------------------------------')
87
+ print(p.functions[0].name)
88
+ ```
89
+
@@ -0,0 +1,65 @@
1
+ # Useful tools for parsing Python programs for algorithm design
2
+
3
+ ------
4
+
5
+ > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
6
+ >
7
+ > More tools will be provided soon.
8
+
9
+ ------
10
+
11
+ The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
12
+
13
+ ![pycode](./assets/pycode.png)
14
+
15
+ ------
16
+
17
+ ## Installation
18
+
19
+ > [!TIP]
20
+ >
21
+ > It is recommended to use Python >= 3.10.
22
+
23
+ Run the following instructions to install adtools.
24
+
25
+ ```shell
26
+ pip install git+https://github.com/RayZhhh/adtool.git
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Parse your code (in string) into Python code instances.
32
+
33
+ ```python
34
+ from adtools import PyProgram
35
+
36
+ code = r'''
37
+ import ast
38
+ import numpy as np
39
+
40
+ def func():
41
+ a = 5
42
+ return a + a
43
+
44
+ class A(B):
45
+ a=1
46
+
47
+ @yes()
48
+ @deco()
49
+ def __init__(self):
50
+ pass
51
+
52
+ def method(self):
53
+ pass
54
+
55
+ b=2
56
+ '''
57
+
58
+ p = PyProgram.from_text(code)
59
+ print(p)
60
+ print(f'-------------------------------------')
61
+ print(p.classes[0].functions[0].decorator)
62
+ print(f'-------------------------------------')
63
+ print(p.functions[0].name)
64
+ ```
65
+
@@ -0,0 +1 @@
1
+ from .py_code import PyScript, PyFunction, PyClass, PyProgram
@@ -0,0 +1,335 @@
1
+ """
2
+ Copyright (c) 2025 Rui Zhang <rzhang.cs@gmail.com>
3
+
4
+ NOTICE: This code is under MIT license. This code is intended for academic/research purposes only.
5
+ Commercial use of this software or its derivatives requires prior written permission.
6
+ """
7
+
8
+ import ast
9
+ import dataclasses
10
+ import textwrap
11
+ from typing import List, Optional
12
+
13
+ __all__ = ['PyScript', 'PyFunction', 'PyClass', 'PyProgram']
14
+
15
+
16
+ @dataclasses.dataclass
17
+ class PyScript:
18
+ """A parsed Python script block (top-level code that's not in classes/functions).
19
+ """
20
+ code: str
21
+
22
+ def __str__(self) -> str:
23
+ return self.code + '\n'
24
+
25
+
26
+ @dataclasses.dataclass
27
+ class PyFunction:
28
+ """A parsed Python function.
29
+ > Part of this class is referenced from:
30
+ > https://github.com/google-deepmind/funsearch/blob/main/implementation/code_manipulation.py
31
+ """
32
+ decorator: str
33
+ name: str
34
+ args: str
35
+ body: str
36
+ return_type: str | None = None
37
+ docstring: str | None = None
38
+
39
+ def __str__(self) -> str:
40
+ return_type = f' -> {self.return_type}' if self.return_type else ''
41
+ function = f'{self.decorator}\ndef {self.name}({self.args}){return_type}:\n'
42
+ if self.docstring:
43
+ # The self.docstring is already indented on every line except the first one.
44
+ # Here, we assume the indentation is always four spaces.
45
+ new_line = '\n' if self.body else ''
46
+ function += f' """{self.docstring}"""{new_line}'
47
+ # self.body is already indented.
48
+ function += self.body + '\n\n'
49
+ return function
50
+
51
+ def __setattr__(self, name: str, value: str) -> None:
52
+ # Ensure there aren't leading & trailing new lines in `body`
53
+ if name == 'body':
54
+ value = value.strip('\n')
55
+ # ensure there aren't leading & trailing quotes in `docstring`
56
+ if name == 'docstring' and value is not None:
57
+ if '"""' in value:
58
+ value = value.strip()
59
+ value = value.replace('"""', '')
60
+ super().__setattr__(name, value)
61
+
62
+ @classmethod
63
+ def extract_first_function_from_text(cls, text: str) -> 'PyFunction':
64
+ tree = ast.parse(text)
65
+ visitor = _ProgramVisitor(text)
66
+ visitor.visit(tree)
67
+ program = visitor.return_program()
68
+ return program.functions[0]
69
+
70
+ @classmethod
71
+ def extract_all_functions_from_text(cls, text: str) -> List['PyFunction']:
72
+ tree = ast.parse(text)
73
+ visitor = _ProgramVisitor(text)
74
+ visitor.visit(tree)
75
+ program = visitor.return_program()
76
+ return program.functions
77
+
78
+
79
+ @dataclasses.dataclass
80
+ class PyClass:
81
+ """A parsed Python class."""
82
+ decorator: str
83
+ name: str
84
+ bases: str
85
+ class_vars_and_code: List[PyScript] = None
86
+ docstring: str | None = None
87
+ functions: list[PyFunction] = dataclasses.field(default_factory=list)
88
+ functions_class_vars_and_code: List[PyScript | PyFunction] | None = None
89
+
90
+ def __str__(self) -> str:
91
+ class_def = f'{self.decorator}\nclass {self.name}'
92
+ if self.bases:
93
+ class_def += f'({self.bases})'
94
+ class_def += ':\n'
95
+
96
+ if self.docstring:
97
+ class_def += f' """{self.docstring}"""\n'
98
+
99
+ for item in self.functions_class_vars_and_code:
100
+ if isinstance(item, PyScript):
101
+ class_def += f'{str(item)}\n'
102
+ else:
103
+ # Add functions with an extra level of indentation
104
+ class_def += textwrap.indent(str(item).strip(), ' ')
105
+ class_def += '\n\n'
106
+ return class_def
107
+
108
+ def __setattr__(self, name: str, value: str) -> None:
109
+ # Ensure there aren't leading & trailing new lines in `body`
110
+ if name == 'body':
111
+ value = value.strip('\n')
112
+ # ensure there aren't leading & trailing quotes in `docstring`
113
+ if name == 'docstring' and value is not None:
114
+ if '"""' in value:
115
+ value = value.strip()
116
+ value = value.replace('"""', '')
117
+ super().__setattr__(name, value)
118
+
119
+ @classmethod
120
+ def extract_first_class_from_text(cls, text: str) -> 'PyClass':
121
+ tree = ast.parse(text)
122
+ visitor = _ProgramVisitor(text)
123
+ visitor.visit(tree)
124
+ program = visitor.return_program()
125
+ return program.classes[0]
126
+
127
+ @classmethod
128
+ def extract_all_classes_from_text(cls, text: str) -> List['PyClass']:
129
+ tree = ast.parse(text)
130
+ visitor = _ProgramVisitor(text)
131
+ visitor.visit(tree)
132
+ program = visitor.return_program()
133
+ return program.classes
134
+
135
+
136
+ @dataclasses.dataclass
137
+ class PyProgram:
138
+ """A parsed Python program."""
139
+
140
+ scripts: list[PyScript] # Top-level code that's not in classes/functions
141
+ functions: list[PyFunction] # Top-level functions in the code
142
+ classes: list[PyClass] # Top-level classes in the code
143
+ classes_functions_scripts: list[PyFunction | PyClass | PyScript]
144
+
145
+ def __str__(self) -> str:
146
+ program = ''
147
+ for class_or_func_or_script in self.classes_functions_scripts:
148
+ program += str(class_or_func_or_script) + '\n'
149
+ return program
150
+
151
+ @classmethod
152
+ def from_text(cls, text: str) -> Optional['PyProgram']:
153
+ tree = ast.parse(text)
154
+ visitor = _ProgramVisitor(text)
155
+ visitor.visit(tree)
156
+ return visitor.return_program()
157
+
158
+
159
+ class _ProgramVisitor(ast.NodeVisitor):
160
+ """Parses code to collect all required information to produce a `Program`.
161
+ Now handles scripts, functions, and classes.
162
+ """
163
+
164
+ def __init__(self, sourcecode: str):
165
+ self._codelines: list[str] = sourcecode.splitlines()
166
+ self._scripts: list[PyScript] = []
167
+ self._functions: list[PyFunction] = []
168
+ self._classes: list[PyClass] = []
169
+ self._classes_functions_scripts: list[PyFunction | PyClass | PyScript] = []
170
+ self._last_script_end = 0
171
+
172
+ def _add_script(self, start_line: int, end_line: int):
173
+ """Add a script segment from the code."""
174
+ if start_line >= end_line:
175
+ return
176
+ script_code = '\n'.join(self._codelines[start_line:end_line]).strip()
177
+ if script_code:
178
+ script = PyScript(code=script_code)
179
+ self._scripts.append(script)
180
+ self._classes_functions_scripts.append(script)
181
+
182
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
183
+ """Collects all information about the function being parsed.
184
+ """
185
+ # We only consider top-level function
186
+ if node.col_offset == 0:
187
+ # Extract decorator first
188
+ has_decorators = bool(node.decorator_list)
189
+ if has_decorators:
190
+ # Find the minimum line number and retain the code above
191
+ decorator_start_line = min(decorator.lineno for decorator in node.decorator_list)
192
+ decorator = '\n'.join(self._codelines[decorator_start_line - 1: node.lineno - 1])
193
+ # Update script end line
194
+ script_end_line = decorator_start_line - 1
195
+ else:
196
+ decorator = ''
197
+ script_end_line = node.lineno - 1
198
+
199
+ # Add any script code before this function
200
+ self._add_script(self._last_script_end, script_end_line)
201
+ self._last_script_end = node.end_lineno
202
+
203
+ function_end_line = node.end_lineno
204
+ body_start_line = node.body[0].lineno - 1
205
+ docstring = None
206
+
207
+ if isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Constant):
208
+ docstring = ast.literal_eval(ast.unparse(node.body[0])).strip()
209
+ if len(node.body) > 1:
210
+ body_start_line = node.body[0].end_lineno
211
+ else:
212
+ body_start_line = function_end_line
213
+
214
+ func = PyFunction(
215
+ decorator=decorator,
216
+ name=node.name,
217
+ args=ast.unparse(node.args),
218
+ return_type=ast.unparse(node.returns) if node.returns else None,
219
+ docstring=docstring,
220
+ body='\n'.join(self._codelines[body_start_line: function_end_line]),
221
+ )
222
+
223
+ self._functions.append(func)
224
+ self._classes_functions_scripts.append(func)
225
+ self.generic_visit(node)
226
+
227
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
228
+ """Collects all information about the class being parsed.
229
+ """
230
+ # We only care about top-level classes
231
+ if node.col_offset == 0:
232
+ # Extract decorator first
233
+ has_decorators = bool(node.decorator_list)
234
+ if has_decorators:
235
+ # Find the minimum line number and retain the code above
236
+ decorator_start_line = min(decorator.lineno for decorator in node.decorator_list)
237
+ class_decorator = '\n'.join(self._codelines[decorator_start_line - 1: node.lineno - 1])
238
+ # Update script end line
239
+ script_end_line = decorator_start_line - 1
240
+ else:
241
+ class_decorator = ''
242
+ script_end_line = node.lineno - 1
243
+
244
+ # Add any script code before this class
245
+ self._add_script(self._last_script_end, script_end_line)
246
+ self._last_script_end = node.end_lineno
247
+
248
+ # Extract class docstring
249
+ docstring = None
250
+ if isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Constant):
251
+ docstring = ast.literal_eval(ast.unparse(node.body[0]))
252
+
253
+ methods = []
254
+ class_vars_and_code = []
255
+ function_class_vars_and_code = [] # record the order of function and class vars and code
256
+
257
+ # Traverse each body, if there is a docstring, skip body[0]
258
+ for item in node.body if docstring is None else node.body[1:]:
259
+ if isinstance(item, ast.FunctionDef):
260
+ # Extract decorators
261
+ has_decorators = bool(item.decorator_list)
262
+ if has_decorators:
263
+ # Find the minimum line number and retain the code above
264
+ decorator_start_line = min(decorator.lineno for decorator in item.decorator_list)
265
+ # Dedent decorator code for 4 spaces
266
+ decorator = []
267
+ for line in range(decorator_start_line - 1, item.lineno - 1):
268
+ dedented_decorator = self._codelines[line][4:]
269
+ decorator.append(dedented_decorator)
270
+ decorator = '\n'.join(decorator)
271
+ else:
272
+ decorator = ''
273
+
274
+ method_end_line = item.end_lineno
275
+ method_body_start_line = item.body[0].lineno - 1
276
+ method_docstring = None
277
+
278
+ # Extract doc-string if there exists
279
+ if isinstance(item.body[0], ast.Expr) and isinstance(item.body[0].value, ast.Constant):
280
+ method_docstring = ast.literal_eval(ast.unparse(item.body[0])).strip()
281
+ if len(item.body) > 1:
282
+ method_body_start_line = item.body[0].end_lineno
283
+ else:
284
+ method_body_start_line = method_end_line
285
+
286
+ # Extract function body and dedent for 4 spaces
287
+ body = []
288
+ for line in range(method_body_start_line, method_end_line):
289
+ dedented_body = self._codelines[line][4:]
290
+ body.append(dedented_body)
291
+ body = '\n'.join(body)
292
+
293
+ py_func = PyFunction(
294
+ decorator=decorator,
295
+ name=item.name,
296
+ args=ast.unparse(item.args),
297
+ return_type=ast.unparse(item.returns) if item.returns else None,
298
+ docstring=method_docstring,
299
+ body=body,
300
+ )
301
+ methods.append(py_func)
302
+ function_class_vars_and_code.append(py_func)
303
+ else: # If the item is not a function definition,add to class variables and code
304
+ code = []
305
+ for i in range(item.lineno - 1, item.end_lineno):
306
+ code.append(self._codelines[i])
307
+ py_script = PyScript(code='\n'.join(code))
308
+ class_vars_and_code.append(py_script)
309
+ function_class_vars_and_code.append(py_script)
310
+
311
+ # Get base classes
312
+ bases = ', '.join([ast.unparse(base) for base in node.bases]) if node.bases else ''
313
+
314
+ class_ = PyClass(
315
+ decorator=class_decorator,
316
+ name=node.name,
317
+ bases=bases,
318
+ docstring=docstring,
319
+ class_vars_and_code=class_vars_and_code if class_vars_and_code else None,
320
+ functions=methods,
321
+ functions_class_vars_and_code=function_class_vars_and_code if function_class_vars_and_code else None
322
+ )
323
+ self._classes.append(class_)
324
+ self._classes_functions_scripts.append(class_)
325
+ self.generic_visit(node)
326
+
327
+ def return_program(self) -> PyProgram:
328
+ # Add any remaining script code after the last class/function
329
+ self._add_script(self._last_script_end, len(self._codelines))
330
+ return PyProgram(
331
+ scripts=self._scripts,
332
+ functions=self._functions,
333
+ classes=self._classes,
334
+ classes_functions_scripts=self._classes_functions_scripts,
335
+ )
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-adtools
3
+ Version: 0.1.0
4
+ Summary: Useful tools for parsing Python programs for algorithm design.
5
+ Home-page: https://github.com/RayZhhh/py-adtools
6
+ Author: Rui Zhang
7
+ Author-email: rzhang.cs@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: license-file
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # Useful tools for parsing Python programs for algorithm design
26
+
27
+ ------
28
+
29
+ > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
30
+ >
31
+ > More tools will be provided soon.
32
+
33
+ ------
34
+
35
+ The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
36
+
37
+ ![pycode](./assets/pycode.png)
38
+
39
+ ------
40
+
41
+ ## Installation
42
+
43
+ > [!TIP]
44
+ >
45
+ > It is recommended to use Python >= 3.10.
46
+
47
+ Run the following instructions to install adtools.
48
+
49
+ ```shell
50
+ pip install git+https://github.com/RayZhhh/adtool.git
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ Parse your code (in string) into Python code instances.
56
+
57
+ ```python
58
+ from adtools import PyProgram
59
+
60
+ code = r'''
61
+ import ast
62
+ import numpy as np
63
+
64
+ def func():
65
+ a = 5
66
+ return a + a
67
+
68
+ class A(B):
69
+ a=1
70
+
71
+ @yes()
72
+ @deco()
73
+ def __init__(self):
74
+ pass
75
+
76
+ def method(self):
77
+ pass
78
+
79
+ b=2
80
+ '''
81
+
82
+ p = PyProgram.from_text(code)
83
+ print(p)
84
+ print(f'-------------------------------------')
85
+ print(p.classes[0].functions[0].decorator)
86
+ print(f'-------------------------------------')
87
+ print(p.functions[0].name)
88
+ ```
89
+
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ adtools/__init__.py
5
+ adtools/py_code.py
6
+ py_adtools.egg-info/PKG-INFO
7
+ py_adtools.egg-info/SOURCES.txt
8
+ py_adtools.egg-info/dependency_links.txt
9
+ py_adtools.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ adtools
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open('README.md', 'r', encoding='utf-8') as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name='py-adtools',
8
+ version='0.1.0',
9
+ author='Rui Zhang',
10
+ author_email='rzhang.cs@gmail.com',
11
+ description='Useful tools for parsing Python programs for algorithm design.',
12
+ long_description=long_description,
13
+ long_description_content_type='text/markdown',
14
+ url='https://github.com/RayZhhh/py-adtools',
15
+ packages=['adtools'],
16
+ classifiers=[
17
+ 'Programming Language :: Python :: 3',
18
+ 'Operating System :: OS Independent',
19
+ 'Intended Audience :: Developers',
20
+ 'Topic :: Scientific/Engineering',
21
+ ],
22
+ python_requires='>=3.10',
23
+ install_requires=[],
24
+ )