Skip to content

Commit ffd70bc

Browse files
committed
Moved here utbot_python_runner
1 parent cb2384b commit ffd70bc

File tree

17 files changed

+1142
-5
lines changed

17 files changed

+1142
-5
lines changed

gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ commonsIOVersion=2.11.0
9292
javaxVersion=2.2
9393
jakartaVersion=3.1.0
9494

95+
# Python support
96+
utbotMypyRunnerVersion=0.2.11
97+
9598
# use latest Java 8 compaitable Spring and Spring Boot versions
9699
springVersion=5.3.28
97100
springBootVersion=2.7.13

utbot-python-types/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
gradle.properties
2+
local_mypy_path
3+
dist/

utbot-python-types/build.gradle.kts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,34 @@ dependencies {
55
implementation("com.squareup.moshi:moshi-kotlin:1.11.0")
66
implementation("com.squareup.moshi:moshi-adapters:1.11.0")
77
implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion)
8+
}
9+
10+
val utbotMypyRunnerVersion = File(project.projectDir, "src/main/resources/utbot_mypy_runner_version").readText()
11+
val pipToken: String? by project
12+
val pythonInterpreter: String? by project
13+
val utbotMypyRunnerPath = File(project.projectDir, "src/main/python/utbot_mypy_runner")
14+
val localMypyPath = File(utbotMypyRunnerPath, "dist")
15+
val localMypyPathText = File(project.projectDir, "src/main/resources/local_mypy_path")
16+
17+
18+
val setMypyRunnerVersion = tasks.register<Exec>("setVersion") {
19+
group = "python"
20+
workingDir = utbotMypyRunnerPath
21+
commandLine(pythonInterpreter!!, "-m", "poetry", "version", utbotMypyRunnerVersion)
22+
}
23+
24+
val buildMypyRunner = tasks.register<Exec>("buildUtbotMypyRunner") {
25+
dependsOn(setMypyRunnerVersion)
26+
group = "python"
27+
workingDir = utbotMypyRunnerPath
28+
commandLine(pythonInterpreter!!, "-m", "poetry", "build")
29+
localMypyPathText.writeText(localMypyPath.canonicalPath)
30+
localMypyPathText.createNewFile()
31+
}
32+
33+
tasks.register<Exec>("publishUtbotMypyRunner") {
34+
dependsOn(buildMypyRunner)
35+
group = "python"
36+
workingDir = utbotMypyRunnerPath
37+
commandLine(pythonInterpreter!!, "-m", "poetry", "publish", "-u", "__token__", "-p", pipToken!!)
838
}

utbot-python-types/src/main/kotlin/org/utbot/python/utils/ProcessUtils.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ data class CmdResult(
1111
val terminatedByTimeout: Boolean = false
1212
)
1313

14-
fun startProcess(command: List<String>): Process = ProcessBuilder(command).start()
14+
fun startProcess(
15+
command: List<String>,
16+
environmentVariables: Map<String, String> = emptyMap()
17+
): Process {
18+
val pb = ProcessBuilder(command)
19+
val env = pb.environment()
20+
env += environmentVariables
21+
return pb.start()
22+
}
1523

1624
fun getResult(process: Process, timeout: Long? = null): CmdResult {
1725
if (timeout != null) {
@@ -36,7 +44,7 @@ fun getResult(process: Process, timeout: Long? = null): CmdResult {
3644
return CmdResult(stdout.trimIndent(), stderr, process.exitValue())
3745
}
3846

39-
fun runCommand(command: List<String>, timeout: Long? = null): CmdResult {
40-
val process = startProcess(command)
47+
fun runCommand(command: List<String>, timeout: Long? = null, environmentVariables: Map<String, String> = emptyMap()): CmdResult {
48+
val process = startProcess(command, environmentVariables)
4149
return getResult(process, timeout)
4250
}

utbot-python-types/src/main/python/utbot_mypy_runner/README.md

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[tool.poetry]
2+
name = "utbot_mypy_runner"
3+
version = "0.2.12.dev1"
4+
description = ""
5+
authors = ["Ekaterina Tochilina <[email protected]>"]
6+
readme = "README.md"
7+
packages = [{include = "utbot_mypy_runner"}]
8+
9+
[tool.poetry.dependencies]
10+
python = "^3.8"
11+
mypy = "1.0.0"
12+
13+
14+
[build-system]
15+
requires = ["poetry-core"]
16+
build-backend = "poetry.core.masonry.api"

utbot-python-types/src/main/python/utbot_mypy_runner/utbot_mypy_runner/__init__.py

Whitespace-only changes.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import argparse
2+
import os
3+
4+
import utbot_mypy_runner.mypy_main as mypy_main
5+
import utbot_mypy_runner.extract_annotations as extraction
6+
7+
8+
parser = argparse.ArgumentParser()
9+
parser.add_argument('--config', required=True)
10+
parser.add_argument('--sources', required=True, nargs='+')
11+
parser.add_argument('--modules', required=True, nargs='+')
12+
parser.add_argument('--annotations_out')
13+
parser.add_argument('--mypy_stdout')
14+
parser.add_argument('--mypy_stderr')
15+
parser.add_argument('--mypy_exit_status')
16+
parser.add_argument('--module_for_types')
17+
parser.add_argument('--indent', type=int)
18+
19+
args = parser.parse_args()
20+
21+
if len(args.sources) != len(args.modules):
22+
print("Sources must correspond to modules")
23+
exit(10)
24+
25+
mypy_args = ["--config-file", args.config]
26+
for module_name in args.modules:
27+
mypy_args += ["-m", module_name]
28+
29+
stdout, stderr, exit_status, build_result = mypy_main.run(mypy_args)
30+
31+
if args.mypy_stdout is not None:
32+
with open(args.mypy_stdout, "w") as file:
33+
file.write(stdout)
34+
print("Wrote mypy stdout to", args.mypy_stdout)
35+
36+
if args.mypy_stderr is not None:
37+
with open(args.mypy_stderr, "w") as file:
38+
file.write(stderr)
39+
print("Wrote mypy stderr to", args.mypy_stderr)
40+
41+
if args.mypy_exit_status is not None:
42+
with open(args.mypy_exit_status, "w") as file:
43+
file.write(str(exit_status))
44+
print("Wrote mypy exit status to", args.mypy_exit_status)
45+
46+
if args.annotations_out is not None:
47+
if build_result is not None:
48+
with open(args.annotations_out, "w") as file:
49+
sources = [os.path.abspath(x) for x in args.sources]
50+
file.write(extraction.get_result_from_mypy_build(build_result, sources, args.module_for_types, args.indent))
51+
print("Extracted annotations and wrote to", args.annotations_out)
52+
else:
53+
print("For some reason BuildResult is None")
54+
exit(11)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import typing as tp
2+
3+
from mypy.nodes import *
4+
from mypy.traverser import *
5+
import mypy.types
6+
7+
8+
class MyTraverserVisitor(TraverserVisitor):
9+
def __init__(self, types, processor: tp.Callable[[int, int, int, int, mypy.types.Type], None]):
10+
self.types = types
11+
self.processor = processor
12+
13+
def process_expression(self, o: Expression) -> None:
14+
if o in self.types.keys() and not isinstance(self.types[o], mypy.types.AnyType) \
15+
and o.end_line is not None and o.end_column is not None and o.line >= 0:
16+
self.processor(o.line, o.column, o.end_line, o.end_column, self.types[o])
17+
18+
def visit_name_expr(self, o: NameExpr) -> None:
19+
self.process_expression(o)
20+
super().visit_name_expr(o)
21+
22+
def visit_member_expr(self, o: MemberExpr) -> None:
23+
self.process_expression(o)
24+
super().visit_member_expr(o)
25+
26+
"""
27+
def visit_yield_expr(self, o: YieldExpr) -> None:
28+
self.process_expression(o)
29+
super().visit_yield_expr(o)
30+
31+
def visit_call_expr(self, o: CallExpr) -> None:
32+
self.process_expression(o)
33+
super().visit_call_expr(o)
34+
35+
def visit_op_expr(self, o: OpExpr) -> None:
36+
self.process_expression(o)
37+
super().visit_op_expr(o)
38+
39+
def visit_comparison_expr(self, o: ComparisonExpr) -> None:
40+
self.process_expression(o)
41+
super().visit_comparison_expr(o)
42+
43+
def visit_slice_expr(self, o: SliceExpr) -> None:
44+
self.process_expression(o)
45+
super().visit_slice_expr(o)
46+
47+
def visit_cast_expr(self, o: CastExpr) -> None:
48+
self.process_expression(o)
49+
super().visit_cast_expr(o)
50+
51+
def visit_assert_type_expr(self, o: AssertTypeExpr) -> None:
52+
self.process_expression(o)
53+
super().visit_assert_type_expr(o)
54+
55+
def visit_reveal_expr(self, o: RevealExpr) -> None:
56+
self.process_expression(o)
57+
super().visit_reveal_expr(o)
58+
59+
def visit_assignment_expr(self, o: AssignmentExpr) -> None:
60+
self.process_expression(o)
61+
super().visit_assignment_expr(o)
62+
63+
def visit_unary_expr(self, o: UnaryExpr) -> None:
64+
self.process_expression(o)
65+
super().visit_unary_expr(o)
66+
67+
def visit_list_expr(self, o: ListExpr) -> None:
68+
self.process_expression(o)
69+
super().visit_list_expr(o)
70+
71+
def visit_tuple_expr(self, o: TupleExpr) -> None:
72+
self.process_expression(o)
73+
super().visit_tuple_expr(o)
74+
75+
def visit_dict_expr(self, o: DictExpr) -> None:
76+
self.process_expression(o)
77+
super().visit_dict_expr(o)
78+
79+
def visit_set_expr(self, o: SetExpr) -> None:
80+
self.process_expression(o)
81+
super().visit_set_expr(o)
82+
83+
def visit_index_expr(self, o: IndexExpr) -> None:
84+
self.process_expression(o)
85+
super().visit_index_expr(o)
86+
87+
def visit_generator_expr(self, o: GeneratorExpr) -> None:
88+
self.process_expression(o)
89+
super().visit_generator_expr(o)
90+
91+
def visit_dictionary_comprehension(self, o: DictionaryComprehension) -> None:
92+
self.process_expression(o)
93+
super().visit_dictionary_comprehension(o)
94+
95+
def visit_list_comprehension(self, o: ListComprehension) -> None:
96+
self.process_expression(o)
97+
super().visit_list_comprehension(o)
98+
99+
def visit_set_comprehension(self, o: SetComprehension) -> None:
100+
self.process_expression(o)
101+
super().visit_set_comprehension(o)
102+
103+
def visit_conditional_expr(self, o: ConditionalExpr) -> None:
104+
self.process_expression(o)
105+
super().visit_conditional_expr(o)
106+
107+
def visit_type_application(self, o: TypeApplication) -> None:
108+
self.process_expression(o)
109+
super().visit_type_application(o)
110+
111+
def visit_lambda_expr(self, o: LambdaExpr) -> None:
112+
self.process_expression(o)
113+
super().visit_lambda_expr(o)
114+
115+
def visit_star_expr(self, o: StarExpr) -> None:
116+
self.process_expression(o)
117+
super().visit_star_expr(o)
118+
119+
def visit_backquote_expr(self, o: BackquoteExpr) -> None:
120+
self.process_expression(o)
121+
super().visit_backquote_expr(o)
122+
123+
def visit_await_expr(self, o: AwaitExpr) -> None:
124+
self.process_expression(o)
125+
super().visit_await_expr(o)
126+
127+
def visit_super_expr(self, o: SuperExpr) -> None:
128+
self.process_expression(o)
129+
super().visit_super_expr(o)
130+
"""
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import json
2+
import typing as tp
3+
from collections import defaultdict
4+
5+
import mypy.nodes
6+
import mypy.types
7+
8+
import utbot_mypy_runner.mypy_main as mypy_main
9+
import utbot_mypy_runner.expression_traverser as expression_traverser
10+
import utbot_mypy_runner.names
11+
from utbot_mypy_runner.utils import get_borders
12+
from utbot_mypy_runner.nodes import *
13+
14+
15+
class ExpressionType:
16+
def __init__(self, start_offset: int, end_offset: int, line: int, type_: Annotation):
17+
self.start_offset = start_offset
18+
self.end_offset = end_offset
19+
self.line = line
20+
self.type_ = type_
21+
22+
def encode(self):
23+
return {
24+
"startOffset": self.start_offset,
25+
"endOffset": self.end_offset,
26+
"line": self.line,
27+
"type": self.type_.encode()
28+
}
29+
30+
31+
def get_output_json(annotations: tp.Dict[str, tp.Dict[str, Definition]],
32+
expression_types: tp.Dict[str, tp.List[ExpressionType]],
33+
names_dict: tp.Dict[str, tp.List[utbot_mypy_runner.names.Name]],
34+
indent: tp.Optional[int]):
35+
node_storage_key = 'nodeStorage'
36+
types_key = 'types'
37+
definitions_key = 'definitions'
38+
names_key = 'names'
39+
40+
result: tp.Dict[str, tp.Any] = {node_storage_key: {}, types_key: {}}
41+
for key in annotation_node_dict:
42+
result[node_storage_key][str(key)] = annotation_node_dict[key].encode()
43+
44+
result[definitions_key] = {}
45+
for module in annotations.keys():
46+
result[definitions_key][module] = {}
47+
for name in annotations[module].keys():
48+
result[definitions_key][module][name] = annotations[module][name].encode()
49+
50+
for module in expression_types.keys():
51+
result[types_key][module] = [x.encode() for x in expression_types[module]]
52+
53+
result[names_key] = {}
54+
for module in names_dict.keys():
55+
result[names_key][module] = [x.encode() for x in names_dict[module]]
56+
57+
return json.dumps(result, indent=indent)
58+
59+
60+
def skip_node(node: mypy.nodes.SymbolTableNode) -> bool:
61+
62+
if isinstance(node.node, mypy.nodes.TypeInfo):
63+
x = node.node
64+
return x.is_named_tuple or (x.typeddict_type is not None) or x.is_newtype or x.is_intersection
65+
66+
return False
67+
68+
69+
def get_result_from_mypy_build(build_result: mypy_main.build.BuildResult, source_paths: tp.List[str],
70+
module_for_types: tp.Optional[str], indent=None) -> str:
71+
annotation_dict: tp.Dict[str, tp.Dict[str, Definition]] = defaultdict(dict)
72+
names_dict: tp.Dict[str, tp.List[utbot_mypy_runner.names.Name]] = utbot_mypy_runner.names.get_names(build_result)
73+
for module in build_result.files.keys():
74+
mypy_file: mypy.nodes.MypyFile = build_result.files[module]
75+
76+
for name in mypy_file.names.keys():
77+
symbol_table_node = build_result.files[module].names[name]
78+
79+
if skip_node(symbol_table_node):
80+
continue
81+
82+
only_types = mypy_file.path not in source_paths
83+
84+
definition = get_definition_from_symbol_node(symbol_table_node, Meta(module), only_types)
85+
if definition is not None:
86+
annotation_dict[module][name] = definition
87+
88+
expression_types: tp.Dict[str, tp.List[ExpressionType]] = defaultdict(list)
89+
if module_for_types is not None:
90+
mypy_file = build_result.files[module_for_types]
91+
with open(mypy_file.path, "r") as file:
92+
content = file.readlines()
93+
processor = lambda line, col, end_line, end_col, type_: \
94+
expression_types[module_for_types].append( # TODO: proper Meta
95+
ExpressionType(*get_borders(line, col, end_line, end_col, content), line, get_annotation(type_, Meta(module_for_types)))
96+
)
97+
traverser = expression_traverser.MyTraverserVisitor(build_result.types, processor)
98+
traverser.visit_mypy_file(build_result.files[module_for_types])
99+
100+
return get_output_json(annotation_dict, expression_types, names_dict, indent)

0 commit comments

Comments
 (0)