py-flow-mapper 0.1.0b5.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.0b5.dev0/src/py_flow_mapper.egg-info → py_flow_mapper-0.1.0b7.dev0}/PKG-INFO +1 -1
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/setup.py +1 -1
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/__init__.py +1 -1
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/analyzer.py +12 -9
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/mermaid_generator.py +38 -24
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/utils.py +1 -1
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0/src/py_flow_mapper.egg-info}/PKG-INFO +1 -1
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/LICENSE +0 -0
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/README.md +0 -0
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/setup.cfg +0 -0
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/cli.py +0 -0
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/SOURCES.txt +0 -0
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/dependency_links.txt +0 -0
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/entry_points.txt +0 -0
- {py_flow_mapper-0.1.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/requires.txt +0 -0
- {py_flow_mapper-0.1.0b5.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.0b5.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,35 +99,42 @@ 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:
|
|
@@ -160,7 +174,7 @@ class MermaidGenerator:
|
|
|
160
174
|
|
|
161
175
|
info = function_map.get(fn_key, {})
|
|
162
176
|
current_module = info.get("module", "") or ""
|
|
163
|
-
|
|
177
|
+
current_module_ctx = current_module
|
|
164
178
|
for c in (info.get("calls") or []):
|
|
165
179
|
target = self._find_function_full_name(c, current_module)
|
|
166
180
|
if target and target in function_map:
|
|
@@ -196,15 +210,15 @@ class MermaidGenerator:
|
|
|
196
210
|
|
|
197
211
|
for _, info in function_map.items():
|
|
198
212
|
current_module = info.get("module", "") or ""
|
|
199
|
-
|
|
213
|
+
current_module_ctx = current_module
|
|
200
214
|
|
|
201
215
|
# prefer call_arguments keys
|
|
202
216
|
call_args = info.get("call_arguments", {}) or {}
|
|
203
217
|
for callee in call_args.keys():
|
|
204
218
|
if resolve_internal(callee, current_module):
|
|
205
219
|
continue
|
|
206
|
-
if keep_external(callee
|
|
207
|
-
external_nodes.add(external_root_name(callee
|
|
220
|
+
if keep_external(callee):
|
|
221
|
+
external_nodes.add(external_root_name(callee) or callee)
|
|
208
222
|
|
|
209
223
|
# scan raw calls for Done + missed externals
|
|
210
224
|
for callee in (info.get("calls") or []):
|
|
@@ -213,8 +227,8 @@ class MermaidGenerator:
|
|
|
213
227
|
|
|
214
228
|
if resolve_internal(callee, current_module):
|
|
215
229
|
continue
|
|
216
|
-
if keep_external(callee
|
|
217
|
-
external_nodes.add(external_root_name(callee
|
|
230
|
+
if keep_external(callee):
|
|
231
|
+
external_nodes.add(external_root_name(callee) or callee)
|
|
218
232
|
|
|
219
233
|
if external_nodes or uses_done:
|
|
220
234
|
lines.append(" subgraph External [External]")
|
|
@@ -233,7 +247,7 @@ class MermaidGenerator:
|
|
|
233
247
|
|
|
234
248
|
info = function_map[caller]
|
|
235
249
|
current_module = info.get("module", "") or ""
|
|
236
|
-
|
|
250
|
+
current_module_ctx = current_module
|
|
237
251
|
src = nid(caller)
|
|
238
252
|
|
|
239
253
|
call_args = info.get("call_arguments", {}) or {}
|
|
@@ -260,8 +274,8 @@ class MermaidGenerator:
|
|
|
260
274
|
else:
|
|
261
275
|
lines.append(f" {src} --> {nid(target_internal)}")
|
|
262
276
|
else:
|
|
263
|
-
if keep_external(callee
|
|
264
|
-
ext = external_root_name(callee
|
|
277
|
+
if keep_external(callee):
|
|
278
|
+
ext = external_root_name(callee) or callee
|
|
265
279
|
edge_key = (src, nid("ext:" + callee), label)
|
|
266
280
|
if edge_key in seen:
|
|
267
281
|
continue
|
|
@@ -280,7 +294,7 @@ class MermaidGenerator:
|
|
|
280
294
|
info = function_map[fn]
|
|
281
295
|
dst = nid(fn)
|
|
282
296
|
current_module = info.get("module", "") or ""
|
|
283
|
-
|
|
297
|
+
current_module_ctx = current_module
|
|
284
298
|
|
|
285
299
|
return_assignments = info.get("return_assignments", {}) or {}
|
|
286
300
|
for var_name, producers in return_assignments.items():
|
|
@@ -289,20 +303,20 @@ class MermaidGenerator:
|
|
|
289
303
|
if internal_p:
|
|
290
304
|
lines.append(f" {nid(internal_p)} -.->|{var_name}| {dst}")
|
|
291
305
|
else:
|
|
292
|
-
if keep_external(p
|
|
293
|
-
ext = external_root_name(p
|
|
306
|
+
if keep_external(p):
|
|
307
|
+
ext = external_root_name(p) or p
|
|
294
308
|
lines.append(f" {nid('ext:' + ext)} -.->|{var_name}| {dst}")
|
|
295
309
|
|
|
296
310
|
# ---------- pipeline heuristic: external tool A -> external tool B ----------
|
|
297
311
|
for _, info in function_map.items():
|
|
298
312
|
current_module = info.get("module", "") or ""
|
|
299
|
-
|
|
313
|
+
current_module_ctx = current_module
|
|
300
314
|
calls = info.get("calls") or []
|
|
301
315
|
call_args = info.get("call_arguments") or {}
|
|
302
316
|
|
|
303
317
|
meaningful = []
|
|
304
318
|
for c in calls:
|
|
305
|
-
if c and keep_external(c
|
|
319
|
+
if c and keep_external(c) and not resolve_internal(c, current_module):
|
|
306
320
|
if c not in meaningful:
|
|
307
321
|
meaningful.append(c)
|
|
308
322
|
|
|
@@ -311,7 +325,7 @@ class MermaidGenerator:
|
|
|
311
325
|
|
|
312
326
|
fileish_callee = None
|
|
313
327
|
for callee, args in call_args.items():
|
|
314
|
-
if keep_external(callee
|
|
328
|
+
if keep_external(callee) and any(is_fileish_arg(a) for a in (args or []) if isinstance(a, str)):
|
|
315
329
|
fileish_callee = callee
|
|
316
330
|
break
|
|
317
331
|
|
|
@@ -324,8 +338,8 @@ class MermaidGenerator:
|
|
|
324
338
|
|
|
325
339
|
vars_passed = call_args.get(fileish_callee, []) or []
|
|
326
340
|
label = normalize_vars(vars_passed) or "value"
|
|
327
|
-
prod_ext = external_root_name(producer
|
|
328
|
-
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
|
|
329
343
|
lines.append(f" {nid('ext:' + prod_ext)} -->|{label}| {nid('ext:' + file_ext)}")
|
|
330
344
|
|
|
331
345
|
# ---------- Done edge ----------
|
|
@@ -130,7 +130,7 @@ def get_project_structure(base_path: Path, exclude_dirs: List[str] = None) -> Di
|
|
|
130
130
|
"node_modules", "site-packages",
|
|
131
131
|
"docs", "doc", "notebooks", ".ipynb_checkpoints",
|
|
132
132
|
"models", "outputs", "results",
|
|
133
|
-
"logs", "tmp", "temp",
|
|
133
|
+
"logs", "tmp", "temp","log","cache"
|
|
134
134
|
]
|
|
135
135
|
|
|
136
136
|
exclude_set = set(exclude_dirs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{py_flow_mapper-0.1.0b5.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.0b5.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|