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.
Files changed (42) hide show
  1. gyomu_python_analysis/__init__.py +0 -0
  2. gyomu_python_analysis/analysis/__init__.py +0 -0
  3. gyomu_python_analysis/analysis/analyzers/__init__.py +0 -0
  4. gyomu_python_analysis/analysis/analyzers/cls.py +343 -0
  5. gyomu_python_analysis/analysis/analyzers/context.py +66 -0
  6. gyomu_python_analysis/analysis/analyzers/decorator.py +88 -0
  7. gyomu_python_analysis/analysis/analyzers/dependency.py +134 -0
  8. gyomu_python_analysis/analysis/analyzers/docstring.py +288 -0
  9. gyomu_python_analysis/analysis/analyzers/expression/__init__.py +0 -0
  10. gyomu_python_analysis/analysis/analyzers/expression/expr.py +400 -0
  11. gyomu_python_analysis/analysis/analyzers/expression/name.py +38 -0
  12. gyomu_python_analysis/analysis/analyzers/functions.py +53 -0
  13. gyomu_python_analysis/analysis/analyzers/imports.py +21 -0
  14. gyomu_python_analysis/analysis/analyzers/internal/__init__.py +0 -0
  15. gyomu_python_analysis/analysis/analyzers/internal/common.py +87 -0
  16. gyomu_python_analysis/analysis/analyzers/internal/location.py +68 -0
  17. gyomu_python_analysis/analysis/analyzers/internal/visibility.py +27 -0
  18. gyomu_python_analysis/analysis/analyzers/pydantic.py +62 -0
  19. gyomu_python_analysis/analysis/analyzers/type_alias.py +24 -0
  20. gyomu_python_analysis/analysis/analyzers/types.py +29 -0
  21. gyomu_python_analysis/analysis/analyzers/variables.py +22 -0
  22. gyomu_python_analysis/analysis/extract/__init__.py +0 -0
  23. gyomu_python_analysis/analysis/extract/symbols.py +118 -0
  24. gyomu_python_analysis/analysis/file/__init__.py +0 -0
  25. gyomu_python_analysis/analysis/file/source_file_context.py +10 -0
  26. gyomu_python_analysis/analysis/get_module.py +55 -0
  27. gyomu_python_analysis/analysis/load.py +42 -0
  28. gyomu_python_analysis/analysis/load_file_context.py +27 -0
  29. gyomu_python_analysis/analysis/load_module.py +65 -0
  30. gyomu_python_analysis/analysis/metadata.py +49 -0
  31. gyomu_python_analysis/analysis/parser/__init__.py +0 -0
  32. gyomu_python_analysis/analysis/parser/docstring.py +7 -0
  33. gyomu_python_analysis/error/__init__.py +0 -0
  34. gyomu_python_analysis/error/analysis.py +36 -0
  35. gyomu_python_analysis/error/update.py +32 -0
  36. gyomu_python_analysis/path/__init__.py +0 -0
  37. gyomu_python_analysis/path/conversion.py +25 -0
  38. gyomu_python_analysis/project/__init__.py +0 -0
  39. gyomu_python_analysis/project/context.py +18 -0
  40. gyomu_python_analysis-0.2.0.dist-info/METADATA +15 -0
  41. gyomu_python_analysis-0.2.0.dist-info/RECORD +42 -0
  42. 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)
@@ -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,10 @@
1
+ from dataclasses import dataclass
2
+
3
+ from griffe import Module
4
+ from gyomu_schema.schemas.python.types import SourceRelativePath
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class SourceFileContext:
9
+ module: Module
10
+ path: SourceRelativePath
@@ -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("/", ".")