skylos 1.2.2__py3-none-any.whl → 2.1.0__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.
Potentially problematic release.
This version of skylos might be problematic. Click here for more details.
- skylos/__init__.py +1 -1
- skylos/analyzer.py +103 -121
- skylos/cli.py +66 -109
- skylos/codemods.py +89 -0
- skylos/constants.py +25 -10
- skylos/framework_aware.py +290 -90
- skylos/server.py +560 -0
- skylos/test_aware.py +0 -1
- skylos/visitor.py +249 -90
- {skylos-1.2.2.dist-info → skylos-2.1.0.dist-info}/METADATA +4 -1
- {skylos-1.2.2.dist-info → skylos-2.1.0.dist-info}/RECORD +16 -13
- test/test_codemods.py +153 -0
- test/test_framework_aware.py +176 -242
- {skylos-1.2.2.dist-info → skylos-2.1.0.dist-info}/WHEEL +0 -0
- {skylos-1.2.2.dist-info → skylos-2.1.0.dist-info}/entry_points.txt +0 -0
- {skylos-1.2.2.dist-info → skylos-2.1.0.dist-info}/top_level.txt +0 -0
test/test_codemods.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
from skylos.codemods import (
|
|
3
|
+
remove_unused_import_cst,
|
|
4
|
+
remove_unused_function_cst,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
def _line_no(code: str, startswith: str) -> int:
|
|
8
|
+
for i, line in enumerate(code.splitlines(), start=1):
|
|
9
|
+
if line.lstrip().startswith(startswith):
|
|
10
|
+
return i
|
|
11
|
+
raise AssertionError(f"Line starting with {startswith!r} not found")
|
|
12
|
+
|
|
13
|
+
def test_remove_simple_import_entire_line():
|
|
14
|
+
code = "import os\nprint(1)\n"
|
|
15
|
+
ln = _line_no(code, "import os")
|
|
16
|
+
new, changed = remove_unused_import_cst(code, "os", ln)
|
|
17
|
+
assert changed is True
|
|
18
|
+
assert "import os" not in new
|
|
19
|
+
assert "print(1)" in new
|
|
20
|
+
|
|
21
|
+
def test_remove_one_name_from_multi_import():
|
|
22
|
+
code = "import os, sys\n"
|
|
23
|
+
ln = _line_no(code, "import os, sys")
|
|
24
|
+
new, changed = remove_unused_import_cst(code, "os", ln)
|
|
25
|
+
assert changed is True
|
|
26
|
+
assert new.strip() == "import sys"
|
|
27
|
+
|
|
28
|
+
def test_remove_from_import_keeps_other_names():
|
|
29
|
+
code = "from a import b, c\n"
|
|
30
|
+
ln = _line_no(code, "from a import")
|
|
31
|
+
new, changed = remove_unused_import_cst(code, "b", ln)
|
|
32
|
+
assert changed is True
|
|
33
|
+
assert new.strip() == "from a import c"
|
|
34
|
+
|
|
35
|
+
def test_remove_from_import_with_alias_uses_bound_name():
|
|
36
|
+
code = "from a import b as c, d\n"
|
|
37
|
+
ln = _line_no(code, "from a import")
|
|
38
|
+
new, changed = remove_unused_import_cst(code, "c", ln)
|
|
39
|
+
assert changed is True
|
|
40
|
+
assert new.strip() == "from a import d"
|
|
41
|
+
|
|
42
|
+
def test_parenthesized_multiline_from_import_preserves_formatting():
|
|
43
|
+
code = textwrap.dedent(
|
|
44
|
+
"""\
|
|
45
|
+
from x.y import (
|
|
46
|
+
a,
|
|
47
|
+
b, # keep me
|
|
48
|
+
c,
|
|
49
|
+
)
|
|
50
|
+
use = b
|
|
51
|
+
"""
|
|
52
|
+
)
|
|
53
|
+
ln = _line_no(code, "from x.y import")
|
|
54
|
+
new, changed = remove_unused_import_cst(code, "a", ln)
|
|
55
|
+
assert changed is True
|
|
56
|
+
assert "a," not in new
|
|
57
|
+
assert "b, # keep me" in new
|
|
58
|
+
assert "c," in new
|
|
59
|
+
|
|
60
|
+
def test_import_star_is_noop():
|
|
61
|
+
code = "from x import *\n"
|
|
62
|
+
ln = _line_no(code, "from x import *")
|
|
63
|
+
new, changed = remove_unused_import_cst(code, "*", ln)
|
|
64
|
+
assert changed is False
|
|
65
|
+
assert new == code
|
|
66
|
+
|
|
67
|
+
def test_dotted_import_requires_bound_leftmost_segment():
|
|
68
|
+
code = "import pkg.sub\n"
|
|
69
|
+
ln = _line_no(code, "import pkg.sub")
|
|
70
|
+
new, changed = remove_unused_import_cst(code, "pkg", ln)
|
|
71
|
+
assert changed is True
|
|
72
|
+
assert "import pkg.sub" not in new
|
|
73
|
+
|
|
74
|
+
new2, changed2 = remove_unused_import_cst(code, "sub", ln)
|
|
75
|
+
assert changed2 is False
|
|
76
|
+
assert new2 == code
|
|
77
|
+
|
|
78
|
+
def test_import_idempotency():
|
|
79
|
+
code = "import os, sys\n"
|
|
80
|
+
ln = _line_no(code, "import os, sys")
|
|
81
|
+
new, changed = remove_unused_import_cst(code, "os", ln)
|
|
82
|
+
assert changed is True
|
|
83
|
+
new2, changed2 = remove_unused_import_cst(new, "os", ln)
|
|
84
|
+
assert changed2 is False
|
|
85
|
+
assert new2 == new
|
|
86
|
+
|
|
87
|
+
def test_remove_simple_function_block():
|
|
88
|
+
code = textwrap.dedent(
|
|
89
|
+
"""\
|
|
90
|
+
def unused():
|
|
91
|
+
x = 1
|
|
92
|
+
return x
|
|
93
|
+
|
|
94
|
+
def used():
|
|
95
|
+
return 42
|
|
96
|
+
"""
|
|
97
|
+
)
|
|
98
|
+
ln = _line_no(code, "def unused")
|
|
99
|
+
new, changed = remove_unused_function_cst(code, "unused", ln)
|
|
100
|
+
assert changed is True
|
|
101
|
+
assert "def unused" not in new
|
|
102
|
+
assert "def used" in new
|
|
103
|
+
|
|
104
|
+
def test_remove_decorated_function_removes_decorators_too():
|
|
105
|
+
code = textwrap.dedent(
|
|
106
|
+
"""\
|
|
107
|
+
@dec1
|
|
108
|
+
@dec2(arg=1)
|
|
109
|
+
def target():
|
|
110
|
+
return 1
|
|
111
|
+
|
|
112
|
+
def other():
|
|
113
|
+
return 2
|
|
114
|
+
"""
|
|
115
|
+
)
|
|
116
|
+
ln = _line_no(code, "def target")
|
|
117
|
+
new, changed = remove_unused_function_cst(code, "target", ln)
|
|
118
|
+
assert changed is True
|
|
119
|
+
assert "@dec1" not in new and "@dec2" not in new
|
|
120
|
+
assert "def target" not in new
|
|
121
|
+
assert "def other" in new
|
|
122
|
+
|
|
123
|
+
def test_remove_async_function():
|
|
124
|
+
code = textwrap.dedent(
|
|
125
|
+
"""\
|
|
126
|
+
async def coro():
|
|
127
|
+
return 1
|
|
128
|
+
|
|
129
|
+
def ok():
|
|
130
|
+
return 2
|
|
131
|
+
"""
|
|
132
|
+
)
|
|
133
|
+
ln = _line_no(code, "async def coro")
|
|
134
|
+
new, changed = remove_unused_function_cst(code, "coro", ln)
|
|
135
|
+
assert changed is True
|
|
136
|
+
assert "async def coro" not in new
|
|
137
|
+
assert "def ok" in new
|
|
138
|
+
|
|
139
|
+
def test_function_wrong_line_noop():
|
|
140
|
+
code = "def f():\n return 1\n"
|
|
141
|
+
ln = 999
|
|
142
|
+
new, changed = remove_unused_function_cst(code, "f", ln)
|
|
143
|
+
assert changed is False
|
|
144
|
+
assert new == code
|
|
145
|
+
|
|
146
|
+
def test_function_idempotency():
|
|
147
|
+
code = "def g():\n return 1\n"
|
|
148
|
+
ln = _line_no(code, "def g")
|
|
149
|
+
new, changed = remove_unused_function_cst(code, "g", ln)
|
|
150
|
+
assert changed is True
|
|
151
|
+
new2, changed2 = remove_unused_function_cst(new, "g", ln)
|
|
152
|
+
assert changed2 is False
|
|
153
|
+
assert new2 == new
|
test/test_framework_aware.py
CHANGED
|
@@ -1,80 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import pytest
|
|
3
3
|
import ast
|
|
4
|
-
from pathlib import Path
|
|
5
4
|
from unittest.mock import Mock, patch
|
|
6
|
-
import fnmatch
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
FrameworkAwareVisitor,
|
|
6
|
+
from skylos.framework_aware import (
|
|
7
|
+
FrameworkAwareVisitor,
|
|
11
8
|
detect_framework_usage,
|
|
12
9
|
FRAMEWORK_DECORATORS,
|
|
13
10
|
FRAMEWORK_FUNCTIONS,
|
|
14
|
-
FRAMEWORK_IMPORTS
|
|
15
|
-
)
|
|
16
|
-
except ImportError:
|
|
17
|
-
import sys
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
20
|
-
from skylos.framework_aware import (
|
|
21
|
-
FrameworkAwareVisitor,
|
|
22
|
-
detect_framework_usage,
|
|
23
|
-
FRAMEWORK_DECORATORS,
|
|
24
|
-
FRAMEWORK_FUNCTIONS,
|
|
25
|
-
FRAMEWORK_IMPORTS
|
|
11
|
+
FRAMEWORK_IMPORTS,
|
|
26
12
|
)
|
|
27
13
|
|
|
28
14
|
class TestFrameworkAwareVisitor:
|
|
29
|
-
|
|
30
15
|
def test_init_default(self):
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
assert
|
|
34
|
-
assert
|
|
35
|
-
|
|
36
|
-
|
|
16
|
+
v = FrameworkAwareVisitor()
|
|
17
|
+
assert v.is_framework_file is False
|
|
18
|
+
assert v.framework_decorated_lines == set()
|
|
19
|
+
assert v.detected_frameworks == set()
|
|
20
|
+
|
|
37
21
|
def test_flask_import_detection(self):
|
|
38
|
-
"""Test Flask import detection"""
|
|
39
22
|
code = """
|
|
40
23
|
import flask
|
|
41
24
|
from flask import Flask, request
|
|
42
25
|
"""
|
|
43
26
|
tree = ast.parse(code)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
assert
|
|
48
|
-
assert
|
|
49
|
-
|
|
27
|
+
v = FrameworkAwareVisitor()
|
|
28
|
+
v.visit(tree)
|
|
29
|
+
v.finalize()
|
|
30
|
+
assert v.is_framework_file is True
|
|
31
|
+
assert "flask" in v.detected_frameworks
|
|
32
|
+
|
|
50
33
|
def test_fastapi_import_detection(self):
|
|
51
|
-
"""Test FastAPI import detection"""
|
|
52
34
|
code = """
|
|
53
35
|
from fastapi import FastAPI
|
|
54
36
|
import fastapi
|
|
55
37
|
"""
|
|
56
38
|
tree = ast.parse(code)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
assert
|
|
61
|
-
assert
|
|
62
|
-
|
|
39
|
+
v = FrameworkAwareVisitor()
|
|
40
|
+
v.visit(tree)
|
|
41
|
+
v.finalize()
|
|
42
|
+
assert v.is_framework_file is True
|
|
43
|
+
assert "fastapi" in v.detected_frameworks
|
|
44
|
+
|
|
63
45
|
def test_django_import_detection(self):
|
|
64
|
-
"""Test Django import detection"""
|
|
65
46
|
code = """
|
|
66
47
|
from django.http import HttpResponse
|
|
67
48
|
from django.views import View
|
|
68
49
|
"""
|
|
69
50
|
tree = ast.parse(code)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
assert
|
|
74
|
-
assert
|
|
75
|
-
|
|
51
|
+
v = FrameworkAwareVisitor()
|
|
52
|
+
v.visit(tree)
|
|
53
|
+
v.finalize()
|
|
54
|
+
assert v.is_framework_file is True
|
|
55
|
+
assert "django" in v.detected_frameworks
|
|
56
|
+
|
|
76
57
|
def test_flask_route_decorator_detection(self):
|
|
77
|
-
"""Test Flask route decorator detection"""
|
|
78
58
|
code = """
|
|
79
59
|
@app.route('/api/users')
|
|
80
60
|
def get_users():
|
|
@@ -85,15 +65,14 @@ def create_user():
|
|
|
85
65
|
return {}
|
|
86
66
|
"""
|
|
87
67
|
tree = ast.parse(code)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
assert
|
|
92
|
-
assert 3 in
|
|
93
|
-
assert 7 in
|
|
94
|
-
|
|
68
|
+
v = FrameworkAwareVisitor()
|
|
69
|
+
v.visit(tree)
|
|
70
|
+
v.finalize()
|
|
71
|
+
assert v.is_framework_file is True
|
|
72
|
+
assert 3 in v.framework_decorated_lines
|
|
73
|
+
assert 7 in v.framework_decorated_lines
|
|
74
|
+
|
|
95
75
|
def test_fastapi_router_decorator_detection(self):
|
|
96
|
-
"""Test FastAPI router decorator detection"""
|
|
97
76
|
code = """
|
|
98
77
|
@router.get('/items')
|
|
99
78
|
async def read_items():
|
|
@@ -104,15 +83,14 @@ async def create_item():
|
|
|
104
83
|
return {}
|
|
105
84
|
"""
|
|
106
85
|
tree = ast.parse(code)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
assert
|
|
111
|
-
assert 3 in
|
|
112
|
-
assert 7 in
|
|
113
|
-
|
|
86
|
+
v = FrameworkAwareVisitor()
|
|
87
|
+
v.visit(tree)
|
|
88
|
+
v.finalize()
|
|
89
|
+
assert v.is_framework_file is True
|
|
90
|
+
assert 3 in v.framework_decorated_lines
|
|
91
|
+
assert 7 in v.framework_decorated_lines
|
|
92
|
+
|
|
114
93
|
def test_django_decorator_detection(self):
|
|
115
|
-
"""Test Django decorator detection"""
|
|
116
94
|
code = """
|
|
117
95
|
@login_required
|
|
118
96
|
def protected_view(request):
|
|
@@ -123,34 +101,14 @@ def admin_view(request):
|
|
|
123
101
|
return HttpResponse("Admin")
|
|
124
102
|
"""
|
|
125
103
|
tree = ast.parse(code)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
assert
|
|
130
|
-
assert 3 in
|
|
131
|
-
assert 7 in
|
|
132
|
-
|
|
133
|
-
def test_celery_task_decorator_detection(self):
|
|
134
|
-
"""Test Celery task decorator detection"""
|
|
135
|
-
code = """
|
|
136
|
-
@task
|
|
137
|
-
def background_task():
|
|
138
|
-
return "done"
|
|
104
|
+
v = FrameworkAwareVisitor()
|
|
105
|
+
v.visit(tree)
|
|
106
|
+
v.finalize()
|
|
107
|
+
assert v.is_framework_file is True
|
|
108
|
+
assert 3 in v.framework_decorated_lines
|
|
109
|
+
assert 7 in v.framework_decorated_lines
|
|
139
110
|
|
|
140
|
-
@shared_task
|
|
141
|
-
def shared_background_task():
|
|
142
|
-
return "shared done"
|
|
143
|
-
"""
|
|
144
|
-
tree = ast.parse(code)
|
|
145
|
-
visitor = FrameworkAwareVisitor()
|
|
146
|
-
visitor.visit(tree)
|
|
147
|
-
|
|
148
|
-
assert visitor.is_framework_file == True
|
|
149
|
-
assert 3 in visitor.framework_decorated_lines
|
|
150
|
-
assert 7 in visitor.framework_decorated_lines
|
|
151
|
-
|
|
152
111
|
def test_django_view_class_detection(self):
|
|
153
|
-
"""Test Django view class detection"""
|
|
154
112
|
code = """
|
|
155
113
|
from django import views
|
|
156
114
|
|
|
@@ -163,55 +121,29 @@ class UserViewSet(ViewSet):
|
|
|
163
121
|
return Response([])
|
|
164
122
|
"""
|
|
165
123
|
tree = ast.parse(code)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
assert
|
|
170
|
-
assert
|
|
171
|
-
assert
|
|
172
|
-
|
|
173
|
-
def test_django_model_methods_in_framework_file(self):
|
|
174
|
-
code = """
|
|
175
|
-
from django.db import models
|
|
176
|
-
|
|
177
|
-
class User(models.Model):
|
|
178
|
-
name = models.CharField(max_length=100)
|
|
179
|
-
|
|
180
|
-
def save(self, *args, **kwargs):
|
|
181
|
-
# custom save logic
|
|
182
|
-
super().save(*args, **kwargs)
|
|
183
|
-
|
|
184
|
-
def delete(self, *args, **kwargs):
|
|
185
|
-
# custom delete logic
|
|
186
|
-
super().delete(*args, **kwargs)
|
|
187
|
-
"""
|
|
188
|
-
tree = ast.parse(code)
|
|
189
|
-
visitor = FrameworkAwareVisitor()
|
|
190
|
-
visitor.visit(tree)
|
|
191
|
-
|
|
192
|
-
assert visitor.is_framework_file == True
|
|
193
|
-
assert 7 in visitor.framework_decorated_lines # save method
|
|
194
|
-
assert 11 in visitor.framework_decorated_lines # delete method
|
|
195
|
-
|
|
124
|
+
v = FrameworkAwareVisitor()
|
|
125
|
+
v.visit(tree)
|
|
126
|
+
v.finalize()
|
|
127
|
+
assert v.is_framework_file is True
|
|
128
|
+
assert 5 in v.framework_decorated_lines
|
|
129
|
+
assert 9 in v.framework_decorated_lines
|
|
130
|
+
|
|
196
131
|
def test_framework_functions_not_detected_in_non_framework_file(self):
|
|
197
132
|
code = """
|
|
198
133
|
def save(self):
|
|
199
|
-
# regular save method, not Django
|
|
200
134
|
pass
|
|
201
135
|
|
|
202
136
|
def get(self):
|
|
203
|
-
# regular get method, not Django view
|
|
204
137
|
pass
|
|
205
138
|
"""
|
|
206
139
|
tree = ast.parse(code)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
assert
|
|
211
|
-
assert
|
|
212
|
-
|
|
140
|
+
v = FrameworkAwareVisitor()
|
|
141
|
+
v.visit(tree)
|
|
142
|
+
v.finalize()
|
|
143
|
+
assert v.is_framework_file is False
|
|
144
|
+
assert v.framework_decorated_lines == set()
|
|
145
|
+
|
|
213
146
|
def test_multiple_decorators(self):
|
|
214
|
-
"""Test function with multiple decorators"""
|
|
215
147
|
code = """
|
|
216
148
|
@app.route('/users')
|
|
217
149
|
@login_required
|
|
@@ -220,14 +152,13 @@ def get_users():
|
|
|
220
152
|
return []
|
|
221
153
|
"""
|
|
222
154
|
tree = ast.parse(code)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
assert
|
|
227
|
-
assert 5 in
|
|
228
|
-
|
|
155
|
+
v = FrameworkAwareVisitor()
|
|
156
|
+
v.visit(tree)
|
|
157
|
+
v.finalize()
|
|
158
|
+
assert v.is_framework_file is True
|
|
159
|
+
assert 5 in v.framework_decorated_lines
|
|
160
|
+
|
|
229
161
|
def test_complex_decorator_patterns(self):
|
|
230
|
-
"""Test complex decorator patterns"""
|
|
231
162
|
code = """
|
|
232
163
|
@app.route('/api/v1/users/<int:user_id>', methods=['GET', 'POST'])
|
|
233
164
|
def user_endpoint(user_id):
|
|
@@ -238,135 +169,138 @@ async def get_item(item_id: int):
|
|
|
238
169
|
return {}
|
|
239
170
|
"""
|
|
240
171
|
tree = ast.parse(code)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
assert
|
|
245
|
-
assert 3 in
|
|
246
|
-
assert 7 in
|
|
247
|
-
|
|
248
|
-
@patch(
|
|
172
|
+
v = FrameworkAwareVisitor()
|
|
173
|
+
v.visit(tree)
|
|
174
|
+
v.finalize()
|
|
175
|
+
assert v.is_framework_file is True
|
|
176
|
+
assert 3 in v.framework_decorated_lines
|
|
177
|
+
assert 7 in v.framework_decorated_lines
|
|
178
|
+
|
|
179
|
+
@patch("skylos.framework_aware.Path")
|
|
249
180
|
def test_file_content_framework_detection(self, mock_path):
|
|
250
|
-
"""Test framework detection from file content"""
|
|
251
181
|
mock_file = Mock()
|
|
252
182
|
mock_file.read_text.return_value = "from flask import Flask\napp = Flask(__name__)"
|
|
253
183
|
mock_path.return_value = mock_file
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
assert
|
|
258
|
-
|
|
259
|
-
|
|
184
|
+
v = FrameworkAwareVisitor(filename="test.py")
|
|
185
|
+
v.finalize()
|
|
186
|
+
assert v.is_framework_file is True
|
|
187
|
+
assert "flask" in v.detected_frameworks
|
|
188
|
+
|
|
260
189
|
def test_normalize_decorator_name(self):
|
|
261
|
-
|
|
262
|
-
|
|
190
|
+
v = FrameworkAwareVisitor()
|
|
263
191
|
node = ast.parse("@decorator\ndef func(): pass").body[0].decorator_list[0]
|
|
264
|
-
|
|
265
|
-
assert result == "@decorator"
|
|
266
|
-
|
|
192
|
+
assert v._normalize_decorator(node) == "@decorator"
|
|
267
193
|
node = ast.parse("@app.route\ndef func(): pass").body[0].decorator_list[0]
|
|
268
|
-
|
|
269
|
-
|
|
194
|
+
assert v._normalize_decorator(node) == "@app.route"
|
|
195
|
+
|
|
196
|
+
def test_depends_marks_dependency_and_flags_framework_file(self):
|
|
197
|
+
code = """
|
|
198
|
+
from fastapi import Depends
|
|
199
|
+
|
|
200
|
+
def dep():
|
|
201
|
+
return 1
|
|
202
|
+
|
|
203
|
+
@router.get("/")
|
|
204
|
+
def foo(x: int = Depends(dep)):
|
|
205
|
+
return {}
|
|
206
|
+
"""
|
|
207
|
+
tree = ast.parse(code)
|
|
208
|
+
v = FrameworkAwareVisitor()
|
|
209
|
+
v.visit(tree)
|
|
210
|
+
v.finalize()
|
|
211
|
+
assert v.is_framework_file is True
|
|
212
|
+
assert 4 in v.framework_decorated_lines
|
|
213
|
+
|
|
214
|
+
def test_typed_model_in_route_marks_model_definition(self):
|
|
215
|
+
code = """
|
|
216
|
+
from pydantic import BaseModel
|
|
217
|
+
|
|
218
|
+
class In(BaseModel):
|
|
219
|
+
x: int
|
|
220
|
+
|
|
221
|
+
@router.post("/")
|
|
222
|
+
def calc(req: In):
|
|
223
|
+
return 1
|
|
224
|
+
"""
|
|
225
|
+
tree = ast.parse(code)
|
|
226
|
+
v = FrameworkAwareVisitor()
|
|
227
|
+
v.visit(tree)
|
|
228
|
+
v.finalize()
|
|
229
|
+
assert v.is_framework_file is True
|
|
230
|
+
assert 4 in v.framework_decorated_lines
|
|
270
231
|
|
|
271
232
|
class TestDetectFrameworkUsage:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
assert
|
|
311
|
-
|
|
312
|
-
def test_non_framework_file_no_confidence(self):
|
|
313
|
-
mock_def = Mock()
|
|
314
|
-
mock_def.line = 25
|
|
315
|
-
mock_def.simple_name = "regular_function"
|
|
316
|
-
mock_def.type = "function"
|
|
317
|
-
|
|
318
|
-
mock_visitor = Mock()
|
|
319
|
-
mock_visitor.framework_decorated_lines = set()
|
|
320
|
-
mock_visitor.is_framework_file = False
|
|
321
|
-
|
|
322
|
-
confidence = detect_framework_usage(mock_def, visitor=mock_visitor)
|
|
323
|
-
assert confidence is None
|
|
324
|
-
|
|
233
|
+
def test_decorated_endpoint_confidence_is_one(self):
|
|
234
|
+
d = Mock()
|
|
235
|
+
d.line = 10
|
|
236
|
+
d.simple_name = "get_users"
|
|
237
|
+
d.type = "function"
|
|
238
|
+
v = Mock()
|
|
239
|
+
v.framework_decorated_lines = {10}
|
|
240
|
+
v.is_framework_file = True
|
|
241
|
+
assert detect_framework_usage(d, visitor=v) == 1
|
|
242
|
+
|
|
243
|
+
def test_undecorated_function_in_framework_file_returns_none(self):
|
|
244
|
+
d = Mock()
|
|
245
|
+
d.line = 15
|
|
246
|
+
d.simple_name = "helper_function"
|
|
247
|
+
d.type = "function"
|
|
248
|
+
v = Mock()
|
|
249
|
+
v.framework_decorated_lines = set()
|
|
250
|
+
v.is_framework_file = True
|
|
251
|
+
assert detect_framework_usage(d, visitor=v) is None
|
|
252
|
+
|
|
253
|
+
def test_private_function_in_framework_file_returns_none(self):
|
|
254
|
+
d = Mock()
|
|
255
|
+
d.line = 20
|
|
256
|
+
d.simple_name = "_private_function"
|
|
257
|
+
d.type = "function"
|
|
258
|
+
v = Mock()
|
|
259
|
+
v.framework_decorated_lines = set()
|
|
260
|
+
v.is_framework_file = True
|
|
261
|
+
assert detect_framework_usage(d, visitor=v) is None
|
|
262
|
+
|
|
263
|
+
def test_non_framework_file_returns_none(self):
|
|
264
|
+
d = Mock()
|
|
265
|
+
d.line = 25
|
|
266
|
+
d.simple_name = "regular_function"
|
|
267
|
+
d.type = "function"
|
|
268
|
+
v = Mock()
|
|
269
|
+
v.framework_decorated_lines = set()
|
|
270
|
+
v.is_framework_file = False
|
|
271
|
+
assert detect_framework_usage(d, visitor=v) is None
|
|
272
|
+
|
|
325
273
|
def test_no_visitor_returns_none(self):
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
assert confidence is None
|
|
329
|
-
|
|
330
|
-
def test_non_function_in_framework_file_no_confidence(self):
|
|
331
|
-
mock_def = Mock()
|
|
332
|
-
mock_def.line = 30
|
|
333
|
-
mock_def.simple_name = "my_variable"
|
|
334
|
-
mock_def.type = "variable"
|
|
335
|
-
|
|
336
|
-
mock_visitor = Mock()
|
|
337
|
-
mock_visitor.framework_decorated_lines = set()
|
|
338
|
-
mock_visitor.is_framework_file = True
|
|
339
|
-
|
|
340
|
-
confidence = detect_framework_usage(mock_def, visitor=mock_visitor)
|
|
341
|
-
assert confidence is None
|
|
274
|
+
d = Mock()
|
|
275
|
+
assert detect_framework_usage(d, visitor=None) is None
|
|
342
276
|
|
|
277
|
+
def test_non_function_in_framework_file_returns_none(self):
|
|
278
|
+
d = Mock()
|
|
279
|
+
d.line = 30
|
|
280
|
+
d.simple_name = "my_variable"
|
|
281
|
+
d.type = "variable"
|
|
282
|
+
v = Mock()
|
|
283
|
+
v.framework_decorated_lines = set()
|
|
284
|
+
v.is_framework_file = True
|
|
285
|
+
assert detect_framework_usage(d, visitor=v) is None
|
|
343
286
|
|
|
344
287
|
class TestFrameworkPatterns:
|
|
345
|
-
|
|
346
288
|
def test_framework_decorators_list(self):
|
|
347
|
-
""
|
|
348
|
-
assert "
|
|
349
|
-
assert "@router.get" in FRAMEWORK_DECORATORS
|
|
289
|
+
assert "@*.route" in FRAMEWORK_DECORATORS
|
|
290
|
+
assert "@*.get" in FRAMEWORK_DECORATORS
|
|
350
291
|
assert "@login_required" in FRAMEWORK_DECORATORS
|
|
351
|
-
|
|
352
|
-
assert "@validator" in FRAMEWORK_DECORATORS
|
|
353
|
-
|
|
292
|
+
|
|
354
293
|
def test_framework_functions_list(self):
|
|
355
|
-
"""Test that FRAMEWORK_FUNCTIONS contains expected patterns"""
|
|
356
294
|
assert "get" in FRAMEWORK_FUNCTIONS
|
|
357
295
|
assert "post" in FRAMEWORK_FUNCTIONS
|
|
358
|
-
assert "save" in FRAMEWORK_FUNCTIONS
|
|
359
296
|
assert "*_queryset" in FRAMEWORK_FUNCTIONS
|
|
360
297
|
assert "get_context_data" in FRAMEWORK_FUNCTIONS
|
|
361
|
-
|
|
362
|
-
def test_framework_imports_set(self):
|
|
363
|
-
"""Test that FRAMEWORK_IMPORTS contains expected frameworks"""
|
|
364
|
-
assert 'flask' in FRAMEWORK_IMPORTS
|
|
365
|
-
assert 'django' in FRAMEWORK_IMPORTS
|
|
366
|
-
assert 'fastapi' in FRAMEWORK_IMPORTS
|
|
367
|
-
assert 'celery' in FRAMEWORK_IMPORTS
|
|
368
|
-
assert 'pydantic' in FRAMEWORK_IMPORTS
|
|
369
298
|
|
|
299
|
+
def test_framework_imports_set(self):
|
|
300
|
+
assert "flask" in FRAMEWORK_IMPORTS
|
|
301
|
+
assert "django" in FRAMEWORK_IMPORTS
|
|
302
|
+
assert "fastapi" in FRAMEWORK_IMPORTS
|
|
303
|
+
assert "pydantic" in FRAMEWORK_IMPORTS
|
|
370
304
|
|
|
371
305
|
if __name__ == "__main__":
|
|
372
306
|
pytest.main([__file__, "-v"])
|
|
File without changes
|
|
File without changes
|