codeanalyzer-python 0.3.0__py3-none-any.whl → 0.3.1__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.
@@ -0,0 +1,61 @@
1
+ """Provenance capture: repository (git) and analyzer identity for a snapshot.
2
+
3
+ Runs git only as a subprocess query against the analyzed project directory —
4
+ never mutates anything. Absence of git (no repo, no binary) degrades to None.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import subprocess
9
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
10
+ from pathlib import Path
11
+ from typing import Optional, Union
12
+ from urllib.parse import urlsplit, urlunsplit
13
+
14
+ from codeanalyzer.schema.py_schema import PyAnalyzerInfo, PyRepositoryInfo
15
+
16
+
17
+ def _strip_userinfo(uri: str) -> str:
18
+ """Drop credentials from URL-style remotes; scp-style git@host:path is a
19
+ username, not a secret, and parses with no netloc — left intact."""
20
+ parts = urlsplit(uri)
21
+ if parts.netloc and "@" in parts.netloc:
22
+ return urlunsplit(parts._replace(netloc=parts.netloc.rpartition("@")[2]))
23
+ return uri
24
+
25
+
26
+ def _git(project_dir: Union[Path, str], *args: str) -> Optional[str]:
27
+ """One git query; None on any failure (no repo, no git, timeout)."""
28
+ try:
29
+ result = subprocess.run(
30
+ ["git", "-C", str(project_dir), *args],
31
+ capture_output=True, text=True, timeout=10, check=False,
32
+ )
33
+ except (OSError, subprocess.TimeoutExpired):
34
+ return None
35
+ if result.returncode != 0:
36
+ return None
37
+ return result.stdout.strip()
38
+
39
+
40
+ def repository_info(project_dir: Union[Path, str]) -> Optional[PyRepositoryInfo]:
41
+ """Git provenance of ``project_dir``, or None when it isn't a git checkout."""
42
+ revision = _git(project_dir, "rev-parse", "HEAD")
43
+ if not revision:
44
+ return None
45
+ raw_uri = _git(project_dir, "remote", "get-url", "origin")
46
+ uri = _strip_userinfo(raw_uri) if raw_uri else None
47
+ status = _git(project_dir, "status", "--porcelain", "--untracked-files=no")
48
+ return PyRepositoryInfo(uri=uri, revision=revision, dirty=bool(status))
49
+
50
+
51
+ def analyzer_info(analysis_level: int) -> PyAnalyzerInfo:
52
+ """Identity + configuration of this analyzer run."""
53
+ try:
54
+ version = _pkg_version("codeanalyzer-python")
55
+ except PackageNotFoundError:
56
+ version = "unknown"
57
+ return PyAnalyzerInfo(
58
+ name="codeanalyzer-python",
59
+ version=version,
60
+ config={"analysis_level": analysis_level},
61
+ )
@@ -176,6 +176,7 @@ class PyImport(BaseModel):
176
176
  module: str
177
177
  name: str
178
178
  alias: Optional[str] = None
179
+ resolved_module: Optional[str] = None
179
180
  start_line: int = -1
180
181
  end_line: int = -1
181
182
  start_column: int = -1
@@ -240,6 +241,19 @@ class PyCallableParameter(BaseModel):
240
241
  end_column: int = -1
241
242
 
242
243
 
244
+ @builder
245
+ @msgpk
246
+ class PyCallArgument(BaseModel):
247
+ """One call-site argument: AST category + inferred type, kept separate.
248
+
249
+ The legacy ``PyCallsite.argument_types`` mixed these two vocabularies
250
+ in one list; this model is the disambiguated replacement (#86).
251
+ """
252
+
253
+ ast_kind: str
254
+ inferred_type: Optional[str] = None
255
+
256
+
243
257
  @builder
244
258
  @msgpk
245
259
  class PyCallsite(BaseModel):
@@ -249,6 +263,7 @@ class PyCallsite(BaseModel):
249
263
  receiver_expr: Optional[str] = None
250
264
  receiver_type: Optional[str] = None
251
265
  argument_types: List[str] = []
266
+ arguments: List[PyCallArgument] = []
252
267
  return_type: Optional[str] = None
253
268
  callee_signature: Optional[str] = None
254
269
  is_constructor_call: bool = False
@@ -295,6 +310,7 @@ class PyClassAttribute(BaseModel):
295
310
 
296
311
  name: str
297
312
  type: Optional[str] = None
313
+ initializer: Optional[str] = None
298
314
  comments: List[PyComment] = []
299
315
  start_line: int = -1
300
316
  end_line: int = -1
@@ -369,6 +385,26 @@ class PyExternalSymbol(BaseModel):
369
385
  module: Optional[str] = None # best-effort owning module, e.g. "requests"
370
386
 
371
387
 
388
+ @builder
389
+ @msgpk
390
+ class PyRepositoryInfo(BaseModel):
391
+ """Where the analyzed source came from: git provenance captured at analysis time."""
392
+
393
+ uri: Optional[str] = None
394
+ revision: str
395
+ dirty: bool = False
396
+
397
+
398
+ @builder
399
+ @msgpk
400
+ class PyAnalyzerInfo(BaseModel):
401
+ """Which analyzer produced this snapshot, and how it was configured."""
402
+
403
+ name: str
404
+ version: str
405
+ config: Dict[str, Any] = {}
406
+
407
+
372
408
  @builder
373
409
  @msgpk
374
410
  class PyApplication(BaseModel):
@@ -380,3 +416,5 @@ class PyApplication(BaseModel):
380
416
  # builtin members), keyed by signature. Populated by the analyzer so every
381
417
  # backend (JSON and Neo4j) shares one authoritative external-symbol set.
382
418
  external_symbols: Dict[str, PyExternalSymbol] = {}
419
+ analyzer: Optional[PyAnalyzerInfo] = None
420
+ repository: Optional[PyRepositoryInfo] = None
@@ -0,0 +1,67 @@
1
+ """Static resolution of import spellings against the analyzed module set.
2
+
3
+ Pure post-pass over a built ``PyApplication``: no filesystem access, no
4
+ sys.path semantics — a spelling resolves iff it names a module that was
5
+ itself analyzed (issue #82). External/library imports stay unresolved by
6
+ design and keep their :PyPackage projection.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Dict, Optional, Union
13
+
14
+ from codeanalyzer.schema.py_schema import PyApplication
15
+
16
+
17
+ def _dotted_candidates(app: PyApplication, project_dir: Union[Path, str]) -> Dict[str, str]:
18
+ """dotted module path -> file_key, for every analyzed module.
19
+
20
+ ``pkg/util.py`` -> ``pkg.util``; ``pkg/__init__.py`` -> ``pkg``.
21
+ file_keys share project_dir's form (both come from the same CLI arg),
22
+ so os.path.relpath keeps mixed absolute/relative setups consistent.
23
+ """
24
+ mapping: Dict[str, str] = {}
25
+ for file_key in app.symbol_table:
26
+ rel = os.path.relpath(file_key, str(project_dir))
27
+ if rel.startswith(".."):
28
+ continue
29
+ parts = Path(rel).with_suffix("").parts
30
+ if parts and parts[-1] == "__init__":
31
+ parts = parts[:-1]
32
+ if parts:
33
+ mapping[".".join(parts)] = file_key
34
+ return mapping
35
+
36
+
37
+ def _resolve_one(
38
+ spelling: str, original_name: str, importer_rel_parts: tuple, candidates: Dict[str, str]
39
+ ) -> Optional[str]:
40
+ if spelling.startswith("."):
41
+ level = len(spelling) - len(spelling.lstrip("."))
42
+ suffix = spelling.lstrip(".")
43
+ # level 1 = the importer's own package; each extra dot walks one up.
44
+ package_parts = importer_rel_parts[:-1] # drop the filename
45
+ if level - 1 > len(package_parts):
46
+ return None
47
+ base = package_parts[: len(package_parts) - (level - 1)]
48
+ stems = list(base) + (suffix.split(".") if suffix else [])
49
+ else:
50
+ stems = spelling.split(".")
51
+ dotted = ".".join(stems)
52
+ with_name = f"{dotted}.{original_name}" if dotted else original_name
53
+ return candidates.get(with_name) or candidates.get(dotted)
54
+
55
+
56
+ def resolve_imports(app: PyApplication, project_dir: Union[Path, str]) -> None:
57
+ """Stamp ``resolved_module`` on every import of every module, in place."""
58
+ candidates = _dotted_candidates(app, project_dir)
59
+ for file_key, module in app.symbol_table.items():
60
+ rel = os.path.relpath(file_key, str(project_dir))
61
+ importer_parts = Path(rel).parts
62
+ for im in module.imports or []:
63
+ if not im.module:
64
+ im.resolved_module = None
65
+ continue
66
+ original = im.alias or im.name
67
+ im.resolved_module = _resolve_one(im.module, original, importer_parts, candidates)
@@ -1,5 +1,6 @@
1
1
  import ast
2
2
  import hashlib
3
+ import os
3
4
  import tokenize
4
5
  from ast import AST, ClassDef
5
6
  from io import StringIO
@@ -13,6 +14,7 @@ from jedi.api.project import Project
13
14
  from codeanalyzer.schema.py_schema import (
14
15
  PyCallable,
15
16
  PyCallableParameter,
17
+ PyCallArgument,
16
18
  PyCallsite,
17
19
  PyClass,
18
20
  PyClassAttribute,
@@ -28,7 +30,11 @@ class SymbolTableBuilder:
28
30
  """A class for building a symbol table for a Python project."""
29
31
 
30
32
  def __init__(self, project_dir: Union[Path, str], virtualenv: Union[Path, str, None]) -> None:
31
- self.project_dir = Path(project_dir)
33
+ # Jedi reports absolute Script paths, so a relative project_dir would
34
+ # crash every relative_to() fallback below. abspath (not resolve())
35
+ # keeps symlinks intact, matching Jedi's own absolute()-style
36
+ # normalization under symlinked roots like macOS /tmp.
37
+ self.project_dir = Path(os.path.abspath(project_dir))
32
38
  if virtualenv is None:
33
39
  # If no virtual environment is provided, create a jedi project without an environment.
34
40
  self.jedi_project: Project = jedi.Project(path=self.project_dir)
@@ -39,6 +45,16 @@ class SymbolTableBuilder:
39
45
  environment_path=Path(virtualenv) / "bin" / "python",
40
46
  )
41
47
 
48
+ def _fallback_signature(self, script_path: Union[Path, str], name: str) -> str:
49
+ """Path-derived qualified name used when Jedi can't name a definition.
50
+
51
+ Strips only the terminal ``.py`` suffix — a bare ``str.replace``
52
+ would also eat interior ``.py`` substrings and corrupt the module
53
+ prefix (``odoo/tools/pycompat.py`` → ``odoo.toolscompat``).
54
+ """
55
+ relative = Path(script_path).relative_to(self.project_dir)
56
+ return ".".join(relative.with_suffix("").parts) + f".{name}"
57
+
42
58
  @staticmethod
43
59
  def _infer_type(script: Script, line: int, column: int) -> str:
44
60
  """Tries to infer the type at a given position using Jedi."""
@@ -97,6 +113,44 @@ class SymbolTableBuilder:
97
113
  except Exception:
98
114
  return None, False
99
115
 
116
+ @staticmethod
117
+ def _callee_anchor(node: ast.Call) -> Tuple[int, int]:
118
+ """Position of the callee *name* for Jedi inference.
119
+
120
+ An ``ast.Call``'s own ``lineno``/``col_offset`` is the first
121
+ character of the whole call expression — for an attribute call
122
+ ``receiver.method(...)`` that is the receiver token, and Jedi
123
+ would infer the receiver's type instead of the invoked method
124
+ (issue #80). Anchor attribute calls inside the attribute name —
125
+ its last character, so one-character names stay in range; other
126
+ callee shapes keep the call-expression start.
127
+ """
128
+ func_expr = node.func
129
+ if isinstance(func_expr, ast.Attribute):
130
+ return func_expr.end_lineno, func_expr.end_col_offset - 1
131
+ return node.lineno, node.col_offset
132
+
133
+ @staticmethod
134
+ def _infer_call_return_type(script: Script, line: int, column: int) -> Optional[str]:
135
+ """Inferred type of the call's *result*, not of the callee itself.
136
+
137
+ ``Script.infer`` at the callee name yields the function/class
138
+ being called; executing that definition yields what the call
139
+ evaluates to — a function's inferred return type, or the
140
+ instance for a constructor call. Returns ``None`` when Jedi
141
+ can't tell, so an unknown stays absent instead of masquerading
142
+ as the callee's own name.
143
+ """
144
+ try:
145
+ definitions = script.infer(line=line, column=column)
146
+ if definitions:
147
+ results = definitions[0].execute()
148
+ if results:
149
+ return results[0].name
150
+ except Exception:
151
+ pass
152
+ return None
153
+
100
154
  def build_pymodule_from_file(self, py_file: Path) -> PyModule:
101
155
  """Builds a PyModule from a Python file.
102
156
 
@@ -203,10 +257,10 @@ class SymbolTableBuilder:
203
257
  definitions = script.goto(line=start_line, column=child.col_offset)
204
258
  signature = next(
205
259
  (d.full_name for d in definitions if d.type == "class"),
206
- f"{Path(script.path).relative_to(self.project_dir).__str__().replace('/', '.').replace('.py', '')}.{class_name}"
260
+ self._fallback_signature(script.path, class_name),
207
261
  )
208
262
  except Exception:
209
- signature = f"{Path(script.path).relative_to(self.project_dir).__str__().replace('/', '.').replace('.py', '')}.{class_name}"
263
+ signature = self._fallback_signature(script.path, class_name)
210
264
  py_class = (
211
265
  PyClass.builder()
212
266
  .name(class_name)
@@ -258,8 +312,7 @@ class SymbolTableBuilder:
258
312
 
259
313
  # If Jedi didn't provide a signature, build one relative to project_dir
260
314
  if not signature:
261
- relative_path = Path(script.path).relative_to(self.project_dir)
262
- signature = f"{str(relative_path).replace('/', '.').replace('.py', '')}.{method_name}"
315
+ signature = self._fallback_signature(script.path, method_name)
263
316
  py_callable = (
264
317
  PyCallable.builder()
265
318
  .name(method_name) # Use the actual method name, not the full signature
@@ -380,6 +433,7 @@ class SymbolTableBuilder:
380
433
  script, target.lineno, target.col_offset
381
434
  )
382
435
  )
436
+ .initializer(ast.unparse(stmt.value) if stmt.value else None)
383
437
  .start_line(getattr(target, "lineno", -1))
384
438
  .end_line(getattr(stmt, "end_lineno", stmt.lineno))
385
439
  .build()
@@ -398,6 +452,7 @@ class SymbolTableBuilder:
398
452
  script, target.lineno, target.col_offset
399
453
  )
400
454
  )
455
+ .initializer(ast.unparse(stmt.value) if stmt.value else None)
401
456
  .start_line(getattr(target, "lineno", -1))
402
457
  .end_line(getattr(stmt, "end_lineno", stmt.lineno))
403
458
  .build()
@@ -588,10 +643,11 @@ class SymbolTableBuilder:
588
643
  func_expr = node.func
589
644
 
590
645
  method_name = "<unknown>"
646
+ anchor_line, anchor_col = self._callee_anchor(node)
591
647
  callee_signature, is_constructor = self._infer_callee(
592
- script, node.lineno, node.col_offset
648
+ script, anchor_line, anchor_col
593
649
  )
594
- return_type = self._infer_type(script, node.lineno, node.col_offset)
650
+ return_type = self._infer_call_return_type(script, anchor_line, anchor_col)
595
651
 
596
652
  receiver_expr = None
597
653
  receiver_type = None
@@ -604,18 +660,26 @@ class SymbolTableBuilder:
604
660
  elif isinstance(func_expr, ast.Name):
605
661
  method_name = func_expr.id
606
662
 
607
- argument_types = [
608
- self._infer_type(script, arg.lineno, arg.col_offset)
609
- or type(arg).__name__
663
+ arguments = [
664
+ PyCallArgument(
665
+ ast_kind=type(arg).__name__,
666
+ inferred_type=self._infer_type(script, arg.lineno, arg.col_offset),
667
+ )
610
668
  for arg in node.args
611
669
  ]
612
670
 
671
+ # Legacy field, derived from the structured arguments above rather
672
+ # than re-inferring: byte-identical to the old
673
+ # `self._infer_type(...) or type(arg).__name__` per argument.
674
+ argument_types = [a.inferred_type or a.ast_kind for a in arguments]
675
+
613
676
  call_sites.append(
614
677
  PyCallsite.builder()
615
678
  .method_name(method_name)
616
679
  .receiver_expr(receiver_expr)
617
680
  .receiver_type(receiver_type)
618
681
  .argument_types(argument_types)
682
+ .arguments(arguments)
619
683
  .return_type(return_type)
620
684
  .callee_signature(callee_signature)
621
685
  .is_constructor_call(is_constructor)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeanalyzer-python
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Static Analysis on Python source code using Jedi, CodeQL and Treesitter — emits analysis.json or a Neo4j property graph.
5
5
  Author-email: Rahul Krishna <i.m.ralk@gmail.com>
6
6
  License-File: LICENSE
@@ -181,6 +181,9 @@ $ canpy --help
181
181
  Static Analysis on Python source code using Jedi, PyCG and Tree sitter.
182
182
 
183
183
  ╭─ Options ────────────────────────────────────────────────────────────────────╮
184
+ │ --version Show the canpy │
185
+ │ version and │
186
+ │ exit. │
184
187
  │ --input -i PATH Path to the │
185
188
  │ project root │
186
189
  │ directory (not │
@@ -1,23 +1,23 @@
1
1
  codeanalyzer/__init__.py,sha256=BZ3Kuwl-F_F-8H8cepLnVJ4Ku4NNUjjqg0Y6ujPQSsI,108
2
- codeanalyzer/__main__.py,sha256=-los-YRXrmZsISNrWh8EjgUcUSkUbSGqEX7yadZuvB8,11920
3
- codeanalyzer/core.py,sha256=rUKEa4jAbQFtvAnkDKinBGznLpb6VquULLlMVoFjMTU,29533
2
+ codeanalyzer/__main__.py,sha256=GOGc6j-euSeRZbUQgfZykQClT32WNxcStKoO96s-VSY,12790
3
+ codeanalyzer/core.py,sha256=BCH5OynbXrnItPvYhcpAChcBtkXdQIMbH_fyFGCxITU,31185
4
+ codeanalyzer/provenance.py,sha256=DT-DqwdVwO7g-GyK3sC0_4vDNCUjZYEY1fyYCt-7VRM,2306
4
5
  codeanalyzer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
6
  codeanalyzer/config/__init__.py,sha256=9XBxAn1oWGRuhg3bEBUuVGs3hFNXEAKrr-Ce7tq9a2k,61
6
7
  codeanalyzer/config/config.py,sha256=ZiKzc5uEUCIvih58-6BDtLLI1hPij41wGQjBcj9KNQM,188
7
8
  codeanalyzer/jedi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
9
  codeanalyzer/jedi/jedi.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- codeanalyzer/neo4j/__init__.py,sha256=AcbFNAMuXwkMFWH4h_HCmla6PCKTF3Xe5yNqg8F_kYk,1575
10
+ codeanalyzer/neo4j/__init__.py,sha256=8Ei6uThTOqmul6GhnKoGOjLXJZbyDwF5Jo-8I8NxnAk,1574
10
11
  codeanalyzer/neo4j/bolt.py,sha256=-IFDb_d67IkGwxvmmbLNI6AvSQHY-XsutI3szibWidw,9635
11
- codeanalyzer/neo4j/catalog.py,sha256=VQWtwcueck2Ug_cu8sitvAoRwbVZ-6KRiexRN8q1Wak,7418
12
12
  codeanalyzer/neo4j/cypher.py,sha256=2zIWXA1AADrwCMhSTeqKjEXRgBjbob6o3bme_cwLu0s,5024
13
- codeanalyzer/neo4j/emit.py,sha256=WtCndN6mA6PIzfzdgv9Xc5S5WP4rHUXCtB_r3G16rkg,3101
14
- codeanalyzer/neo4j/project.py,sha256=2GDHkFjmWibrpMaOiDMo21K6Lnj3aefl2QPCM7ANA7g,12485
13
+ codeanalyzer/neo4j/emit.py,sha256=NZL5BVY1Fb32igH22986_cUFUIgHNWJHbii2bfX2E3M,3099
14
+ codeanalyzer/neo4j/project.py,sha256=YMVtF1GjLZYwnhFl-7fy9Qj7fpGgd5aEGOp0JMF3VAY,14513
15
15
  codeanalyzer/neo4j/rows.py,sha256=5xI3X-l-vwPe_gmKYUg7VuUQARcOlVAZnGmiQr9QyRk,7326
16
- codeanalyzer/neo4j/schema.py,sha256=5xz8cZVuL73GAF-vs9QtN2TuKxIty0rHEe68nwQmLTY,2136
16
+ codeanalyzer/neo4j/schema.py,sha256=tZjnpIFdTV3-GB1x2zTbDMxQOH16GqELYCf2cR2XhgU,8765
17
17
  codeanalyzer/options/__init__.py,sha256=Ki4qhHFqpyuUWVsntO-NYJMVWrkeFOzPW4nQ7oxiUVI,155
18
18
  codeanalyzer/options/options.py,sha256=5w3DZYlAv-0LVPavF1P5EEJp8XBRrSXidbDusdbXQAo,1976
19
19
  codeanalyzer/schema/__init__.py,sha256=cLPjvowrnz8xzi7tZAsKQeIOjdOKRGHy4I7wbG0jHk8,2024
20
- codeanalyzer/schema/py_schema.py,sha256=6hISjVzoWTq-SHLjT7FsL__uzHT5DY0NYqlpJTLsc9s,12314
20
+ codeanalyzer/schema/py_schema.py,sha256=yYKkg3ufNDteNvtiPiRJQz-pzK5NnnnNldhXcW7p2RI,13274
21
21
  codeanalyzer/semantic_analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  codeanalyzer/semantic_analysis/call_graph.py,sha256=H3IkfGp1VCkDxI75JPyLSSA-wOaJp_I-NYPsjCY04Bg,11185
23
23
  codeanalyzer/semantic_analysis/pycg/__init__.py,sha256=Lsgz25iFM_RGGu_i2psY-LN-KwvYIZQPnKgRPL1JsjU,928
@@ -26,13 +26,14 @@ codeanalyzer/semantic_analysis/pycg/pycg_exceptions.py,sha256=n4tRderrSYw9cUYgR2
26
26
  codeanalyzer/semantic_analysis/pycg/shard_planner.py,sha256=PTXMG9YcrZX9hgnlnIlpQx6xmWkizP3aQrf0elcrnmw,15865
27
27
  codeanalyzer/syntactic_analysis/__init__.py,sha256=EUQkJEh6wHjWx2qTTKbTbUgwSbfKeNieKHNy7RknVXA,476
28
28
  codeanalyzer/syntactic_analysis/exceptions.py,sha256=whs_n0vIu655Jkk1a7iOoXY6iIca4pZqJnU40V9Ejaw,537
29
- codeanalyzer/syntactic_analysis/symbol_table_builder.py,sha256=zmHFt8pN50jG-Ex4fnisvbLmn1XaW05jwbV_xSG4qfU,38177
29
+ codeanalyzer/syntactic_analysis/import_resolver.py,sha256=Q8noZwSdDNt4P4NMqDTB9FaS-M0_d4ASK9GnJop9MDI,2751
30
+ codeanalyzer/syntactic_analysis/symbol_table_builder.py,sha256=hQOEk2qRYM9Y_UoejNbZFCWSkDjBa-vRcU3kPURrMNc,41148
30
31
  codeanalyzer/utils/__init__.py,sha256=hC6VWdR5rerSqBxzu9KQHTASWqwrrYJv-CMDwrTlzkc,137
31
32
  codeanalyzer/utils/logging.py,sha256=Fw0tattPAOMs3o0JMjjXhRVLIF64f-SCcygUXF9jqeg,904
32
33
  codeanalyzer/utils/progress_bar.py,sha256=C9JtzVdd10lIxTv-KA6PebqjKWueC_vMGwVzAtHuHIw,2818
33
- codeanalyzer_python-0.3.0.dist-info/METADATA,sha256=l7oUfSjJiPF1mvfQQOmm6JyzIWOBveKKp8xHJF0dw-E,33777
34
- codeanalyzer_python-0.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
35
- codeanalyzer_python-0.3.0.dist-info/entry_points.txt,sha256=v4Vux0Nnx7sOntVk_CH7W9RX6SkIkvR1FQYq73oVlCQ,105
36
- codeanalyzer_python-0.3.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
37
- codeanalyzer_python-0.3.0.dist-info/licenses/NOTICE,sha256=YU0Z9NDWqKY-2jfFcbxeZ6fbnzz0oZeKmnUcO8a-bcQ,901
38
- codeanalyzer_python-0.3.0.dist-info/RECORD,,
34
+ codeanalyzer_python-0.3.1.dist-info/METADATA,sha256=F-I41TfuL_typkjLjnjSxR8fPvUu1VgbAX8K1_vnm80,34032
35
+ codeanalyzer_python-0.3.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
36
+ codeanalyzer_python-0.3.1.dist-info/entry_points.txt,sha256=v4Vux0Nnx7sOntVk_CH7W9RX6SkIkvR1FQYq73oVlCQ,105
37
+ codeanalyzer_python-0.3.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
38
+ codeanalyzer_python-0.3.1.dist-info/licenses/NOTICE,sha256=YU0Z9NDWqKY-2jfFcbxeZ6fbnzz0oZeKmnUcO8a-bcQ,901
39
+ codeanalyzer_python-0.3.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any