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.
Files changed (16) hide show
  1. {py_flow_mapper-0.1.0b6.dev0/src/py_flow_mapper.egg-info → py_flow_mapper-0.1.0b7.dev0}/PKG-INFO +1 -1
  2. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/setup.py +1 -1
  3. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/__init__.py +1 -1
  4. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/analyzer.py +12 -9
  5. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/mermaid_generator.py +41 -56
  6. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0/src/py_flow_mapper.egg-info}/PKG-INFO +1 -1
  7. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/LICENSE +0 -0
  8. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/README.md +0 -0
  9. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/setup.cfg +0 -0
  10. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/cli.py +0 -0
  11. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper/utils.py +0 -0
  12. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/SOURCES.txt +0 -0
  13. {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
  14. {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
  15. {py_flow_mapper-0.1.0b6.dev0 → py_flow_mapper-0.1.0b7.dev0}/src/py_flow_mapper.egg-info/requires.txt +0 -0
  16. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-flow-mapper
3
- Version: 0.1.0b6.dev0
3
+ Version: 0.1.0b7.dev0
4
4
  Summary: Python project analyzer and visualization tool
5
5
  Home-page: https://github.com/ArunKoundinya/py-flow-mapper
6
6
  Author: Arun Koundinya Parasa
@@ -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.0b6.dev",
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.0b6.dev"
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
- if node.targets and isinstance(node.targets[0], ast.Name):
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
- func_name = call_info["func_name"]
531
-
532
- if func_name not in self.calls:
533
- self.calls.append(func_name)
534
- self.return_assignments.setdefault(var_name, []).append(func_name)
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):
@@ -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(mod: str) -> dict:
76
- return (modules.get(mod, {}) or {}).get("import_mapping", {}) or {}
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, current_module: str) -> 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
- imp_map = module_import_mapping(current_module)
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, current_module: str) -> bool:
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
- root = external_root_name(call_name, current_module)
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
- imp_map = module_import_mapping(current_module)
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
- call = call or ""
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
- imp_map = module_import_mapping(current_module)
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
- #current_module_ctx = current_module
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
- #current_module_ctx = current_module
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, current_module):
236
- external_nodes.add(external_root_name(callee, current_module) or 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, current_module):
246
- external_nodes.add(external_root_name(callee, current_module) or 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
- #current_module_ctx = current_module
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, current_module):
293
- ext = external_root_name(callee, current_module) or 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
- #current_module_ctx = current_module
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, current_module):
322
- ext = external_root_name(p,current_module) or 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
- #current_module_ctx = current_module
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,current_module) and not resolve_internal(c, current_module):
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, current_module) and any(is_fileish_arg(a) for a in (args or []) if isinstance(a, str)):
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,current_module) or producer
357
- file_ext = external_root_name(fileish_callee,current_module) or 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 ----------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-flow-mapper
3
- Version: 0.1.0b6.dev0
3
+ Version: 0.1.0b7.dev0
4
4
  Summary: Python project analyzer and visualization tool
5
5
  Home-page: https://github.com/ArunKoundinya/py-flow-mapper
6
6
  Author: Arun Koundinya Parasa