py-flow-mapper 0.1.0b3.dev0__tar.gz → 0.1.0b5.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.0b3.dev0/src/py_flow_mapper.egg-info → py_flow_mapper-0.1.0b5.dev0}/PKG-INFO +1 -1
  2. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/setup.py +1 -1
  3. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper/__init__.py +1 -1
  4. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper/analyzer.py +27 -14
  5. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper/mermaid_generator.py +24 -38
  6. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper/utils.py +30 -11
  7. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0/src/py_flow_mapper.egg-info}/PKG-INFO +1 -1
  8. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/LICENSE +0 -0
  9. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/README.md +0 -0
  10. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/setup.cfg +0 -0
  11. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper/cli.py +0 -0
  12. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper.egg-info/SOURCES.txt +0 -0
  13. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper.egg-info/dependency_links.txt +0 -0
  14. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper.egg-info/entry_points.txt +0 -0
  15. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.dev0}/src/py_flow_mapper.egg-info/requires.txt +0 -0
  16. {py_flow_mapper-0.1.0b3.dev0 → py_flow_mapper-0.1.0b5.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.0b3.dev0
3
+ Version: 0.1.0b5.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.0b3.dev",
8
+ version="0.1.0b5.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.0b3.dev"
5
+ __version__ = "0.1.0b5.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"
@@ -107,8 +107,24 @@ class ProjectAnalyzer:
107
107
  """Find all Python files in the project, excluding virtual environments and other unwanted directories."""
108
108
  python_files = []
109
109
 
110
- # Prefixes of directories to exclude
111
- exclude_prefixes = ('venv', '.venv', 'env', '.env', '__pycache__', '.git', 'node_modules')
110
+ # Prefixes of directories to exclude
111
+ exclude_prefixes = (
112
+ "venv", ".venv", "env", ".env", "virtualenv", ".virtualenv",
113
+ "__pycache__", ".mypy_cache", ".pytest_cache", ".ruff_cache",
114
+ ".coverage", "htmlcov", ".hypothesis",
115
+ ".git", ".hg", ".svn",
116
+ "build", "dist", ".eggs", "egg-info", "*.egg-info",
117
+ ".tox", ".nox", ".pdm-build",
118
+ "site-packages", "node_modules", "bower_components",
119
+ "docs", "doc", "site", "_site", "mkdocs",
120
+ ".ipynb_checkpoints", "notebooks", "examples", "experiments", "scratch",
121
+ "data", "datasets", "models", "checkpoints", "outputs", "results",
122
+ "logs", "tmp", "temp", "cache",
123
+ ".idea", ".vscode", ".DS_Store", "__MACOSX",
124
+ ".github", ".gitlab", ".circleci",
125
+ "tests", "test", "testing",
126
+ )
127
+
112
128
 
113
129
  for root, dirs, files in os.walk(self.base_path):
114
130
  # Filter out directories that start with any of the exclude prefixes
@@ -505,24 +521,21 @@ class DataFlowAnalyzer(ast.NodeVisitor):
505
521
 
506
522
  def visit_Assign(self, node):
507
523
  """Track assignments of function return values."""
508
- # Get the variable name being assigned to
509
- if isinstance(node.targets[0], ast.Name):
524
+ if node.targets and isinstance(node.targets[0], ast.Name):
510
525
  var_name = node.targets[0].id
511
-
512
- # Check if the value is a function call
526
+
513
527
  if isinstance(node.value, ast.Call):
514
528
  call_info = self._extract_call_info(node.value)
515
529
  if call_info:
516
- self.calls.append(call_info['func_name'])
517
- if var_name not in self.return_assignments:
518
- self.return_assignments[var_name] = []
519
- self.return_assignments[var_name].append(call_info['func_name'])
520
-
521
- # Check if the value is a Name (could be a variable holding a return value)
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
+
522
536
  elif isinstance(node.value, ast.Name):
523
- # Track variable assignments for data flow
524
537
  pass
525
-
538
+
526
539
  self.generic_visit(node)
527
540
 
528
541
  def visit_Call(self, node):
@@ -71,23 +71,16 @@ class MermaidGenerator:
71
71
  return ""
72
72
  return ",".join(sorted(set(cleaned)))
73
73
 
74
- current_module_ctx = ""
75
74
 
76
- def module_import_mapping() -> dict:
77
- return (modules.get(current_module_ctx, {}) or {}).get("import_mapping", {}) or {}
75
+ def module_import_mapping(mod: str) -> dict:
76
+ return (modules.get(mod, {}) or {}).get("import_mapping", {}) or {}
78
77
 
79
- def external_root_name(call_name: str) -> str:
78
+ def external_root_name(call_name: str, current_module: str) -> str:
80
79
  if not call_name:
81
80
  return ""
82
-
83
81
  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)
82
+ imp_map = module_import_mapping(current_module)
89
83
  merged_map = {**COMMON_ALIAS_MAP, **imp_map}
90
-
91
84
  return merged_map.get(root, root)
92
85
 
93
86
  # Keep the graph clean: ignore common builtins + attribute-noise
@@ -99,42 +92,35 @@ class MermaidGenerator:
99
92
  "Path",
100
93
  }
101
94
 
102
- def keep_external(call_name: str) -> bool:
95
+ def keep_external(call_name: str, current_module: str) -> bool:
103
96
  if not call_name:
104
97
  return False
105
98
 
106
99
  base = short_label(call_name)
107
100
 
108
- # forced external always wins
109
- root = external_root_name(call_name)
101
+ root = external_root_name(call_name, current_module)
110
102
  if root and root in self.force_external:
111
103
  return True
112
104
  if base in self.force_external:
113
105
  return True
114
106
 
115
- # NEW: module-specific import mapping
116
- imp_map = module_import_mapping() or {}
107
+ imp_map = module_import_mapping(current_module)
117
108
  if base in imp_map:
118
109
  mapped = imp_map[base]
119
110
  mapped_module = ".".join(mapped.split(".")[:-1])
120
-
121
111
  if mapped_module in modules:
122
112
  return False
123
-
124
113
  return True
125
114
 
126
- # internal class => NOT external
127
115
  if base in internal_classes:
128
116
  return False
129
117
 
130
118
  if base in NOISY_EXTERNAL:
131
119
  return False
132
120
 
133
- # dotted calls like obj.method: too noisy at this level
134
121
  if "." in call_name:
135
122
  return False
136
123
 
137
- # keep CamelCase (tool-ish classes / constructors)
138
124
  return is_camel_case(base)
139
125
 
140
126
  def resolve_internal(call: str, current_module: str) -> str:
@@ -174,7 +160,7 @@ class MermaidGenerator:
174
160
 
175
161
  info = function_map.get(fn_key, {})
176
162
  current_module = info.get("module", "") or ""
177
- current_module_ctx = current_module
163
+ #current_module_ctx = current_module
178
164
  for c in (info.get("calls") or []):
179
165
  target = self._find_function_full_name(c, current_module)
180
166
  if target and target in function_map:
@@ -210,15 +196,15 @@ class MermaidGenerator:
210
196
 
211
197
  for _, info in function_map.items():
212
198
  current_module = info.get("module", "") or ""
213
- current_module_ctx = current_module
199
+ #current_module_ctx = current_module
214
200
 
215
201
  # prefer call_arguments keys
216
202
  call_args = info.get("call_arguments", {}) or {}
217
203
  for callee in call_args.keys():
218
204
  if resolve_internal(callee, current_module):
219
205
  continue
220
- if keep_external(callee):
221
- external_nodes.add(external_root_name(callee) or callee)
206
+ if keep_external(callee, current_module):
207
+ external_nodes.add(external_root_name(callee, current_module) or callee)
222
208
 
223
209
  # scan raw calls for Done + missed externals
224
210
  for callee in (info.get("calls") or []):
@@ -227,8 +213,8 @@ class MermaidGenerator:
227
213
 
228
214
  if resolve_internal(callee, current_module):
229
215
  continue
230
- if keep_external(callee):
231
- external_nodes.add(external_root_name(callee) or callee)
216
+ if keep_external(callee, current_module):
217
+ external_nodes.add(external_root_name(callee, current_module) or callee)
232
218
 
233
219
  if external_nodes or uses_done:
234
220
  lines.append(" subgraph External [External]")
@@ -247,7 +233,7 @@ class MermaidGenerator:
247
233
 
248
234
  info = function_map[caller]
249
235
  current_module = info.get("module", "") or ""
250
- current_module_ctx = current_module
236
+ #current_module_ctx = current_module
251
237
  src = nid(caller)
252
238
 
253
239
  call_args = info.get("call_arguments", {}) or {}
@@ -274,8 +260,8 @@ class MermaidGenerator:
274
260
  else:
275
261
  lines.append(f" {src} --> {nid(target_internal)}")
276
262
  else:
277
- if keep_external(callee):
278
- ext = external_root_name(callee) or callee
263
+ if keep_external(callee, current_module):
264
+ ext = external_root_name(callee, current_module) or callee
279
265
  edge_key = (src, nid("ext:" + callee), label)
280
266
  if edge_key in seen:
281
267
  continue
@@ -294,7 +280,7 @@ class MermaidGenerator:
294
280
  info = function_map[fn]
295
281
  dst = nid(fn)
296
282
  current_module = info.get("module", "") or ""
297
- current_module_ctx = current_module
283
+ #current_module_ctx = current_module
298
284
 
299
285
  return_assignments = info.get("return_assignments", {}) or {}
300
286
  for var_name, producers in return_assignments.items():
@@ -303,20 +289,20 @@ class MermaidGenerator:
303
289
  if internal_p:
304
290
  lines.append(f" {nid(internal_p)} -.->|{var_name}| {dst}")
305
291
  else:
306
- if keep_external(p):
307
- ext = external_root_name(p) or p
292
+ if keep_external(p, current_module):
293
+ ext = external_root_name(p,current_module) or p
308
294
  lines.append(f" {nid('ext:' + ext)} -.->|{var_name}| {dst}")
309
295
 
310
296
  # ---------- pipeline heuristic: external tool A -> external tool B ----------
311
297
  for _, info in function_map.items():
312
298
  current_module = info.get("module", "") or ""
313
- current_module_ctx = current_module
299
+ #current_module_ctx = current_module
314
300
  calls = info.get("calls") or []
315
301
  call_args = info.get("call_arguments") or {}
316
302
 
317
303
  meaningful = []
318
304
  for c in calls:
319
- if c and keep_external(c) and not resolve_internal(c, current_module):
305
+ if c and keep_external(c,current_module) and not resolve_internal(c, current_module):
320
306
  if c not in meaningful:
321
307
  meaningful.append(c)
322
308
 
@@ -325,7 +311,7 @@ class MermaidGenerator:
325
311
 
326
312
  fileish_callee = None
327
313
  for callee, args in call_args.items():
328
- if keep_external(callee) and any(is_fileish_arg(a) for a in (args or []) if isinstance(a, str)):
314
+ if keep_external(callee, current_module) and any(is_fileish_arg(a) for a in (args or []) if isinstance(a, str)):
329
315
  fileish_callee = callee
330
316
  break
331
317
 
@@ -338,8 +324,8 @@ class MermaidGenerator:
338
324
 
339
325
  vars_passed = call_args.get(fileish_callee, []) or []
340
326
  label = normalize_vars(vars_passed) or "value"
341
- prod_ext = external_root_name(producer) or producer
342
- file_ext = external_root_name(fileish_callee) or fileish_callee
327
+ prod_ext = external_root_name(producer,current_module) or producer
328
+ file_ext = external_root_name(fileish_callee,current_module) or fileish_callee
343
329
  lines.append(f" {nid('ext:' + prod_ext)} -->|{label}| {nid('ext:' + file_ext)}")
344
330
 
345
331
  # ---------- Done edge ----------
@@ -120,17 +120,36 @@ def load_json(file_path: Path) -> Any:
120
120
  def get_project_structure(base_path: Path, exclude_dirs: List[str] = None) -> Dict[str, Any]:
121
121
  """Get the project directory structure."""
122
122
  if exclude_dirs is None:
123
- exclude_dirs = ['venv', '.venv', '__pycache__', '.git', '.idea', '.vscode']
124
-
125
- structure = {}
126
-
127
- for item in sorted(base_path.iterdir()):
128
- if item.name in exclude_dirs:
123
+ exclude_dirs = [
124
+ "venv", ".venv", "env", ".env", "virtualenv",
125
+ "__pycache__", ".mypy_cache", ".pytest_cache", ".ruff_cache",
126
+ ".coverage", "htmlcov",
127
+ ".git", ".hg", ".svn",
128
+ ".idea", ".vscode", ".DS_Store", "__MACOSX",
129
+ "build", "dist", ".eggs", ".tox", ".nox",
130
+ "node_modules", "site-packages",
131
+ "docs", "doc", "notebooks", ".ipynb_checkpoints",
132
+ "models", "outputs", "results",
133
+ "logs", "tmp", "temp",
134
+ ]
135
+
136
+ exclude_set = set(exclude_dirs)
137
+ structure: Dict[str, Any] = {}
138
+
139
+ for item in sorted(base_path.iterdir(), key=lambda p: p.name):
140
+ name = item.name
141
+
142
+ # Unified exclusion logic (works for both files and dirs)
143
+ if (
144
+ name in exclude_set
145
+ or name.endswith(".egg-info") # e.g., mypkg.egg-info
146
+ or any(name.startswith(excl) for excl in exclude_set) # e.g., venv311, build_temp
147
+ ):
129
148
  continue
130
-
149
+
131
150
  if item.is_dir():
132
- structure[item.name] = get_project_structure(item, exclude_dirs)
151
+ structure[name] = get_project_structure(item, exclude_dirs)
133
152
  else:
134
- structure[item.name] = 'file'
135
-
136
- return structure
153
+ structure[name] = "file"
154
+
155
+ return structure
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-flow-mapper
3
- Version: 0.1.0b3.dev0
3
+ Version: 0.1.0b5.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