type-less 0.1.1__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.
- type_less/__init__.py +4 -0
- type_less/inference.py +379 -0
- type_less/inject.py +40 -0
- type_less-0.1.1.dist-info/METADATA +65 -0
- type_less-0.1.1.dist-info/RECORD +6 -0
- type_less-0.1.1.dist-info/WHEEL +4 -0
type_less/__init__.py
ADDED
type_less/inference.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import ast
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Callable,
|
|
7
|
+
Dict,
|
|
8
|
+
Literal,
|
|
9
|
+
Optional,
|
|
10
|
+
Tuple,
|
|
11
|
+
TypedDict,
|
|
12
|
+
Type,
|
|
13
|
+
Union,
|
|
14
|
+
)
|
|
15
|
+
import textwrap
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _snake_case_to_capital_case(name: str) -> str:
|
|
19
|
+
return "".join(word.capitalize() for word in name.split("_"))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _sanitize_name(name: str) -> str:
|
|
23
|
+
return "".join(c if c.isalnum() else "" for c in name)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_module_type(func: Callable, name: str) -> Type:
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
# TODO: support types not at the root
|
|
30
|
+
module = sys.modules.get(func.__module__, None)
|
|
31
|
+
if hasattr(module, name):
|
|
32
|
+
result = getattr(module, name)
|
|
33
|
+
if isinstance(result, type):
|
|
34
|
+
return result
|
|
35
|
+
elif isinstance(result, Callable):
|
|
36
|
+
return guess_return_type(result)
|
|
37
|
+
|
|
38
|
+
return Any
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def guess_return_type(func: Callable, use_literals=True) -> Type:
|
|
42
|
+
"""
|
|
43
|
+
Infer the return type of a Python function by analyzing its AST.
|
|
44
|
+
For dictionary returns, creates a TypedDict representation.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
func: The function to analyze
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The inferred return type
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# If annotations exist, return the return type
|
|
54
|
+
if hasattr(func, "__annotations__") and "return" in func.__annotations__:
|
|
55
|
+
return func.__annotations__["return"]
|
|
56
|
+
|
|
57
|
+
# Get function source code and create AST
|
|
58
|
+
try:
|
|
59
|
+
source = inspect.getsource(func)
|
|
60
|
+
source = textwrap.dedent(source)
|
|
61
|
+
except Exception:
|
|
62
|
+
return Any
|
|
63
|
+
|
|
64
|
+
module = ast.parse(source)
|
|
65
|
+
|
|
66
|
+
# Extract the function definition node
|
|
67
|
+
func_def = module.body[0]
|
|
68
|
+
if not isinstance(func_def, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
69
|
+
raise ValueError("Input is not a function definition")
|
|
70
|
+
|
|
71
|
+
# Create a symbol table for type analysis
|
|
72
|
+
symbol_table = {}
|
|
73
|
+
|
|
74
|
+
# Populate the symbol table with type hints from function annotations
|
|
75
|
+
if func_def.returns:
|
|
76
|
+
# If function has a return type annotation, use it directly
|
|
77
|
+
return _resolve_annotation(func_def.returns, {}, func)
|
|
78
|
+
|
|
79
|
+
# Gather type information from annotations and assignments
|
|
80
|
+
_analyze_function_body(func_def, symbol_table, func, use_literals)
|
|
81
|
+
|
|
82
|
+
# Find all return statements
|
|
83
|
+
return_types = []
|
|
84
|
+
for node in ast.walk(func_def):
|
|
85
|
+
if isinstance(node, ast.Return) and node.value:
|
|
86
|
+
return_type = _infer_expr_type(node.value, symbol_table, func, [], use_literals)
|
|
87
|
+
return_types.append(return_type)
|
|
88
|
+
|
|
89
|
+
# If we found return statements
|
|
90
|
+
if return_types:
|
|
91
|
+
if len(return_types) == 1:
|
|
92
|
+
return return_types[0]
|
|
93
|
+
else:
|
|
94
|
+
# Multiple return types - use Union
|
|
95
|
+
return Union[tuple(set(return_types))]
|
|
96
|
+
|
|
97
|
+
# Default to Any if no return statements or couldn't infer
|
|
98
|
+
return Any
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _analyze_function_body(
|
|
102
|
+
func_def: ast.FunctionDef, symbol_table: dict[str, Type], func: Callable, use_literals: bool
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Analyze function body to populate symbol table with type information"""
|
|
105
|
+
# First gather parameter types
|
|
106
|
+
for arg in func_def.args.args:
|
|
107
|
+
if arg.annotation:
|
|
108
|
+
symbol_table[arg.arg] = _resolve_annotation(arg.annotation, {}, func)
|
|
109
|
+
|
|
110
|
+
# Analyze assignments to track variable types
|
|
111
|
+
for node in ast.walk(func_def):
|
|
112
|
+
if isinstance(node, ast.Assign):
|
|
113
|
+
assigned_type = _infer_expr_type(node.value, symbol_table, func, [], use_literals)
|
|
114
|
+
for target in node.targets:
|
|
115
|
+
if isinstance(target, ast.Name):
|
|
116
|
+
symbol_table[target.id] = assigned_type
|
|
117
|
+
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
118
|
+
# Handle annotated assignments
|
|
119
|
+
symbol_table[node.target.id] = _resolve_annotation(
|
|
120
|
+
node.annotation, {}, func
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _resolve_annotation(
|
|
125
|
+
annotation: ast.AST, type_context: dict[str, Any], func: Callable
|
|
126
|
+
) -> Type:
|
|
127
|
+
"""Resolve a type annotation AST node to a real type"""
|
|
128
|
+
if isinstance(annotation, ast.Name):
|
|
129
|
+
# Simple types like int, str, etc.
|
|
130
|
+
type_name = annotation.id
|
|
131
|
+
# Check Python's built-in types first
|
|
132
|
+
if type_name in __builtins__:
|
|
133
|
+
return __builtins__[type_name]
|
|
134
|
+
|
|
135
|
+
print(annotation.id)
|
|
136
|
+
|
|
137
|
+
# Otherwise check if it's imported
|
|
138
|
+
return _get_module_type(func, type_name)
|
|
139
|
+
|
|
140
|
+
elif isinstance(annotation, ast.Subscript):
|
|
141
|
+
# Handle generic types like list[int], dict[str, int], etc.
|
|
142
|
+
if isinstance(annotation.value, ast.Name):
|
|
143
|
+
base_type = annotation.value.id
|
|
144
|
+
if base_type == "List" or base_type == "list":
|
|
145
|
+
elem_type = _resolve_annotation(annotation.slice, type_context, func)
|
|
146
|
+
return list[elem_type]
|
|
147
|
+
elif base_type == "Dict" or base_type == "dict":
|
|
148
|
+
if isinstance(annotation.slice, ast.Tuple):
|
|
149
|
+
key_type = _resolve_annotation(
|
|
150
|
+
annotation.slice.elts[0], type_context, func
|
|
151
|
+
)
|
|
152
|
+
val_type = _resolve_annotation(
|
|
153
|
+
annotation.slice.elts[1], type_context, func
|
|
154
|
+
)
|
|
155
|
+
return dict[key_type, val_type]
|
|
156
|
+
return Dict
|
|
157
|
+
elif base_type == "Set" or base_type == "set":
|
|
158
|
+
elem_type = _resolve_annotation(annotation.slice, type_context, func)
|
|
159
|
+
return set[elem_type]
|
|
160
|
+
elif base_type == "Tuple" or base_type == "tuple":
|
|
161
|
+
if isinstance(annotation.slice, ast.Tuple):
|
|
162
|
+
elem_types = [
|
|
163
|
+
_resolve_annotation(e, type_context, func)
|
|
164
|
+
for e in annotation.slice.elts
|
|
165
|
+
]
|
|
166
|
+
return tuple[tuple(elem_types)]
|
|
167
|
+
return Tuple
|
|
168
|
+
elif base_type == "Optional":
|
|
169
|
+
elem_type = _resolve_annotation(annotation.slice, type_context, func)
|
|
170
|
+
return Optional[elem_type]
|
|
171
|
+
elif base_type == "Union":
|
|
172
|
+
if isinstance(annotation.slice, ast.Tuple):
|
|
173
|
+
elem_types = [
|
|
174
|
+
_resolve_annotation(e, type_context, func)
|
|
175
|
+
for e in annotation.slice.elts
|
|
176
|
+
]
|
|
177
|
+
return Union[tuple(elem_types)]
|
|
178
|
+
return Union
|
|
179
|
+
|
|
180
|
+
# Fallback for unresolved or complex annotations
|
|
181
|
+
print("FAIL3")
|
|
182
|
+
return Any
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _infer_expr_type(
|
|
186
|
+
node: ast.AST, symbol_table: dict[str, Type], func: Callable, nested_path: list[str], use_literals: bool
|
|
187
|
+
) -> Type:
|
|
188
|
+
"""Infer the type of an expression"""
|
|
189
|
+
if isinstance(node, ast.Dict):
|
|
190
|
+
# For dictionary literals, create a TypedDict
|
|
191
|
+
return _create_typed_dict_from_dict(node, symbol_table, func, nested_path, use_literals)
|
|
192
|
+
|
|
193
|
+
elif isinstance(node, ast.List):
|
|
194
|
+
# Handle list literals
|
|
195
|
+
if not node.elts:
|
|
196
|
+
return list[Any]
|
|
197
|
+
element_types = [
|
|
198
|
+
_infer_expr_type(elt, symbol_table, func, nested_path, use_literals) for elt in node.elts
|
|
199
|
+
]
|
|
200
|
+
if len(set(element_types)) == 1:
|
|
201
|
+
return list[element_types[0]]
|
|
202
|
+
return list[Union[tuple(set(element_types))]]
|
|
203
|
+
|
|
204
|
+
elif isinstance(node, ast.Tuple):
|
|
205
|
+
# Handle tuple literals
|
|
206
|
+
if not node.elts:
|
|
207
|
+
return tuple[()]
|
|
208
|
+
element_types = [
|
|
209
|
+
_infer_expr_type(elt, symbol_table, func, nested_path, use_literals) for elt in node.elts
|
|
210
|
+
]
|
|
211
|
+
return tuple[tuple(element_types)]
|
|
212
|
+
|
|
213
|
+
elif isinstance(node, ast.Set):
|
|
214
|
+
# Handle set literals
|
|
215
|
+
if not node.elts:
|
|
216
|
+
return set[Any]
|
|
217
|
+
element_types = [
|
|
218
|
+
_infer_expr_type(elt, symbol_table, func, nested_path, use_literals) for elt in node.elts
|
|
219
|
+
]
|
|
220
|
+
if len(set(element_types)) == 1:
|
|
221
|
+
return set[element_types[0]]
|
|
222
|
+
return set[Union[tuple(set(element_types))]]
|
|
223
|
+
|
|
224
|
+
elif isinstance(node, ast.Constant):
|
|
225
|
+
# Handle literals
|
|
226
|
+
if use_literals:
|
|
227
|
+
return Literal[node.value]
|
|
228
|
+
else:
|
|
229
|
+
return type(node.value)
|
|
230
|
+
|
|
231
|
+
elif isinstance(node, ast.Name):
|
|
232
|
+
# Look up variable types in the symbol table
|
|
233
|
+
if node.id in symbol_table:
|
|
234
|
+
return symbol_table[node.id]
|
|
235
|
+
|
|
236
|
+
# Handle built-in types referenced by name
|
|
237
|
+
if node.id in __builtins__ and isinstance(__builtins__[node.id], type):
|
|
238
|
+
return __builtins__[node.id]
|
|
239
|
+
|
|
240
|
+
return _get_module_type(func, node.id)
|
|
241
|
+
|
|
242
|
+
elif isinstance(node, ast.Call):
|
|
243
|
+
# Handle function calls - this is complex, so we'll use a simplified approach
|
|
244
|
+
if isinstance(node.func, ast.Name):
|
|
245
|
+
func_name = node.func.id
|
|
246
|
+
# Handle some common built-in functions
|
|
247
|
+
if func_name == "int":
|
|
248
|
+
return int
|
|
249
|
+
elif func_name == "str":
|
|
250
|
+
return str
|
|
251
|
+
elif func_name == "float":
|
|
252
|
+
return float
|
|
253
|
+
elif func_name == "list":
|
|
254
|
+
return list[Any]
|
|
255
|
+
elif func_name == "dict":
|
|
256
|
+
return dict[Any, Any]
|
|
257
|
+
elif func_name == "set":
|
|
258
|
+
return set[Any]
|
|
259
|
+
elif func_name == "tuple":
|
|
260
|
+
return Tuple
|
|
261
|
+
|
|
262
|
+
return _get_module_type(func, func_name)
|
|
263
|
+
|
|
264
|
+
# For other function calls, we default to Any
|
|
265
|
+
return Any
|
|
266
|
+
|
|
267
|
+
elif isinstance(node, ast.BinOp):
|
|
268
|
+
# Handle binary operations
|
|
269
|
+
left_type = _infer_expr_type(node.left, symbol_table, func, nested_path, use_literals)
|
|
270
|
+
right_type = _infer_expr_type(node.right, symbol_table, func, nested_path, use_literals)
|
|
271
|
+
|
|
272
|
+
# String concatenation
|
|
273
|
+
if isinstance(node.op, ast.Add) and (left_type == str or right_type == str):
|
|
274
|
+
return str
|
|
275
|
+
|
|
276
|
+
# Numeric operations typically return numeric types
|
|
277
|
+
if isinstance(node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div)):
|
|
278
|
+
if left_type == float or right_type == float:
|
|
279
|
+
return float
|
|
280
|
+
return int
|
|
281
|
+
|
|
282
|
+
return Any
|
|
283
|
+
|
|
284
|
+
elif isinstance(node, ast.Compare):
|
|
285
|
+
return bool
|
|
286
|
+
|
|
287
|
+
elif isinstance(node, ast.IfExp):
|
|
288
|
+
body_type = _infer_expr_type(node.body, symbol_table, func, nested_path, use_literals)
|
|
289
|
+
orelse_type = _infer_expr_type(node.orelse, symbol_table, func, nested_path, use_literals)
|
|
290
|
+
return body_type | orelse_type
|
|
291
|
+
|
|
292
|
+
elif isinstance(node, ast.Attribute):
|
|
293
|
+
# Handle attribute access (e.g., obj.attr)
|
|
294
|
+
if isinstance(node.value, ast.Name) and node.value.id in symbol_table:
|
|
295
|
+
print(f"TRYYYY {node.value.id}")
|
|
296
|
+
# Get the type of the object
|
|
297
|
+
obj_type = symbol_table[node.value.id]
|
|
298
|
+
print(obj_type)
|
|
299
|
+
print(type(obj_type))
|
|
300
|
+
|
|
301
|
+
# If the object has type annotations, try to get the attribute type
|
|
302
|
+
if hasattr(obj_type, "__annotations__") and node.attr in obj_type.__annotations__:
|
|
303
|
+
return obj_type.__annotations__[node.attr]
|
|
304
|
+
|
|
305
|
+
# If the object is a class with class variables
|
|
306
|
+
if isinstance(obj_type, type):
|
|
307
|
+
if hasattr(obj_type, node.attr):
|
|
308
|
+
attr_value = getattr(obj_type, node.attr)
|
|
309
|
+
# If it's a Literal type or other type annotation
|
|
310
|
+
if hasattr(attr_value, "__origin__") and attr_value.__origin__ is Literal:
|
|
311
|
+
return attr_value
|
|
312
|
+
# For regular attributes, infer their type
|
|
313
|
+
return type(attr_value)
|
|
314
|
+
|
|
315
|
+
# For other attribute access, default to Any
|
|
316
|
+
print("FAIL3,5")
|
|
317
|
+
return Any
|
|
318
|
+
|
|
319
|
+
# Default for complex or unknown expressions
|
|
320
|
+
print("FAIL4")
|
|
321
|
+
print(node)
|
|
322
|
+
print(type(node))
|
|
323
|
+
return Any
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _create_typed_dict_from_dict(
|
|
327
|
+
dict_node: ast.Dict,
|
|
328
|
+
symbol_table: dict[str, Type],
|
|
329
|
+
func: Callable,
|
|
330
|
+
nested_path: list[str],
|
|
331
|
+
use_literals: bool,
|
|
332
|
+
) -> Type:
|
|
333
|
+
"""Create a TypedDict from a dictionary literal"""
|
|
334
|
+
# Check if all keys are string literals
|
|
335
|
+
field_types = {}
|
|
336
|
+
is_valid_typeddict = True
|
|
337
|
+
|
|
338
|
+
for i, key in enumerate(dict_node.keys):
|
|
339
|
+
if isinstance(key, ast.Constant) and isinstance(key.value, str):
|
|
340
|
+
value_type = _infer_expr_type(
|
|
341
|
+
dict_node.values[i], symbol_table, func, nested_path + [key.value], use_literals
|
|
342
|
+
)
|
|
343
|
+
field_types[key.value] = value_type
|
|
344
|
+
else:
|
|
345
|
+
is_valid_typeddict = False
|
|
346
|
+
break
|
|
347
|
+
|
|
348
|
+
if is_valid_typeddict and field_types:
|
|
349
|
+
# Create a dynamic TypedDict class
|
|
350
|
+
# Capitalize function name and remove underscores
|
|
351
|
+
class_name = f"{_snake_case_to_capital_case(func.__name__)}Return"
|
|
352
|
+
|
|
353
|
+
# Add nested path components
|
|
354
|
+
for component in nested_path:
|
|
355
|
+
class_name += _snake_case_to_capital_case(_sanitize_name(component))
|
|
356
|
+
return TypedDict(class_name, field_types)
|
|
357
|
+
|
|
358
|
+
# If not a valid TypedDict, return a regular Dict with inferred types
|
|
359
|
+
if dict_node.keys:
|
|
360
|
+
key_types = [
|
|
361
|
+
_infer_expr_type(key, symbol_table, func, nested_path + [key], use_literals)
|
|
362
|
+
for key in dict_node.keys
|
|
363
|
+
]
|
|
364
|
+
value_types = [
|
|
365
|
+
_infer_expr_type(value, symbol_table, func, nested_path, use_literals)
|
|
366
|
+
for value in dict_node.values
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
# Determine common types
|
|
370
|
+
if len(set(key_types)) == 1 and len(set(value_types)) == 1:
|
|
371
|
+
return dict[key_types[0], value_types[0]]
|
|
372
|
+
elif len(set(key_types)) == 1:
|
|
373
|
+
return dict[key_types[0], Union[tuple(set(value_types))]]
|
|
374
|
+
elif len(set(value_types)) == 1:
|
|
375
|
+
return dict[Union[tuple(set(key_types))], value_types[0]]
|
|
376
|
+
else:
|
|
377
|
+
return dict[Union[tuple(set(key_types))], Union[tuple(set(value_types))]]
|
|
378
|
+
|
|
379
|
+
return dict[Any, Any]
|
type_less/inject.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .inference import guess_return_type
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fill_type_hints(func: Callable, use_literals=False):
|
|
7
|
+
if not getattr(func, "__annotations__", None):
|
|
8
|
+
func.__annotations__ = {}
|
|
9
|
+
|
|
10
|
+
if not "return" in func.__annotations__:
|
|
11
|
+
func.__annotations__["return"] = guess_return_type(func, use_literals=use_literals)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
def fastapi_app_inject_types(app: T, use_literals=False) -> T:
|
|
16
|
+
"""
|
|
17
|
+
Auto-injects return types into FastAPI untyped routes.
|
|
18
|
+
|
|
19
|
+
Can be run before or after route initialization
|
|
20
|
+
Arguments:
|
|
21
|
+
app: FastAPI Application
|
|
22
|
+
"""
|
|
23
|
+
routes = getattr(app, 'routes', None)
|
|
24
|
+
if not type(routes) is list:
|
|
25
|
+
raise ValueError("Invalid app provided, no routes found. Please be sure this is a FastAPI app.")
|
|
26
|
+
for route in routes:
|
|
27
|
+
endpoint = getattr(route, "endpoint", None)
|
|
28
|
+
if not endpoint:
|
|
29
|
+
raise ValueError(f"Route {route} is missing an endpoint function")
|
|
30
|
+
fill_type_hints(endpoint, use_literals=use_literals)
|
|
31
|
+
|
|
32
|
+
# Auto-fill type hings
|
|
33
|
+
app_add_route = app.router.add_api_route
|
|
34
|
+
@wraps(app_add_route)
|
|
35
|
+
def add_route_injected(path: str, func, *args, **kwargs):
|
|
36
|
+
fill_type_hints(func, use_literals=use_literals)
|
|
37
|
+
app_add_route(path=path, endpoint=func, *args, **kwargs)
|
|
38
|
+
app.router.add_api_route = add_route_injected
|
|
39
|
+
|
|
40
|
+
return app
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: type-less
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Type less types with inferred return types
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# Type-less
|
|
9
|
+
|
|
10
|
+
Type less with automatic type inference for Python! Inject function return types at runtime for code generation.
|
|
11
|
+
|
|
12
|
+
## FastAPI Example
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
@app.get("/user/me")
|
|
16
|
+
def user_me():
|
|
17
|
+
return {
|
|
18
|
+
"id": 1,
|
|
19
|
+
"balance": 13.37,
|
|
20
|
+
"name": {
|
|
21
|
+
"first": "Testy",
|
|
22
|
+
"last": "McTestFace",
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
🚀 Generates Return Types:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
class UserMeReturnName(TypedDict):
|
|
31
|
+
first: str
|
|
32
|
+
last: str
|
|
33
|
+
|
|
34
|
+
class UserMeReturn(TypedDict):
|
|
35
|
+
id: str
|
|
36
|
+
balance: str
|
|
37
|
+
name: UserMeReturnName
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
📋 OpenAPI Spec:
|
|
41
|
+
|
|
42
|
+
<img src="docs/example.png" alt="OpenAPI Example" height="170">
|
|
43
|
+
|
|
44
|
+
## Using in FastAPI Project
|
|
45
|
+
|
|
46
|
+
Inject types before by hooking before setting up routes. Types will be automatically generated when new routes are added.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from type_less.inject import inject_fastapi_route_types
|
|
50
|
+
from fastapi import FastAPI
|
|
51
|
+
|
|
52
|
+
app = FastAPI()
|
|
53
|
+
app = inject_fastapi_route_types(app)
|
|
54
|
+
|
|
55
|
+
@app.get("/test")
|
|
56
|
+
def test(request):
|
|
57
|
+
...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## TODO:
|
|
61
|
+
### Add Support:
|
|
62
|
+
* Nested class inference
|
|
63
|
+
* Deep function call / return inference
|
|
64
|
+
### Better Way?:
|
|
65
|
+
* Possibly use pyre, mypy, or anything else to infer the type?
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type_less/__init__.py,sha256=ClsxzRxElZeEe-kqjZ_k7Z0AZVL3pgOe0Dz6g2lJ_pQ,128
|
|
2
|
+
type_less/inference.py,sha256=Obtucq_mAK1QkUJvSVEN_ClWZRkD8cc5kgnhzE7ZeIw,13766
|
|
3
|
+
type_less/inject.py,sha256=s-JGJlrTMOKV0m_alcImPMCnZQ0W2a1wRbQfR2nnE8c,1422
|
|
4
|
+
type_less-0.1.1.dist-info/METADATA,sha256=GMlDvalmZniLrJDP7SJ8tewMb1337sa0Exph-P19hik,1332
|
|
5
|
+
type_less-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
type_less-0.1.1.dist-info/RECORD,,
|