danielutils 1.1.28__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.
- danielutils/__init__.py +50 -0
- danielutils/abstractions/__init__.py +3 -0
- danielutils/abstractions/db/__init__.py +10 -0
- danielutils/abstractions/db/database.py +206 -0
- danielutils/abstractions/db/database_definitions.py +219 -0
- danielutils/abstractions/db/database_exceptions.py +34 -0
- danielutils/abstractions/db/database_factory.py +70 -0
- danielutils/abstractions/db/database_initializer.py +261 -0
- danielutils/abstractions/db/database_models.py +213 -0
- danielutils/abstractions/db/dependencies.py +6 -0
- danielutils/abstractions/db/implementations/__init__.py +4 -0
- danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
- danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
- danielutils/abstractions/db/implementations/redis_database.py +403 -0
- danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
- danielutils/abstractions/multiprogramming/__init__.py +3 -0
- danielutils/abstractions/multiprogramming/multi_id.py +26 -0
- danielutils/abstractions/multiprogramming/worker.py +104 -0
- danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
- danielutils/abstractions/repl.py +125 -0
- danielutils/aliases.py +174 -0
- danielutils/async_/__init__.py +7 -0
- danielutils/async_/async_cmd.py +28 -0
- danielutils/async_/async_command.py +879 -0
- danielutils/async_/async_layered_command.py +125 -0
- danielutils/async_/async_retry_executor.py +86 -0
- danielutils/async_/async_worker_pool.py +110 -0
- danielutils/async_/time_strategy.py +73 -0
- danielutils/async_/utils.py +57 -0
- danielutils/better_builtins/__init__.py +3 -0
- danielutils/better_builtins/counter.py +48 -0
- danielutils/better_builtins/frange.py +266 -0
- danielutils/better_builtins/typed_builtins/__init__.py +4 -0
- danielutils/better_builtins/typed_builtins/factory.py +126 -0
- danielutils/better_builtins/typed_builtins/tdict.py +36 -0
- danielutils/better_builtins/typed_builtins/tlist.py +223 -0
- danielutils/better_builtins/typed_builtins/tset.py +67 -0
- danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
- danielutils/colors.py +203 -0
- danielutils/context_managers/__init__.py +5 -0
- danielutils/context_managers/attr_context.py +40 -0
- danielutils/context_managers/multi_context.py +34 -0
- danielutils/context_managers/optional_context.py +33 -0
- danielutils/context_managers/state_context.py +25 -0
- danielutils/context_managers/temporary_file.py +108 -0
- danielutils/convenience.py +12 -0
- danielutils/conversions/__init__.py +2 -0
- danielutils/conversions/main_conversions.py +131 -0
- danielutils/conversions/specialized_conversions/__init__.py +2 -0
- danielutils/conversions/specialized_conversions/to_hex.py +25 -0
- danielutils/conversions/specialized_conversions/to_int.py +19 -0
- danielutils/custom_types.py +18 -0
- danielutils/data_structures/__init__.py +8 -0
- danielutils/data_structures/algorithms.py +52 -0
- danielutils/data_structures/comparer.py +47 -0
- danielutils/data_structures/default_dict.py +23 -0
- danielutils/data_structures/functions.py +35 -0
- danielutils/data_structures/graph/__init__.py +4 -0
- danielutils/data_structures/graph/binary_node.py +71 -0
- danielutils/data_structures/graph/graph.py +208 -0
- danielutils/data_structures/graph/multinode.py +101 -0
- danielutils/data_structures/graph/node.py +60 -0
- danielutils/data_structures/heap/__init__.py +3 -0
- danielutils/data_structures/heap/heap.py +119 -0
- danielutils/data_structures/heap/max_heap.py +18 -0
- danielutils/data_structures/heap/min_heap.py +18 -0
- danielutils/data_structures/queue/__init__.py +3 -0
- danielutils/data_structures/queue/atomic_queue.py +15 -0
- danielutils/data_structures/queue/priority_queue.py +110 -0
- danielutils/data_structures/queue/queue.py +92 -0
- danielutils/data_structures/stack.py +102 -0
- danielutils/data_structures/trees/__init__.py +2 -0
- danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
- danielutils/data_structures/trees/binary_tree.py +63 -0
- danielutils/date.py +43 -0
- danielutils/date_time.py +23 -0
- danielutils/decorators/__init__.py +18 -0
- danielutils/decorators/atomic.py +45 -0
- danielutils/decorators/attach.py +61 -0
- danielutils/decorators/chain_decorators.py +39 -0
- danielutils/decorators/decorate_conditionally.py +32 -0
- danielutils/decorators/delay_call.py +47 -0
- danielutils/decorators/deprecate.py +46 -0
- danielutils/decorators/final.py +39 -0
- danielutils/decorators/limit_recursion.py +63 -0
- danielutils/decorators/memo.py +68 -0
- danielutils/decorators/normalize_decorator.py +55 -0
- danielutils/decorators/overload.py +204 -0
- danielutils/decorators/partially_implemented.py +32 -0
- danielutils/decorators/processify.py +74 -0
- danielutils/decorators/property.py +42 -0
- danielutils/decorators/singleton.py +59 -0
- danielutils/decorators/threadify.py +40 -0
- danielutils/decorators/timeout.py +82 -0
- danielutils/decorators/total_ordering.py +82 -0
- danielutils/decorators/validate.py +227 -0
- danielutils/exceptions.py +90 -0
- danielutils/functions/__init__.py +13 -0
- danielutils/functions/areoneof.py +47 -0
- danielutils/functions/check_foreach.py +39 -0
- danielutils/functions/factorial.py +24 -0
- danielutils/functions/flatten.py +33 -0
- danielutils/functions/foreach.py +20 -0
- danielutils/functions/isoftype.py +475 -0
- danielutils/functions/isoneof.py +71 -0
- danielutils/functions/multiloop.py +51 -0
- danielutils/functions/parallel_for.py +47 -0
- danielutils/functions/partition.py +45 -0
- danielutils/functions/powerset.py +31 -0
- danielutils/functions/subseteq.py +25 -0
- danielutils/functions/types_subseteq.py +51 -0
- danielutils/generators/__init__.py +3 -0
- danielutils/generators/conditional_generator.py +59 -0
- danielutils/generators/generator_from_stream.py +31 -0
- danielutils/generators/join_generators.py +109 -0
- danielutils/internet.py +92 -0
- danielutils/io_.py +451 -0
- danielutils/java/__init__.py +18 -0
- danielutils/java/interfaces/__init__.py +1 -0
- danielutils/java/interfaces/comparable.py +31 -0
- danielutils/java/java_interface.py +59 -0
- danielutils/logging_/__init__.py +2 -0
- danielutils/logging_/_impl/__init__.py +4 -0
- danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
- danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
- danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
- danielutils/logging_/_impl/log_level.py +13 -0
- danielutils/logging_/_impl/logger.py +109 -0
- danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
- danielutils/logging_/utils.py +199 -0
- danielutils/lombok/__init__.py +1 -0
- danielutils/lombok/builder.py +68 -0
- danielutils/math_/__init__.py +2 -0
- danielutils/math_/constants.py +103 -0
- danielutils/math_/functions.py +34 -0
- danielutils/math_/math_print.py +52 -0
- danielutils/math_/math_symbols.py +125 -0
- danielutils/metaclasses/__init__.py +5 -0
- danielutils/metaclasses/atomic_class_meta.py +36 -0
- danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
- danielutils/metaclasses/instance_cache_meta.py +66 -0
- danielutils/metaclasses/interface.py +328 -0
- danielutils/metaclasses/overload_meta.py +48 -0
- danielutils/mock_/__init__.py +1 -0
- danielutils/mock_/mock_module.py +27 -0
- danielutils/path.py +55 -0
- danielutils/print_.py +123 -0
- danielutils/progress_bar/__init__.py +3 -0
- danielutils/progress_bar/ascii_progress_bar.py +161 -0
- danielutils/progress_bar/progress_bar.py +75 -0
- danielutils/progress_bar/progress_bar_pool.py +72 -0
- danielutils/protocols/__init__.py +3 -0
- danielutils/protocols/dictable.py +38 -0
- danielutils/protocols/evaluable.py +26 -0
- danielutils/protocols/serializable.py +51 -0
- danielutils/py.typed +0 -0
- danielutils/random_.py +47 -0
- danielutils/reflection/__init__.py +6 -0
- danielutils/reflection/declarations.py +288 -0
- danielutils/reflection/file/__init__.py +1 -0
- danielutils/reflection/file/file_reflection.py +85 -0
- danielutils/reflection/function/__init__.py +1 -0
- danielutils/reflection/function/function_reflections.py +152 -0
- danielutils/reflection/info_classes/__init__.py +6 -0
- danielutils/reflection/info_classes/argument_info.py +278 -0
- danielutils/reflection/info_classes/class_info.py +204 -0
- danielutils/reflection/info_classes/decorator_info.py +54 -0
- danielutils/reflection/info_classes/file_info.py +1180 -0
- danielutils/reflection/info_classes/folder_info.py +592 -0
- danielutils/reflection/info_classes/function_info.py +551 -0
- danielutils/reflection/info_classes/import_info.py +207 -0
- danielutils/reflection/interpreter/__init__.py +9 -0
- danielutils/reflection/interpreter/callstack.py +57 -0
- danielutils/reflection/interpreter/get_traceback.py +27 -0
- danielutils/reflection/interpreter/interpreter.py +41 -0
- danielutils/reflection/interpreter/is_debugging.py +13 -0
- danielutils/reflection/interpreter/os_.py +33 -0
- danielutils/reflection/interpreter/packages.py +33 -0
- danielutils/reflection/interpreter/python_version.py +33 -0
- danielutils/reflection/interpreter/signals.py +18 -0
- danielutils/reflection/interpreter/tracer.py +175 -0
- danielutils/reflection/module/__init__.py +3 -0
- danielutils/reflection/module/lazy_module.py +104 -0
- danielutils/reflection/module/module_reflections.py +34 -0
- danielutils/reflection/module/package_reflection.py +161 -0
- danielutils/retry_executor/__init__.py +3 -0
- danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
- danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
- danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
- danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
- danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
- danielutils/retry_executor/backoff_strategy.py +28 -0
- danielutils/retry_executor/retry_executor.py +55 -0
- danielutils/snippets/__init__.py +1 -0
- danielutils/snippets/try_get.py +28 -0
- danielutils/system/__init__.py +3 -0
- danielutils/system/independent.py +197 -0
- danielutils/system/layered_command.py +130 -0
- danielutils/system/windows/__init__.py +2 -0
- danielutils/system/windows/utils/__init__.py +1 -0
- danielutils/system/windows/utils/filetime.py +19 -0
- danielutils/system/windows/win32_ctime.py +183 -0
- danielutils/system/windows/windows.py +78 -0
- danielutils/testing/__init__.py +1 -0
- danielutils/testing/unittest_/__init__.py +2 -0
- danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
- danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
- danielutils/text.py +169 -0
- danielutils/time.py +64 -0
- danielutils/tools/__init__.py +1 -0
- danielutils/tools/unittest_test_runner/__init__.py +9 -0
- danielutils/tools/unittest_test_runner/cli.py +70 -0
- danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
- danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
- danielutils/tools/unittest_test_runner/core/execution.py +244 -0
- danielutils/tools/unittest_test_runner/core/parser.py +163 -0
- danielutils/tools/unittest_test_runner/core/runner.py +561 -0
- danielutils/tools/unittest_test_runner/models.py +82 -0
- danielutils/university/__init__.py +7 -0
- danielutils/university/computability_and_complexity/__init__.py +2 -0
- danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
- danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
- danielutils/university/computability_and_complexity/languages/language.py +17 -0
- danielutils/university/computability_and_complexity/languages/sat.py +71 -0
- danielutils/university/computability_and_complexity/turing_machine.py +47 -0
- danielutils/university/databases/__init__.py +1 -0
- danielutils/university/databases/all.py +756 -0
- danielutils/university/image_proccesing/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/__init__.py +3 -0
- danielutils/university/image_proccesing/encoding/encoding.py +35 -0
- danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
- danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
- danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
- danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
- danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
- danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
- danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
- danielutils/university/image_proccesing/tansformations/hough.py +0 -0
- danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
- danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
- danielutils/university/linear_algebra/__init__.py +1 -0
- danielutils/university/linear_algebra/matrix.py +404 -0
- danielutils/university/machine_learning/__init__.py +0 -0
- danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
- danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
- danielutils/university/machine_learning/activation_functions/relu.py +11 -0
- danielutils/university/machine_learning/neuron.py +15 -0
- danielutils/university/oop/__init__.py +1 -0
- danielutils/university/oop/observer.py +31 -0
- danielutils/university/oop/strategy.py +9 -0
- danielutils/university/probability/__init__.py +7 -0
- danielutils/university/probability/conditional_variable/__init__.py +3 -0
- danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
- danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
- danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
- danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
- danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
- danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
- danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
- danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
- danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
- danielutils/university/probability/distributions.py +35 -0
- danielutils/university/probability/expressions/__init__.py +2 -0
- danielutils/university/probability/expressions/accumulation_expression.py +291 -0
- danielutils/university/probability/expressions/probability_expression.py +110 -0
- danielutils/university/probability/funcs/__init__.py +4 -0
- danielutils/university/probability/funcs/covariance.py +12 -0
- danielutils/university/probability/funcs/expected_value.py +41 -0
- danielutils/university/probability/funcs/probability_function.py +14 -0
- danielutils/university/probability/funcs/variance.py +17 -0
- danielutils/university/probability/operator.py +79 -0
- danielutils/university/probability/protocols.py +32 -0
- danielutils/university/probability/supp.py +101 -0
- danielutils/university/probability/transformation.py +44 -0
- danielutils/versioned_imports.py +31 -0
- danielutils-1.1.28.dist-info/METADATA +390 -0
- danielutils-1.1.28.dist-info/RECORD +287 -0
- danielutils-1.1.28.dist-info/WHEEL +5 -0
- danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
- danielutils-1.1.28.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
ARGUMENT_INFO_REGEX: re.Pattern = re.compile(
|
|
6
|
+
r"(?P<kwargs>\*\*\w[\w\d]*)|(?P<args>\*(?:\w[\w\d]*)?)|(?P<kwarg_only>\/)|(?P<pname>\w[\w\d]*)\[(?P<parameters>.+)\]|(?P<name>\w[\w\d]*)(?:(?:\s*:(?P<type>[^\=\n]+))?(?:\s*=(?P<default_value>[\s\S]+))?)?")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ArgumentInfo:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
name: Optional[str],
|
|
13
|
+
type: Optional[str],
|
|
14
|
+
default: Optional[str],
|
|
15
|
+
is_kwargs: bool,
|
|
16
|
+
is_args: bool,
|
|
17
|
+
is_kwargs_only: bool,
|
|
18
|
+
parameters: Optional[List]
|
|
19
|
+
) -> None:
|
|
20
|
+
self._name = name
|
|
21
|
+
self._type = type
|
|
22
|
+
self._default = default
|
|
23
|
+
self._is_kwargs = is_kwargs
|
|
24
|
+
self._is_args = is_args
|
|
25
|
+
self._is_kwargs_only = is_kwargs_only
|
|
26
|
+
self._parameters = parameters
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def name(self) -> Optional[str]:
|
|
30
|
+
return self._name
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def type(self) -> Optional[str]:
|
|
34
|
+
return self._type
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def default(self) -> Optional[str]:
|
|
38
|
+
return self._default
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_kwargs(self) -> bool:
|
|
42
|
+
return self._is_kwargs
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def is_args(self) -> bool:
|
|
46
|
+
return self._is_args
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def is_kwargs_only(self) -> bool:
|
|
50
|
+
return self._is_kwargs_only
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def parameters(self) -> Optional[List]:
|
|
54
|
+
return self._parameters
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def is_parameterized(self) -> bool:
|
|
58
|
+
return self._parameters is not None and len(self._parameters) > 0
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
res = f"{self.__class__.__name__}(name=\"{self.name}\""
|
|
62
|
+
if self.type is not None:
|
|
63
|
+
res += f", type={self.type}"
|
|
64
|
+
if self.default is not None:
|
|
65
|
+
res += f", default={self.default}"
|
|
66
|
+
if self.is_parameterized:
|
|
67
|
+
res += f", parameters={self.parameters}"
|
|
68
|
+
return res + ")"
|
|
69
|
+
|
|
70
|
+
def __str__(self) -> str:
|
|
71
|
+
return repr(self)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _parse_one(string: str) -> 'ArgumentInfo':
|
|
75
|
+
string = string.strip()
|
|
76
|
+
|
|
77
|
+
# Handle string literals (decorator context)
|
|
78
|
+
if string.startswith('"') or string.startswith("'"):
|
|
79
|
+
# Extract the string literal (handling escaped quotes)
|
|
80
|
+
quote_char = string[0]
|
|
81
|
+
# Find the matching closing quote
|
|
82
|
+
i = 1
|
|
83
|
+
while i < len(string):
|
|
84
|
+
if string[i] == quote_char and (i == 0 or string[i-1] != '\\'):
|
|
85
|
+
# Found matching quote
|
|
86
|
+
literal_value = string[:i+1]
|
|
87
|
+
return ArgumentInfo(
|
|
88
|
+
name=None,
|
|
89
|
+
type=None,
|
|
90
|
+
default=literal_value,
|
|
91
|
+
is_kwargs=False,
|
|
92
|
+
is_args=False,
|
|
93
|
+
is_kwargs_only=False,
|
|
94
|
+
parameters=None
|
|
95
|
+
)
|
|
96
|
+
i += 1
|
|
97
|
+
# If no closing quote found, treat the whole string as the literal
|
|
98
|
+
return ArgumentInfo(
|
|
99
|
+
name=None,
|
|
100
|
+
type=None,
|
|
101
|
+
default=string,
|
|
102
|
+
is_kwargs=False,
|
|
103
|
+
is_args=False,
|
|
104
|
+
is_kwargs_only=False,
|
|
105
|
+
parameters=None
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Handle other literals (numbers, booleans, None, etc.) - check BEFORE regex
|
|
109
|
+
# In decorator context, these are always literals, not parameter definitions
|
|
110
|
+
if string:
|
|
111
|
+
# Check for numeric literal (starts with digit, possibly with decimal point or negative)
|
|
112
|
+
if string[0].isdigit() or (len(string) > 1 and string[0] == '-' and string[1].isdigit()):
|
|
113
|
+
return ArgumentInfo(
|
|
114
|
+
name=None,
|
|
115
|
+
type=None,
|
|
116
|
+
default=string,
|
|
117
|
+
is_kwargs=False,
|
|
118
|
+
is_args=False,
|
|
119
|
+
is_kwargs_only=False,
|
|
120
|
+
parameters=None
|
|
121
|
+
)
|
|
122
|
+
# Check for boolean or None literals
|
|
123
|
+
if string in ('True', 'False', 'None'):
|
|
124
|
+
return ArgumentInfo(
|
|
125
|
+
name=None,
|
|
126
|
+
type=None,
|
|
127
|
+
default=string,
|
|
128
|
+
is_kwargs=False,
|
|
129
|
+
is_args=False,
|
|
130
|
+
is_kwargs_only=False,
|
|
131
|
+
parameters=None
|
|
132
|
+
)
|
|
133
|
+
# Check for list/dict literals or function calls (starts with bracket, brace, or identifier followed by paren)
|
|
134
|
+
if string.startswith('[') or string.startswith('{') or string.startswith('('):
|
|
135
|
+
return ArgumentInfo(
|
|
136
|
+
name=None,
|
|
137
|
+
type=None,
|
|
138
|
+
default=string,
|
|
139
|
+
is_kwargs=False,
|
|
140
|
+
is_args=False,
|
|
141
|
+
is_kwargs_only=False,
|
|
142
|
+
parameters=None
|
|
143
|
+
)
|
|
144
|
+
# Check for function call pattern (identifier followed by opening paren)
|
|
145
|
+
# This handles cases like "some_function(1, 2)"
|
|
146
|
+
if '(' in string and not string.startswith('='):
|
|
147
|
+
# Check if it looks like a function call (has identifier before paren)
|
|
148
|
+
parts = string.split('(', 1)
|
|
149
|
+
if len(parts) == 2 and parts[0].strip() and (parts[0].strip()[0].isalpha() or parts[0].strip()[0] == '_'):
|
|
150
|
+
return ArgumentInfo(
|
|
151
|
+
name=None,
|
|
152
|
+
type=None,
|
|
153
|
+
default=string,
|
|
154
|
+
is_kwargs=False,
|
|
155
|
+
is_args=False,
|
|
156
|
+
is_kwargs_only=False,
|
|
157
|
+
parameters=None
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Try regex matching for normal argument patterns
|
|
161
|
+
m = ARGUMENT_INFO_REGEX.match(string)
|
|
162
|
+
if m is None:
|
|
163
|
+
raise ValueError(f"Invalid argument info string: {string}")
|
|
164
|
+
|
|
165
|
+
kwargs, args, kwarg_only, pname, parameters, name, type, default_value = m.groups()
|
|
166
|
+
type = None if type is None else type.strip()
|
|
167
|
+
default_value = None if default_value is None else default_value.strip()
|
|
168
|
+
|
|
169
|
+
return ArgumentInfo(
|
|
170
|
+
name=name or pname or (args.strip("*") if args else None) or (
|
|
171
|
+
kwargs.strip("*") if kwargs else None) or (kwarg_only if kwarg_only else None) or None,
|
|
172
|
+
type=type,
|
|
173
|
+
default=default_value,
|
|
174
|
+
is_kwargs=kwargs is not None,
|
|
175
|
+
is_args=args is not None,
|
|
176
|
+
is_kwargs_only=kwarg_only is not None,
|
|
177
|
+
parameters=[parameters]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def _strip_comments(string: str) -> str:
|
|
182
|
+
"""Strip comments from string while preserving string literals."""
|
|
183
|
+
result = []
|
|
184
|
+
in_string = False
|
|
185
|
+
string_char = None
|
|
186
|
+
i = 0
|
|
187
|
+
while i < len(string):
|
|
188
|
+
c = string[i]
|
|
189
|
+
# Track string literals
|
|
190
|
+
if c in {'"', "'"} and (i == 0 or string[i-1] != '\\'):
|
|
191
|
+
if not in_string:
|
|
192
|
+
in_string = True
|
|
193
|
+
string_char = c
|
|
194
|
+
elif c == string_char:
|
|
195
|
+
in_string = False
|
|
196
|
+
string_char = None
|
|
197
|
+
result.append(c)
|
|
198
|
+
# Handle comments (only when not in string)
|
|
199
|
+
elif not in_string and c == '#':
|
|
200
|
+
# Skip everything from # to end of line (or end of string)
|
|
201
|
+
# Keep the newline if present to preserve line structure
|
|
202
|
+
while i < len(string) and string[i] != '\n':
|
|
203
|
+
i += 1
|
|
204
|
+
# Include the newline if we found one
|
|
205
|
+
if i < len(string) and string[i] == '\n':
|
|
206
|
+
result.append('\n')
|
|
207
|
+
i += 1
|
|
208
|
+
continue
|
|
209
|
+
else:
|
|
210
|
+
# End of string, break
|
|
211
|
+
break
|
|
212
|
+
else:
|
|
213
|
+
result.append(c)
|
|
214
|
+
i += 1
|
|
215
|
+
return ''.join(result)
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def from_str(string: str) -> List['ArgumentInfo']:
|
|
219
|
+
if string is None:
|
|
220
|
+
return []
|
|
221
|
+
string = string.strip()
|
|
222
|
+
if not string:
|
|
223
|
+
return []
|
|
224
|
+
# Strip comments before processing
|
|
225
|
+
string = ArgumentInfo._strip_comments(string).strip()
|
|
226
|
+
indices = [-1]
|
|
227
|
+
stack: List[str] = []
|
|
228
|
+
in_string = False
|
|
229
|
+
string_char = None
|
|
230
|
+
for i, c in enumerate(string):
|
|
231
|
+
# Track string literals
|
|
232
|
+
if c in {'"', "'"} and (i == 0 or string[i-1] != '\\'):
|
|
233
|
+
if not in_string:
|
|
234
|
+
in_string = True
|
|
235
|
+
string_char = c
|
|
236
|
+
elif c == string_char:
|
|
237
|
+
in_string = False
|
|
238
|
+
string_char = None
|
|
239
|
+
# Track brackets, braces, and parentheses only when not in string
|
|
240
|
+
elif not in_string:
|
|
241
|
+
if c in {'[', ']', '{', '}', '(', ')'}:
|
|
242
|
+
if c in {'[', '{', '('}:
|
|
243
|
+
stack.append(c)
|
|
244
|
+
else:
|
|
245
|
+
# Match closing brackets/braces/parens with opening ones
|
|
246
|
+
if stack:
|
|
247
|
+
opening = stack[-1]
|
|
248
|
+
if (c == ']' and opening == '[') or \
|
|
249
|
+
(c == '}' and opening == '{') or \
|
|
250
|
+
(c == ')' and opening == '('):
|
|
251
|
+
stack.pop()
|
|
252
|
+
elif len(stack) == 0:
|
|
253
|
+
if c == ",":
|
|
254
|
+
indices.append(i)
|
|
255
|
+
indices.append(len(string))
|
|
256
|
+
res = []
|
|
257
|
+
for idx, (start, end) in enumerate(zip(indices[:-1], indices[1:])):
|
|
258
|
+
substr = string[start + 1:end].strip()
|
|
259
|
+
# Skip standalone * (keyword-only separator)
|
|
260
|
+
if substr == "*":
|
|
261
|
+
continue
|
|
262
|
+
# Skip empty strings (from comment-only lines)
|
|
263
|
+
if not substr:
|
|
264
|
+
continue
|
|
265
|
+
try:
|
|
266
|
+
res.append(ArgumentInfo._parse_one(substr))
|
|
267
|
+
except ValueError as e:
|
|
268
|
+
raise ValueError(
|
|
269
|
+
f"Failed to parse argument {idx + 1}: {e}\n"
|
|
270
|
+
f"Argument string: {repr(substr)}\n"
|
|
271
|
+
f"Full arguments string: {repr(string)}"
|
|
272
|
+
) from e
|
|
273
|
+
return res
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
__all__ = [
|
|
277
|
+
"ArgumentInfo",
|
|
278
|
+
]
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import inspect
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Iterable, Type, TypeVar, Generic, get_origin
|
|
6
|
+
from .function_info import FunctionInfo
|
|
7
|
+
from .decorator_info import DecoratorInfo
|
|
8
|
+
from .argument_info import ArgumentInfo
|
|
9
|
+
|
|
10
|
+
from ...logging_.utils import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _A(Generic[T]):
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_GenericAlias = type(_A[int])
|
|
22
|
+
|
|
23
|
+
del T, _A
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ClassInfo:
|
|
27
|
+
CLASS_DEFINITION_REGEX: re.Pattern = re.compile(
|
|
28
|
+
r"(?P<decorations>[\s\S]*)?(^)?class (?P<name>\w[\w\d]*)(?:\((?P<bases>.*)\))?:(?P<body>[\s\S]+)", re.MULTILINE)
|
|
29
|
+
|
|
30
|
+
# r"(?P<decorations>[\s\S]*)?^class (?P<name>\w[\w\d]*)(?:\((?P<bases>.*)\))?:(?P<body>[\s\S]+)"
|
|
31
|
+
|
|
32
|
+
def __init__(self, cls: Type) -> None:
|
|
33
|
+
logger.debug("Creating ClassInfo for: %s", cls)
|
|
34
|
+
if isinstance(cls, _GenericAlias):
|
|
35
|
+
logger.debug("Converting generic alias to origin type")
|
|
36
|
+
cls = get_origin(cls) # type:ignore
|
|
37
|
+
if not inspect.isclass(cls):
|
|
38
|
+
error_msg = f"'{getattr(cls, '__name__', type(cls).__name__)}' is not a class"
|
|
39
|
+
logger.error("ClassInfo creation failed: %s", error_msg)
|
|
40
|
+
raise TypeError(error_msg)
|
|
41
|
+
self._cls = cls
|
|
42
|
+
self._src_code: str = ""
|
|
43
|
+
self._name: str = ""
|
|
44
|
+
self._bases: List[ArgumentInfo] = []
|
|
45
|
+
self._functions: List[FunctionInfo] = []
|
|
46
|
+
self._decorations: List[DecoratorInfo] = []
|
|
47
|
+
logger.debug("Parsing source code for class: %s", cls.__name__)
|
|
48
|
+
self._parse_src_code()
|
|
49
|
+
logger.info("ClassInfo created successfully for: %s", cls.__name__)
|
|
50
|
+
|
|
51
|
+
def _parse_src_code(self) -> None:
|
|
52
|
+
logger.debug("Parsing source code")
|
|
53
|
+
try:
|
|
54
|
+
self._src_code = inspect.getsource(self._cls)
|
|
55
|
+
except (OSError, TypeError) as e:
|
|
56
|
+
logger.warning(
|
|
57
|
+
"Source code not available for class: %s - %s", self._cls.__name__, e)
|
|
58
|
+
self._src_code = ""
|
|
59
|
+
# Fall back to runtime introspection
|
|
60
|
+
self._name = self._cls.__name__
|
|
61
|
+
self._bases = []
|
|
62
|
+
self._parse_body()
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
class_name = self._cls.__name__
|
|
66
|
+
class_pattern = re.compile(
|
|
67
|
+
rf"(?P<decorations>[\s\S]*?)(?:^|\n)\s*class {re.escape(class_name)}\b"
|
|
68
|
+
rf"(?:\((?P<bases>.*)\))?:(?P<body>[\s\S]+)",
|
|
69
|
+
re.MULTILINE,
|
|
70
|
+
)
|
|
71
|
+
m = class_pattern.search(self._src_code)
|
|
72
|
+
if m is None:
|
|
73
|
+
logger.warning(
|
|
74
|
+
"Failed to match class definition regex, falling back to runtime introspection")
|
|
75
|
+
# Fall back to runtime introspection
|
|
76
|
+
self._name = class_name
|
|
77
|
+
self._bases = []
|
|
78
|
+
self._parse_body()
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
decorators = m.group("decorations")
|
|
82
|
+
bases = m.group("bases")
|
|
83
|
+
name = class_name
|
|
84
|
+
logger.debug("Parsed class name: %s, bases: %s", name, bases)
|
|
85
|
+
self._name = name
|
|
86
|
+
try:
|
|
87
|
+
self._bases = ArgumentInfo.from_str(bases)
|
|
88
|
+
except ValueError as e:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Failed to parse base classes for class '{name}': {e}\n"
|
|
91
|
+
f"Bases string: {repr(bases)}"
|
|
92
|
+
) from e
|
|
93
|
+
logger.debug("Parsed %s base classes", len(self._bases))
|
|
94
|
+
self._parse_body()
|
|
95
|
+
|
|
96
|
+
if decorators is not None:
|
|
97
|
+
logger.debug("Parsing %s decorators", len(
|
|
98
|
+
decorators.strip().splitlines()))
|
|
99
|
+
for substr in decorators.strip().splitlines():
|
|
100
|
+
try:
|
|
101
|
+
self._decorations.append(
|
|
102
|
+
DecoratorInfo.from_str(substr.strip()))
|
|
103
|
+
except ValueError as e:
|
|
104
|
+
error_msg = (
|
|
105
|
+
f"Failed to parse decorator for class '{name}': {e}\n"
|
|
106
|
+
f"Decorator string: {repr(substr.strip())}"
|
|
107
|
+
)
|
|
108
|
+
logger.warning(error_msg)
|
|
109
|
+
# Skip invalid decorators
|
|
110
|
+
continue
|
|
111
|
+
logger.debug("Parsed %s decorators", len(self._decorations))
|
|
112
|
+
|
|
113
|
+
def _parse_body(self) -> None:
|
|
114
|
+
for attr in dir(self._cls):
|
|
115
|
+
obj = getattr(self._cls, attr, None)
|
|
116
|
+
if inspect.isbuiltin(obj):
|
|
117
|
+
continue
|
|
118
|
+
try:
|
|
119
|
+
if inspect.isroutine(obj):
|
|
120
|
+
inspect.getsource(obj)
|
|
121
|
+
elif inspect.isdatadescriptor(obj):
|
|
122
|
+
inspect.getsource(obj.fget) # type:ignore
|
|
123
|
+
else:
|
|
124
|
+
continue
|
|
125
|
+
except:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
self._functions.append(FunctionInfo(
|
|
130
|
+
obj, self._cls)) # type: ignore
|
|
131
|
+
except TypeError as e:
|
|
132
|
+
# Skip functions that can't be parsed (e.g., lambda, built-ins)
|
|
133
|
+
logger.debug("Skipping function '%s': %s", attr, e)
|
|
134
|
+
continue
|
|
135
|
+
except Exception as e:
|
|
136
|
+
raise Exception(
|
|
137
|
+
f"Error parsing function '{attr}' of class '{self._name}': {e}", e) from e
|
|
138
|
+
|
|
139
|
+
def __str__(self) -> str:
|
|
140
|
+
body = json.dumps({
|
|
141
|
+
"name": self.name,
|
|
142
|
+
"bases": self.bases,
|
|
143
|
+
"decorations": self.decorations,
|
|
144
|
+
"static_methods": self.static_methods,
|
|
145
|
+
"class_methods": self.class_methods,
|
|
146
|
+
"instance_methods": self.instance_methods
|
|
147
|
+
}, default=str, indent=4)[1:-1]
|
|
148
|
+
return f"{self.__class__.__name__}({body})"
|
|
149
|
+
|
|
150
|
+
def __repr__(self):
|
|
151
|
+
return f"{self.__class__.__name__}(name=\"{self.name}\")"
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def name(self) -> str:
|
|
155
|
+
return self._name
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def decorations(self) -> List[DecoratorInfo]:
|
|
159
|
+
return self._decorations
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def bases(self) -> List[ArgumentInfo]:
|
|
163
|
+
return self._bases
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def static_methods(self) -> Iterable[FunctionInfo]:
|
|
167
|
+
return sorted(filter(lambda f: f.is_static_method, self._functions), key=lambda f: f.name)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def class_methods(self) -> Iterable[FunctionInfo]:
|
|
171
|
+
return sorted(filter(lambda f: f.is_class_method, self._functions), key=lambda f: f.name)
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def instance_methods(self) -> Iterable[FunctionInfo]:
|
|
175
|
+
return sorted(filter(lambda f: f.is_instance_method, self._functions), key=lambda f: f.name)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def inherited_methods(self) -> Iterable[FunctionInfo]:
|
|
179
|
+
return sorted(filter(lambda f: f.is_inherited, self._functions), key=lambda f: f.name)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def abstract_methods(self) -> Iterable[FunctionInfo]:
|
|
183
|
+
return sorted(filter(lambda f: f.is_abstract, self._functions), key=lambda f: f.name)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def functions(self) -> List[FunctionInfo]:
|
|
187
|
+
return self._functions
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def properties(self):
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def instance_properties(self):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def class_properties(self):
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
__all__ = [
|
|
203
|
+
"ClassInfo"
|
|
204
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass, Field
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
from .argument_info import ArgumentInfo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DecoratorInfo:
|
|
9
|
+
DECORATOR_INFO_REGEX: re.Pattern = re.compile(
|
|
10
|
+
r"^@?(?P<name>\w[\w\d]*)(?:\((?P<arguments>.*)\))?$")
|
|
11
|
+
|
|
12
|
+
def __init__(self, name: str, arguments: List[ArgumentInfo]):
|
|
13
|
+
self._name = name
|
|
14
|
+
self._arguments = arguments
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def name(self) -> str:
|
|
18
|
+
return self._name
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def arguments(self) -> List[ArgumentInfo]:
|
|
22
|
+
return self._arguments
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def from_str(string: str) -> 'DecoratorInfo':
|
|
26
|
+
m = DecoratorInfo.DECORATOR_INFO_REGEX.match(string)
|
|
27
|
+
if m is None:
|
|
28
|
+
raise ValueError(f"Invalid decorator format: {repr(string)}")
|
|
29
|
+
|
|
30
|
+
name, arguments = m.groups()
|
|
31
|
+
if arguments:
|
|
32
|
+
try:
|
|
33
|
+
args = ArgumentInfo.from_str(arguments)
|
|
34
|
+
except ValueError as e:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Failed to parse arguments for decorator '{name}': {e}\n"
|
|
37
|
+
f"Arguments string: {repr(arguments)}"
|
|
38
|
+
) from e
|
|
39
|
+
else:
|
|
40
|
+
args = []
|
|
41
|
+
return DecoratorInfo(name, args)
|
|
42
|
+
|
|
43
|
+
def __str__(self) -> str:
|
|
44
|
+
return repr(self)
|
|
45
|
+
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
if self.arguments:
|
|
48
|
+
return f"{self.__class__.__name__}(name=\"{self.name}\", arguments={self.arguments})"
|
|
49
|
+
return f"{self.__class__.__name__}(name=\"{self.name}\")"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
'DecoratorInfo',
|
|
54
|
+
]
|