py-flow-mapper 0.1.0b6.dev0__tar.gz → 0.1.0b7.dev0__tar.gz
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.
- {py_flow_mapper-0.1.0b6.dev0/src/py_flow_mapper.egg-info → py_flow_mapper-0.1.0b7.dev0}/PKG-INFO +1 -1
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/setup.py +1 -1
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/__init__.py +1 -1
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/analyzer.py +12 -9
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/mermaid_generator.py +41 -56
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0/src/py_flow_mapper.egg-info}/PKG-INFO +1 -1
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/LICENSE +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/README.md +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/setup.cfg +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/cli.py +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/utils.py +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/SOURCES.txt +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/dependency_links.txt +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/entry_points.txt +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/requires.txt +0 -0
- {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="py-flow-mapper",
|
|
8
|
-
version="0.1.
|
|
8
|
+
version="0.1.0b7.dev",
|
|
9
9
|
author="Arun Koundinya Parasa",
|
|
10
10
|
author_email="parasa.arunkoundinya@gmail.com",
|
|
11
11
|
description="Python project analyzer and visualization tool",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
PyFlowMapper - A Python project analyzer and visualization tool.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "0.1.
|
|
5
|
+
__version__ = "0.1.0b7.dev"
|
|
6
6
|
__author__ = "Arun Koundinya Parasa"
|
|
7
7
|
__description__ = "Analyze Python projects and generate dependency graphs"
|
|
8
8
|
__github__ = "https://github.com/ArunKoundinya/py-flow-mapper"
|
|
@@ -521,21 +521,24 @@ class DataFlowAnalyzer(ast.NodeVisitor):
|
|
|
521
521
|
|
|
522
522
|
def visit_Assign(self, node):
|
|
523
523
|
"""Track assignments of function return values."""
|
|
524
|
-
|
|
524
|
+
# Get the variable name being assigned to
|
|
525
|
+
if isinstance(node.targets[0], ast.Name):
|
|
525
526
|
var_name = node.targets[0].id
|
|
526
|
-
|
|
527
|
+
|
|
528
|
+
# Check if the value is a function call
|
|
527
529
|
if isinstance(node.value, ast.Call):
|
|
528
530
|
call_info = self._extract_call_info(node.value)
|
|
529
531
|
if call_info:
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
532
|
+
self.calls.append(call_info['func_name'])
|
|
533
|
+
if var_name not in self.return_assignments:
|
|
534
|
+
self.return_assignments[var_name] = []
|
|
535
|
+
self.return_assignments[var_name].append(call_info['func_name'])
|
|
536
|
+
|
|
537
|
+
# Check if the value is a Name (could be a variable holding a return value)
|
|
536
538
|
elif isinstance(node.value, ast.Name):
|
|
539
|
+
# Track variable assignments for data flow
|
|
537
540
|
pass
|
|
538
|
-
|
|
541
|
+
|
|
539
542
|
self.generic_visit(node)
|
|
540
543
|
|
|
541
544
|
def visit_Call(self, node):
|
{py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/mermaid_generator.py
RENAMED
|
@@ -71,16 +71,23 @@ class MermaidGenerator:
|
|
|
71
71
|
return ""
|
|
72
72
|
return ",".join(sorted(set(cleaned)))
|
|
73
73
|
|
|
74
|
+
current_module_ctx = ""
|
|
74
75
|
|
|
75
|
-
def module_import_mapping(
|
|
76
|
-
return (modules.get(
|
|
76
|
+
def module_import_mapping() -> dict:
|
|
77
|
+
return (modules.get(current_module_ctx, {}) or {}).get("import_mapping", {}) or {}
|
|
77
78
|
|
|
78
|
-
def external_root_name(call_name: str
|
|
79
|
+
def external_root_name(call_name: str) -> str:
|
|
79
80
|
if not call_name:
|
|
80
81
|
return ""
|
|
82
|
+
|
|
81
83
|
root = call_name.split(".")[0]
|
|
82
|
-
|
|
84
|
+
|
|
85
|
+
# module-specific import mapping (from analyzer)
|
|
86
|
+
imp_map = module_import_mapping() or {}
|
|
87
|
+
|
|
88
|
+
# merge common aliases (module imports override defaults)
|
|
83
89
|
merged_map = {**COMMON_ALIAS_MAP, **imp_map}
|
|
90
|
+
|
|
84
91
|
return merged_map.get(root, root)
|
|
85
92
|
|
|
86
93
|
# Keep the graph clean: ignore common builtins + attribute-noise
|
|
@@ -92,74 +99,52 @@ class MermaidGenerator:
|
|
|
92
99
|
"Path",
|
|
93
100
|
}
|
|
94
101
|
|
|
95
|
-
def keep_external(call_name: str
|
|
102
|
+
def keep_external(call_name: str) -> bool:
|
|
96
103
|
if not call_name:
|
|
97
104
|
return False
|
|
98
105
|
|
|
99
106
|
base = short_label(call_name)
|
|
100
107
|
|
|
101
|
-
|
|
108
|
+
# forced external always wins
|
|
109
|
+
root = external_root_name(call_name)
|
|
102
110
|
if root and root in self.force_external:
|
|
103
111
|
return True
|
|
104
112
|
if base in self.force_external:
|
|
105
113
|
return True
|
|
106
114
|
|
|
107
|
-
|
|
115
|
+
# ✅ NEW: module-specific import mapping
|
|
116
|
+
imp_map = module_import_mapping() or {}
|
|
108
117
|
if base in imp_map:
|
|
109
118
|
mapped = imp_map[base]
|
|
110
119
|
mapped_module = ".".join(mapped.split(".")[:-1])
|
|
120
|
+
|
|
111
121
|
if mapped_module in modules:
|
|
112
122
|
return False
|
|
123
|
+
|
|
113
124
|
return True
|
|
114
125
|
|
|
126
|
+
# internal class => NOT external
|
|
115
127
|
if base in internal_classes:
|
|
116
128
|
return False
|
|
117
129
|
|
|
118
130
|
if base in NOISY_EXTERNAL:
|
|
119
131
|
return False
|
|
120
132
|
|
|
133
|
+
# dotted calls like obj.method: too noisy at this level
|
|
121
134
|
if "." in call_name:
|
|
122
135
|
return False
|
|
123
136
|
|
|
137
|
+
# keep CamelCase (tool-ish classes / constructors)
|
|
124
138
|
return is_camel_case(base)
|
|
125
139
|
|
|
126
140
|
def resolve_internal(call: str, current_module: str) -> str:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# 1) existing resolver
|
|
141
|
+
# First try your normal resolver
|
|
130
142
|
target = self._find_function_full_name(call, current_module)
|
|
131
143
|
if target and target in internal_funcs:
|
|
132
144
|
return target
|
|
133
145
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# 2) If call is a bare name imported via "from X import name"
|
|
137
|
-
# import_mapping might contain: name -> "pkg.mod.name"
|
|
138
|
-
if call in imp_map:
|
|
139
|
-
mapped = imp_map[call]
|
|
140
|
-
if mapped in internal_funcs:
|
|
141
|
-
return mapped
|
|
142
|
-
# if mapped is "pkg.mod.name", try resolving it (or its tail)
|
|
143
|
-
target = self._find_function_full_name(mapped, current_module)
|
|
144
|
-
if target and target in internal_funcs:
|
|
145
|
-
return target
|
|
146
|
-
|
|
147
|
-
# 3) If call is "alias.something", rewrite alias using import_mapping
|
|
148
|
-
# import_mapping might contain: alias -> "pkg.mod"
|
|
149
|
-
if "." in call:
|
|
150
|
-
root, rest = call.split(".", 1)
|
|
151
|
-
if root in imp_map:
|
|
152
|
-
mapped_root = imp_map[root]
|
|
153
|
-
# If mapping is to a specific object like "pkg.mod.func",
|
|
154
|
-
# treat it as root too (best-effort)
|
|
155
|
-
candidate = f"{mapped_root}.{rest}"
|
|
156
|
-
if candidate in internal_funcs:
|
|
157
|
-
return candidate
|
|
158
|
-
target = self._find_function_full_name(candidate, current_module)
|
|
159
|
-
if target and target in internal_funcs:
|
|
160
|
-
return target
|
|
161
|
-
|
|
162
|
-
# 4) your existing "obj.method" heuristic
|
|
146
|
+
# ✅ NEW: if call is like "obj.method", try to map by method name
|
|
147
|
+
if "." in (call or ""):
|
|
163
148
|
method = call.split(".")[-1]
|
|
164
149
|
matches = [k for k in internal_funcs if k.endswith("." + method)]
|
|
165
150
|
if len(matches) == 1:
|
|
@@ -189,7 +174,7 @@ class MermaidGenerator:
|
|
|
189
174
|
|
|
190
175
|
info = function_map.get(fn_key, {})
|
|
191
176
|
current_module = info.get("module", "") or ""
|
|
192
|
-
|
|
177
|
+
current_module_ctx = current_module
|
|
193
178
|
for c in (info.get("calls") or []):
|
|
194
179
|
target = self._find_function_full_name(c, current_module)
|
|
195
180
|
if target and target in function_map:
|
|
@@ -225,15 +210,15 @@ class MermaidGenerator:
|
|
|
225
210
|
|
|
226
211
|
for _, info in function_map.items():
|
|
227
212
|
current_module = info.get("module", "") or ""
|
|
228
|
-
|
|
213
|
+
current_module_ctx = current_module
|
|
229
214
|
|
|
230
215
|
# prefer call_arguments keys
|
|
231
216
|
call_args = info.get("call_arguments", {}) or {}
|
|
232
217
|
for callee in call_args.keys():
|
|
233
218
|
if resolve_internal(callee, current_module):
|
|
234
219
|
continue
|
|
235
|
-
if keep_external(callee
|
|
236
|
-
external_nodes.add(external_root_name(callee
|
|
220
|
+
if keep_external(callee):
|
|
221
|
+
external_nodes.add(external_root_name(callee) or callee)
|
|
237
222
|
|
|
238
223
|
# scan raw calls for Done + missed externals
|
|
239
224
|
for callee in (info.get("calls") or []):
|
|
@@ -242,8 +227,8 @@ class MermaidGenerator:
|
|
|
242
227
|
|
|
243
228
|
if resolve_internal(callee, current_module):
|
|
244
229
|
continue
|
|
245
|
-
if keep_external(callee
|
|
246
|
-
external_nodes.add(external_root_name(callee
|
|
230
|
+
if keep_external(callee):
|
|
231
|
+
external_nodes.add(external_root_name(callee) or callee)
|
|
247
232
|
|
|
248
233
|
if external_nodes or uses_done:
|
|
249
234
|
lines.append(" subgraph External [External]")
|
|
@@ -262,7 +247,7 @@ class MermaidGenerator:
|
|
|
262
247
|
|
|
263
248
|
info = function_map[caller]
|
|
264
249
|
current_module = info.get("module", "") or ""
|
|
265
|
-
|
|
250
|
+
current_module_ctx = current_module
|
|
266
251
|
src = nid(caller)
|
|
267
252
|
|
|
268
253
|
call_args = info.get("call_arguments", {}) or {}
|
|
@@ -289,8 +274,8 @@ class MermaidGenerator:
|
|
|
289
274
|
else:
|
|
290
275
|
lines.append(f" {src} --> {nid(target_internal)}")
|
|
291
276
|
else:
|
|
292
|
-
if keep_external(callee
|
|
293
|
-
ext = external_root_name(callee
|
|
277
|
+
if keep_external(callee):
|
|
278
|
+
ext = external_root_name(callee) or callee
|
|
294
279
|
edge_key = (src, nid("ext:" + callee), label)
|
|
295
280
|
if edge_key in seen:
|
|
296
281
|
continue
|
|
@@ -309,7 +294,7 @@ class MermaidGenerator:
|
|
|
309
294
|
info = function_map[fn]
|
|
310
295
|
dst = nid(fn)
|
|
311
296
|
current_module = info.get("module", "") or ""
|
|
312
|
-
|
|
297
|
+
current_module_ctx = current_module
|
|
313
298
|
|
|
314
299
|
return_assignments = info.get("return_assignments", {}) or {}
|
|
315
300
|
for var_name, producers in return_assignments.items():
|
|
@@ -318,20 +303,20 @@ class MermaidGenerator:
|
|
|
318
303
|
if internal_p:
|
|
319
304
|
lines.append(f" {nid(internal_p)} -.->|{var_name}| {dst}")
|
|
320
305
|
else:
|
|
321
|
-
if keep_external(p
|
|
322
|
-
ext = external_root_name(p
|
|
306
|
+
if keep_external(p):
|
|
307
|
+
ext = external_root_name(p) or p
|
|
323
308
|
lines.append(f" {nid('ext:' + ext)} -.->|{var_name}| {dst}")
|
|
324
309
|
|
|
325
310
|
# ---------- pipeline heuristic: external tool A -> external tool B ----------
|
|
326
311
|
for _, info in function_map.items():
|
|
327
312
|
current_module = info.get("module", "") or ""
|
|
328
|
-
|
|
313
|
+
current_module_ctx = current_module
|
|
329
314
|
calls = info.get("calls") or []
|
|
330
315
|
call_args = info.get("call_arguments") or {}
|
|
331
316
|
|
|
332
317
|
meaningful = []
|
|
333
318
|
for c in calls:
|
|
334
|
-
if c and keep_external(c
|
|
319
|
+
if c and keep_external(c) and not resolve_internal(c, current_module):
|
|
335
320
|
if c not in meaningful:
|
|
336
321
|
meaningful.append(c)
|
|
337
322
|
|
|
@@ -340,7 +325,7 @@ class MermaidGenerator:
|
|
|
340
325
|
|
|
341
326
|
fileish_callee = None
|
|
342
327
|
for callee, args in call_args.items():
|
|
343
|
-
if keep_external(callee
|
|
328
|
+
if keep_external(callee) and any(is_fileish_arg(a) for a in (args or []) if isinstance(a, str)):
|
|
344
329
|
fileish_callee = callee
|
|
345
330
|
break
|
|
346
331
|
|
|
@@ -353,8 +338,8 @@ class MermaidGenerator:
|
|
|
353
338
|
|
|
354
339
|
vars_passed = call_args.get(fileish_callee, []) or []
|
|
355
340
|
label = normalize_vars(vars_passed) or "value"
|
|
356
|
-
prod_ext = external_root_name(producer
|
|
357
|
-
file_ext = external_root_name(fileish_callee
|
|
341
|
+
prod_ext = external_root_name(producer) or producer
|
|
342
|
+
file_ext = external_root_name(fileish_callee) or fileish_callee
|
|
358
343
|
lines.append(f" {nid('ext:' + prod_ext)} -->|{label}| {nid('ext:' + file_ext)}")
|
|
359
344
|
|
|
360
345
|
# ---------- Done edge ----------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|