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.
Files changed (287) hide show
  1. danielutils/__init__.py +50 -0
  2. danielutils/abstractions/__init__.py +3 -0
  3. danielutils/abstractions/db/__init__.py +10 -0
  4. danielutils/abstractions/db/database.py +206 -0
  5. danielutils/abstractions/db/database_definitions.py +219 -0
  6. danielutils/abstractions/db/database_exceptions.py +34 -0
  7. danielutils/abstractions/db/database_factory.py +70 -0
  8. danielutils/abstractions/db/database_initializer.py +261 -0
  9. danielutils/abstractions/db/database_models.py +213 -0
  10. danielutils/abstractions/db/dependencies.py +6 -0
  11. danielutils/abstractions/db/implementations/__init__.py +4 -0
  12. danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
  13. danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
  14. danielutils/abstractions/db/implementations/redis_database.py +403 -0
  15. danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
  16. danielutils/abstractions/multiprogramming/__init__.py +3 -0
  17. danielutils/abstractions/multiprogramming/multi_id.py +26 -0
  18. danielutils/abstractions/multiprogramming/worker.py +104 -0
  19. danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
  20. danielutils/abstractions/repl.py +125 -0
  21. danielutils/aliases.py +174 -0
  22. danielutils/async_/__init__.py +7 -0
  23. danielutils/async_/async_cmd.py +28 -0
  24. danielutils/async_/async_command.py +879 -0
  25. danielutils/async_/async_layered_command.py +125 -0
  26. danielutils/async_/async_retry_executor.py +86 -0
  27. danielutils/async_/async_worker_pool.py +110 -0
  28. danielutils/async_/time_strategy.py +73 -0
  29. danielutils/async_/utils.py +57 -0
  30. danielutils/better_builtins/__init__.py +3 -0
  31. danielutils/better_builtins/counter.py +48 -0
  32. danielutils/better_builtins/frange.py +266 -0
  33. danielutils/better_builtins/typed_builtins/__init__.py +4 -0
  34. danielutils/better_builtins/typed_builtins/factory.py +126 -0
  35. danielutils/better_builtins/typed_builtins/tdict.py +36 -0
  36. danielutils/better_builtins/typed_builtins/tlist.py +223 -0
  37. danielutils/better_builtins/typed_builtins/tset.py +67 -0
  38. danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
  39. danielutils/colors.py +203 -0
  40. danielutils/context_managers/__init__.py +5 -0
  41. danielutils/context_managers/attr_context.py +40 -0
  42. danielutils/context_managers/multi_context.py +34 -0
  43. danielutils/context_managers/optional_context.py +33 -0
  44. danielutils/context_managers/state_context.py +25 -0
  45. danielutils/context_managers/temporary_file.py +108 -0
  46. danielutils/convenience.py +12 -0
  47. danielutils/conversions/__init__.py +2 -0
  48. danielutils/conversions/main_conversions.py +131 -0
  49. danielutils/conversions/specialized_conversions/__init__.py +2 -0
  50. danielutils/conversions/specialized_conversions/to_hex.py +25 -0
  51. danielutils/conversions/specialized_conversions/to_int.py +19 -0
  52. danielutils/custom_types.py +18 -0
  53. danielutils/data_structures/__init__.py +8 -0
  54. danielutils/data_structures/algorithms.py +52 -0
  55. danielutils/data_structures/comparer.py +47 -0
  56. danielutils/data_structures/default_dict.py +23 -0
  57. danielutils/data_structures/functions.py +35 -0
  58. danielutils/data_structures/graph/__init__.py +4 -0
  59. danielutils/data_structures/graph/binary_node.py +71 -0
  60. danielutils/data_structures/graph/graph.py +208 -0
  61. danielutils/data_structures/graph/multinode.py +101 -0
  62. danielutils/data_structures/graph/node.py +60 -0
  63. danielutils/data_structures/heap/__init__.py +3 -0
  64. danielutils/data_structures/heap/heap.py +119 -0
  65. danielutils/data_structures/heap/max_heap.py +18 -0
  66. danielutils/data_structures/heap/min_heap.py +18 -0
  67. danielutils/data_structures/queue/__init__.py +3 -0
  68. danielutils/data_structures/queue/atomic_queue.py +15 -0
  69. danielutils/data_structures/queue/priority_queue.py +110 -0
  70. danielutils/data_structures/queue/queue.py +92 -0
  71. danielutils/data_structures/stack.py +102 -0
  72. danielutils/data_structures/trees/__init__.py +2 -0
  73. danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
  74. danielutils/data_structures/trees/binary_tree.py +63 -0
  75. danielutils/date.py +43 -0
  76. danielutils/date_time.py +23 -0
  77. danielutils/decorators/__init__.py +18 -0
  78. danielutils/decorators/atomic.py +45 -0
  79. danielutils/decorators/attach.py +61 -0
  80. danielutils/decorators/chain_decorators.py +39 -0
  81. danielutils/decorators/decorate_conditionally.py +32 -0
  82. danielutils/decorators/delay_call.py +47 -0
  83. danielutils/decorators/deprecate.py +46 -0
  84. danielutils/decorators/final.py +39 -0
  85. danielutils/decorators/limit_recursion.py +63 -0
  86. danielutils/decorators/memo.py +68 -0
  87. danielutils/decorators/normalize_decorator.py +55 -0
  88. danielutils/decorators/overload.py +204 -0
  89. danielutils/decorators/partially_implemented.py +32 -0
  90. danielutils/decorators/processify.py +74 -0
  91. danielutils/decorators/property.py +42 -0
  92. danielutils/decorators/singleton.py +59 -0
  93. danielutils/decorators/threadify.py +40 -0
  94. danielutils/decorators/timeout.py +82 -0
  95. danielutils/decorators/total_ordering.py +82 -0
  96. danielutils/decorators/validate.py +227 -0
  97. danielutils/exceptions.py +90 -0
  98. danielutils/functions/__init__.py +13 -0
  99. danielutils/functions/areoneof.py +47 -0
  100. danielutils/functions/check_foreach.py +39 -0
  101. danielutils/functions/factorial.py +24 -0
  102. danielutils/functions/flatten.py +33 -0
  103. danielutils/functions/foreach.py +20 -0
  104. danielutils/functions/isoftype.py +475 -0
  105. danielutils/functions/isoneof.py +71 -0
  106. danielutils/functions/multiloop.py +51 -0
  107. danielutils/functions/parallel_for.py +47 -0
  108. danielutils/functions/partition.py +45 -0
  109. danielutils/functions/powerset.py +31 -0
  110. danielutils/functions/subseteq.py +25 -0
  111. danielutils/functions/types_subseteq.py +51 -0
  112. danielutils/generators/__init__.py +3 -0
  113. danielutils/generators/conditional_generator.py +59 -0
  114. danielutils/generators/generator_from_stream.py +31 -0
  115. danielutils/generators/join_generators.py +109 -0
  116. danielutils/internet.py +92 -0
  117. danielutils/io_.py +451 -0
  118. danielutils/java/__init__.py +18 -0
  119. danielutils/java/interfaces/__init__.py +1 -0
  120. danielutils/java/interfaces/comparable.py +31 -0
  121. danielutils/java/java_interface.py +59 -0
  122. danielutils/logging_/__init__.py +2 -0
  123. danielutils/logging_/_impl/__init__.py +4 -0
  124. danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
  125. danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
  126. danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
  127. danielutils/logging_/_impl/log_level.py +13 -0
  128. danielutils/logging_/_impl/logger.py +109 -0
  129. danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
  130. danielutils/logging_/utils.py +199 -0
  131. danielutils/lombok/__init__.py +1 -0
  132. danielutils/lombok/builder.py +68 -0
  133. danielutils/math_/__init__.py +2 -0
  134. danielutils/math_/constants.py +103 -0
  135. danielutils/math_/functions.py +34 -0
  136. danielutils/math_/math_print.py +52 -0
  137. danielutils/math_/math_symbols.py +125 -0
  138. danielutils/metaclasses/__init__.py +5 -0
  139. danielutils/metaclasses/atomic_class_meta.py +36 -0
  140. danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
  141. danielutils/metaclasses/instance_cache_meta.py +66 -0
  142. danielutils/metaclasses/interface.py +328 -0
  143. danielutils/metaclasses/overload_meta.py +48 -0
  144. danielutils/mock_/__init__.py +1 -0
  145. danielutils/mock_/mock_module.py +27 -0
  146. danielutils/path.py +55 -0
  147. danielutils/print_.py +123 -0
  148. danielutils/progress_bar/__init__.py +3 -0
  149. danielutils/progress_bar/ascii_progress_bar.py +161 -0
  150. danielutils/progress_bar/progress_bar.py +75 -0
  151. danielutils/progress_bar/progress_bar_pool.py +72 -0
  152. danielutils/protocols/__init__.py +3 -0
  153. danielutils/protocols/dictable.py +38 -0
  154. danielutils/protocols/evaluable.py +26 -0
  155. danielutils/protocols/serializable.py +51 -0
  156. danielutils/py.typed +0 -0
  157. danielutils/random_.py +47 -0
  158. danielutils/reflection/__init__.py +6 -0
  159. danielutils/reflection/declarations.py +288 -0
  160. danielutils/reflection/file/__init__.py +1 -0
  161. danielutils/reflection/file/file_reflection.py +85 -0
  162. danielutils/reflection/function/__init__.py +1 -0
  163. danielutils/reflection/function/function_reflections.py +152 -0
  164. danielutils/reflection/info_classes/__init__.py +6 -0
  165. danielutils/reflection/info_classes/argument_info.py +278 -0
  166. danielutils/reflection/info_classes/class_info.py +204 -0
  167. danielutils/reflection/info_classes/decorator_info.py +54 -0
  168. danielutils/reflection/info_classes/file_info.py +1180 -0
  169. danielutils/reflection/info_classes/folder_info.py +592 -0
  170. danielutils/reflection/info_classes/function_info.py +551 -0
  171. danielutils/reflection/info_classes/import_info.py +207 -0
  172. danielutils/reflection/interpreter/__init__.py +9 -0
  173. danielutils/reflection/interpreter/callstack.py +57 -0
  174. danielutils/reflection/interpreter/get_traceback.py +27 -0
  175. danielutils/reflection/interpreter/interpreter.py +41 -0
  176. danielutils/reflection/interpreter/is_debugging.py +13 -0
  177. danielutils/reflection/interpreter/os_.py +33 -0
  178. danielutils/reflection/interpreter/packages.py +33 -0
  179. danielutils/reflection/interpreter/python_version.py +33 -0
  180. danielutils/reflection/interpreter/signals.py +18 -0
  181. danielutils/reflection/interpreter/tracer.py +175 -0
  182. danielutils/reflection/module/__init__.py +3 -0
  183. danielutils/reflection/module/lazy_module.py +104 -0
  184. danielutils/reflection/module/module_reflections.py +34 -0
  185. danielutils/reflection/module/package_reflection.py +161 -0
  186. danielutils/retry_executor/__init__.py +3 -0
  187. danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
  188. danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
  189. danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
  190. danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
  191. danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
  192. danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
  193. danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
  194. danielutils/retry_executor/backoff_strategy.py +28 -0
  195. danielutils/retry_executor/retry_executor.py +55 -0
  196. danielutils/snippets/__init__.py +1 -0
  197. danielutils/snippets/try_get.py +28 -0
  198. danielutils/system/__init__.py +3 -0
  199. danielutils/system/independent.py +197 -0
  200. danielutils/system/layered_command.py +130 -0
  201. danielutils/system/windows/__init__.py +2 -0
  202. danielutils/system/windows/utils/__init__.py +1 -0
  203. danielutils/system/windows/utils/filetime.py +19 -0
  204. danielutils/system/windows/win32_ctime.py +183 -0
  205. danielutils/system/windows/windows.py +78 -0
  206. danielutils/testing/__init__.py +1 -0
  207. danielutils/testing/unittest_/__init__.py +2 -0
  208. danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
  209. danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
  210. danielutils/text.py +169 -0
  211. danielutils/time.py +64 -0
  212. danielutils/tools/__init__.py +1 -0
  213. danielutils/tools/unittest_test_runner/__init__.py +9 -0
  214. danielutils/tools/unittest_test_runner/cli.py +70 -0
  215. danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
  216. danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
  217. danielutils/tools/unittest_test_runner/core/execution.py +244 -0
  218. danielutils/tools/unittest_test_runner/core/parser.py +163 -0
  219. danielutils/tools/unittest_test_runner/core/runner.py +561 -0
  220. danielutils/tools/unittest_test_runner/models.py +82 -0
  221. danielutils/university/__init__.py +7 -0
  222. danielutils/university/computability_and_complexity/__init__.py +2 -0
  223. danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
  224. danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
  225. danielutils/university/computability_and_complexity/languages/language.py +17 -0
  226. danielutils/university/computability_and_complexity/languages/sat.py +71 -0
  227. danielutils/university/computability_and_complexity/turing_machine.py +47 -0
  228. danielutils/university/databases/__init__.py +1 -0
  229. danielutils/university/databases/all.py +756 -0
  230. danielutils/university/image_proccesing/__init__.py +1 -0
  231. danielutils/university/image_proccesing/encoding/__init__.py +3 -0
  232. danielutils/university/image_proccesing/encoding/encoding.py +35 -0
  233. danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
  234. danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
  235. danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
  236. danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
  237. danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
  238. danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
  239. danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
  240. danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
  241. danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
  242. danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
  243. danielutils/university/image_proccesing/tansformations/hough.py +0 -0
  244. danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
  245. danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
  246. danielutils/university/linear_algebra/__init__.py +1 -0
  247. danielutils/university/linear_algebra/matrix.py +404 -0
  248. danielutils/university/machine_learning/__init__.py +0 -0
  249. danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
  250. danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
  251. danielutils/university/machine_learning/activation_functions/relu.py +11 -0
  252. danielutils/university/machine_learning/neuron.py +15 -0
  253. danielutils/university/oop/__init__.py +1 -0
  254. danielutils/university/oop/observer.py +31 -0
  255. danielutils/university/oop/strategy.py +9 -0
  256. danielutils/university/probability/__init__.py +7 -0
  257. danielutils/university/probability/conditional_variable/__init__.py +3 -0
  258. danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
  259. danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
  260. danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
  261. danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
  262. danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
  263. danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
  264. danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
  265. danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
  266. danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
  267. danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
  268. danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
  269. danielutils/university/probability/distributions.py +35 -0
  270. danielutils/university/probability/expressions/__init__.py +2 -0
  271. danielutils/university/probability/expressions/accumulation_expression.py +291 -0
  272. danielutils/university/probability/expressions/probability_expression.py +110 -0
  273. danielutils/university/probability/funcs/__init__.py +4 -0
  274. danielutils/university/probability/funcs/covariance.py +12 -0
  275. danielutils/university/probability/funcs/expected_value.py +41 -0
  276. danielutils/university/probability/funcs/probability_function.py +14 -0
  277. danielutils/university/probability/funcs/variance.py +17 -0
  278. danielutils/university/probability/operator.py +79 -0
  279. danielutils/university/probability/protocols.py +32 -0
  280. danielutils/university/probability/supp.py +101 -0
  281. danielutils/university/probability/transformation.py +44 -0
  282. danielutils/versioned_imports.py +31 -0
  283. danielutils-1.1.28.dist-info/METADATA +390 -0
  284. danielutils-1.1.28.dist-info/RECORD +287 -0
  285. danielutils-1.1.28.dist-info/WHEEL +5 -0
  286. danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
  287. 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
+ ]