py-flow-mapper 0.1.0b2.dev0__tar.gz → 0.1.0b3.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.0b2.dev0/src/py_flow_mapper.egg-info → py_flow_mapper-0.1.0b3.dev0}/PKG-INFO +1 -1
  2. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/setup.py +1 -1
  3. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper/__init__.py +1 -1
  4. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper/analyzer.py +13 -7
  5. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper/cli.py +6 -2
  6. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper/mermaid_generator.py +68 -15
  7. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0/src/py_flow_mapper.egg-info}/PKG-INFO +1 -1
  8. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/LICENSE +0 -0
  9. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/README.md +0 -0
  10. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/setup.cfg +0 -0
  11. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper/utils.py +0 -0
  12. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper.egg-info/SOURCES.txt +0 -0
  13. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper.egg-info/dependency_links.txt +0 -0
  14. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper.egg-info/entry_points.txt +0 -0
  15. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.dev0}/src/py_flow_mapper.egg-info/requires.txt +0 -0
  16. {py_flow_mapper-0.1.0b2.dev0 → py_flow_mapper-0.1.0b3.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.0b2.dev0
3
+ Version: 0.1.0b3.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.0b2.dev",
8
+ version="0.1.0b3.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.0b2.dev"
5
+ __version__ = "0.1.0b3.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"
@@ -368,20 +368,21 @@ class ModuleVisitor(ast.NodeVisitor):
368
368
  def visit_ImportFrom(self, node):
369
369
  """Process from ... import statements."""
370
370
  module_name = node.module or ""
371
-
371
+
372
372
  for alias in node.names:
373
373
  full_import = f"{module_name}.{alias.name}" if module_name else alias.name
374
374
  self.imports.append(full_import)
375
-
375
+
376
+ alias_name = alias.asname or alias.name
377
+ self.import_mapping[alias_name] = full_import
378
+
376
379
  if self._is_internal_import(module_name):
377
380
  self.internal_imports.append(full_import)
378
- # Map the imported function/class
379
- alias_name = alias.asname or alias.name
380
- self.import_mapping[alias_name] = full_import
381
381
  else:
382
382
  self.external_imports.append(full_import)
383
-
383
+
384
384
  self.generic_visit(node)
385
+
385
386
 
386
387
  def visit_FunctionDef(self, node):
387
388
  """Process function definitions with data flow analysis."""
@@ -560,7 +561,12 @@ class DataFlowAnalyzer(ast.NodeVisitor):
560
561
  return {'func_name': node.func.id, 'full_name': None}
561
562
  elif isinstance(node.func, ast.Attribute):
562
563
  # Handle module.function calls
563
- method_name = node.func.attr
564
+
565
+ # https://github.com/ArunKoundinya/py-flow-mapper/issues/1 : forced external
566
+ #method_name = node.func.attr
567
+
568
+ method_name = self._get_attribute_name(node.func)
569
+
564
570
  return {'func_name': method_name, 'full_name': None}
565
571
 
566
572
  return None
@@ -24,7 +24,11 @@ def main():
24
24
  # Generate diagrams command
25
25
  diagram_parser = subparsers.add_parser('diagram', help='Generate diagrams from metadata')
26
26
  diagram_parser.add_argument('metadata', type=str, help='Path to metadata JSON file')
27
-
27
+ # https://github.com/ArunKoundinya/py-flow-mapper/issues/1 : forced external
28
+ diagram_parser.add_argument( "--include-external", type=str, default="",
29
+ help="Comma-separated list of external libraries to include in diagrams"
30
+ )
31
+
28
32
  # Structure command
29
33
  struct_parser = subparsers.add_parser('structure', help='Show project structure')
30
34
  struct_parser.add_argument('path', type=str, help='Path to the project directory')
@@ -96,7 +100,7 @@ def generate_diagrams(args):
96
100
 
97
101
  print(f"Generating diagrams from: {metadata_path}")
98
102
 
99
- generator = MermaidGenerator(metadata_path)
103
+ generator = MermaidGenerator(metadata_path,include_external=args.include_external)
100
104
 
101
105
  try:
102
106
  master_path = generator.generate_all_diagrams()
@@ -2,12 +2,25 @@ import json
2
2
  from typing import Dict, List, Any
3
3
  from pathlib import Path
4
4
 
5
+ COMMON_ALIAS_MAP = {
6
+ "pd": "pandas",
7
+ "np": "numpy",
8
+ "plt": "matplotlib",
9
+ "sns": "seaborn",
10
+ "sk": "sklearn",
11
+ }
12
+
13
+
5
14
  class MermaidGenerator:
6
15
  """Generate Mermaid diagrams from project metadata with data flow."""
7
16
 
8
- def __init__(self, metadata_path: Path):
17
+ def __init__(self, metadata_path: Path,include_external: str = ""):
9
18
  self.metadata = self._load_metadata(metadata_path)
10
19
  self.output_dir = metadata_path.parent
20
+ # https://github.com/ArunKoundinya/py-flow-mapper/issues/1 : forced external
21
+ self.force_external = {
22
+ x.strip() for x in include_external.split(",") if x.strip()
23
+ }
11
24
 
12
25
  def _load_metadata(self, metadata_path: Path) -> Dict[str, Any]:
13
26
  """Load metadata from JSON file."""
@@ -25,7 +38,7 @@ class MermaidGenerator:
25
38
  - Adds a generic pipeline edge between two external tool-like nodes when file-ish arguments suggest a handoff.
26
39
  - Adds a Done node when output-generation-like calls are detected.
27
40
  """
28
- lines = ["```mermaid", "graph TD"]
41
+ lines = ["```mermaid", "graph LR"]
29
42
 
30
43
  function_map = self.metadata.get("function_map", {}) or {}
31
44
  internal_funcs = set(function_map.keys())
@@ -57,6 +70,25 @@ class MermaidGenerator:
57
70
  if not cleaned:
58
71
  return ""
59
72
  return ",".join(sorted(set(cleaned)))
73
+
74
+ current_module_ctx = ""
75
+
76
+ def module_import_mapping() -> dict:
77
+ return (modules.get(current_module_ctx, {}) or {}).get("import_mapping", {}) or {}
78
+
79
+ def external_root_name(call_name: str) -> str:
80
+ if not call_name:
81
+ return ""
82
+
83
+ root = call_name.split(".")[0]
84
+
85
+ # module-specific import mapping (from analyzer)
86
+ imp_map = module_import_mapping() or {}
87
+
88
+ # merge common aliases (module imports override defaults)
89
+ merged_map = {**COMMON_ALIAS_MAP, **imp_map}
90
+
91
+ return merged_map.get(root, root)
60
92
 
61
93
  # Keep the graph clean: ignore common builtins + attribute-noise
62
94
  NOISY_EXTERNAL = {
@@ -68,18 +100,29 @@ class MermaidGenerator:
68
100
  }
69
101
 
70
102
  def keep_external(call_name: str) -> bool:
71
- """
72
- Meaningful external nodes only:
73
- - drop noise builtins
74
- - drop internal classes
75
- - drop dotted calls (obj.method) to avoid clutter
76
- - keep CamelCase (tool-ish classes / constructors)
77
- """
78
103
  if not call_name:
79
104
  return False
80
105
 
81
106
  base = short_label(call_name)
82
107
 
108
+ # forced external always wins
109
+ root = external_root_name(call_name)
110
+ if root and root in self.force_external:
111
+ return True
112
+ if base in self.force_external:
113
+ return True
114
+
115
+ # ✅ NEW: module-specific import mapping
116
+ imp_map = module_import_mapping() or {}
117
+ if base in imp_map:
118
+ mapped = imp_map[base]
119
+ mapped_module = ".".join(mapped.split(".")[:-1])
120
+
121
+ if mapped_module in modules:
122
+ return False
123
+
124
+ return True
125
+
83
126
  # internal class => NOT external
84
127
  if base in internal_classes:
85
128
  return False
@@ -91,6 +134,7 @@ class MermaidGenerator:
91
134
  if "." in call_name:
92
135
  return False
93
136
 
137
+ # keep CamelCase (tool-ish classes / constructors)
94
138
  return is_camel_case(base)
95
139
 
96
140
  def resolve_internal(call: str, current_module: str) -> str:
@@ -130,6 +174,7 @@ class MermaidGenerator:
130
174
 
131
175
  info = function_map.get(fn_key, {})
132
176
  current_module = info.get("module", "") or ""
177
+ current_module_ctx = current_module
133
178
  for c in (info.get("calls") or []):
134
179
  target = self._find_function_full_name(c, current_module)
135
180
  if target and target in function_map:
@@ -165,6 +210,7 @@ class MermaidGenerator:
165
210
 
166
211
  for _, info in function_map.items():
167
212
  current_module = info.get("module", "") or ""
213
+ current_module_ctx = current_module
168
214
 
169
215
  # prefer call_arguments keys
170
216
  call_args = info.get("call_arguments", {}) or {}
@@ -172,7 +218,7 @@ class MermaidGenerator:
172
218
  if resolve_internal(callee, current_module):
173
219
  continue
174
220
  if keep_external(callee):
175
- external_nodes.add(callee)
221
+ external_nodes.add(external_root_name(callee) or callee)
176
222
 
177
223
  # scan raw calls for Done + missed externals
178
224
  for callee in (info.get("calls") or []):
@@ -182,7 +228,7 @@ class MermaidGenerator:
182
228
  if resolve_internal(callee, current_module):
183
229
  continue
184
230
  if keep_external(callee):
185
- external_nodes.add(callee)
231
+ external_nodes.add(external_root_name(callee) or callee)
186
232
 
187
233
  if external_nodes or uses_done:
188
234
  lines.append(" subgraph External [External]")
@@ -201,6 +247,7 @@ class MermaidGenerator:
201
247
 
202
248
  info = function_map[caller]
203
249
  current_module = info.get("module", "") or ""
250
+ current_module_ctx = current_module
204
251
  src = nid(caller)
205
252
 
206
253
  call_args = info.get("call_arguments", {}) or {}
@@ -228,15 +275,16 @@ class MermaidGenerator:
228
275
  lines.append(f" {src} --> {nid(target_internal)}")
229
276
  else:
230
277
  if keep_external(callee):
278
+ ext = external_root_name(callee) or callee
231
279
  edge_key = (src, nid("ext:" + callee), label)
232
280
  if edge_key in seen:
233
281
  continue
234
282
  seen.add(edge_key)
235
283
 
236
284
  if label:
237
- lines.append(f" {src} --> |{label}| {nid('ext:' + callee)}")
285
+ lines.append(f" {src} --> |{label}| {nid('ext:' + ext)}")
238
286
  else:
239
- lines.append(f" {src} --> {nid('ext:' + callee)}")
287
+ lines.append(f" {src} --> {nid('ext:' + ext)}")
240
288
 
241
289
  # ---------- data flow edges (return_assignments) ----------
242
290
  for fn in caller_order:
@@ -246,6 +294,7 @@ class MermaidGenerator:
246
294
  info = function_map[fn]
247
295
  dst = nid(fn)
248
296
  current_module = info.get("module", "") or ""
297
+ current_module_ctx = current_module
249
298
 
250
299
  return_assignments = info.get("return_assignments", {}) or {}
251
300
  for var_name, producers in return_assignments.items():
@@ -255,11 +304,13 @@ class MermaidGenerator:
255
304
  lines.append(f" {nid(internal_p)} -.->|{var_name}| {dst}")
256
305
  else:
257
306
  if keep_external(p):
258
- lines.append(f" {nid('ext:' + p)} -.->|{var_name}| {dst}")
307
+ ext = external_root_name(p) or p
308
+ lines.append(f" {nid('ext:' + ext)} -.->|{var_name}| {dst}")
259
309
 
260
310
  # ---------- pipeline heuristic: external tool A -> external tool B ----------
261
311
  for _, info in function_map.items():
262
312
  current_module = info.get("module", "") or ""
313
+ current_module_ctx = current_module
263
314
  calls = info.get("calls") or []
264
315
  call_args = info.get("call_arguments") or {}
265
316
 
@@ -287,7 +338,9 @@ class MermaidGenerator:
287
338
 
288
339
  vars_passed = call_args.get(fileish_callee, []) or []
289
340
  label = normalize_vars(vars_passed) or "value"
290
- lines.append(f" {nid('ext:' + producer)} -->|{label}| {nid('ext:' + fileish_callee)}")
341
+ prod_ext = external_root_name(producer) or producer
342
+ file_ext = external_root_name(fileish_callee) or fileish_callee
343
+ lines.append(f" {nid('ext:' + prod_ext)} -->|{label}| {nid('ext:' + file_ext)}")
291
344
 
292
345
  # ---------- Done edge ----------
293
346
  if uses_done:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-flow-mapper
3
- Version: 0.1.0b2.dev0
3
+ Version: 0.1.0b3.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