codemap-python 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

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.
@@ -1,6 +1,7 @@
1
1
  # AST Call detection
2
2
 
3
3
  import ast
4
+ from analysis.utils.bom_handler import remove_bom
4
5
 
5
6
  class FunctionCallVisitor(ast.NodeVisitor):
6
7
  def __init__(self, file_path):
@@ -83,7 +84,11 @@ class FunctionCallVisitor(ast.NodeVisitor):
83
84
 
84
85
  def extract_function_calls(file_path):
85
86
  with open(file_path, "r", encoding="utf-8") as f:
86
- tree = ast.parse(f.read())
87
+ source = f.read()
88
+
89
+ # Remove BOM if present
90
+ source = remove_bom(source)
91
+ tree = ast.parse(source)
87
92
 
88
93
  visitor = FunctionCallVisitor(file_path)
89
94
  visitor.visit(tree)
@@ -1,8 +1,31 @@
1
1
  # AST Parser Module
2
2
  import ast
3
+ from analysis.utils.bom_handler import remove_bom
4
+
5
+
3
6
  def parse_python_file(file_path):
7
+ """Parse a Python file, automatically handling UTF-8 BOM.
8
+
9
+ This function:
10
+ 1. Reads the file with UTF-8 encoding
11
+ 2. Removes any BOM characters automatically
12
+ 3. Parses the cleaned source code
13
+
14
+ Args:
15
+ file_path: Path to Python file to parse
16
+
17
+ Returns:
18
+ ast.Module: Parsed AST tree
19
+
20
+ Raises:
21
+ SyntaxError: If source code has syntax errors
22
+ FileNotFoundError: If file doesn't exist
23
+ """
4
24
  with open(file_path, "r", encoding="utf-8") as f:
5
25
  source = f.read()
6
26
 
27
+ # Remove BOM if present (handles files from Windows editors, etc.)
28
+ source = remove_bom(source)
29
+
7
30
  return ast.parse(source)
8
31
 
@@ -2,11 +2,17 @@
2
2
  # analysis/import_extractor.py
3
3
 
4
4
  import ast
5
+ from analysis.utils.bom_handler import remove_bom
6
+
5
7
 
6
8
  def extract_imports(file_path):
9
+ """Extract imports from a Python file, handling UTF-8 BOM automatically."""
7
10
  with open(file_path, "r", encoding="utf-8") as f:
8
11
  source = f.read()
9
12
 
13
+ # Remove BOM if present
14
+ source = remove_bom(source)
15
+
10
16
  tree = ast.parse(source)
11
17
  imports = []
12
18
 
@@ -8,6 +8,7 @@ from typing import Optional, Dict, Any
8
8
  import ast
9
9
  import json
10
10
  import os
11
+ from analysis.utils.bom_handler import remove_bom
11
12
 
12
13
  from analysis.indexing.symbol_index import SymbolIndex, SymbolInfo
13
14
  from analysis.graph.callgraph_index import CallGraphIndex, CallSite
@@ -27,8 +28,12 @@ def collect_python_files(root_dir: str):
27
28
 
28
29
 
29
30
  def parse_ast(file_path: str) -> ast.AST:
31
+ """Parse a Python file, automatically handling UTF-8 BOM."""
30
32
  with open(file_path, "r", encoding="utf-8") as f:
31
- return ast.parse(f.read())
33
+ source = f.read()
34
+ # Remove BOM if present
35
+ source = remove_bom(source)
36
+ return ast.parse(source)
32
37
 
33
38
 
34
39
  def file_to_module(file_path: str, repo_root: str) -> str:
@@ -12,6 +12,7 @@ from analysis.call_graph.cross_file_resolver import CrossFileResolver
12
12
  from analysis.call_graph.call_extractor import extract_function_calls
13
13
  from analysis.core.import_extractor import extract_imports
14
14
  from analysis.graph.callgraph_index import build_caller_fqn
15
+ from analysis.utils.bom_handler import remove_bom
15
16
 
16
17
 
17
18
  PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
@@ -29,8 +30,12 @@ def collect_python_files(root_dir: str) -> List[str]:
29
30
 
30
31
 
31
32
  def parse_ast(file_path: str):
33
+ """Parse a Python file, automatically handling UTF-8 BOM."""
32
34
  with open(file_path, "r", encoding="utf-8") as f:
33
- return ast.parse(f.read())
35
+ source = f.read()
36
+ # Remove BOM if present
37
+ source = remove_bom(source)
38
+ return ast.parse(source)
34
39
 
35
40
 
36
41
  def file_to_module(file_path: str, repo_root: str) -> str:
@@ -0,0 +1,55 @@
1
+ """BOM (Byte Order Mark) handling utilities for CodeMap.
2
+
3
+ This module provides utilities to handle UTF-8 BOM characters that are
4
+ sometimes added to Python files by certain editors (especially on Windows).
5
+
6
+ BOM (U+FEFF) is an invisible character that Python's AST parser cannot handle,
7
+ causing: "invalid non-printable character U+FEFF"
8
+
9
+ Solution: Strip BOM before parsing Python files.
10
+ """
11
+
12
+
13
+ def remove_bom(source: str) -> str:
14
+ """Remove UTF-8 BOM (Byte Order Mark) from source code if present.
15
+
16
+ BOM is a special character (U+FEFF) that some editors (especially Notepad
17
+ on Windows) add to the start of files. Python's AST parser doesn't handle it.
18
+
19
+ This function silently removes it if present, or returns the source unchanged.
20
+
21
+ Args:
22
+ source: Python source code as string
23
+
24
+ Returns:
25
+ Source code with BOM removed if present
26
+
27
+ Example:
28
+ >>> source_with_bom = '\\ufeffdef hello(): pass'
29
+ >>> clean_source = remove_bom(source_with_bom)
30
+ >>> print(clean_source)
31
+ def hello(): pass
32
+ """
33
+ if source.startswith('\ufeff'):
34
+ return source[1:]
35
+ return source
36
+
37
+
38
+ def read_source_file(file_path: str) -> str:
39
+ """Read a Python file and remove BOM if present.
40
+
41
+ This is a convenience function that combines file reading with BOM removal.
42
+
43
+ Args:
44
+ file_path: Path to Python file to read
45
+
46
+ Returns:
47
+ Source code with BOM removed
48
+
49
+ Raises:
50
+ FileNotFoundError: If file doesn't exist
51
+ UnicodeDecodeError: If file encoding is not UTF-8
52
+ """
53
+ with open(file_path, "r", encoding="utf-8") as f:
54
+ source = f.read()
55
+ return remove_bom(source)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codemap-python
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Local Python code analysis tool - understand architecture, dependencies, and call graphs
5
5
  Author-email: ADITYA <aditykushwaha69@gmail.com>
6
6
  License: MIT
@@ -7,7 +7,7 @@ analysis/architecture/architecture_engine.py,sha256=8aWqfvdGHP9gIf03VdLrkn1vF670
7
7
  analysis/architecture/dependency_cycles.py,sha256=fu1kQY1N4jmAdCdh2V2g9e8aZRoC17WtGEy6rPStDbI,3342
8
8
  analysis/architecture/risk_radar.py,sha256=3nCxjH96cicb_UQ67T4Hk63cIWWgG5syQhlaoFsoWEA,8681
9
9
  analysis/call_graph/__init__.py,sha256=w0foWNOGbFJTxtZUs08tnR3MM5mRTfA0xz6DtGRNGOk,22
10
- analysis/call_graph/call_extractor.py,sha256=ePYwybRNn5U6dZ21wiovtnVzCskaohNMINvaM_zOEaU,2673
10
+ analysis/call_graph/call_extractor.py,sha256=SZEFMYh0GtqyLiXNOZyGH8xd0Nr7kGbwXky_m0amIes,2813
11
11
  analysis/call_graph/call_graph_builder.py,sha256=h3D2SzvNmyPtVEBvwGTHM2LVsUwxq9MBLEMhkMi8gGc,29
12
12
  analysis/call_graph/call_resolver.py,sha256=gdSAy_042xxXby65FfSIMGBYI3yU4-jT-zMvpajHdF8,1600
13
13
  analysis/call_graph/context_models.py,sha256=pYCvdJOEdR35p-BO42BtXoJIFdieF6CEp-f_rSFbd0Q,29
@@ -17,13 +17,13 @@ analysis/call_graph/flow_builder.py,sha256=B029swJPLTaB6MazmHfK-XS9rLpg7HSTwiEj1
17
17
  analysis/call_graph/models.py,sha256=bj3YVEVIKhSNrYdczUewhckocjYp9qrdQzV65ICPJvQ,23
18
18
  analysis/core/__init__.py,sha256=Sl3CBLE_4V96jffhhcEY6C_-grRWDCQyoB-LVtNnP_A,39
19
19
  analysis/core/ast_context.py,sha256=kQZB3jM4Mnqy8VtnlT5Towdo5Rfg6sr2hF-eVxjQY6Q,29
20
- analysis/core/ast_parser.py,sha256=JKmnMu9jmTj3gktRXamzPp94OjAlPCLCkQ20Xfa9IOE,184
20
+ analysis/core/ast_parser.py,sha256=Rq7wxM_3U2lR3t6ewDfCRnfAX5BrciWOvwbrjH0bRWg,834
21
21
  analysis/core/class_extractor.py,sha256=YJctW3TigWa7gra02qgZJXoVboDDTikkIyPzEDfNiI8,1039
22
22
  analysis/core/function_extractor.py,sha256=5y0teAJ8vzGTZ8Te80m9i0ihP8g2jgR2FEcddxIvhwc,436
23
- analysis/core/import_extractor.py,sha256=hefburHlOhvfHneADTk7Nxi1pAlp29KH7EirLhOjPK0,1262
23
+ analysis/core/import_extractor.py,sha256=WnrljYmpVxbQky2j48pXBXXGnw4GMwZIhgQ8SZpYg4M,1464
24
24
  analysis/explain/__init__.py,sha256=2FsKrvLP_OgFFTuoFToaXRs6lM3lmz6lpEqjy5Agko8,55
25
25
  analysis/explain/docstring_extractor.py,sha256=9AO1ITf48KMg5ezZm_GYJzTlFi8sOBYih3uXlyectWM,1449
26
- analysis/explain/explain_runner.py,sha256=bgB7PFUidR_aRBr8TdWr3KWNMWsy5o1H_-0IzNQli7M,5587
26
+ analysis/explain/explain_runner.py,sha256=onnN00D9Nb2oFS3m0q0OFMFlQuFZW64KjJpQ8hpShTE,5787
27
27
  analysis/explain/repo_summary_generator.py,sha256=eCNve4-eHG0d2XLJttFA-Bh5suZIvKack_jBRaMHrHc,6474
28
28
  analysis/explain/return_analyzer.py,sha256=KjHj7SHWTUMQAzLUi0A8BL5cv5tCxr5XeAw2VG-uyYg,3100
29
29
  analysis/explain/risk_flags.py,sha256=QNV101q4PMMIJ_LKv80EwX6uaAysiZC-aGyYTSbwPb8,39
@@ -37,22 +37,23 @@ analysis/indexing/__init__.py,sha256=eiE_smnnd-0xjpMBKDQilIypx5oiLA9N_40xBxDQSe4
37
37
  analysis/indexing/import_resolver.py,sha256=ZkTyx3RdzrEwdHMVax0uM5D4AjJNEo1FoXAD1rsx5QY,4697
38
38
  analysis/indexing/symbol_index.py,sha256=RoBsehau9u0tYB8hUEvN5K4dINgtiGH07qV0vznKAhA,5491
39
39
  analysis/runners/__init__.py,sha256=4caStudxQFG1nCXBGpoyV7DvxN5ySfwVm4ENQc46w6s,21
40
- analysis/runners/phase4_runner.py,sha256=HASv0IsVye7TdxwZ2TQfVjShJ8Q4b533IZ8Hd1CVTWU,4875
40
+ analysis/runners/phase4_runner.py,sha256=RUu-LlBGbfeZBi6oduISzHJo_5hlDVunhjVwMbOsTvs,5075
41
41
  analysis/utils/__init__.py,sha256=sJCGmNWVjSiFK21dDvTzQFMUPyQYNP3b4-Lj42DNA-s,17
42
42
  analysis/utils/ast_helpers.py,sha256=qMny-x6kvMt7F1b9Q8HtR2o0nen6bR-MyQsClBzLNpI,20
43
+ analysis/utils/bom_handler.py,sha256=fh1xYQ5P9Y7YCtur4coPx59hL5OUL-43ZQG5AZgidIM,1768
43
44
  analysis/utils/cache_manager.py,sha256=FUuZOO_CSHP_A2cFO4Kk8WQc34AYfqTol2w6n7DmTBE,24579
44
45
  analysis/utils/path_resolver.py,sha256=NprapbBX8E1c_5jp2R1OLIYMeqrFeNfaxnmIlwqs9hI,27
45
46
  analysis/utils/repo_fetcher.py,sha256=bCUBG0qUypcXXgQPAAdJAaRoQSc75Nd53B7HD72Euno,15105
46
47
  ui/__init__.py,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
47
- ui/app.py,sha256=BDjfHMRzBWBwFuRrMYen-KKFfeotn7RdXHG20gu5_4c,81661
48
+ ui/app.py,sha256=BUUM55EpHSPYKwog7tcbHI4gVfnQllnAbf6abO05p7g,81986
48
49
  ui/device_id.py,sha256=gAan8gMnSY_lDpDSllafK27CSskFT-_OdFw2m8y2QUA,708
49
50
  ui/static/app.js,sha256=Nmfgs6pqMkS6Q44CtSBpEV9uW_xks2mS8BIxW7WqAXg,115957
50
51
  ui/static/styles.css,sha256=B_QkNK8zziUDl-m8HzcpyO1FFujYf-DdO05Ly7JZbB8,23639
51
52
  ui/templates/index.html,sha256=x8isBJyxzt8UdkLJK4NDqBlEjn_FbXdFc1O_llti-R0,11945
52
53
  ui/utils/__init__.py,sha256=8Bo3TpyB49uJs6QpQMTWpUR2hJhqEpbkK_E_GW7tYpU,5
53
54
  ui/utils/registry_manager.py,sha256=jEhWMQJ3H1ZtyUP2ObneqFOVDTdDqiBvUh-46-rGFDw,6730
54
- codemap_python-0.1.1.dist-info/METADATA,sha256=gd2H56fRIsVq-dtcHVFkHMVyU-Ws7dWGlTGgULwMcCE,10382
55
- codemap_python-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
56
- codemap_python-0.1.1.dist-info/entry_points.txt,sha256=71TCgwo56CPxSfh-YSmofpNwq4-kSsjmBL_qzJ5kfmk,45
57
- codemap_python-0.1.1.dist-info/top_level.txt,sha256=JOu1LG-DyeBXc4u2Cn7KznNz-bZExhsf1CB7fV4r-t8,43
58
- codemap_python-0.1.1.dist-info/RECORD,,
55
+ codemap_python-0.1.3.dist-info/METADATA,sha256=P-5de7a2Ja2t8s_ZNwbgad7Fy6JjbslJ9t9FHwp-M7M,10382
56
+ codemap_python-0.1.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
57
+ codemap_python-0.1.3.dist-info/entry_points.txt,sha256=71TCgwo56CPxSfh-YSmofpNwq4-kSsjmBL_qzJ5kfmk,45
58
+ codemap_python-0.1.3.dist-info/top_level.txt,sha256=JOu1LG-DyeBXc4u2Cn7KznNz-bZExhsf1CB7fV4r-t8,43
59
+ codemap_python-0.1.3.dist-info/RECORD,,
ui/app.py CHANGED
@@ -36,20 +36,24 @@ from security_utils import redact_payload, redact_secrets
36
36
 
37
37
 
38
38
  # Custom cache class that doesn't cache (to avoid TypeError with unhashable Request objects)
39
- class NoCache:
40
- """A cache implementation that doesn't cache anything.
39
+ class NoCache(dict):
40
+ """A dict-like cache implementation that doesn't actually cache anything.
41
41
 
42
42
  This prevents Jinja2 from trying to cache templates with unhashable objects
43
- like the Starlette Request in the context.
43
+ like the Starlette Request in the context. Inherits from dict to satisfy
44
+ Jinja2's duck-typing requirements while not actually storing anything.
44
45
  """
45
- def get(self, key: Any, default: Any = None) -> Any:
46
- return default
47
-
48
- def set(self, key: Any, value: Any, timeout: Any = None) -> None:
46
+ def __setitem__(self, key: Any, value: Any) -> None:
47
+ """Silently ignore all cache assignments."""
49
48
  pass
50
49
 
51
- def clear(self) -> None:
52
- pass
50
+ def __getitem__(self, key: Any) -> Any:
51
+ """Always return KeyError to indicate cache miss."""
52
+ raise KeyError(key)
53
+
54
+ def get(self, key: Any, default: Any = None) -> Any:
55
+ """Always return the default value (cache miss)."""
56
+ return default
53
57
 
54
58
 
55
59
  PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))