gyomu-python-analysis 0.2.0__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.
- gyomu_python_analysis/__init__.py +0 -0
- gyomu_python_analysis/analysis/__init__.py +0 -0
- gyomu_python_analysis/analysis/analyzers/__init__.py +0 -0
- gyomu_python_analysis/analysis/analyzers/cls.py +343 -0
- gyomu_python_analysis/analysis/analyzers/context.py +66 -0
- gyomu_python_analysis/analysis/analyzers/decorator.py +88 -0
- gyomu_python_analysis/analysis/analyzers/dependency.py +134 -0
- gyomu_python_analysis/analysis/analyzers/docstring.py +288 -0
- gyomu_python_analysis/analysis/analyzers/expression/__init__.py +0 -0
- gyomu_python_analysis/analysis/analyzers/expression/expr.py +400 -0
- gyomu_python_analysis/analysis/analyzers/expression/name.py +38 -0
- gyomu_python_analysis/analysis/analyzers/functions.py +53 -0
- gyomu_python_analysis/analysis/analyzers/imports.py +21 -0
- gyomu_python_analysis/analysis/analyzers/internal/__init__.py +0 -0
- gyomu_python_analysis/analysis/analyzers/internal/common.py +87 -0
- gyomu_python_analysis/analysis/analyzers/internal/location.py +68 -0
- gyomu_python_analysis/analysis/analyzers/internal/visibility.py +27 -0
- gyomu_python_analysis/analysis/analyzers/pydantic.py +62 -0
- gyomu_python_analysis/analysis/analyzers/type_alias.py +24 -0
- gyomu_python_analysis/analysis/analyzers/types.py +29 -0
- gyomu_python_analysis/analysis/analyzers/variables.py +22 -0
- gyomu_python_analysis/analysis/extract/__init__.py +0 -0
- gyomu_python_analysis/analysis/extract/symbols.py +118 -0
- gyomu_python_analysis/analysis/file/__init__.py +0 -0
- gyomu_python_analysis/analysis/file/source_file_context.py +10 -0
- gyomu_python_analysis/analysis/get_module.py +55 -0
- gyomu_python_analysis/analysis/load.py +42 -0
- gyomu_python_analysis/analysis/load_file_context.py +27 -0
- gyomu_python_analysis/analysis/load_module.py +65 -0
- gyomu_python_analysis/analysis/metadata.py +49 -0
- gyomu_python_analysis/analysis/parser/__init__.py +0 -0
- gyomu_python_analysis/analysis/parser/docstring.py +7 -0
- gyomu_python_analysis/error/__init__.py +0 -0
- gyomu_python_analysis/error/analysis.py +36 -0
- gyomu_python_analysis/error/update.py +32 -0
- gyomu_python_analysis/path/__init__.py +0 -0
- gyomu_python_analysis/path/conversion.py +25 -0
- gyomu_python_analysis/project/__init__.py +0 -0
- gyomu_python_analysis/project/context.py +18 -0
- gyomu_python_analysis-0.2.0.dist-info/METADATA +15 -0
- gyomu_python_analysis-0.2.0.dist-info/RECORD +42 -0
- gyomu_python_analysis-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# from griffe import ExprName
|
|
2
|
+
# from gyomu_schema.schemas.python.type.structure import (
|
|
3
|
+
# NameStructureAnalysis,
|
|
4
|
+
# NoneStructureAnalysis,
|
|
5
|
+
# TypeStructureKind,
|
|
6
|
+
# )
|
|
7
|
+
|
|
8
|
+
# from gyomu_python_analysis.analysis.analyzers.context import SymbolContext
|
|
9
|
+
# from gyomu_python_analysis.analysis.analyzers.dependency import register_dependency
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# def analyze_expression_name(
|
|
13
|
+
# expression: ExprName,
|
|
14
|
+
# context: SymbolContext,
|
|
15
|
+
# need_registration_dependency: bool,
|
|
16
|
+
# ) -> NameStructureAnalysis | NoneStructureAnalysis:
|
|
17
|
+
# # print(
|
|
18
|
+
# # dict(
|
|
19
|
+
# # name=expression.name,
|
|
20
|
+
# # member=expression.member,
|
|
21
|
+
# # path=expression.path,
|
|
22
|
+
# # canonical_name=expression.canonical_name,
|
|
23
|
+
# # is_enum_class=expression.is_enum_class,
|
|
24
|
+
# # is_enum_instance=expression.is_enum_instance,
|
|
25
|
+
# # is_enum_value=expression.is_enum_value,
|
|
26
|
+
# # is_type_parameter=expression.is_type_parameter,
|
|
27
|
+
# # )
|
|
28
|
+
# # )
|
|
29
|
+
|
|
30
|
+
# if expression.name == "None":
|
|
31
|
+
# return NoneStructureAnalysis(
|
|
32
|
+
# kind=TypeStructureKind.NONE,
|
|
33
|
+
# )
|
|
34
|
+
# if need_registration_dependency:
|
|
35
|
+
# register_dependency(context.declaration, expression.name, context)
|
|
36
|
+
# return NameStructureAnalysis(
|
|
37
|
+
# name=expression.name,
|
|
38
|
+
# )
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from griffe import Function
|
|
2
|
+
from griffe import ParameterKind as GriffeParameterKind
|
|
3
|
+
from gyomu_schema.schemas.python.function_analysis import FunctionAnalysis
|
|
4
|
+
from gyomu_schema.schemas.python.parameter import ParameterAnalysis, ParameterKind
|
|
5
|
+
|
|
6
|
+
from gyomu_python_analysis.analysis.analyzers.context import (
|
|
7
|
+
SymbolContext,
|
|
8
|
+
)
|
|
9
|
+
from gyomu_python_analysis.analysis.analyzers.internal.common import build_symbol_common
|
|
10
|
+
from gyomu_python_analysis.analysis.analyzers.types import analyze_type
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_function_parameter_kind(kind: GriffeParameterKind | None) -> ParameterKind:
|
|
14
|
+
match kind:
|
|
15
|
+
case GriffeParameterKind.keyword_only:
|
|
16
|
+
return ParameterKind.KEYWORD_ONLY
|
|
17
|
+
case GriffeParameterKind.positional_only:
|
|
18
|
+
return ParameterKind.POSITIONAL_ONLY
|
|
19
|
+
case GriffeParameterKind.positional_or_keyword:
|
|
20
|
+
return ParameterKind.POSITIONAL_OR_KEYWORD
|
|
21
|
+
case GriffeParameterKind.var_keyword:
|
|
22
|
+
return ParameterKind.VAR_KEYWORD
|
|
23
|
+
case GriffeParameterKind.var_positional:
|
|
24
|
+
return ParameterKind.VAR_POSITIONAL
|
|
25
|
+
raise ValueError(f"Invalid Parameter Kind: {str(kind)}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def analyze_function(
|
|
29
|
+
func: Function, name: str, context: SymbolContext
|
|
30
|
+
) -> FunctionAnalysis:
|
|
31
|
+
for dec in func.decorators:
|
|
32
|
+
print(dec.as_dict())
|
|
33
|
+
parameters: list[ParameterAnalysis] = []
|
|
34
|
+
for param in func.parameters:
|
|
35
|
+
parameters.append(
|
|
36
|
+
ParameterAnalysis(
|
|
37
|
+
name=param.name,
|
|
38
|
+
kind=_get_function_parameter_kind(param.kind),
|
|
39
|
+
type=analyze_type(param.annotation, context),
|
|
40
|
+
default=None,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
# pprint(func.as_dict())
|
|
44
|
+
func_common = build_symbol_common(symbol=func, name=name, context=context)
|
|
45
|
+
return_type = analyze_type(func.returns, context)
|
|
46
|
+
return FunctionAnalysis(
|
|
47
|
+
**func_common,
|
|
48
|
+
dependencies=tuple([]),
|
|
49
|
+
parameters=tuple(parameters),
|
|
50
|
+
is_async="async" in func.labels,
|
|
51
|
+
return_type=return_type,
|
|
52
|
+
identity=context.declaration,
|
|
53
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from griffe import Alias
|
|
2
|
+
from gyomu_schema.schemas.python.import_analysis import ImportAnalysis, ImportKind
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def analyze_import(
|
|
6
|
+
alias: Alias,
|
|
7
|
+
name: str,
|
|
8
|
+
source_lines: list[str],
|
|
9
|
+
) -> ImportAnalysis:
|
|
10
|
+
target_path = alias.target_path
|
|
11
|
+
attr = alias.as_dict()
|
|
12
|
+
print(attr)
|
|
13
|
+
# print(f"path: {alias.path}")
|
|
14
|
+
# print(f"target-path: {alias.target_path}")
|
|
15
|
+
# print(f"canonical_path: {alias.canonical_path}")
|
|
16
|
+
assert attr["lineno"]
|
|
17
|
+
target_line = source_lines[int(attr["lineno"]) - 1]
|
|
18
|
+
kind = ImportKind.MODULE
|
|
19
|
+
if target_line.strip().startswith("from"):
|
|
20
|
+
kind = ImportKind.SYMBOL
|
|
21
|
+
return ImportAnalysis(imported_name=target_path, local_name=name, kind=kind)
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from griffe import Class, Docstring, Function, Object
|
|
2
|
+
from gyomu_schema.schemas.python.decorator import DecoratorAnalysis
|
|
3
|
+
from gyomu_schema.schemas.python.docstring import DocstringCommon
|
|
4
|
+
from gyomu_schema.schemas.python.location import SourceLocation
|
|
5
|
+
from gyomu_schema.schemas.python.symbol_base import MemberCommon, SymbolCommon
|
|
6
|
+
|
|
7
|
+
from gyomu_python_analysis.analysis.analyzers.context import SymbolContext
|
|
8
|
+
from gyomu_python_analysis.analysis.analyzers.decorator import analyze_decorators
|
|
9
|
+
from gyomu_python_analysis.analysis.analyzers.docstring import analyze_docstring
|
|
10
|
+
from gyomu_python_analysis.analysis.analyzers.internal.location import (
|
|
11
|
+
calculate_docstring_location,
|
|
12
|
+
calculate_member_location,
|
|
13
|
+
calculate_symbol_location,
|
|
14
|
+
)
|
|
15
|
+
from gyomu_python_analysis.analysis.analyzers.internal.visibility import (
|
|
16
|
+
calculate_visibility,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_symbol_common(
|
|
21
|
+
symbol: Object,
|
|
22
|
+
name: str,
|
|
23
|
+
context: SymbolContext,
|
|
24
|
+
) -> SymbolCommon:
|
|
25
|
+
location = calculate_symbol_location(symbol=symbol, context=context)
|
|
26
|
+
docstring = (
|
|
27
|
+
analyze_docstring(
|
|
28
|
+
symbol.docstring,
|
|
29
|
+
doc_common=build_docstring_common(symbol.docstring, context=context),
|
|
30
|
+
context=context,
|
|
31
|
+
)
|
|
32
|
+
if symbol.docstring is not None
|
|
33
|
+
else None
|
|
34
|
+
)
|
|
35
|
+
decorators: list[DecoratorAnalysis] = []
|
|
36
|
+
if isinstance(symbol, Class | Function):
|
|
37
|
+
decorators = analyze_decorators(symbol.decorators, context=context)
|
|
38
|
+
return {
|
|
39
|
+
"name": name,
|
|
40
|
+
"location": location,
|
|
41
|
+
"visibility": calculate_visibility(name),
|
|
42
|
+
"indent": location.start_column,
|
|
43
|
+
"docstring": docstring,
|
|
44
|
+
"decorators": tuple(decorators),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_member_common(
|
|
49
|
+
symbol: Object,
|
|
50
|
+
name: str,
|
|
51
|
+
context: SymbolContext,
|
|
52
|
+
parent_location: SourceLocation | None = None,
|
|
53
|
+
) -> MemberCommon:
|
|
54
|
+
location = calculate_member_location(
|
|
55
|
+
symbol=symbol,
|
|
56
|
+
context=context,
|
|
57
|
+
parent_location=parent_location,
|
|
58
|
+
)
|
|
59
|
+
docstring = (
|
|
60
|
+
analyze_docstring(
|
|
61
|
+
symbol.docstring,
|
|
62
|
+
doc_common=build_docstring_common(symbol.docstring, context=context),
|
|
63
|
+
context=context,
|
|
64
|
+
)
|
|
65
|
+
if symbol.docstring is not None
|
|
66
|
+
else None
|
|
67
|
+
)
|
|
68
|
+
decorators: list[DecoratorAnalysis] = []
|
|
69
|
+
if isinstance(symbol, Function):
|
|
70
|
+
decorators = analyze_decorators(symbol.decorators, context=context)
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"name": name,
|
|
74
|
+
"location": location,
|
|
75
|
+
"visibility": calculate_visibility(name),
|
|
76
|
+
"indent": location.start_column if location is not None else None,
|
|
77
|
+
"docstring": docstring,
|
|
78
|
+
"decorators": tuple(decorators),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def build_docstring_common(doc: Docstring, context: SymbolContext) -> DocstringCommon:
|
|
83
|
+
location = calculate_docstring_location(doc=doc, context=context)
|
|
84
|
+
return {
|
|
85
|
+
"location": location,
|
|
86
|
+
"indent": location.start_column,
|
|
87
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from griffe import Decorator, Docstring, Object
|
|
2
|
+
from gyomu_schema.schemas.python.location import SourceLocation
|
|
3
|
+
|
|
4
|
+
from gyomu_python_analysis.analysis.analyzers.context import SymbolContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _calculate_location(
|
|
8
|
+
target: Object | Docstring | Decorator, context: SymbolContext
|
|
9
|
+
) -> SourceLocation:
|
|
10
|
+
# source_full_path = project.project_root / project.source_root / source_file.path
|
|
11
|
+
start_line_no = target.lineno
|
|
12
|
+
end_line_no = target.endlineno
|
|
13
|
+
|
|
14
|
+
assert start_line_no is not None
|
|
15
|
+
assert end_line_no is not None
|
|
16
|
+
|
|
17
|
+
start_line = context.source_lines[start_line_no - 1]
|
|
18
|
+
end_line = context.source_lines[end_line_no - 1]
|
|
19
|
+
|
|
20
|
+
start_column = len(start_line) - len(start_line.lstrip())
|
|
21
|
+
end_column = len(end_line.rstrip())
|
|
22
|
+
|
|
23
|
+
start_offset = context.line_start_offsets[start_line_no - 1] + start_column
|
|
24
|
+
end_offset = context.line_start_offsets[end_line_no - 1] + end_column
|
|
25
|
+
|
|
26
|
+
return SourceLocation(
|
|
27
|
+
start_line=start_line_no,
|
|
28
|
+
start_column=start_column,
|
|
29
|
+
end_line=end_line_no,
|
|
30
|
+
end_column=end_column,
|
|
31
|
+
start_offset=start_offset,
|
|
32
|
+
end_offset=end_offset,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def calculate_symbol_location(
|
|
37
|
+
symbol: Object,
|
|
38
|
+
context: SymbolContext,
|
|
39
|
+
) -> SourceLocation:
|
|
40
|
+
return _calculate_location(symbol, context)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def calculate_member_location(
|
|
44
|
+
symbol: Object,
|
|
45
|
+
context: SymbolContext,
|
|
46
|
+
parent_location: SourceLocation | None,
|
|
47
|
+
) -> SourceLocation | None:
|
|
48
|
+
location = calculate_symbol_location(symbol=symbol, context=context)
|
|
49
|
+
if parent_location is not None and (
|
|
50
|
+
location.start_line >= parent_location.start_line
|
|
51
|
+
and location.end_line <= parent_location.end_line
|
|
52
|
+
):
|
|
53
|
+
return None
|
|
54
|
+
return location
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def calculate_docstring_location(
|
|
58
|
+
doc: Docstring,
|
|
59
|
+
context: SymbolContext,
|
|
60
|
+
) -> SourceLocation:
|
|
61
|
+
return _calculate_location(target=doc, context=context)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def calculate_decorator_location(
|
|
65
|
+
decorator: Decorator,
|
|
66
|
+
context: SymbolContext,
|
|
67
|
+
) -> SourceLocation:
|
|
68
|
+
return _calculate_location(target=decorator, context=context)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from gyomu_schema.schemas.python.visibility import Visibility
|
|
2
|
+
|
|
3
|
+
_SPECIAL_NAMES = frozenset(
|
|
4
|
+
{
|
|
5
|
+
"__all__",
|
|
6
|
+
"__annotations__",
|
|
7
|
+
"__builtins__",
|
|
8
|
+
"__cached__",
|
|
9
|
+
"__doc__",
|
|
10
|
+
"__file__",
|
|
11
|
+
"__loader__",
|
|
12
|
+
"__name__",
|
|
13
|
+
"__package__",
|
|
14
|
+
"__path__",
|
|
15
|
+
"__spec__",
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def calculate_visibility(name: str) -> Visibility:
|
|
21
|
+
if name in _SPECIAL_NAMES:
|
|
22
|
+
return Visibility.SPECIAL
|
|
23
|
+
|
|
24
|
+
if name.startswith("_"):
|
|
25
|
+
return Visibility.PRIVATE
|
|
26
|
+
|
|
27
|
+
return Visibility.PUBLIC
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from gyomu_schema.schemas.python.pydantic import PydanticFieldAnalysis
|
|
2
|
+
from gyomu_schema.schemas.python.type.structure import (
|
|
3
|
+
LiteralValue,
|
|
4
|
+
NameStructureAnalysis,
|
|
5
|
+
NoneStructureAnalysis,
|
|
6
|
+
)
|
|
7
|
+
from gyomu_schema.schemas.python.type.type_analysis import (
|
|
8
|
+
CallStructureAnalysis,
|
|
9
|
+
ExpressionAnalysis,
|
|
10
|
+
KeywordStructureAnalysis,
|
|
11
|
+
TypeExpression,
|
|
12
|
+
UnionStructureAnalysis,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _is_field_required(field_type: ExpressionAnalysis) -> bool:
|
|
17
|
+
if isinstance(field_type, NoneStructureAnalysis):
|
|
18
|
+
return False
|
|
19
|
+
if isinstance(field_type, UnionStructureAnalysis):
|
|
20
|
+
for element in field_type.types:
|
|
21
|
+
if isinstance(element, NoneStructureAnalysis):
|
|
22
|
+
return False
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def retrieve_str_value(value: TypeExpression) -> str | None:
|
|
27
|
+
if isinstance(value, LiteralValue):
|
|
28
|
+
return str(value.value)
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def analyze_pydantic(
|
|
33
|
+
field_type: ExpressionAnalysis, expression: TypeExpression
|
|
34
|
+
) -> PydanticFieldAnalysis | None:
|
|
35
|
+
is_required = _is_field_required(field_type)
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
isinstance(expression, CallStructureAnalysis)
|
|
39
|
+
and isinstance(expression.function, NameStructureAnalysis)
|
|
40
|
+
and expression.function.name == "Field"
|
|
41
|
+
):
|
|
42
|
+
description = None
|
|
43
|
+
alias = None
|
|
44
|
+
default = None
|
|
45
|
+
for argument in expression.arguments:
|
|
46
|
+
if isinstance(argument, KeywordStructureAnalysis):
|
|
47
|
+
match argument.name:
|
|
48
|
+
case "description":
|
|
49
|
+
description = retrieve_str_value(argument.value)
|
|
50
|
+
|
|
51
|
+
case "alias":
|
|
52
|
+
alias = retrieve_str_value(argument.value)
|
|
53
|
+
else:
|
|
54
|
+
default = retrieve_str_value(argument)
|
|
55
|
+
|
|
56
|
+
return PydanticFieldAnalysis(
|
|
57
|
+
required=is_required,
|
|
58
|
+
description=description,
|
|
59
|
+
alias=alias,
|
|
60
|
+
default_source=default,
|
|
61
|
+
)
|
|
62
|
+
return None
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from griffe import TypeAlias
|
|
2
|
+
from gyomu_schema.schemas.python.type_alias import TypeAliasAnalysis
|
|
3
|
+
|
|
4
|
+
from gyomu_python_analysis.analysis.analyzers.context import (
|
|
5
|
+
MemberPath,
|
|
6
|
+
SymbolContext,
|
|
7
|
+
build_declaration_identity,
|
|
8
|
+
)
|
|
9
|
+
from gyomu_python_analysis.analysis.analyzers.internal.common import build_symbol_common
|
|
10
|
+
from gyomu_python_analysis.analysis.analyzers.types import analyze_type
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def analyze_type_alias(
|
|
14
|
+
alias: TypeAlias, name: str, context: SymbolContext
|
|
15
|
+
) -> TypeAliasAnalysis:
|
|
16
|
+
member_path: MemberPath = ()
|
|
17
|
+
alias_common = build_symbol_common(symbol=alias, name=name, context=context)
|
|
18
|
+
type = analyze_type(alias.value, context)
|
|
19
|
+
return TypeAliasAnalysis(
|
|
20
|
+
**alias_common,
|
|
21
|
+
dependencies=tuple(),
|
|
22
|
+
alias_type=type,
|
|
23
|
+
identity=build_declaration_identity(context, member_path),
|
|
24
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from griffe import Expr
|
|
4
|
+
from gyomu_schema.schemas.python.type.structure import NoneStructureAnalysis
|
|
5
|
+
from gyomu_schema.schemas.python.type.type_analysis import (
|
|
6
|
+
TypeAnalysis,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from gyomu_python_analysis.analysis.analyzers.context import SymbolContext
|
|
10
|
+
from gyomu_python_analysis.analysis.analyzers.expression.expr import analyze_expression
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def analyze_type(
|
|
14
|
+
annotation: str | Expr | None, context: SymbolContext
|
|
15
|
+
) -> TypeAnalysis | None:
|
|
16
|
+
if annotation is None:
|
|
17
|
+
return None
|
|
18
|
+
if isinstance(annotation, str):
|
|
19
|
+
# print(annotation)
|
|
20
|
+
if annotation == "None":
|
|
21
|
+
return TypeAnalysis(text=annotation, structure=NoneStructureAnalysis())
|
|
22
|
+
return TypeAnalysis(text=annotation)
|
|
23
|
+
if isinstance(annotation, Expr):
|
|
24
|
+
text = str(annotation)
|
|
25
|
+
print(annotation.as_dict())
|
|
26
|
+
return TypeAnalysis(
|
|
27
|
+
text=text, structure=analyze_expression(annotation, context)
|
|
28
|
+
)
|
|
29
|
+
raise ValueError(f"Unsupported annotation type: {type(annotation)}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from griffe import Attribute
|
|
2
|
+
from gyomu_schema.schemas.python.variable import VariableAnalysis
|
|
3
|
+
|
|
4
|
+
from gyomu_python_analysis.analysis.analyzers.context import (
|
|
5
|
+
SymbolContext,
|
|
6
|
+
)
|
|
7
|
+
from gyomu_python_analysis.analysis.analyzers.internal.common import build_symbol_common
|
|
8
|
+
from gyomu_python_analysis.analysis.analyzers.types import analyze_type
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def analyze_variable(
|
|
12
|
+
variable: Attribute, name: str, context: SymbolContext
|
|
13
|
+
) -> VariableAnalysis:
|
|
14
|
+
variable_common = build_symbol_common(symbol=variable, name=name, context=context)
|
|
15
|
+
type = analyze_type(variable.annotation, context)
|
|
16
|
+
return VariableAnalysis(
|
|
17
|
+
**variable_common,
|
|
18
|
+
dependencies=tuple(),
|
|
19
|
+
type=type,
|
|
20
|
+
value_source=str(variable.value) if variable.value is not None else None,
|
|
21
|
+
identity=context.declaration,
|
|
22
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from griffe import Alias, Attribute, Class, Function, Module, TypeAlias
|
|
4
|
+
from gyomu_infra.logger import logger
|
|
5
|
+
from gyomu_schema.schemas.python.import_analysis import ImportAnalysis
|
|
6
|
+
from gyomu_schema.schemas.python.symbol import SymbolAnalysis
|
|
7
|
+
from gyomu_schema.schemas.python.types import (
|
|
8
|
+
PythonPath,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from gyomu_python_analysis.analysis.analyzers.cls import analyze_class
|
|
12
|
+
from gyomu_python_analysis.analysis.analyzers.context import (
|
|
13
|
+
DependencyInformation,
|
|
14
|
+
initialize_symbol_context,
|
|
15
|
+
)
|
|
16
|
+
from gyomu_python_analysis.analysis.analyzers.dependency import (
|
|
17
|
+
resolve_dependencies,
|
|
18
|
+
)
|
|
19
|
+
from gyomu_python_analysis.analysis.analyzers.functions import analyze_function
|
|
20
|
+
from gyomu_python_analysis.analysis.analyzers.imports import analyze_import
|
|
21
|
+
from gyomu_python_analysis.analysis.analyzers.type_alias import analyze_type_alias
|
|
22
|
+
from gyomu_python_analysis.analysis.analyzers.variables import analyze_variable
|
|
23
|
+
from gyomu_python_analysis.analysis.file.source_file_context import SourceFileContext
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SymbolExtractContext:
|
|
28
|
+
imported: tuple[ImportAnalysis, ...]
|
|
29
|
+
symbols: tuple[SymbolAnalysis, ...]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def extract_symbols(
|
|
33
|
+
source_file: SourceFileContext,
|
|
34
|
+
source_lines: list[str],
|
|
35
|
+
) -> SymbolExtractContext:
|
|
36
|
+
imported: list[ImportAnalysis] = _extract_imports(source_file.module, source_lines)
|
|
37
|
+
symbols: list[SymbolAnalysis] = _extract_symbols_internal(
|
|
38
|
+
source_file, source_lines, imported
|
|
39
|
+
)
|
|
40
|
+
# for symbol_name, value in module.members.items():
|
|
41
|
+
# if isinstance(value, Alias):
|
|
42
|
+
return SymbolExtractContext(imported=tuple(imported), symbols=tuple(symbols))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _extract_imports(
|
|
46
|
+
module: Module,
|
|
47
|
+
source_lines: list[str],
|
|
48
|
+
) -> list[ImportAnalysis]:
|
|
49
|
+
imported: list[ImportAnalysis] = []
|
|
50
|
+
for symbol_name, value in module.members.items():
|
|
51
|
+
if isinstance(value, Alias):
|
|
52
|
+
imported.append(analyze_import(value, symbol_name, source_lines))
|
|
53
|
+
return imported
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _extract_symbols_internal(
|
|
57
|
+
source_file: SourceFileContext,
|
|
58
|
+
source_lines: list[str],
|
|
59
|
+
imported: list[ImportAnalysis],
|
|
60
|
+
) -> list[SymbolAnalysis]:
|
|
61
|
+
symbols: list[SymbolAnalysis] = []
|
|
62
|
+
dependencies: list[DependencyInformation] = []
|
|
63
|
+
module_name: PythonPath = PythonPath(source_file.module.path)
|
|
64
|
+
logger.info(f"module_name:{module_name}")
|
|
65
|
+
for symbol_name, symbol in source_file.module.members.items():
|
|
66
|
+
if isinstance(symbol, Alias):
|
|
67
|
+
continue
|
|
68
|
+
# pprint(f"Extracting symbol: {symbol_name} ({type(symbol)})")
|
|
69
|
+
# pprint(symbol.as_dict())
|
|
70
|
+
context = initialize_symbol_context(
|
|
71
|
+
module_name=module_name, name=symbol_name, source_lines=source_lines
|
|
72
|
+
)
|
|
73
|
+
if isinstance(symbol, Attribute):
|
|
74
|
+
symbols.append(
|
|
75
|
+
analyze_variable(
|
|
76
|
+
variable=symbol,
|
|
77
|
+
name=symbol_name,
|
|
78
|
+
context=context,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
elif isinstance(symbol, Function):
|
|
82
|
+
symbols.append(
|
|
83
|
+
analyze_function(
|
|
84
|
+
func=symbol,
|
|
85
|
+
name=symbol_name,
|
|
86
|
+
context=context,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
elif isinstance(symbol, Class):
|
|
90
|
+
symbols.append(
|
|
91
|
+
analyze_class(
|
|
92
|
+
cls=symbol,
|
|
93
|
+
name=symbol_name,
|
|
94
|
+
context=context,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
elif isinstance(symbol, TypeAlias):
|
|
99
|
+
symbols.append(
|
|
100
|
+
analyze_type_alias(
|
|
101
|
+
alias=symbol,
|
|
102
|
+
name=symbol_name,
|
|
103
|
+
context=context,
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
for item in context.dependencies:
|
|
107
|
+
dependencies.append(item)
|
|
108
|
+
|
|
109
|
+
dependency_map = resolve_dependencies(dependencies, imported, symbols)
|
|
110
|
+
|
|
111
|
+
symbols_by_identity = {symbol.identity: symbol for symbol in symbols}
|
|
112
|
+
for identity, dependency_list in dependency_map.items():
|
|
113
|
+
source_symbol = symbols_by_identity.get(identity)
|
|
114
|
+
if source_symbol is None:
|
|
115
|
+
raise ValueError(f"Unexpected Error. Should not happen. {repr(identity)}")
|
|
116
|
+
source_symbol.dependencies = dependency_list
|
|
117
|
+
|
|
118
|
+
return symbols
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from gyomu_infra.filesystem.file_io import read_json, write_json
|
|
2
|
+
from gyomu_infra.logger import logger
|
|
3
|
+
from gyomu_schema.schemas.python.module import ModuleAnalysis
|
|
4
|
+
from gyomu_schema.schemas.python.types import ProjectRelativePath
|
|
5
|
+
from gyomu_schema.schemas.types import FullPath
|
|
6
|
+
from returns.result import Failure, Result, Success
|
|
7
|
+
|
|
8
|
+
from gyomu_python_analysis.analysis.load_module import load_module_analysis
|
|
9
|
+
from gyomu_python_analysis.error.analysis import AnalysisError
|
|
10
|
+
from gyomu_python_analysis.path.conversion import (
|
|
11
|
+
project_relative_path_to_source_relative_path,
|
|
12
|
+
source_relative_path_to_python_path,
|
|
13
|
+
)
|
|
14
|
+
from gyomu_python_analysis.project.context import ProjectContext
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_module_analysis(
|
|
18
|
+
context: ProjectContext, file_path: ProjectRelativePath
|
|
19
|
+
) -> Result[ModuleAnalysis, AnalysisError]:
|
|
20
|
+
cache_path = _get_cache_path(context, file_path)
|
|
21
|
+
if cache_path.exists():
|
|
22
|
+
cache_result = read_json(cache_path, ModuleAnalysis)
|
|
23
|
+
if isinstance(cache_result, Success):
|
|
24
|
+
return cache_result
|
|
25
|
+
else:
|
|
26
|
+
message = (
|
|
27
|
+
f"fail to parse ModuleAnalysis on {cache_path}, \n"
|
|
28
|
+
f"error: {repr(cache_result.failure())}"
|
|
29
|
+
)
|
|
30
|
+
logger.error(message)
|
|
31
|
+
|
|
32
|
+
source_relative_path = project_relative_path_to_source_relative_path(
|
|
33
|
+
path=file_path, context=context
|
|
34
|
+
)
|
|
35
|
+
module_path = source_relative_path_to_python_path(path=source_relative_path)
|
|
36
|
+
result = load_module_analysis(context, module_path)
|
|
37
|
+
if isinstance(result, Success):
|
|
38
|
+
write_result = write_json(cache_path, result.unwrap())
|
|
39
|
+
if isinstance(write_result, Failure):
|
|
40
|
+
return Failure(
|
|
41
|
+
AnalysisError(
|
|
42
|
+
"fail to write ModuleAnalysis",
|
|
43
|
+
file_path=module_path,
|
|
44
|
+
phase="post-analysis",
|
|
45
|
+
).chain(write_result.failure())
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_cache_path(
|
|
52
|
+
context: ProjectContext, source_path: ProjectRelativePath
|
|
53
|
+
) -> FullPath:
|
|
54
|
+
cache_root = context.project_root / ".gyomu" / "cache"
|
|
55
|
+
return cache_root / f"{source_path}.json"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from griffe import Module
|
|
4
|
+
from gyomu_schema.schemas.python.types import PythonPath, SourceRelativePath
|
|
5
|
+
from gyomu_schema.utility.returns import from_sync
|
|
6
|
+
from returns.result import Result
|
|
7
|
+
|
|
8
|
+
from gyomu_python_analysis.analysis.file.source_file_context import SourceFileContext
|
|
9
|
+
from gyomu_python_analysis.error.analysis import AnalysisError
|
|
10
|
+
from gyomu_python_analysis.project.context import ProjectContext
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_module(
|
|
14
|
+
context: ProjectContext, module_path: PythonPath
|
|
15
|
+
) -> Result[SourceFileContext, AnalysisError]:
|
|
16
|
+
|
|
17
|
+
def load_griffe() -> SourceFileContext:
|
|
18
|
+
|
|
19
|
+
module = context.loader.load(module_path)
|
|
20
|
+
if not isinstance(module, Module):
|
|
21
|
+
raise ValueError(f"Invalid Module Path: {module_path}")
|
|
22
|
+
full_path = module.filepath
|
|
23
|
+
if not isinstance(full_path, Path):
|
|
24
|
+
raise ValueError(f"module full path is Not Path: {full_path}")
|
|
25
|
+
source_path = full_path.relative_to(context.project_root / context.source_root)
|
|
26
|
+
return SourceFileContext(module=module, path=SourceRelativePath(source_path))
|
|
27
|
+
|
|
28
|
+
return from_sync(
|
|
29
|
+
load_griffe,
|
|
30
|
+
build_error=lambda e: AnalysisError(
|
|
31
|
+
message="fail to load source",
|
|
32
|
+
file_path=module_path,
|
|
33
|
+
phase="source-file-load",
|
|
34
|
+
context="gyomu_python_analysis.analysis.load_sourde_file",
|
|
35
|
+
).chain(e),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# def _get_module_name(
|
|
40
|
+
# source_file_path: SourceRelativePath,
|
|
41
|
+
# ) -> str:
|
|
42
|
+
# return source_file_path.with_suffix("").as_posix().replace("/", ".")
|