skylos 1.1.11__tar.gz → 1.1.12__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 (32) hide show
  1. {skylos-1.1.11 → skylos-1.1.12}/PKG-INFO +1 -1
  2. {skylos-1.1.11 → skylos-1.1.12}/README.md +15 -9
  3. {skylos-1.1.11 → skylos-1.1.12}/pyproject.toml +1 -1
  4. {skylos-1.1.11 → skylos-1.1.12}/setup.py +1 -1
  5. {skylos-1.1.11 → skylos-1.1.12}/skylos/analyzer.py +89 -26
  6. skylos-1.1.12/skylos/constants.py +35 -0
  7. {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/PKG-INFO +1 -1
  8. {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/SOURCES.txt +1 -0
  9. {skylos-1.1.11 → skylos-1.1.12}/setup.cfg +0 -0
  10. {skylos-1.1.11 → skylos-1.1.12}/skylos/__init__.py +0 -0
  11. {skylos-1.1.11 → skylos-1.1.12}/skylos/cli.py +0 -0
  12. {skylos-1.1.11 → skylos-1.1.12}/skylos/visitor.py +0 -0
  13. {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/dependency_links.txt +0 -0
  14. {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/entry_points.txt +0 -0
  15. {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/requires.txt +0 -0
  16. {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/top_level.txt +0 -0
  17. {skylos-1.1.11 → skylos-1.1.12}/test/__init__.py +0 -0
  18. {skylos-1.1.11 → skylos-1.1.12}/test/compare_tools.py +0 -0
  19. {skylos-1.1.11 → skylos-1.1.12}/test/conftest.py +0 -0
  20. {skylos-1.1.11 → skylos-1.1.12}/test/diagnostics.py +0 -0
  21. {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/__init__.py +0 -0
  22. {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/app.py +0 -0
  23. {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/__init__.py +0 -0
  24. {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/commands.py +0 -0
  25. {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/models.py +0 -0
  26. {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/routes.py +0 -0
  27. {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/utils.py +0 -0
  28. {skylos-1.1.11 → skylos-1.1.12}/test/test_analyzer.py +0 -0
  29. {skylos-1.1.11 → skylos-1.1.12}/test/test_cli.py +0 -0
  30. {skylos-1.1.11 → skylos-1.1.12}/test/test_integration.py +0 -0
  31. {skylos-1.1.11 → skylos-1.1.12}/test/test_skylos.py +0 -0
  32. {skylos-1.1.11 → skylos-1.1.12}/test/test_visitor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skylos
3
- Version: 1.1.11
3
+ Version: 1.1.12
4
4
  Summary: A static analysis tool for Python codebases
5
5
  Author-email: oha <aaronoh2015@gmail.com>
6
6
  Requires-Python: >=3.9
@@ -72,7 +72,7 @@ skylos --interactive --dry-run /path/to/your/project
72
72
  skylos --json /path/to/your/project
73
73
  ```
74
74
 
75
- ## Folder Management
75
+ ## **NEW** Folder Management
76
76
 
77
77
  ### Default Exclusions
78
78
  By default, Skylos excludes common folders: `__pycache__`, `.git`, `.pytest_cache`, `.mypy_cache`, `.tox`, `htmlcov`, `.coverage`, `build`, `dist`, `*.egg-info`, `venv`, `.venv`
@@ -93,9 +93,9 @@ skylos /path/to/your/project --include-folder venv
93
93
 
94
94
  # Scan everything (no exclusions)
95
95
  skylos path/to/your/project --no-default-excludes
96
+ ```
96
97
 
97
98
  ## CLI Options
98
-
99
99
  ```
100
100
  Usage: skylos [OPTIONS] PATH
101
101
 
@@ -118,33 +118,39 @@ Options:
118
118
  ## Example Output
119
119
 
120
120
  ```
121
- 🔍 Python Static Analysis Results
121
+ Python Static Analysis Results
122
122
  ===================================
123
123
 
124
124
  Summary:
125
125
  • Unreachable functions: 48
126
126
  • Unused imports: 8
127
127
 
128
- 📦 Unreachable Functions
128
+ Unreachable Functions
129
129
  ========================
130
+
130
131
  1. module_13.test_function
131
132
  └─ /Users/oha/project/module_13.py:5
132
133
  2. module_13.unused_function
133
134
  └─ /Users/oha/project/module_13.py:13
134
135
  ...
135
136
 
136
- 📥 Unused Imports
137
+
138
+ Unused Imports
137
139
  =================
140
+
138
141
  1. os
139
142
  └─ /Users/oha/project/module_13.py:1
140
143
  2. json
141
144
  └─ /Users/oha/project/module_13.py:3
142
145
  ...
146
+ ```
143
147
 
144
148
  Next steps:
145
- • Use --interactive to select specific items to remove
146
- • Use --dry-run to preview changes before applying them
147
- ```
149
+
150
+ • Use `--interactive` to select specific items to remove
151
+
152
+ • Use `--dry-run` to preview changes before applying them
153
+
148
154
 
149
155
  ## Interactive Mode
150
156
 
@@ -201,7 +207,7 @@ A: Like all other tools, Ruff is focused on detecting specific, surface-level is
201
207
  ## Limitations
202
208
 
203
209
  - **Dynamic code**: `getattr()`, `globals()`, runtime imports are hard to detect
204
- - **Frameworks**: Django models, Flask routes may appear unused but aren't
210
+ - **Frameworks**: Django models, Flask, FastAPI routes may appear unused but aren't
205
211
  - **Test data**: Limited scenarios, your mileage may vary
206
212
  - **False positives**: Always manually review before deleting code
207
213
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "skylos"
7
- version = "1.1.11"
7
+ version = "1.1.12"
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.1.11",
5
+ version="1.1.12",
6
6
  packages=find_packages(),
7
7
  python_requires=">=3.9",
8
8
  install_requires=["inquirer>=3.0.0"],
@@ -3,29 +3,15 @@ import ast,sys,json,logging,re
3
3
  from pathlib import Path
4
4
  from collections import defaultdict
5
5
  from skylos.visitor import Visitor
6
+ from skylos.constants import (
7
+ AUTO_CALLED, TEST_METHOD_PATTERN, MAGIC_METHODS,
8
+ TEST_LIFECYCLE_METHODS, TEST_IMPORT_PATTERNS, TEST_DECORATORS,
9
+ DEFAULT_EXCLUDE_FOLDERS
10
+ )
6
11
 
7
12
  logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
8
13
  logger=logging.getLogger('Skylos')
9
14
 
10
- AUTO_CALLED={"__init__","__enter__","__exit__"}
11
- TEST_METHOD_PATTERN = re.compile(r"^test_\w+$")
12
- MAGIC_METHODS={f"__{n}__"for n in["init","new","call","getattr","getattribute","enter","exit","str","repr","hash","eq","ne","lt","gt","le","ge","iter","next","contains","len","getitem","setitem","delitem","iadd","isub","imul","itruediv","ifloordiv","imod","ipow","ilshift","irshift","iand","ixor","ior","round","format","dir","abs","complex","int","float","bool","bytes","reduce","await","aiter","anext","add","sub","mul","truediv","floordiv","mod","divmod","pow","lshift","rshift","and","or","xor","radd","rsub","rmul","rtruediv","rfloordiv","rmod","rdivmod","rpow","rlshift","rrshift","rand","ror","rxor"]}
13
-
14
- DEFAULT_EXCLUDE_FOLDERS = {
15
- "__pycache__",
16
- ".git",
17
- ".pytest_cache",
18
- ".mypy_cache",
19
- ".tox",
20
- "htmlcov",
21
- ".coverage",
22
- "build",
23
- "dist",
24
- "*.egg-info",
25
- "venv",
26
- ".venv"
27
- }
28
-
29
15
  def parse_exclude_folders(user_exclude_folders, use_defaults=True, include_folders=None):
30
16
  exclude_set = set()
31
17
 
@@ -156,7 +142,79 @@ class Skylos:
156
142
  return class_def.base_classes
157
143
 
158
144
  return []
145
+
146
+ def _has_test_imports(self, file_path):
147
+ try:
148
+ with open(file_path, 'r', encoding='utf-8') as f:
149
+ content = f.read()
150
+
151
+ for test_import in TEST_IMPORT_PATTERNS:
152
+ if f"import {test_import}" in content or f"from {test_import}" in content:
153
+ return True
154
+
155
+ return False
156
+ except:
157
+ return False
158
+
159
+ def _is_test_file(self, file_path):
160
+ """check if file locs indicates its a test file"""
161
+ file_str = str(file_path).lower()
162
+
163
+ if (file_str.endswith("test.py") or
164
+ file_str.endswith("_test.py") or
165
+ "test_" in file_str or
166
+ "/test/" in file_str or
167
+ "/tests/" in file_str or
168
+ "\\test\\" in file_str or
169
+ "\\tests\\" in file_str):
170
+ return True
171
+
172
+ return False
173
+
174
+ def _has_test_decorators(self, file_path):
175
+ """Check if file uses test-related decorators"""
176
+ try:
177
+ with open(file_path, 'r', encoding='utf-8') as f:
178
+ content = f.read()
179
+
180
+ for decorator in TEST_DECORATORS:
181
+ if f"@{decorator}" in content:
182
+ return True
183
+
184
+ return False
185
+ except:
186
+ return False
187
+
188
+ def _is_test_related(self, definition):
189
+
190
+ if "." in definition.name:
191
+ class_name = definition.name.rsplit(".", 1)[0]
192
+ class_simple_name = class_name.split(".")[-1]
159
193
 
194
+ if (class_simple_name.startswith("Test") or
195
+ class_simple_name.endswith("Test") or
196
+ class_simple_name.endswith("TestCase")):
197
+ return True
198
+
199
+ if (definition.type == "method" and
200
+ (TEST_METHOD_PATTERN.match(definition.simple_name) or
201
+ definition.simple_name in TEST_LIFECYCLE_METHODS)):
202
+ return True
203
+
204
+ # NOT for imports, variables, parameters
205
+ if definition.type in ("function", "method", "class"):
206
+ if self._is_test_file(definition.filename):
207
+ return True
208
+
209
+ if self._has_test_imports(definition.filename):
210
+ return True
211
+
212
+ ## check decorators -- test related
213
+ if self._has_test_decorators(definition.filename):
214
+ return True
215
+
216
+ return False
217
+
160
218
  def _apply_heuristics(self):
161
219
  class_methods=defaultdict(list)
162
220
  for d in self.defs.values():
@@ -180,6 +238,16 @@ class Skylos:
180
238
  if d.type != "parameter" and (d.simple_name in MAGIC_METHODS or (d.simple_name.startswith("__") and d.simple_name.endswith("__"))):
181
239
  d.confidence = 0
182
240
 
241
+ if (d.type == "import" and d.name.startswith("__future__.") and
242
+ d.simple_name in ("annotations", "absolute_import", "division",
243
+ "print_function", "unicode_literals", "generator_stop")):
244
+ d.confidence = 0
245
+
246
+ if (d.simple_name.startswith("_") and
247
+ not d.simple_name.startswith("__") and
248
+ d.simple_name != "_"):
249
+ d.confidence = 0
250
+
183
251
  if not d.simple_name.startswith("_") and d.type in ("function", "method", "class"):
184
252
  d.confidence = min(d.confidence, 90)
185
253
 
@@ -192,13 +260,8 @@ class Skylos:
192
260
  if d.type == "variable" and d.simple_name == "_":
193
261
  d.confidence = 0
194
262
 
195
- if d.type == "method" and TEST_METHOD_PATTERN.match(d.simple_name):
196
- class_name = d.name.rsplit(".", 1)[0]
197
- class_simple_name = class_name.split(".")[-1]
198
- if (class_simple_name.startswith("Test") or
199
- class_simple_name.endswith("Test") or
200
- class_simple_name.endswith("TestCase")):
201
- d.confidence = 0
263
+ if self._is_test_related(d):
264
+ d.confidence = 0
202
265
 
203
266
  def analyze(self, path, thr=60, exclude_folders=None):
204
267
 
@@ -0,0 +1,35 @@
1
+ import re
2
+
3
+ AUTO_CALLED={"__init__","__enter__","__exit__"}
4
+ TEST_METHOD_PATTERN = re.compile(r"^test_\w+$")
5
+ MAGIC_METHODS={f"__{n}__"for n in["init","new","call","getattr","getattribute","enter","exit","str","repr","hash","eq","ne","lt","gt","le","ge","iter","next","contains","len","getitem","setitem","delitem","iadd","isub","imul","itruediv","ifloordiv","imod","ipow","ilshift","irshift","iand","ixor","ior","round","format","dir","abs","complex","int","float","bool","bytes","reduce","await","aiter","anext","add","sub","mul","truediv","floordiv","mod","divmod","pow","lshift","rshift","and","or","xor","radd","rsub","rmul","rtruediv","rfloordiv","rmod","rdivmod","rpow","rlshift","rrshift","rand","ror","rxor"]}
6
+ TEST_LIFECYCLE_METHODS = {
7
+ "setUp", "tearDown", "setUpClass", "tearDownClass",
8
+ "setUpModule", "tearDownModule", "setup_method", "teardown_method",
9
+ "setup_class", "teardown_class", "setup_function", "teardown_function"
10
+ }
11
+ TEST_IMPORT_PATTERNS = {
12
+ "unittest", "unittest.mock", "mock", "pytest", "nose", "nose2",
13
+ "responses", "requests_mock", "freezegun", "factory_boy",
14
+ "hypothesis", "sure", "expects", "testfixtures", "faker"
15
+ }
16
+
17
+ TEST_DECORATORS = {
18
+ "patch", "mock", "pytest.fixture", "pytest.mark", "given",
19
+ "responses.activate", "freeze_time", "patch.object", "patch.dict"
20
+ }
21
+
22
+ DEFAULT_EXCLUDE_FOLDERS = {
23
+ "__pycache__",
24
+ ".git",
25
+ ".pytest_cache",
26
+ ".mypy_cache",
27
+ ".tox",
28
+ "htmlcov",
29
+ ".coverage",
30
+ "build",
31
+ "dist",
32
+ "*.egg-info",
33
+ "venv",
34
+ ".venv"
35
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skylos
3
- Version: 1.1.11
3
+ Version: 1.1.12
4
4
  Summary: A static analysis tool for Python codebases
5
5
  Author-email: oha <aaronoh2015@gmail.com>
6
6
  Requires-Python: >=3.9
@@ -4,6 +4,7 @@ setup.py
4
4
  skylos/__init__.py
5
5
  skylos/analyzer.py
6
6
  skylos/cli.py
7
+ skylos/constants.py
7
8
  skylos/visitor.py
8
9
  skylos.egg-info/PKG-INFO
9
10
  skylos.egg-info/SOURCES.txt
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