skylos 1.2.0__tar.gz → 1.2.2__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.2}/PKG-INFO +1 -1
  2. {skylos-1.2.0 → skylos-1.2.2}/README.md +2 -0
  3. {skylos-1.2.0 → skylos-1.2.2}/pyproject.toml +1 -1
  4. {skylos-1.2.0 → skylos-1.2.2}/setup.py +1 -1
  5. {skylos-1.2.0 → skylos-1.2.2}/skylos/analyzer.py +42 -4
  6. {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/PKG-INFO +1 -1
  7. {skylos-1.2.0 → skylos-1.2.2}/test/test_analyzer.py +33 -3
  8. {skylos-1.2.0 → skylos-1.2.2}/setup.cfg +0 -0
  9. {skylos-1.2.0 → skylos-1.2.2}/skylos/__init__.py +0 -0
  10. {skylos-1.2.0 → skylos-1.2.2}/skylos/cli.py +0 -0
  11. {skylos-1.2.0 → skylos-1.2.2}/skylos/constants.py +0 -0
  12. {skylos-1.2.0 → skylos-1.2.2}/skylos/framework_aware.py +0 -0
  13. {skylos-1.2.0 → skylos-1.2.2}/skylos/test_aware.py +0 -0
  14. {skylos-1.2.0 → skylos-1.2.2}/skylos/visitor.py +0 -0
  15. {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/SOURCES.txt +0 -0
  16. {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/dependency_links.txt +0 -0
  17. {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/entry_points.txt +0 -0
  18. {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/requires.txt +0 -0
  19. {skylos-1.2.0 → skylos-1.2.2}/skylos.egg-info/top_level.txt +0 -0
  20. {skylos-1.2.0 → skylos-1.2.2}/test/__init__.py +0 -0
  21. {skylos-1.2.0 → skylos-1.2.2}/test/compare_tools.py +0 -0
  22. {skylos-1.2.0 → skylos-1.2.2}/test/conftest.py +0 -0
  23. {skylos-1.2.0 → skylos-1.2.2}/test/diagnostics.py +0 -0
  24. {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/__init__.py +0 -0
  25. {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/app.py +0 -0
  26. {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/__init__.py +0 -0
  27. {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/commands.py +0 -0
  28. {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/models.py +0 -0
  29. {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/routes.py +0 -0
  30. {skylos-1.2.0 → skylos-1.2.2}/test/sample_repo/sample_repo/utils.py +0 -0
  31. {skylos-1.2.0 → skylos-1.2.2}/test/test_changes_analyzer.py +0 -0
  32. {skylos-1.2.0 → skylos-1.2.2}/test/test_cli.py +0 -0
  33. {skylos-1.2.0 → skylos-1.2.2}/test/test_constants.py +0 -0
  34. {skylos-1.2.0 → skylos-1.2.2}/test/test_framework_aware.py +0 -0
  35. {skylos-1.2.0 → skylos-1.2.2}/test/test_integration.py +0 -0
  36. {skylos-1.2.0 → skylos-1.2.2}/test/test_skylos.py +0 -0
  37. {skylos-1.2.0 → skylos-1.2.2}/test/test_test_aware.py +0 -0
  38. {skylos-1.2.0 → skylos-1.2.2}/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.2
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">
@@ -40,6 +41,7 @@
40
41
  * **Unused Classes**: Detects classes that are not instantiated or inherited
41
42
  * **Unused Imports**: Identifies imports that are not used
42
43
  * **Folder Management**: Inclusion/exclusion of directories
44
+ * **Ignore Pragmas**: Skip lines tagged with `# pragma: no skylos`, `# pragma: no cover`, or `# noqa`
43
45
 
44
46
  ## Benchmark (You can find this benchmark test in `test` folder)
45
47
 
@@ -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.2"
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.2",
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,7 +283,12 @@ 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:
286
+ # skip anything on line that carries ignore pragma
287
+ if d.line in self.ignored_lines:
288
+ continue
289
+
290
+ if (d.references == 0 and not d.is_exported
291
+ and d.confidence >= thr):
255
292
  unused.append(d.to_dict())
256
293
 
257
294
  result = {
@@ -288,6 +325,7 @@ def proc_file(file_or_args, mod=None):
288
325
 
289
326
  try:
290
327
  source = Path(file).read_text(encoding="utf-8")
328
+ ignored = _collect_ignored_lines(source)
291
329
  tree = ast.parse(source)
292
330
 
293
331
  tv = TestAwareVisitor(filename=file)
@@ -299,7 +337,7 @@ def proc_file(file_or_args, mod=None):
299
337
  v = Visitor(mod, file)
300
338
  v.visit(tree)
301
339
 
302
- return v.defs, v.refs, v.dyn, v.exports, tv, fv
340
+ return v.defs, v.refs, v.dyn, v.exports, tv, fv, ignored
303
341
  except Exception as e:
304
342
  logger.error(f"{file}: {e}")
305
343
  if os.getenv("SKYLOS_DEBUG"):
@@ -307,7 +345,7 @@ def proc_file(file_or_args, mod=None):
307
345
  dummy_visitor = TestAwareVisitor(filename=file)
308
346
  dummy_framework_visitor = FrameworkAwareVisitor(filename=file)
309
347
 
310
- return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
348
+ return [], [], set(), set(), dummy_visitor, dummy_framework_visitor, set()
311
349
 
312
350
  def analyze(path,conf=60, exclude_folders=None):
313
351
  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.2
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