skylos 1.2.0__tar.gz → 1.2.1__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.

Potentially problematic release.


This version of skylos might be problematic. Click here for more details.

Files changed (38) hide show
  1. {skylos-1.2.0 → skylos-1.2.1}/PKG-INFO +1 -1
  2. {skylos-1.2.0 → skylos-1.2.1}/README.md +1 -0
  3. {skylos-1.2.0 → skylos-1.2.1}/pyproject.toml +1 -1
  4. {skylos-1.2.0 → skylos-1.2.1}/setup.py +1 -1
  5. {skylos-1.2.0 → skylos-1.2.1}/skylos/analyzer.py +40 -5
  6. {skylos-1.2.0 → skylos-1.2.1}/skylos.egg-info/PKG-INFO +1 -1
  7. {skylos-1.2.0 → skylos-1.2.1}/test/test_analyzer.py +33 -3
  8. {skylos-1.2.0 → skylos-1.2.1}/setup.cfg +0 -0
  9. {skylos-1.2.0 → skylos-1.2.1}/skylos/__init__.py +0 -0
  10. {skylos-1.2.0 → skylos-1.2.1}/skylos/cli.py +0 -0
  11. {skylos-1.2.0 → skylos-1.2.1}/skylos/constants.py +0 -0
  12. {skylos-1.2.0 → skylos-1.2.1}/skylos/framework_aware.py +0 -0
  13. {skylos-1.2.0 → skylos-1.2.1}/skylos/test_aware.py +0 -0
  14. {skylos-1.2.0 → skylos-1.2.1}/skylos/visitor.py +0 -0
  15. {skylos-1.2.0 → skylos-1.2.1}/skylos.egg-info/SOURCES.txt +0 -0
  16. {skylos-1.2.0 → skylos-1.2.1}/skylos.egg-info/dependency_links.txt +0 -0
  17. {skylos-1.2.0 → skylos-1.2.1}/skylos.egg-info/entry_points.txt +0 -0
  18. {skylos-1.2.0 → skylos-1.2.1}/skylos.egg-info/requires.txt +0 -0
  19. {skylos-1.2.0 → skylos-1.2.1}/skylos.egg-info/top_level.txt +0 -0
  20. {skylos-1.2.0 → skylos-1.2.1}/test/__init__.py +0 -0
  21. {skylos-1.2.0 → skylos-1.2.1}/test/compare_tools.py +0 -0
  22. {skylos-1.2.0 → skylos-1.2.1}/test/conftest.py +0 -0
  23. {skylos-1.2.0 → skylos-1.2.1}/test/diagnostics.py +0 -0
  24. {skylos-1.2.0 → skylos-1.2.1}/test/sample_repo/__init__.py +0 -0
  25. {skylos-1.2.0 → skylos-1.2.1}/test/sample_repo/app.py +0 -0
  26. {skylos-1.2.0 → skylos-1.2.1}/test/sample_repo/sample_repo/__init__.py +0 -0
  27. {skylos-1.2.0 → skylos-1.2.1}/test/sample_repo/sample_repo/commands.py +0 -0
  28. {skylos-1.2.0 → skylos-1.2.1}/test/sample_repo/sample_repo/models.py +0 -0
  29. {skylos-1.2.0 → skylos-1.2.1}/test/sample_repo/sample_repo/routes.py +0 -0
  30. {skylos-1.2.0 → skylos-1.2.1}/test/sample_repo/sample_repo/utils.py +0 -0
  31. {skylos-1.2.0 → skylos-1.2.1}/test/test_changes_analyzer.py +0 -0
  32. {skylos-1.2.0 → skylos-1.2.1}/test/test_cli.py +0 -0
  33. {skylos-1.2.0 → skylos-1.2.1}/test/test_constants.py +0 -0
  34. {skylos-1.2.0 → skylos-1.2.1}/test/test_framework_aware.py +0 -0
  35. {skylos-1.2.0 → skylos-1.2.1}/test/test_integration.py +0 -0
  36. {skylos-1.2.0 → skylos-1.2.1}/test/test_skylos.py +0 -0
  37. {skylos-1.2.0 → skylos-1.2.1}/test/test_test_aware.py +0 -0
  38. {skylos-1.2.0 → skylos-1.2.1}/test/test_visitor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skylos
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: A static analysis tool for Python codebases
5
5
  Author-email: oha <aaronoh2015@gmail.com>
6
6
  Requires-Python: >=3.9
@@ -3,6 +3,7 @@
3
3
  ![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)
4
4
  ![100% Local](https://img.shields.io/badge/privacy-100%25%20local-brightgreen)
5
5
  ![PyPI version](https://img.shields.io/pypi/v/skylos)
6
+ ![Security Policy](https://img.shields.io/badge/security-policy-brightgreen)
6
7
 
7
8
  <div align="center">
8
9
  <img src="assets/SKYLOS.png" alt="Skylos Logo" width="200">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "skylos"
7
- version = "1.2.0"
7
+ version = "1.2.1"
8
8
  requires-python = ">=3.9"
9
9
  description = "A static analysis tool for Python codebases"
10
10
  authors = [{name = "oha", email = "aaronoh2015@gmail.com"}]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="skylos",
5
- version="1.2.0",
5
+ version="1.2.1",
6
6
  packages=find_packages(),
7
7
  python_requires=">=3.9",
8
8
  install_requires=["inquirer>=3.0.0"],
@@ -11,6 +11,10 @@ from skylos.test_aware import TestAwareVisitor
11
11
  import os
12
12
  import traceback
13
13
  from skylos.framework_aware import FrameworkAwareVisitor, detect_framework_usage
14
+ import io
15
+ import tokenize
16
+ import re
17
+ import warnings
14
18
 
15
19
  logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
16
20
  logger=logging.getLogger('Skylos')
@@ -30,12 +34,27 @@ def parse_exclude_folders(user_exclude_folders, use_defaults=True, include_folde
30
34
 
31
35
  return exclude_set
32
36
 
37
+ IGNORE_PATTERNS = (
38
+ r"#\s*pragma:\s*no\s+skylos", ## our own pragma
39
+ r"#\s*pragma:\s*no\s+cover",
40
+ r"#\s*noqa(?:\b|:)", # flake8 style
41
+ )
42
+
43
+ def _collect_ignored_lines(source: str) -> set[int]:
44
+ ignores = set()
45
+ for tok in tokenize.generate_tokens(io.StringIO(source).readline):
46
+ if tok.type == tokenize.COMMENT:
47
+ if any(re.search(pat, tok.string, flags=re.I) for pat in IGNORE_PATTERNS):
48
+ ignores.add(tok.start[0])
49
+ return ignores
50
+
33
51
  class Skylos:
34
52
  def __init__(self):
35
53
  self.defs={}
36
54
  self.refs=[]
37
55
  self.dynamic=set()
38
56
  self.exports=defaultdict(set)
57
+ self.ignored_lines:set[int]=set()
39
58
 
40
59
  def _module(self,root,f):
41
60
  p=list(f.relative_to(root).parts)
@@ -232,7 +251,20 @@ class Skylos:
232
251
 
233
252
  for file in files:
234
253
  mod = modmap[file]
235
- defs, refs, dyn, exports, test_flags, framework_flags = proc_file(file, mod)
254
+
255
+ result = proc_file(file, mod)
256
+
257
+ if len(result) == 7: ##new
258
+ defs, refs, dyn, exports, test_flags, framework_flags, ignored = result
259
+ self.ignored_lines.update(ignored)
260
+ else: ##legacy
261
+ warnings.warn(
262
+ "proc_file() now returns 7 values (added ignored_lines). "
263
+ "The 6-value form is deprecated and will disappear.",
264
+ DeprecationWarning,
265
+ stacklevel=2,
266
+ )
267
+ defs, refs, dyn, exports, test_flags, framework_flags = result
236
268
 
237
269
  # apply penalties while we still have the file-specific flags
238
270
  for d in defs:
@@ -251,8 +283,10 @@ class Skylos:
251
283
 
252
284
  unused = []
253
285
  for d in self.defs.values():
254
- if d.references == 0 and not d.is_exported and d.confidence > 0 and d.confidence >= thr:
255
- unused.append(d.to_dict())
286
+ if (d.references == 0 and not d.is_exported
287
+ and d.confidence >= thr
288
+ and d.line not in self.ignored_lines):
289
+ unused.append(d.to_dict())
256
290
 
257
291
  result = {
258
292
  "unused_functions": [],
@@ -288,6 +322,7 @@ def proc_file(file_or_args, mod=None):
288
322
 
289
323
  try:
290
324
  source = Path(file).read_text(encoding="utf-8")
325
+ ignored = _collect_ignored_lines(source)
291
326
  tree = ast.parse(source)
292
327
 
293
328
  tv = TestAwareVisitor(filename=file)
@@ -299,7 +334,7 @@ def proc_file(file_or_args, mod=None):
299
334
  v = Visitor(mod, file)
300
335
  v.visit(tree)
301
336
 
302
- return v.defs, v.refs, v.dyn, v.exports, tv, fv
337
+ return v.defs, v.refs, v.dyn, v.exports, tv, fv, ignored
303
338
  except Exception as e:
304
339
  logger.error(f"{file}: {e}")
305
340
  if os.getenv("SKYLOS_DEBUG"):
@@ -307,7 +342,7 @@ def proc_file(file_or_args, mod=None):
307
342
  dummy_visitor = TestAwareVisitor(filename=file)
308
343
  dummy_framework_visitor = FrameworkAwareVisitor(filename=file)
309
344
 
310
- return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
345
+ return [], [], set(), set(), dummy_visitor, dummy_framework_visitor, set()
311
346
 
312
347
  def analyze(path,conf=60, exclude_folders=None):
313
348
  return Skylos().analyze(path,conf, exclude_folders)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skylos
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: A static analysis tool for Python codebases
5
5
  Author-email: oha <aaronoh2015@gmail.com>
6
6
  Requires-Python: >=3.9
@@ -451,7 +451,7 @@ class TestClass:
451
451
  mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
452
452
  mock_framework_visitor_class.return_value = mock_framework_visitor
453
453
 
454
- defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
454
+ defs, refs, dyn, exports, test_flags, framework_flags, _ = proc_file(f.name, "test_module")
455
455
 
456
456
  mock_visitor_class.assert_called_once_with("test_module", f.name)
457
457
  mock_visitor.visit.assert_called_once()
@@ -472,7 +472,7 @@ class TestClass:
472
472
  f.flush()
473
473
 
474
474
  try:
475
- defs, refs, dyn, exports, test_flags, framework_flags = proc_file(f.name, "test_module")
475
+ defs, refs, dyn, exports, test_flags, framework_flags, _ = proc_file(f.name, "test_module")
476
476
 
477
477
  assert defs == []
478
478
  assert refs == []
@@ -506,7 +506,7 @@ class TestClass:
506
506
  mock_framework_visitor = Mock(spec=FrameworkAwareVisitor)
507
507
  mock_framework_visitor_class.return_value = mock_framework_visitor
508
508
 
509
- # defs, refs, dyn, exports, test_flags, framework_flags = proc_file((f.name, "test_module"))
509
+ defs, refs, dyn, exports, test_flags, framework_flags, _ = proc_file((f.name, "test_module"))
510
510
 
511
511
  mock_visitor_class.assert_called_once_with("test_module", f.name)
512
512
  finally:
@@ -608,5 +608,35 @@ class TestApplyPenalties:
608
608
  skylos._apply_penalties(mock_def, mock_test_aware_visitor, mock_framework_aware_visitor)
609
609
  assert mock_def.confidence == 0
610
610
 
611
+ class TestIgnorePragmas:
612
+ def test_analyze_respects_ignore_pragmas(self, tmp_path):
613
+ src = tmp_path / "demo.py"
614
+ src.write_text(
615
+ """
616
+ def used():
617
+ pass
618
+
619
+ def unused_no_ignore():
620
+ pass
621
+
622
+ def unused_ignore(): # pragma: no skylos
623
+ pass
624
+
625
+ used()
626
+ """
627
+ )
628
+
629
+ result_json = analyze(str(tmp_path), conf=0)
630
+ result = json.loads(result_json)
631
+
632
+ # collect names of functions flagged as unreachable
633
+ unreachable = {item["name"].split(".")[-1] for item in result["unused_functions"]}
634
+
635
+ # expectations
636
+ assert "unused_no_ignore" in unreachable
637
+ assert "unused_ignore" not in unreachable
638
+ assert "used" not in unreachable
639
+
640
+
611
641
  if __name__ == "__main__":
612
642
  pytest.main([__file__, "-v"])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes