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.
- {skylos-1.1.11 → skylos-1.1.12}/PKG-INFO +1 -1
- {skylos-1.1.11 → skylos-1.1.12}/README.md +15 -9
- {skylos-1.1.11 → skylos-1.1.12}/pyproject.toml +1 -1
- {skylos-1.1.11 → skylos-1.1.12}/setup.py +1 -1
- {skylos-1.1.11 → skylos-1.1.12}/skylos/analyzer.py +89 -26
- skylos-1.1.12/skylos/constants.py +35 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/PKG-INFO +1 -1
- {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/SOURCES.txt +1 -0
- {skylos-1.1.11 → skylos-1.1.12}/setup.cfg +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos/__init__.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos/cli.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos/visitor.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/dependency_links.txt +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/entry_points.txt +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/requires.txt +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/skylos.egg-info/top_level.txt +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/__init__.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/compare_tools.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/conftest.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/diagnostics.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/__init__.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/app.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/__init__.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/commands.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/models.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/routes.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/sample_repo/sample_repo/utils.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/test_analyzer.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/test_cli.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/test_integration.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/test_skylos.py +0 -0
- {skylos-1.1.11 → skylos-1.1.12}/test/test_visitor.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
• Use
|
|
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
|
|
|
@@ -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
|
|
196
|
-
|
|
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
|
+
}
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|