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,551 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import re
|
|
3
|
+
import ast
|
|
4
|
+
import logging
|
|
5
|
+
import types
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Type, List, Callable, Optional, Tuple, Dict, Any, Union
|
|
8
|
+
from .decorator_info import DecoratorInfo
|
|
9
|
+
from .argument_info import ArgumentInfo
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class FunctionComplexityStats:
|
|
16
|
+
"""Statistics about function complexity and structure."""
|
|
17
|
+
max_indentation_level: int
|
|
18
|
+
cyclomatic_complexity: int
|
|
19
|
+
nesting_depth: int
|
|
20
|
+
has_nested_functions: bool
|
|
21
|
+
has_nested_classes: bool
|
|
22
|
+
has_lambda_expressions: bool
|
|
23
|
+
has_list_comprehensions: bool
|
|
24
|
+
has_dict_comprehensions: bool
|
|
25
|
+
has_set_comprehensions: bool
|
|
26
|
+
has_generator_expressions: bool
|
|
27
|
+
|
|
28
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
29
|
+
"""Convert to dictionary for JSON serialization."""
|
|
30
|
+
return {
|
|
31
|
+
"max_indentation_level": self.max_indentation_level,
|
|
32
|
+
"cyclomatic_complexity": self.cyclomatic_complexity,
|
|
33
|
+
"nesting_depth": self.nesting_depth,
|
|
34
|
+
"has_nested_functions": self.has_nested_functions,
|
|
35
|
+
"has_nested_classes": self.has_nested_classes,
|
|
36
|
+
"has_lambda_expressions": self.has_lambda_expressions,
|
|
37
|
+
"has_list_comprehensions": self.has_list_comprehensions,
|
|
38
|
+
"has_dict_comprehensions": self.has_dict_comprehensions,
|
|
39
|
+
"has_set_comprehensions": self.has_set_comprehensions,
|
|
40
|
+
"has_generator_expressions": self.has_generator_expressions
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class FunctionTypeStats:
|
|
46
|
+
"""Statistics about function typing."""
|
|
47
|
+
is_fully_typed: bool
|
|
48
|
+
has_return_type: bool
|
|
49
|
+
has_argument_types: bool
|
|
50
|
+
typed_arguments_count: int
|
|
51
|
+
total_arguments_count: int
|
|
52
|
+
typing_score: float # 0.0 to 1.0
|
|
53
|
+
missing_type_arguments: List[str]
|
|
54
|
+
has_generic_types: bool
|
|
55
|
+
has_union_types: bool
|
|
56
|
+
has_optional_types: bool
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
59
|
+
"""Convert to dictionary for JSON serialization."""
|
|
60
|
+
return {
|
|
61
|
+
"is_fully_typed": self.is_fully_typed,
|
|
62
|
+
"has_return_type": self.has_return_type,
|
|
63
|
+
"has_argument_types": self.has_argument_types,
|
|
64
|
+
"typed_arguments_count": self.typed_arguments_count,
|
|
65
|
+
"total_arguments_count": self.total_arguments_count,
|
|
66
|
+
"typing_score": round(self.typing_score, 2),
|
|
67
|
+
"missing_type_arguments": self.missing_type_arguments,
|
|
68
|
+
"has_generic_types": self.has_generic_types,
|
|
69
|
+
"has_union_types": self.has_union_types,
|
|
70
|
+
"has_optional_types": self.has_optional_types
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class FunctionCodeStats:
|
|
76
|
+
"""Statistics about function code structure."""
|
|
77
|
+
total_lines: int
|
|
78
|
+
code_lines: int
|
|
79
|
+
comment_lines: int
|
|
80
|
+
empty_lines: int
|
|
81
|
+
docstring_lines: int
|
|
82
|
+
average_line_length: float
|
|
83
|
+
max_line_length: int
|
|
84
|
+
min_line_length: int
|
|
85
|
+
has_docstring: bool
|
|
86
|
+
docstring_length: int
|
|
87
|
+
function_signature_line: int
|
|
88
|
+
|
|
89
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
90
|
+
"""Convert to dictionary for JSON serialization."""
|
|
91
|
+
return {
|
|
92
|
+
"total_lines": self.total_lines,
|
|
93
|
+
"code_lines": self.code_lines,
|
|
94
|
+
"comment_lines": self.comment_lines,
|
|
95
|
+
"empty_lines": self.empty_lines,
|
|
96
|
+
"docstring_lines": self.docstring_lines,
|
|
97
|
+
"average_line_length": round(self.average_line_length, 2),
|
|
98
|
+
"max_line_length": self.max_line_length,
|
|
99
|
+
"min_line_length": self.min_line_length,
|
|
100
|
+
"has_docstring": self.has_docstring,
|
|
101
|
+
"docstring_length": self.docstring_length,
|
|
102
|
+
"function_signature_line": self.function_signature_line
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class FunctionStats:
|
|
108
|
+
"""Comprehensive function statistics."""
|
|
109
|
+
complexity: FunctionComplexityStats
|
|
110
|
+
typing: FunctionTypeStats
|
|
111
|
+
code: FunctionCodeStats
|
|
112
|
+
overall_score: float # 0.0 to 1.0
|
|
113
|
+
quality_assessment: str # "excellent", "good", "fair", "poor"
|
|
114
|
+
|
|
115
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
116
|
+
"""Convert to dictionary for JSON serialization."""
|
|
117
|
+
return {
|
|
118
|
+
"complexity": self.complexity.to_dict(),
|
|
119
|
+
"typing": self.typing.to_dict(),
|
|
120
|
+
"code": self.code.to_dict(),
|
|
121
|
+
"overall_score": round(self.overall_score, 2),
|
|
122
|
+
"quality_assessment": self.quality_assessment
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class FunctionInfo:
|
|
127
|
+
FUNCTION_DEFINITION_REGEX: re.Pattern = re.compile(
|
|
128
|
+
r"(?P<decorators>(?:@[\s\S]+?)+?)?\s*(?P<async>async )?def (?P<name>\w[\w\d]*)\s*\((?P<arguments>[\s\S]+?)?\)\s*(?:\s*\-\>\s*(?P<return_type>[\s\S]+?)\s*)?:(?P<body>[\s\S]+)",
|
|
129
|
+
re.MULTILINE)
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
func: Callable,
|
|
134
|
+
owner: Optional[Union[Type, types.ModuleType]] = None,
|
|
135
|
+
) -> None:
|
|
136
|
+
func = inspect.unwrap(func)
|
|
137
|
+
|
|
138
|
+
# Check for lambda functions
|
|
139
|
+
if getattr(func, '__name__', None) == '<lambda>':
|
|
140
|
+
raise TypeError(
|
|
141
|
+
f"'{func.__name__}' is not a user defined function")
|
|
142
|
+
|
|
143
|
+
self._func = func
|
|
144
|
+
self._decorators: List[DecoratorInfo] = []
|
|
145
|
+
self._arguments: List[ArgumentInfo] = []
|
|
146
|
+
self._return_type: str = ""
|
|
147
|
+
self._owner = owner
|
|
148
|
+
self._source_code: str = ""
|
|
149
|
+
self._ast_tree: Optional[ast.AST] = None
|
|
150
|
+
self._is_async = False
|
|
151
|
+
self._is_property = False
|
|
152
|
+
self._name = getattr(func, "__name__", "<unknown>")
|
|
153
|
+
|
|
154
|
+
if getattr(func, '__isabstractmethod__', False):
|
|
155
|
+
try:
|
|
156
|
+
if inspect.isdatadescriptor(func):
|
|
157
|
+
inspect.getsource(func.fget) # type: ignore
|
|
158
|
+
self._is_property = True
|
|
159
|
+
else:
|
|
160
|
+
inspect.getsource(func)
|
|
161
|
+
self._parse_src_code()
|
|
162
|
+
except (OSError, TypeError):
|
|
163
|
+
pass
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
if inspect.isdatadescriptor(func):
|
|
168
|
+
inspect.getsource(func.fget) # type: ignore
|
|
169
|
+
self._is_property = True
|
|
170
|
+
else:
|
|
171
|
+
inspect.getsource(func)
|
|
172
|
+
self._is_property = False # type: ignore
|
|
173
|
+
except (OSError, TypeError):
|
|
174
|
+
raise TypeError(
|
|
175
|
+
f"'{getattr(func, '__name__', type(func).__name__)}' is not a user defined function")
|
|
176
|
+
self._parse_src_code()
|
|
177
|
+
|
|
178
|
+
def _parse_src_code(self) -> None:
|
|
179
|
+
f = self._func if not self.is_property else self._func.fget # type:ignore
|
|
180
|
+
self._source_code = inspect.getsource(f).strip()
|
|
181
|
+
m = FunctionInfo.FUNCTION_DEFINITION_REGEX.match(self._source_code)
|
|
182
|
+
if m is None:
|
|
183
|
+
raise ValueError("Invalid function source code")
|
|
184
|
+
decorators, async_, name, arguments, return_type, body = m.groups()
|
|
185
|
+
if decorators is not None:
|
|
186
|
+
for substr in decorators.strip().splitlines():
|
|
187
|
+
try:
|
|
188
|
+
self._decorators.append(
|
|
189
|
+
DecoratorInfo.from_str(substr.strip()))
|
|
190
|
+
except ValueError as e:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
f"Failed to parse decorator for function '{name}': {e}\n"
|
|
193
|
+
f"Decorator string: {repr(substr.strip())}"
|
|
194
|
+
) from e
|
|
195
|
+
|
|
196
|
+
self._is_async = async_ is not None
|
|
197
|
+
|
|
198
|
+
self._name = name
|
|
199
|
+
if arguments is not None:
|
|
200
|
+
try:
|
|
201
|
+
all_args = ArgumentInfo.from_str(arguments)
|
|
202
|
+
except ValueError as e:
|
|
203
|
+
raise ValueError(
|
|
204
|
+
f"Failed to parse arguments for function '{name}': {e}\n"
|
|
205
|
+
f"Arguments string: {repr(arguments)}"
|
|
206
|
+
) from e
|
|
207
|
+
# Filter out separator arguments (/, *) - they are syntax markers, not actual arguments
|
|
208
|
+
self._arguments = [arg for arg in all_args if not (
|
|
209
|
+
arg.is_kwargs_only and arg.name == "/") and not (arg.is_args and arg.name is None)]
|
|
210
|
+
|
|
211
|
+
self._return_type = "None"
|
|
212
|
+
if return_type is not None:
|
|
213
|
+
self._return_type = return_type
|
|
214
|
+
|
|
215
|
+
# Parse AST for detailed analysis
|
|
216
|
+
try:
|
|
217
|
+
self._ast_tree = ast.parse(self._source_code)
|
|
218
|
+
except SyntaxError:
|
|
219
|
+
self._ast_tree = None
|
|
220
|
+
|
|
221
|
+
def _analyze_complexity(self) -> FunctionComplexityStats:
|
|
222
|
+
"""Analyze function complexity metrics."""
|
|
223
|
+
if not self._ast_tree:
|
|
224
|
+
return FunctionComplexityStats(
|
|
225
|
+
max_indentation_level=0,
|
|
226
|
+
cyclomatic_complexity=1,
|
|
227
|
+
nesting_depth=0,
|
|
228
|
+
has_nested_functions=False,
|
|
229
|
+
has_nested_classes=False,
|
|
230
|
+
has_lambda_expressions=False,
|
|
231
|
+
has_list_comprehensions=False,
|
|
232
|
+
has_dict_comprehensions=False,
|
|
233
|
+
has_set_comprehensions=False,
|
|
234
|
+
has_generator_expressions=False
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
max_indent = 0
|
|
238
|
+
nesting_depth = 0
|
|
239
|
+
cyclomatic = 1 # Base complexity
|
|
240
|
+
has_nested_funcs = False
|
|
241
|
+
has_nested_classes = False
|
|
242
|
+
has_lambdas = False
|
|
243
|
+
has_list_comp = False
|
|
244
|
+
has_dict_comp = False
|
|
245
|
+
has_set_comp = False
|
|
246
|
+
has_gen_exp = False
|
|
247
|
+
|
|
248
|
+
for node in ast.walk(self._ast_tree):
|
|
249
|
+
# Calculate indentation level
|
|
250
|
+
if hasattr(node, 'lineno') and hasattr(node, 'col_offset'):
|
|
251
|
+
# Estimate indentation from column offset
|
|
252
|
+
indent_level = node.col_offset // 4 # Assuming 4 spaces per indent
|
|
253
|
+
max_indent = max(max_indent, indent_level)
|
|
254
|
+
|
|
255
|
+
# Count complexity factors
|
|
256
|
+
if isinstance(node, (ast.If, ast.While, ast.For, ast.AsyncFor, ast.ExceptHandler)):
|
|
257
|
+
cyclomatic += 1
|
|
258
|
+
nesting_depth = max(nesting_depth, 1)
|
|
259
|
+
elif isinstance(node, ast.BoolOp):
|
|
260
|
+
cyclomatic += len(node.values) - 1
|
|
261
|
+
elif isinstance(node, ast.FunctionDef):
|
|
262
|
+
has_nested_funcs = True
|
|
263
|
+
nesting_depth = max(nesting_depth, 2)
|
|
264
|
+
elif isinstance(node, ast.ClassDef):
|
|
265
|
+
has_nested_classes = True
|
|
266
|
+
nesting_depth = max(nesting_depth, 2)
|
|
267
|
+
elif isinstance(node, ast.Lambda):
|
|
268
|
+
has_lambdas = True
|
|
269
|
+
elif isinstance(node, ast.ListComp):
|
|
270
|
+
has_list_comp = True
|
|
271
|
+
elif isinstance(node, ast.DictComp):
|
|
272
|
+
has_dict_comp = True
|
|
273
|
+
elif isinstance(node, ast.SetComp):
|
|
274
|
+
has_set_comp = True
|
|
275
|
+
elif isinstance(node, ast.GeneratorExp):
|
|
276
|
+
has_gen_exp = True
|
|
277
|
+
|
|
278
|
+
return FunctionComplexityStats(
|
|
279
|
+
max_indentation_level=max_indent,
|
|
280
|
+
cyclomatic_complexity=cyclomatic,
|
|
281
|
+
nesting_depth=nesting_depth,
|
|
282
|
+
has_nested_functions=has_nested_funcs,
|
|
283
|
+
has_nested_classes=has_nested_classes,
|
|
284
|
+
has_lambda_expressions=has_lambdas,
|
|
285
|
+
has_list_comprehensions=has_list_comp,
|
|
286
|
+
has_dict_comprehensions=has_dict_comp,
|
|
287
|
+
has_set_comprehensions=has_set_comp,
|
|
288
|
+
has_generator_expressions=has_gen_exp
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def _analyze_typing(self) -> FunctionTypeStats:
|
|
292
|
+
"""Analyze function typing statistics."""
|
|
293
|
+
typed_args = sum(1 for arg in self._arguments if arg.type is not None)
|
|
294
|
+
total_args = len(self._arguments)
|
|
295
|
+
|
|
296
|
+
has_return = self._return_type != "None"
|
|
297
|
+
has_arg_types = typed_args > 0
|
|
298
|
+
is_fully_typed = has_return and has_arg_types and typed_args == total_args
|
|
299
|
+
|
|
300
|
+
missing_types = [
|
|
301
|
+
arg.name for arg in self._arguments if arg.type is None and arg.name]
|
|
302
|
+
|
|
303
|
+
# Calculate typing score (0.0 to 1.0)
|
|
304
|
+
typing_score = 0.0
|
|
305
|
+
if total_args > 0:
|
|
306
|
+
typing_score += (typed_args / total_args) * \
|
|
307
|
+
0.6 # 60% weight for arguments
|
|
308
|
+
if has_return:
|
|
309
|
+
typing_score += 0.4 # 40% weight for return type
|
|
310
|
+
|
|
311
|
+
# Check for advanced typing features
|
|
312
|
+
has_generics = any('[' in str(
|
|
313
|
+
arg.type) for arg in self._arguments if arg.type) or '[' in self._return_type
|
|
314
|
+
has_unions = any('Union' in str(arg.type) or '|' in str(
|
|
315
|
+
arg.type) for arg in self._arguments if arg.type) or 'Union' in self._return_type or '|' in self._return_type
|
|
316
|
+
has_optionals = any('Optional' in str(
|
|
317
|
+
arg.type) for arg in self._arguments if arg.type) or 'Optional' in self._return_type
|
|
318
|
+
|
|
319
|
+
return FunctionTypeStats(
|
|
320
|
+
is_fully_typed=is_fully_typed,
|
|
321
|
+
has_return_type=has_return,
|
|
322
|
+
has_argument_types=has_arg_types,
|
|
323
|
+
typed_arguments_count=typed_args,
|
|
324
|
+
total_arguments_count=total_args,
|
|
325
|
+
typing_score=typing_score,
|
|
326
|
+
missing_type_arguments=missing_types,
|
|
327
|
+
has_generic_types=has_generics,
|
|
328
|
+
has_union_types=has_unions,
|
|
329
|
+
has_optional_types=has_optionals
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def _analyze_code_structure(self) -> FunctionCodeStats:
|
|
333
|
+
"""Analyze function code structure statistics."""
|
|
334
|
+
lines = self._source_code.splitlines()
|
|
335
|
+
total_lines = len(lines)
|
|
336
|
+
|
|
337
|
+
# Find function signature line
|
|
338
|
+
signature_line = 0
|
|
339
|
+
for i, line in enumerate(lines):
|
|
340
|
+
if line.strip().startswith('def ') or line.strip().startswith('async def '):
|
|
341
|
+
signature_line = i + 1
|
|
342
|
+
break
|
|
343
|
+
|
|
344
|
+
# Analyze lines
|
|
345
|
+
code_lines = 0
|
|
346
|
+
comment_lines = 0
|
|
347
|
+
empty_lines = 0
|
|
348
|
+
docstring_lines = 0
|
|
349
|
+
line_lengths = []
|
|
350
|
+
|
|
351
|
+
in_docstring = False
|
|
352
|
+
docstring_delimiter = None
|
|
353
|
+
|
|
354
|
+
for line in lines:
|
|
355
|
+
stripped = line.strip()
|
|
356
|
+
line_lengths.append(len(line))
|
|
357
|
+
|
|
358
|
+
if not stripped:
|
|
359
|
+
empty_lines += 1
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
# Check for docstring start/end
|
|
363
|
+
if '"""' in line or "'''" in line:
|
|
364
|
+
if not in_docstring:
|
|
365
|
+
in_docstring = True
|
|
366
|
+
docstring_delimiter = '"""' if '"""' in line else "'''"
|
|
367
|
+
docstring_lines += 1
|
|
368
|
+
elif docstring_delimiter is not None and docstring_delimiter in line:
|
|
369
|
+
in_docstring = False
|
|
370
|
+
docstring_delimiter = None
|
|
371
|
+
else:
|
|
372
|
+
docstring_lines += 1
|
|
373
|
+
continue
|
|
374
|
+
elif in_docstring:
|
|
375
|
+
docstring_lines += 1
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
# Check for comments
|
|
379
|
+
if stripped.startswith('#'):
|
|
380
|
+
comment_lines += 1
|
|
381
|
+
else:
|
|
382
|
+
code_lines += 1
|
|
383
|
+
|
|
384
|
+
has_docstring = docstring_lines > 0
|
|
385
|
+
avg_line_length = sum(line_lengths) / \
|
|
386
|
+
len(line_lengths) if line_lengths else 0
|
|
387
|
+
max_line_length = max(line_lengths) if line_lengths else 0
|
|
388
|
+
min_line_length = min(line_lengths) if line_lengths else 0
|
|
389
|
+
|
|
390
|
+
return FunctionCodeStats(
|
|
391
|
+
total_lines=total_lines,
|
|
392
|
+
code_lines=code_lines,
|
|
393
|
+
comment_lines=comment_lines,
|
|
394
|
+
empty_lines=empty_lines,
|
|
395
|
+
docstring_lines=docstring_lines,
|
|
396
|
+
average_line_length=avg_line_length,
|
|
397
|
+
max_line_length=max_line_length,
|
|
398
|
+
min_line_length=min_line_length,
|
|
399
|
+
has_docstring=has_docstring,
|
|
400
|
+
docstring_length=docstring_lines,
|
|
401
|
+
function_signature_line=signature_line
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def _calculate_overall_score(self, complexity: FunctionComplexityStats,
|
|
405
|
+
typing: FunctionTypeStats,
|
|
406
|
+
code: FunctionCodeStats) -> Tuple[float, str]:
|
|
407
|
+
"""Calculate overall function quality score."""
|
|
408
|
+
score = 0.0
|
|
409
|
+
|
|
410
|
+
# Complexity score (30% weight)
|
|
411
|
+
if complexity.cyclomatic_complexity <= 5:
|
|
412
|
+
score += 0.3
|
|
413
|
+
elif complexity.cyclomatic_complexity <= 10:
|
|
414
|
+
score += 0.2
|
|
415
|
+
elif complexity.cyclomatic_complexity <= 20:
|
|
416
|
+
score += 0.1
|
|
417
|
+
|
|
418
|
+
if complexity.max_indentation_level <= 3:
|
|
419
|
+
score += 0.1
|
|
420
|
+
elif complexity.max_indentation_level <= 5:
|
|
421
|
+
score += 0.05
|
|
422
|
+
|
|
423
|
+
# Typing score (30% weight)
|
|
424
|
+
score += typing.typing_score * 0.3
|
|
425
|
+
|
|
426
|
+
# Code structure score (40% weight)
|
|
427
|
+
if code.has_docstring:
|
|
428
|
+
score += 0.1
|
|
429
|
+
|
|
430
|
+
if code.total_lines <= 50:
|
|
431
|
+
score += 0.1
|
|
432
|
+
elif code.total_lines <= 100:
|
|
433
|
+
score += 0.05
|
|
434
|
+
|
|
435
|
+
if code.average_line_length <= 80:
|
|
436
|
+
score += 0.1
|
|
437
|
+
elif code.average_line_length <= 120:
|
|
438
|
+
score += 0.05
|
|
439
|
+
|
|
440
|
+
if code.empty_lines > 0: # Some whitespace is good
|
|
441
|
+
score += 0.05
|
|
442
|
+
|
|
443
|
+
if code.comment_lines > 0:
|
|
444
|
+
score += 0.05
|
|
445
|
+
|
|
446
|
+
# Determine quality level
|
|
447
|
+
if score >= 0.8:
|
|
448
|
+
quality = "excellent"
|
|
449
|
+
elif score >= 0.6:
|
|
450
|
+
quality = "good"
|
|
451
|
+
elif score >= 0.4:
|
|
452
|
+
quality = "fair"
|
|
453
|
+
else:
|
|
454
|
+
quality = "poor"
|
|
455
|
+
|
|
456
|
+
return score, quality
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def stats(self) -> FunctionStats:
|
|
460
|
+
"""Returns comprehensive function statistics."""
|
|
461
|
+
# TODO: FunctionInfo.stats property is not yet ready for production use
|
|
462
|
+
# This functionality is experimental and may not provide accurate results
|
|
463
|
+
# The complexity analysis, typing analysis, and code structure analysis
|
|
464
|
+
# need further testing and refinement before being considered stable
|
|
465
|
+
logger.warning(
|
|
466
|
+
"FunctionInfo.stats property is experimental and not yet ready for production use")
|
|
467
|
+
|
|
468
|
+
complexity = self._analyze_complexity()
|
|
469
|
+
typing = self._analyze_typing()
|
|
470
|
+
code = self._analyze_code_structure()
|
|
471
|
+
overall_score, quality = self._calculate_overall_score(
|
|
472
|
+
complexity, typing, code)
|
|
473
|
+
|
|
474
|
+
return FunctionStats(
|
|
475
|
+
complexity=complexity,
|
|
476
|
+
typing=typing,
|
|
477
|
+
code=code,
|
|
478
|
+
overall_score=overall_score,
|
|
479
|
+
quality_assessment=quality
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def source_code(self) -> str:
|
|
484
|
+
"""Returns the function's source code."""
|
|
485
|
+
return self._source_code
|
|
486
|
+
|
|
487
|
+
def __str__(self) -> str:
|
|
488
|
+
# body = json.dumps({
|
|
489
|
+
# "name": self.name,
|
|
490
|
+
# "decorators": self.decorators,
|
|
491
|
+
# "arguments": self.arguments
|
|
492
|
+
# }, default=str, indent=4)
|
|
493
|
+
return f"{self.__class__.__name__}(name=\"{self.name}\", decorators={self.decorators}, arguments={self.arguments})"
|
|
494
|
+
|
|
495
|
+
def __repr__(self) -> str:
|
|
496
|
+
return f"{self.__class__.__name__}(name=\"{self.name}\")"
|
|
497
|
+
|
|
498
|
+
@property
|
|
499
|
+
def is_async(self) -> bool:
|
|
500
|
+
return self._is_async
|
|
501
|
+
|
|
502
|
+
@property
|
|
503
|
+
def is_inherited(self) -> bool:
|
|
504
|
+
if self._owner is None:
|
|
505
|
+
return False
|
|
506
|
+
return self._func in set(self._owner.__dict__.keys())
|
|
507
|
+
|
|
508
|
+
@property
|
|
509
|
+
def is_class_method(self) -> bool:
|
|
510
|
+
return "classmethod" in set(d.name for d in self.decorators)
|
|
511
|
+
|
|
512
|
+
@property
|
|
513
|
+
def is_static_method(self) -> bool:
|
|
514
|
+
return "staticmethod" in set(d.name for d in self.decorators)
|
|
515
|
+
|
|
516
|
+
@property
|
|
517
|
+
def is_instance_method(self) -> bool:
|
|
518
|
+
return not self.is_class_method and not self.is_static_method
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def is_abstract(self) -> bool:
|
|
522
|
+
return getattr(self._func, '__isabstractmethod__', False)
|
|
523
|
+
|
|
524
|
+
@property
|
|
525
|
+
def is_property(self) -> bool:
|
|
526
|
+
return self._is_property
|
|
527
|
+
|
|
528
|
+
@property
|
|
529
|
+
def name(self) -> str:
|
|
530
|
+
return self._name
|
|
531
|
+
|
|
532
|
+
@property
|
|
533
|
+
def return_type(self) -> str:
|
|
534
|
+
return self._return_type
|
|
535
|
+
|
|
536
|
+
@property
|
|
537
|
+
def arguments(self) -> List[ArgumentInfo]:
|
|
538
|
+
return self._arguments
|
|
539
|
+
|
|
540
|
+
@property
|
|
541
|
+
def decorators(self) -> List[DecoratorInfo]:
|
|
542
|
+
return self._decorators
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
__all__ = [
|
|
546
|
+
"FunctionInfo",
|
|
547
|
+
"FunctionStats",
|
|
548
|
+
"FunctionComplexityStats",
|
|
549
|
+
"FunctionTypeStats",
|
|
550
|
+
"FunctionCodeStats",
|
|
551
|
+
]
|