-
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathpython_data.py
More file actions
364 lines (262 loc) · 12.5 KB
/
Copy pathpython_data.py
File metadata and controls
364 lines (262 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
import argparse
import re
import sys
from typing import Any, Iterator, List, Union
# @decorators.map_firstp_arg
def python_functions_signatures(
code_text: str,
*,
ignore_private_functions: bool = False,
ignore_nested_functions: bool = False,
keep_function_name: bool = False,
) -> List[str]:
"""Return the function signatures for all of the functions in the given code_text."""
from d8s_strings import string_remove_from_start
from .ast_data import python_function_names
signatures = []
function_names = python_function_names(
code_text, ignore_private_functions=ignore_private_functions, ignore_nested_functions=ignore_nested_functions
)
for name in function_names:
regex_for_signature = fr'(def {name}\((?:.|\s)*?\).*?):'
sig = re.findall(regex_for_signature, code_text)
if any(sig):
new_sig = string_remove_from_start(sig[0], 'def ')
if not keep_function_name:
new_sig = string_remove_from_start(new_sig, name)
signatures.append(new_sig)
else:
message = f'Unable to find signature for the {name} function'
print(message)
signatures.append(None)
return signatures
def python_todos(code_text: str, todo_regex: str = 'TODO:.*') -> List[str]:
"""Return all todos in the given code_text that match the given todo_regex."""
todos = re.findall(todo_regex, code_text)
return todos
# @decorators.map_first_arg
def python_make_pythonic(name: str) -> str:
"""Make the name pythonic.
(e.g. 'fooBar' => 'foo_bar', 'foo-bar' => 'foo_bar', 'foo bar' => 'foo_bar', 'Foo Bar' => 'foo_bar').
"""
from d8s_strings import lowercase, snake_case, string_split_on_uppercase
split_string = '_'.join(
[
string.strip()
for string in string_split_on_uppercase(name, include_uppercase_characters=True, split_acronyms=False)
]
)
split_string = lowercase(split_string)
result = snake_case(split_string)
return result
# @decorators.map_first_arg
def python_namespace_has_argument(namespace: argparse.Namespace, argument_name: str) -> bool:
"""."""
result = argument_name in namespace
return result
# @decorators.map_first_arg
def python_traceback_prettify(traceback: str) -> str:
"""Return a string with the given traceback pretty-printed."""
pretty_traceback = re.sub(' File ', '\nFile ', traceback)
return pretty_traceback
# @decorators.map_first_arg
def python_traceback_pretty_print(traceback: str) -> None:
"""Return a string with the given traceback pretty-printed."""
prettified_string = python_traceback_prettify(traceback)
print(prettified_string)
# @decorators.map_first_arg
def python_clean(code_text: str) -> str:
"""Clean python code as it is often found in documentation and snippets."""
code_text = code_text.replace('>>> ', '')
code_text = code_text.replace('... ', '')
return code_text
def python_function_blocks( # noqa: CCR001
code_text: str, *, ignore_private_functions: bool = False, ignore_nested_functions: bool = False
) -> List[str]:
"""Find the code (as a string) for every function in the given code_text."""
from d8s_lists import has_index
from d8s_strings import string_chars_at_start_len
from .ast_data import python_ast_function_defs, python_ast_object_line_numbers
function_block_strings = []
code_text_as_lines = code_text.splitlines()
ast_function_defs = python_ast_function_defs(code_text, recursive_search=not ignore_nested_functions)
function_block_line_numbers = [(f.name, python_ast_object_line_numbers(f)) for f in ast_function_defs]
for function_name, (start, end) in function_block_line_numbers:
function_block_lines = code_text_as_lines[start - 1 : end] # noqa=E203
function_block_string = '\n'.join(function_block_lines)
if ignore_private_functions:
if function_name.startswith('_'):
continue
# the code below checks to see if the line after what was determined to be the last line of the function...
# should also be included in the function block (which is the case when the closing parenthesis of a...
# function call in another function is on a newline (see the...
# python_data_tests.py::test_python_function_blocks_edge_cases_1 for an example))
if has_index(code_text_as_lines, end):
# find the indentation level of the function definition (the first line of the function)
function_indentation = string_chars_at_start_len(function_block_lines[0], ' ')
# TODO: the check below assumes that spaces are used instead of tabs
next_line_is_indented = (
code_text_as_lines[end].startswith(' ')
and string_chars_at_start_len(code_text_as_lines[end], ' ') > function_indentation
)
next_line_has_only_parenthesis = code_text_as_lines[end].strip(' ') == ')'
if next_line_is_indented and next_line_has_only_parenthesis:
function_block_string += f'\n{code_text_as_lines[end]}'
function_block_strings.append(function_block_string)
return function_block_strings
def python_line_count(python_code: str, *, ignore_empty_lines: bool = True) -> int:
"""Return the number of lines in the given function_text."""
from d8s_lists import truthy_items
lines = python_code.splitlines()
if ignore_empty_lines:
return len(tuple(truthy_items(lines)))
else:
return len(lines)
def python_function_lengths(code_text: str) -> List[int]:
"""Find the lengths of each function in the given code_text."""
function_blocks = python_function_blocks(code_text)
return [python_line_count(function_block) for function_block in function_blocks]
def python_version() -> str:
"""Return the python version of the current environment."""
return '{}.{}.{}'.format(sys.version_info.major, sys.version_info.minor, sys.version_info.micro)
def python_is_version_2() -> bool:
"""Return whether or not the python version of the current environment is v2.x."""
return python_version().startswith('2.')
def python_is_version_3() -> bool:
"""Return whether or not the python version of the current environment is v3.x."""
return not python_is_version_2()
# TODO: need to standardize the order of arguments between functions like this an the directorySearch function
# @decorators.map_first_arg
def python_files_using_function(function_name: str, search_path: str) -> List[str]:
"""Find where the given function is used in the given search path."""
from d8s_file_system import directory_read_files_with_path_matching
files_using_function = []
function_pattern = f'{function_name}('
python_files = directory_read_files_with_path_matching(search_path, '*.py')
for file_path, file_contents in python_files:
if function_pattern in file_contents:
files_using_function.append(file_path)
return files_using_function
def python_keywords() -> List[str]:
"""Get a list of the python keywords."""
import keyword
return keyword.kwlist
# if code_text is None:
# return python_keywords
# else:
# from d8s_strings import string_words
# words_in_code_text = string_words(code_text)
# keywords_used = []
# for palabra in words_in_code_text:
# if palabra in python_keywords:
# keywords_used.append(palabra)
# return keywords_used
# @decorators.map_first_arg
def python_object_properties_enumerate( # noqa: CCR001
python_object: Any, *, run_methods: bool = True, internal_properties: bool = True
) -> None:
"""Enumerate and print out the properties of the given object."""
for i in python_object.__dir__():
if not internal_properties:
if i.startswith('_'):
continue
string_to_eval_as_property = 'python_object.{}'.format(i)
try:
if run_methods:
eval_result = eval(string_to_eval_as_property) # pylint: disable=W0123
if callable(eval_result):
string_to_eval_as_function = 'python_object.{}()'.format(i)
try:
print(f'{i}: {eval(string_to_eval_as_function)}') # pylint: disable=W0123
except TypeError:
print(f'{i}: {eval(string_to_eval_as_property)}') # pylint: disable=W0123
else:
print(f'{i}: {eval_result}')
else:
print(f'{i}: {eval(string_to_eval_as_property)}') # pylint: disable=W0123
except AttributeError:
print(f'! Unable to get the {i} attribute for the item.')
def python_copy_deep(python_object: Any) -> Any:
"""Return a deep (complete, recursive) copy of the given python object."""
import copy
return copy.deepcopy(python_object)
def python_copy_shallow(python_object: Any) -> Any:
"""Return shallow copy of the given python object."""
import copy
return copy.copy(python_object)
# @decorators.map_first_arg
def python_file_names(path: str, *, exclude_tests: bool = False) -> List[str]: # noqa: CCR001
"""Find all python files in the given directory."""
# from d8s_file_system import directory_file_names_matching
#
# files = directory_file_names_matching(path, '*.py')
from pathlib import Path
files = Path(path).glob('*.py')
if not exclude_tests:
return files
else:
non_test_files = []
for file in files:
if '_test' not in file and 'test_' not in file:
non_test_files.append(file)
return non_test_files
# @decorators.map_first_arg
def python_fstrings(code_text: str, *, include_braces: bool = False) -> Iterator[str]:
"""Find all of the python formatted string literals in the given text.
See https://realpython.com/python-f-strings/ for more details about f-strings.
"""
from d8s_grammars import python_formatted_string_literal
from d8s_lists import flatten
python_f_strings = flatten(python_formatted_string_literal.searchString(code_text).asList())
if not include_braces:
python_f_strings = (f_string.strip('{').strip('}') for f_string in python_f_strings)
return python_f_strings
# @decorators.map_first_arg
def python_code_details(code_text: str):
"""Get details about the given code_text. This is a wrapper for `dis.code_info`"""
import dis
return dis.code_info(code_text)
# @decorators.map_first_arg
def python_disassemble(code_text: str):
"""Disassemble the python code_text. This is a wrapper for `dis.dis`"""
import dis
return dis.Bytecode(code_text).dis()
def python_stack_local_data():
"""Get local data in the current python environment."""
import inspect
return inspect.currentframe().f_locals
# @decorators.map_first_arg
def python_object_doc_string(python_object: Any) -> Union[str, None]:
"""Get the doc string for the given python object (e.g. module, function, or class)."""
import inspect
return inspect.getdoc(python_object)
# @decorators.map_first_arg
def python_object_source_file(python_object: Any) -> str:
"""Get the source file for the given python object (e.g. module, function, or class)."""
import inspect
return inspect.getsourcefile(python_object)
# @decorators.map_first_arg
def python_object_module(python_object: Any) -> str:
"""Get the module for the given python object (e.g. function or class)."""
return python_object.__module__
# @decorators.map_first_arg
def python_object_source_code(python_object: Any) -> str:
"""Get the source code for the given python object (e.g. module, function, or class)."""
import inspect
return inspect.getsource(python_object)
# @decorators.map_first_arg
def python_object_signature(python_object: Any) -> str:
"""Get the argument signature for the given python object (e.g. module, function, or class)."""
import inspect
return inspect.signature(python_object)
# TODO: improve the type annotations to be lists of types
def python_sort_type_list_by_name(python_type_list: List[type], **kwargs) -> List[type]:
"""."""
return sorted(python_type_list, key=lambda x: python_type_name(x), **kwargs) # pylint: disable=W0108
# @decorators.map_first_arg
def python_type_name(python_type: type) -> str:
"""Return the common name of the given type."""
return python_type.__name__
def python_object_type_to_word(python_object: Any) -> str:
"""Convert the given python type to a string."""
return python_type_name(type(python_object))