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
File without changes
File without changes
File without changes
@@ -0,0 +1,343 @@
1
+ from griffe import Attribute, Class, Function, TypeAlias
2
+ from gyomu_infra.logger import logger
3
+ from gyomu_schema.schemas.python.class_analysis import (
4
+ ClassAnalysis,
5
+ ClassCommon,
6
+ ClassTypeAliasAnalysis,
7
+ ClassVariableAnalysis,
8
+ InnerClassAnalysis,
9
+ )
10
+ from gyomu_schema.schemas.python.location import SourceLocation
11
+ from gyomu_schema.schemas.python.method_analysis import MethodAnalysis
12
+ from gyomu_schema.schemas.python.parameter import ParameterAnalysis
13
+ from gyomu_schema.schemas.python.pydantic import PydanticFieldAnalysis
14
+ from gyomu_schema.schemas.python.type.structure import NameStructureAnalysis
15
+ from gyomu_schema.schemas.python.type.type_analysis import TypeAnalysis
16
+
17
+ from gyomu_python_analysis.analysis.analyzers.context import (
18
+ MemberPath,
19
+ SymbolContext,
20
+ build_declaration_identity,
21
+ )
22
+ from gyomu_python_analysis.analysis.analyzers.expression.expr import (
23
+ analyze_type_expression,
24
+ )
25
+ from gyomu_python_analysis.analysis.analyzers.functions import (
26
+ _get_function_parameter_kind,
27
+ )
28
+ from gyomu_python_analysis.analysis.analyzers.internal.common import (
29
+ build_member_common,
30
+ build_symbol_common,
31
+ )
32
+ from gyomu_python_analysis.analysis.analyzers.pydantic import analyze_pydantic
33
+ from gyomu_python_analysis.analysis.analyzers.types import analyze_type
34
+
35
+
36
+ def _retrieve_constructor_location(
37
+ cls: Class,
38
+ context: SymbolContext,
39
+ ) -> SourceLocation | None:
40
+ constructor_location: SourceLocation | None = None
41
+ if "__init__" in cls.members:
42
+ init_member = cls.members["__init__"]
43
+ if isinstance(init_member, Function):
44
+ constructor_common = build_member_common(
45
+ symbol=init_member,
46
+ name="__init__",
47
+ parent_location=None,
48
+ context=context,
49
+ )
50
+ constructor_location = constructor_common["location"]
51
+ return constructor_location
52
+
53
+
54
+ def _build_class_type_aliases(
55
+ cls: Class,
56
+ parent_location: SourceLocation | None,
57
+ context: SymbolContext,
58
+ member_path: MemberPath,
59
+ ) -> list[ClassTypeAliasAnalysis]:
60
+ aliases: list[ClassTypeAliasAnalysis] = []
61
+ for member_name, member in cls.members.items():
62
+ if isinstance(member, TypeAlias):
63
+ aliases.append(
64
+ _build_class_type_alias_analysis(
65
+ member=member,
66
+ name=member_name,
67
+ parent_location=parent_location,
68
+ context=context,
69
+ member_path=member_path,
70
+ )
71
+ )
72
+ return aliases
73
+
74
+
75
+ def _build_class_type_alias_analysis(
76
+ member: TypeAlias,
77
+ name: str,
78
+ parent_location: SourceLocation | None,
79
+ context: SymbolContext,
80
+ member_path: MemberPath,
81
+ ) -> ClassTypeAliasAnalysis:
82
+ new_member_path = (*member_path, name)
83
+ alias_common = build_member_common(
84
+ symbol=member,
85
+ name=name,
86
+ parent_location=parent_location,
87
+ context=context,
88
+ )
89
+ return ClassTypeAliasAnalysis(
90
+ **alias_common,
91
+ alias_type=analyze_type(member.value, context),
92
+ identity=build_declaration_identity(
93
+ context=context, member_path=new_member_path
94
+ ),
95
+ )
96
+
97
+
98
+ def _build_class_variables(
99
+ cls: Class,
100
+ parent_location: SourceLocation | None,
101
+ context: SymbolContext,
102
+ member_path: MemberPath,
103
+ is_pydantic_base_class: bool,
104
+ ) -> list[ClassVariableAnalysis]:
105
+ variables: list[ClassVariableAnalysis] = []
106
+ for member_name, member in cls.members.items():
107
+ if isinstance(member, Attribute):
108
+ variables.append(
109
+ _build_class_variable_analysis(
110
+ member=member,
111
+ name=member_name,
112
+ parent_location=parent_location,
113
+ context=context,
114
+ member_path=member_path,
115
+ is_pydantic_base_class=is_pydantic_base_class,
116
+ )
117
+ )
118
+ return variables
119
+
120
+
121
+ def _build_class_variable_analysis(
122
+ member: Attribute,
123
+ name: str,
124
+ parent_location: SourceLocation | None,
125
+ context: SymbolContext,
126
+ member_path: MemberPath,
127
+ is_pydantic_base_class: bool,
128
+ ) -> ClassVariableAnalysis:
129
+ new_member_path = (*member_path, name)
130
+ variable_common = build_member_common(
131
+ symbol=member,
132
+ name=name,
133
+ parent_location=parent_location,
134
+ context=context,
135
+ )
136
+ variable_type = analyze_type(member.annotation, context)
137
+ value_expression = (
138
+ analyze_type_expression(member.value, context)
139
+ if member.value is not None
140
+ else None
141
+ )
142
+ pydantic: PydanticFieldAnalysis | None = None
143
+ logger.info(f"pydantic_base:{is_pydantic_base_class}")
144
+ if (
145
+ value_expression
146
+ and variable_type
147
+ and variable_type.structure
148
+ and is_pydantic_base_class
149
+ ):
150
+ print(repr(variable_type.structure))
151
+ print(repr(value_expression))
152
+ pydantic = analyze_pydantic(variable_type.structure, value_expression)
153
+
154
+ return ClassVariableAnalysis(
155
+ **variable_common,
156
+ type=variable_type,
157
+ value_source=str(member.value) if member.value is not None else None,
158
+ value_expression=analyze_type_expression(member.value, context)
159
+ if member.value is not None
160
+ else None,
161
+ pydantic=pydantic,
162
+ identity=build_declaration_identity(
163
+ context=context, member_path=new_member_path
164
+ ),
165
+ )
166
+
167
+
168
+ def _build_class_method_analysis(
169
+ member: Function,
170
+ name: str,
171
+ parent_location: SourceLocation | None,
172
+ context: SymbolContext,
173
+ member_path: MemberPath,
174
+ ) -> MethodAnalysis:
175
+ new_member_path = (*member_path, name)
176
+ method_parameters: list[ParameterAnalysis] = []
177
+ for param in member.parameters:
178
+ method_parameters.append(
179
+ ParameterAnalysis(
180
+ name=param.name,
181
+ kind=_get_function_parameter_kind(param.kind),
182
+ type=analyze_type(param.annotation, context),
183
+ default=None,
184
+ )
185
+ )
186
+ method_common = build_member_common(
187
+ symbol=member,
188
+ name=name,
189
+ parent_location=parent_location,
190
+ context=context,
191
+ )
192
+
193
+ return MethodAnalysis(
194
+ **method_common,
195
+ parameters=tuple(method_parameters),
196
+ return_type=analyze_type(member.returns, context),
197
+ is_async="async" in member.labels,
198
+ identity=build_declaration_identity(
199
+ context=context, member_path=new_member_path
200
+ ),
201
+ )
202
+
203
+
204
+ def _build_class_methods(
205
+ cls: Class,
206
+ parent_location: SourceLocation | None,
207
+ context: SymbolContext,
208
+ member_path: MemberPath,
209
+ ) -> list[MethodAnalysis]:
210
+ methods: list[MethodAnalysis] = []
211
+ for member_name, member in cls.members.items():
212
+ if isinstance(member, Function):
213
+ methods.append(
214
+ _build_class_method_analysis(
215
+ member=member,
216
+ name=member_name,
217
+ parent_location=parent_location,
218
+ context=context,
219
+ member_path=member_path,
220
+ )
221
+ )
222
+ return methods
223
+
224
+
225
+ def _build_inner_classes(
226
+ cls: Class, context: SymbolContext, member_path: MemberPath
227
+ ) -> list[InnerClassAnalysis]:
228
+ inner_classes: list[InnerClassAnalysis] = []
229
+ for member_name, member in cls.members.items():
230
+ if isinstance(member, Class):
231
+ inner_classes.append(
232
+ _analyze_inner_class(
233
+ cls=member,
234
+ name=member_name,
235
+ context=context,
236
+ member_path=member_path,
237
+ )
238
+ )
239
+ return inner_classes
240
+
241
+
242
+ def _is_pydantic_base_class(bases: list[TypeAnalysis]) -> bool:
243
+ for base in bases:
244
+ if (
245
+ isinstance(base.structure, NameStructureAnalysis)
246
+ and base.structure.name == "BaseModel"
247
+ ):
248
+ return True
249
+
250
+ return False
251
+
252
+
253
+ def _analyze_class_common(
254
+ cls: Class,
255
+ name: str,
256
+ context: SymbolContext,
257
+ member_path: MemberPath,
258
+ ) -> ClassCommon:
259
+ bases: list[TypeAnalysis] = [
260
+ analyzed
261
+ for base in cls.bases
262
+ if (analyzed := analyze_type(base, context)) is not None
263
+ ]
264
+
265
+ is_pydantic_base_class = _is_pydantic_base_class(bases)
266
+
267
+ constructor_location: SourceLocation | None = _retrieve_constructor_location(
268
+ cls, context
269
+ )
270
+
271
+ parameters: list[ClassVariableAnalysis] = _build_class_variables(
272
+ cls=cls,
273
+ parent_location=constructor_location,
274
+ context=context,
275
+ member_path=member_path,
276
+ is_pydantic_base_class=is_pydantic_base_class,
277
+ )
278
+
279
+ methods: list[MethodAnalysis] = _build_class_methods(
280
+ cls=cls,
281
+ parent_location=constructor_location,
282
+ context=context,
283
+ member_path=member_path,
284
+ )
285
+
286
+ type_aliases: list[ClassTypeAliasAnalysis] = _build_class_type_aliases(
287
+ cls=cls,
288
+ parent_location=constructor_location,
289
+ context=context,
290
+ member_path=member_path,
291
+ )
292
+
293
+ inner_classes: list[InnerClassAnalysis] = _build_inner_classes(
294
+ cls=cls, context=context, member_path=member_path
295
+ )
296
+
297
+ return {
298
+ "bases": tuple(bases),
299
+ "inner_classes": tuple(inner_classes),
300
+ "methods": tuple(methods),
301
+ "variables": tuple(parameters),
302
+ "type_aliases": tuple(type_aliases),
303
+ }
304
+
305
+
306
+ def _analyze_inner_class(
307
+ cls: Class,
308
+ name: str,
309
+ context: SymbolContext,
310
+ member_path: MemberPath,
311
+ ) -> InnerClassAnalysis:
312
+ new_member_path = (*member_path, name)
313
+ class_common = _analyze_class_common(
314
+ cls, name, context, member_path=new_member_path
315
+ )
316
+ # pprint(cls.as_dict())
317
+ base_common = build_member_common(
318
+ symbol=cls,
319
+ name=name,
320
+ parent_location=None,
321
+ context=context,
322
+ )
323
+ return InnerClassAnalysis(
324
+ **base_common,
325
+ **class_common,
326
+ identity=build_declaration_identity(
327
+ context=context, member_path=new_member_path
328
+ ),
329
+ )
330
+
331
+
332
+ def analyze_class(cls: Class, name: str, context: SymbolContext) -> ClassAnalysis:
333
+ member_path: MemberPath = ()
334
+ class_common = _analyze_class_common(cls, name, context, member_path)
335
+ # pprint(cls.as_dict())
336
+ base_common = build_symbol_common(symbol=cls, name=name, context=context)
337
+
338
+ return ClassAnalysis(
339
+ **base_common,
340
+ **class_common,
341
+ dependencies=tuple(),
342
+ identity=context.declaration,
343
+ )
@@ -0,0 +1,66 @@
1
+ from dataclasses import dataclass
2
+
3
+ from gyomu_schema.schemas.python.types import (
4
+ DeclarationId,
5
+ DeclarationIdentity,
6
+ PythonPath,
7
+ SymbolId,
8
+ )
9
+
10
+
11
+ @dataclass
12
+ class DependencyInformation:
13
+ source: DeclarationIdentity
14
+ target_name: str
15
+
16
+
17
+ @dataclass
18
+ class SymbolContext:
19
+ dependencies: list[DependencyInformation]
20
+ declaration: DeclarationIdentity
21
+ source_lines: list[str]
22
+ line_start_offsets: list[int]
23
+
24
+
25
+ type MemberPath = tuple[str, ...]
26
+
27
+
28
+ def initialize_symbol_context(
29
+ module_name: PythonPath, name: str, source_lines: list[str]
30
+ ) -> SymbolContext:
31
+ symbol_id = build_symbol_id(module_name=module_name, name=name)
32
+ line_start_offsets = [0]
33
+ for line in source_lines[:-1]:
34
+ line_start_offsets.append(
35
+ line_start_offsets[-1] + len(line),
36
+ )
37
+ return SymbolContext(
38
+ dependencies=[],
39
+ declaration=DeclarationIdentity(
40
+ symbol_id=symbol_id,
41
+ declaration_id=_build_declaration_id(tuple()),
42
+ ),
43
+ source_lines=source_lines,
44
+ line_start_offsets=line_start_offsets,
45
+ )
46
+
47
+
48
+ def build_symbol_id(module_name: PythonPath, name: str) -> SymbolId:
49
+ if name == "":
50
+ return SymbolId(f"{module_name}")
51
+ return SymbolId(f"{module_name}::{name}")
52
+
53
+
54
+ def _build_declaration_id(member_path: MemberPath) -> DeclarationId:
55
+ if not member_path:
56
+ return DeclarationId(".")
57
+ return DeclarationId(".::" + "::".join(member_path))
58
+
59
+
60
+ def build_declaration_identity(
61
+ context: SymbolContext, member_path: MemberPath
62
+ ) -> DeclarationIdentity:
63
+ return DeclarationIdentity(
64
+ symbol_id=context.declaration.symbol_id,
65
+ declaration_id=_build_declaration_id(member_path),
66
+ )
@@ -0,0 +1,88 @@
1
+ from griffe import Decorator
2
+ from gyomu_infra.logger import logger
3
+ from gyomu_schema.schemas.python.decorator import DecoratorAnalysis, DecoratorArgument
4
+ from gyomu_schema.schemas.python.type.structure import (
5
+ LiteralValue,
6
+ NameStructureAnalysis,
7
+ )
8
+ from gyomu_schema.schemas.python.type.type_analysis import (
9
+ AttributeStructureAnalysis,
10
+ CallStructureAnalysis,
11
+ ExpressionAnalysis,
12
+ KeywordStructureAnalysis,
13
+ TypeExpression,
14
+ )
15
+
16
+ from gyomu_python_analysis.analysis.analyzers.context import SymbolContext
17
+ from gyomu_python_analysis.analysis.analyzers.expression.expr import (
18
+ analyze_type_expression,
19
+ )
20
+ from gyomu_python_analysis.analysis.analyzers.internal.location import (
21
+ calculate_decorator_location,
22
+ )
23
+
24
+
25
+ def analyze_decorators(
26
+ decorators: list[Decorator], context: SymbolContext
27
+ ) -> list[DecoratorAnalysis]:
28
+ returns: list[DecoratorAnalysis] = []
29
+ for dec in decorators:
30
+ returns.append(analyze_decorator(dec, context))
31
+
32
+ return returns
33
+
34
+
35
+ def analyze_decorator(
36
+ decorator: Decorator, context: SymbolContext
37
+ ) -> DecoratorAnalysis:
38
+ value = analyze_type_expression(decorator.value, context)
39
+ name: str
40
+ arguments: list[DecoratorArgument] = []
41
+ if isinstance(value, LiteralValue):
42
+ name = str(value.value)
43
+
44
+ else:
45
+ name, arguments = _retrieve_expression_name(value)
46
+
47
+ return DecoratorAnalysis(
48
+ location=calculate_decorator_location(decorator=decorator, context=context),
49
+ name=name,
50
+ arguments=tuple(arguments),
51
+ )
52
+
53
+
54
+ def _retrieve_expression_name(
55
+ expression: ExpressionAnalysis,
56
+ ) -> tuple[str, list[DecoratorArgument]]:
57
+ if isinstance(expression, NameStructureAnalysis):
58
+ return expression.name, []
59
+ elif isinstance(expression, AttributeStructureAnalysis):
60
+ return ".".join(_retrieve_attribute_names(expression)), []
61
+ elif isinstance(expression, CallStructureAnalysis):
62
+ name, _ = _retrieve_expression_name(expression.function)
63
+ arguments: list[DecoratorArgument] = []
64
+ for arg in expression.arguments:
65
+ arguments.append(_retrieve_expression_argument(arg))
66
+ return name, arguments
67
+ else:
68
+ logger.error(f"Unsupported Expression: {expression.kind}")
69
+ return "", []
70
+
71
+
72
+ def _retrieve_expression_argument(expression: TypeExpression) -> DecoratorArgument:
73
+ if isinstance(expression, KeywordStructureAnalysis):
74
+ return DecoratorArgument(expression=expression.value, name=expression.name)
75
+ return DecoratorArgument(expression=expression)
76
+
77
+
78
+ def _retrieve_attribute_names(attribute: AttributeStructureAnalysis) -> list[str]:
79
+ strings: list[str] = []
80
+ for value in attribute.values:
81
+ if isinstance(value, LiteralValue):
82
+ strings.append(str(value.value))
83
+ elif isinstance(value, NameStructureAnalysis):
84
+ strings.append(value.name)
85
+ elif isinstance(value, AttributeStructureAnalysis):
86
+ for child in _retrieve_attribute_names(value):
87
+ strings.append(child)
88
+ return strings
@@ -0,0 +1,134 @@
1
+ from gyomu_schema.schemas.python.dependency import (
2
+ DependencyAnalysis,
3
+ ImportedSymbolDependency,
4
+ LocalFileDependency,
5
+ )
6
+ from gyomu_schema.schemas.python.import_analysis import ImportAnalysis, ImportKind
7
+ from gyomu_schema.schemas.python.symbol import SymbolAnalysis
8
+ from gyomu_schema.schemas.python.types import DeclarationIdentity, SymbolId
9
+
10
+ from gyomu_python_analysis.analysis.analyzers.context import (
11
+ DependencyInformation,
12
+ SymbolContext,
13
+ )
14
+
15
+ PYTHON_RESERVED_TYPE_NAMES: frozenset[str] = frozenset(
16
+ {
17
+ # Built-in scalar types
18
+ "bool",
19
+ "int",
20
+ "float",
21
+ "complex",
22
+ "str",
23
+ "bytes",
24
+ "bytearray",
25
+ "memoryview",
26
+ # Built-in container types
27
+ "list",
28
+ "tuple",
29
+ "dict",
30
+ "set",
31
+ "frozenset",
32
+ # Built-in utility types
33
+ "range",
34
+ "object",
35
+ "type",
36
+ # Typing primitives
37
+ "Any",
38
+ "Never",
39
+ "NoReturn",
40
+ "Literal",
41
+ "Union",
42
+ "Optional",
43
+ "Annotated",
44
+ "Final",
45
+ "ClassVar",
46
+ "Type",
47
+ "Callable",
48
+ "TypeVar",
49
+ "Generic",
50
+ "Protocol",
51
+ "Self",
52
+ }
53
+ )
54
+
55
+
56
+ def register_dependency(
57
+ identity: DeclarationIdentity, name: str, context: SymbolContext
58
+ ) -> None:
59
+ if name in PYTHON_RESERVED_TYPE_NAMES:
60
+ return
61
+
62
+ context.dependencies.append(
63
+ DependencyInformation(source=identity, target_name=name)
64
+ )
65
+
66
+
67
+ def _find_imported(
68
+ name: str,
69
+ imported: list[ImportAnalysis],
70
+ ) -> ImportAnalysis | None:
71
+ for item in imported:
72
+ if item.local_name == name:
73
+ return item
74
+ return None
75
+
76
+
77
+ def _retrieve_imported_symbol_id(imported_item: ImportAnalysis) -> SymbolId:
78
+ if imported_item.kind == ImportKind.MODULE:
79
+ return SymbolId(imported_item.imported_name)
80
+ module_name, symbol_name = imported_item.imported_name.rsplit(".", 1)
81
+ return SymbolId(f"{module_name}::{symbol_name}")
82
+
83
+
84
+ def _find_symbol(
85
+ name: str,
86
+ symbols: list[SymbolAnalysis],
87
+ ) -> SymbolAnalysis | None:
88
+ for symbol in symbols:
89
+ if symbol.name == name:
90
+ return symbol
91
+
92
+ return None
93
+
94
+
95
+ def analyze_dependency(
96
+ record: DependencyInformation,
97
+ imported: list[ImportAnalysis],
98
+ symbols: list[SymbolAnalysis],
99
+ ) -> DependencyAnalysis | None:
100
+ symbol_id: SymbolId
101
+ if imported_item := _find_imported(record.target_name, imported):
102
+ symbol_id = _retrieve_imported_symbol_id(imported_item)
103
+ return DependencyAnalysis(
104
+ source=record.source, target=ImportedSymbolDependency(symbol_id=symbol_id)
105
+ )
106
+ else:
107
+ result = _find_symbol(record.target_name, symbols)
108
+ if result is None:
109
+ return None
110
+ symbol_id = result.identity.symbol_id
111
+ return DependencyAnalysis(
112
+ source=record.source, target=LocalFileDependency(symbol_id=symbol_id)
113
+ )
114
+
115
+
116
+ def resolve_dependencies(
117
+ dependencies: list[DependencyInformation],
118
+ imported: list[ImportAnalysis],
119
+ symbols: list[SymbolAnalysis],
120
+ ) -> dict[DeclarationIdentity, tuple[DependencyAnalysis, ...]]:
121
+ result: dict[
122
+ DeclarationIdentity,
123
+ list[DependencyAnalysis],
124
+ ] = {}
125
+
126
+ for dependency in dependencies:
127
+ parsed = analyze_dependency(dependency, imported, symbols)
128
+
129
+ if parsed is None:
130
+ continue
131
+
132
+ result.setdefault(parsed.source, []).append(parsed)
133
+
134
+ return {source: tuple(items) for source, items in result.items()}