skylos 1.0.7__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/visitor.py ADDED
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ import ast,re
3
+ from pathlib import Path
4
+
5
+ PYTHON_BUILTINS={"print","len","str","int","float","list","dict","set","tuple","range","open","super","object","type","enumerate","zip","map","filter","sorted","reversed","sum","min","max","all","any","next","iter","repr","chr","ord","bytes","bytearray","memoryview","format","round","abs","pow","divmod","complex","hash","id","bool","callable","getattr","setattr","delattr","hasattr","isinstance","issubclass","globals","locals","vars","dir","property","classmethod","staticmethod"}
6
+ DYNAMIC_PATTERNS={"getattr","globals","eval","exec"}
7
+
8
+ class Definition:
9
+ __slots__ = ('name', 'type', 'filename', 'line', 'simple_name', 'confidence', 'references', 'is_exported', 'in_init')
10
+
11
+ def __init__(self, n, t, f, l):
12
+ self.name = n
13
+ self.type = t
14
+ self.filename = f
15
+ self.line = l
16
+ self.simple_name = n.split('.')[-1]
17
+ self.confidence = 100
18
+ self.references = 0
19
+ self.is_exported = False
20
+ self.in_init = "__init__.py" in str(f)
21
+
22
+ def to_dict(self):
23
+ if self.type == "method" and "." in self.name:
24
+ parts = self.name.split(".")
25
+ if len(parts) >= 3:
26
+ output_name = ".".join(parts[-2:])
27
+ else:
28
+ output_name = self.name
29
+ else:
30
+ output_name = self.simple_name
31
+
32
+ return{
33
+ "name": output_name,
34
+ "full_name": self.name,
35
+ "simple_name": self.simple_name,
36
+ "type": self.type,
37
+ "file": str(self.filename),
38
+ "basename": Path(self.filename).name,
39
+ "line": self.line,
40
+ "confidence": self.confidence,
41
+ "references": self.references
42
+ }
43
+
44
+ class Visitor(ast.NodeVisitor):
45
+ def __init__(self,mod,file):
46
+ self.mod=mod
47
+ self.file=file
48
+ self.defs=[]
49
+ self.refs=[]
50
+ self.cls=None
51
+ self.alias={}
52
+ self.dyn=set()
53
+ self.exports=set()
54
+ self.current_function_scope = []
55
+
56
+ def add_def(self,n,t,l):
57
+ if n not in{d.name for d in self.defs}:self.defs.append(Definition(n,t,self.file,l))
58
+
59
+ def add_ref(self,n):self.refs.append((n,self.file))
60
+
61
+ def qual(self,n):
62
+ if n in self.alias:return self.alias[n]
63
+ if n in PYTHON_BUILTINS:return n
64
+ return f"{self.mod}.{n}"if self.mod else n
65
+
66
+ def visit_Import(self,node):
67
+ for a in node.names:
68
+ full=a.name
69
+ self.alias[a.asname or a.name.split(".")[-1]]=full
70
+ self.add_def(full,"import",node.lineno)
71
+
72
+ def visit_ImportFrom(self,node):
73
+ if node.module is None:return
74
+ for a in node.names:
75
+ if a.name=="*":continue
76
+ base=node.module
77
+ if node.level:
78
+ parts=self.mod.split(".")
79
+ base=".".join(parts[:-node.level])+(f".{node.module}"if node.module else"")
80
+ full=f"{base}.{a.name}"
81
+ self.alias[a.asname or a.name]=full
82
+ self.add_def(full,"import",node.lineno)
83
+
84
+ def visit_FunctionDef(self,node):
85
+ outer_scope_prefix = '.'.join(self.current_function_scope) + '.' if self.current_function_scope else ''
86
+
87
+ if self.cls:
88
+ name_parts = [self.mod, self.cls, outer_scope_prefix + node.name]
89
+ else:
90
+ name_parts = [self.mod, outer_scope_prefix + node.name]
91
+
92
+ qualified_name = ".".join(filter(None, name_parts))
93
+
94
+ self.add_def(qualified_name,"method"if self.cls else"function",node.lineno)
95
+
96
+ self.current_function_scope.append(node.name)
97
+ for d_node in node.decorator_list:
98
+ self.visit(d_node)
99
+ for stmt in node.body:
100
+ self.visit(stmt)
101
+ self.current_function_scope.pop()
102
+
103
+ visit_AsyncFunctionDef=visit_FunctionDef
104
+
105
+ def visit_ClassDef(self,node):
106
+ cname=f"{self.mod}.{node.name}"
107
+ self.add_def(cname,"class",node.lineno)
108
+ prev=self.cls;self.cls=node.name
109
+ for b in node.body:self.visit(b)
110
+ self.cls=prev
111
+
112
+ def visit_Assign(self, node):
113
+ for target in node.targets:
114
+ if isinstance(target, ast.Name) and target.id == "__all__":
115
+ if isinstance(node.value, (ast.List, ast.Tuple)):
116
+ for elt in node.value.elts:
117
+ value = None
118
+ if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
119
+ value = elt.value
120
+ elif hasattr(elt, 's') and isinstance(elt.s, str):
121
+ value = elt.s
122
+
123
+ if value is not None:
124
+ full_name = f"{self.mod}.{value}"
125
+ self.add_ref(full_name)
126
+ self.add_ref(value)
127
+ self.generic_visit(node)
128
+
129
+ def visit_Call(self, node):
130
+ self.generic_visit(node)
131
+
132
+ if isinstance(node.func, ast.Name) and node.func.id in ("getattr", "hasattr") and len(node.args) >= 2:
133
+ if isinstance(node.args[1], ast.Constant) and isinstance(node.args[1].value, str):
134
+ attr_name = node.args[1].value
135
+ self.add_ref(attr_name)
136
+
137
+ if isinstance(node.args[0], ast.Name):
138
+ module_name = node.args[0].id
139
+ if module_name != "self":
140
+ qualified_name = f"{self.qual(module_name)}.{attr_name}"
141
+ self.add_ref(qualified_name)
142
+
143
+ elif isinstance(node.func, ast.Name) and node.func.id == "globals":
144
+ parent = getattr(node, 'parent', None)
145
+ if (isinstance(parent, ast.Subscript) and
146
+ isinstance(parent.slice, ast.Constant) and
147
+ isinstance(parent.slice.value, str)):
148
+ func_name = parent.slice.value
149
+ self.add_ref(func_name)
150
+ self.add_ref(f"{self.mod}.{func_name}")
151
+
152
+ elif (isinstance(node.func, ast.Attribute) and
153
+ node.func.attr == "format" and
154
+ isinstance(node.func.value, ast.Constant) and
155
+ isinstance(node.func.value.value, str)):
156
+ fmt = node.func.value.value
157
+ if any(isinstance(k.arg, str) and k.arg is None for k in node.keywords):
158
+ for _, n, _, _ in re.findall(r'\{([^}:!]+)', fmt):
159
+ if n:
160
+ self.add_ref(self.qual(n))
161
+
162
+ def visit_Name(self,node):
163
+ if isinstance(node.ctx,ast.Load):
164
+ self.add_ref(self.qual(node.id))
165
+ if node.id in DYNAMIC_PATTERNS:self.dyn.add(self.mod.split(".")[0])
166
+
167
+ def visit_Attribute(self,node):
168
+ self.generic_visit(node)
169
+ if isinstance(node.ctx,ast.Load)and isinstance(node.value,ast.Name):
170
+ self.add_ref(f"{self.qual(node.value.id)}.{node.attr}")
171
+
172
+ def generic_visit(self, node):
173
+ for field, value in ast.iter_fields(node):
174
+ if isinstance(value, list):
175
+ for item in value:
176
+ if isinstance(item, ast.AST):
177
+ item.parent = node
178
+ self.visit(item)
179
+ elif isinstance(value, ast.AST):
180
+ value.parent = node
181
+ self.visit(value)
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: skylos
3
+ Version: 1.0.7
4
+ Summary: A static analysis tool for Python codebases
5
+ Author-email: oha <aaronoh2015@gmail.com>
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: inquirer>=3.0.0
8
+ Dynamic: requires-python
@@ -0,0 +1,21 @@
1
+ skylos/__init__.py,sha256=QzhqFsdZqpu52fXFnvDem4P56R3SOC0dKOHfekwFuEQ,151
2
+ skylos/analyzer.py,sha256=EtL0IIw1NYzdGpXiOFQk4SXTSuTayVGpmGi0mZXL_Hk,7347
3
+ skylos/cli.py,sha256=Pq71Gtc3d8E7JVYnEORK_LoLclG96A6b76SIGZh-C6s,13821
4
+ skylos/visitor.py,sha256=DvRLXI-N2B6XG7VAB7cXEooD52oG1YNkl86iJbXe2-w,7336
5
+ test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ test/compare_tools.py,sha256=0g9PDeJlbst-7hOaQzrL4MiJFQKpqM8q8VeBGzpPczg,22738
7
+ test/diagnostics.py,sha256=ExuFOCVpc9BDwNYapU96vj9RXLqxji32Sv6wVF4nJYU,13802
8
+ test/test_skylos.py,sha256=kz77STrS4k3Eez5RDYwGxOg2WH3e7zNZPUYEaTLbGTs,15608
9
+ test/test_visitor.py,sha256=bxUY_Zn_gLadZlz_n3Mu6rhVcExqElISwwVBo4eqVAY,7337
10
+ test/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ test/sample_repo/app.py,sha256=M5XgoAn-LPz50mKAj_ZacRKf-Pg7I4HbjWP7Z9jE4a0,226
12
+ test/sample_repo/sample_repo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ test/sample_repo/sample_repo/commands.py,sha256=b6gQ9YDabt2yyfqGbOpLo0osF7wya8O4Lm7m8gtCr3g,2575
14
+ test/sample_repo/sample_repo/models.py,sha256=xXIg3pToEZwKuUCmKX2vTlCF_VeFA0yZlvlBVPIy5Qw,3320
15
+ test/sample_repo/sample_repo/routes.py,sha256=8yITrt55BwS01G7nWdESdx8LuxmReqop1zrGUKPeLi8,2475
16
+ test/sample_repo/sample_repo/utils.py,sha256=S56hEYh8wkzwsD260MvQcmUFOkw2EjFU27nMLFE6G2k,1103
17
+ skylos-1.0.7.dist-info/METADATA,sha256=Aqcg8zkHwx-l7ZGIXVVUPmXNQn9DZzZACiGOqM-YIks,224
18
+ skylos-1.0.7.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
19
+ skylos-1.0.7.dist-info/entry_points.txt,sha256=zzRpN2ByznlQoLeuLolS_TFNYSQxUGBL1EXQsAd6bIA,43
20
+ skylos-1.0.7.dist-info/top_level.txt,sha256=f8GA_7KwfaEopPMP8-EXDQXaqd4IbsOQPakZy01LkdQ,12
21
+ skylos-1.0.7.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.7.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ skylos = skylos.cli:main
@@ -0,0 +1,2 @@
1
+ skylos
2
+ test
test/__init__.py ADDED
File without changes