richapi 0.2.0__tar.gz → 0.2.1__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.
- {richapi-0.2.0 → richapi-0.2.1}/PKG-INFO +1 -1
- {richapi-0.2.0 → richapi-0.2.1}/pyproject.toml +1 -1
- richapi-0.2.1/richapi/__version__.py +1 -0
- {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/compiler.py +18 -36
- {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/openapi.py +3 -0
- {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/protocol.py +13 -9
- richapi-0.2.0/richapi/__version__.py +0 -1
- {richapi-0.2.0 → richapi-0.2.1}/.envrc +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/.github/workflows/deploy-docs-on-demand.yml +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/.github/workflows/main.yml +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/.github/workflows/on-release-main.yml +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/.gitignore +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/README.md +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/deploy_docs.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/docs/advance.md +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/docs/conventions.md +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/docs/images/with-richapi.png +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/docs/images/without-richapi.png +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/docs/implementation.md +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/docs/index.md +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/makefile +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/mkdocs.yml +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/richapi/__init__.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/richapi/cli.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/__init__.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/richapi/exceptions.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/tests/__init__.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/tests/test_exc_raising_cases.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/tests/test_exception_edge_cases.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/tests/test_fastapi_auth_functions.py +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/tox.ini +0 -0
- {richapi-0.2.0 → richapi-0.2.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: richapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Find HTTPExceptions and turn them into documented responses!
|
|
5
5
|
Project-URL: repository, https://github.com/ManiMozaffar/richapi
|
|
6
6
|
Project-URL: Homepage, https://github.com/ManiMozaffar/richapi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -95,14 +95,10 @@ def _find_all_class_exceptions(
|
|
|
95
95
|
try:
|
|
96
96
|
tree = tree or ast.parse(inspect.getsource(cls))
|
|
97
97
|
except Exception as error:
|
|
98
|
-
logger.exception(
|
|
99
|
-
f"Failed to parse source code for {cls.__name__}.", exc_info=error
|
|
100
|
-
)
|
|
98
|
+
logger.exception(f"Failed to parse source code for {cls.__name__}.", exc_info=error)
|
|
101
99
|
return []
|
|
102
100
|
|
|
103
|
-
result = _find_explicit_expection_recursively(
|
|
104
|
-
cls.__init__, to_filter_predicate, tree
|
|
105
|
-
)
|
|
101
|
+
result = _find_explicit_expection_recursively(cls.__init__, to_filter_predicate, tree)
|
|
106
102
|
if hasattr(cls, "__call__"):
|
|
107
103
|
# Not sure if __call__ was called or init, so check for both
|
|
108
104
|
|
|
@@ -120,9 +116,7 @@ def _find_all_class_exceptions(
|
|
|
120
116
|
|
|
121
117
|
def is_absolutely_callable_object(obj: Callable) -> bool:
|
|
122
118
|
return (
|
|
123
|
-
inspect.isclass(type(obj))
|
|
124
|
-
and not inspect.isfunction(obj)
|
|
125
|
-
and not inspect.ismethod(obj)
|
|
119
|
+
inspect.isclass(type(obj)) and (not inspect.isfunction(obj)) and (not inspect.ismethod(obj))
|
|
126
120
|
)
|
|
127
121
|
|
|
128
122
|
|
|
@@ -142,6 +136,12 @@ def _find_explicit_expection_recursively(
|
|
|
142
136
|
return []
|
|
143
137
|
|
|
144
138
|
if is_absolutely_callable_object(func_obj):
|
|
139
|
+
cls_module = inspect.getmodule(type(func_obj))
|
|
140
|
+
if cls_module and is_stdlib(cls_module.__name__):
|
|
141
|
+
return []
|
|
142
|
+
if cls_module and to_filter_predicate(cls_module.__name__) is False:
|
|
143
|
+
return []
|
|
144
|
+
|
|
145
145
|
return _find_all_class_exceptions(type(func_obj), to_filter_predicate, tree)
|
|
146
146
|
|
|
147
147
|
ExceptionFinder.visited.add(func_obj)
|
|
@@ -169,9 +169,7 @@ def _find_explicit_expection_recursively(
|
|
|
169
169
|
try:
|
|
170
170
|
finder.visit(tree)
|
|
171
171
|
except Exception as error:
|
|
172
|
-
logger.exception(
|
|
173
|
-
f"Failed to analyze function {get_func_name(func_obj)}.", exc_info=error
|
|
174
|
-
)
|
|
172
|
+
logger.exception(f"Failed to analyze function {get_func_name(func_obj)}.", exc_info=error)
|
|
175
173
|
|
|
176
174
|
return finder.exceptions
|
|
177
175
|
|
|
@@ -249,9 +247,7 @@ def _resolve_function_from_call_node(
|
|
|
249
247
|
elif isinstance(node, ast.Attribute):
|
|
250
248
|
attr_chain = _resolve_full_attribute_path(node)
|
|
251
249
|
if attr_chain is None:
|
|
252
|
-
logger.debug(
|
|
253
|
-
f"Failed to get full attribute name: {ast.dump(node)} in {func.__name__}"
|
|
254
|
-
)
|
|
250
|
+
logger.debug(f"Failed to get full attribute name: {ast.dump(node)} in {func.__name__}")
|
|
255
251
|
return None
|
|
256
252
|
|
|
257
253
|
try:
|
|
@@ -271,9 +267,7 @@ def _resolve_function_from_call_node(
|
|
|
271
267
|
parent_attr = assignments.get(node_id, None)
|
|
272
268
|
|
|
273
269
|
if parent_attr is None:
|
|
274
|
-
if
|
|
275
|
-
parent_part == "self"
|
|
276
|
-
): # don't need to log -> we already know this is class
|
|
270
|
+
if parent_part == "self": # don't need to log -> we already know this is class
|
|
277
271
|
return None
|
|
278
272
|
|
|
279
273
|
logger.debug(
|
|
@@ -301,9 +295,7 @@ def _extract_node_name(
|
|
|
301
295
|
Extract the exception name a node recursively.
|
|
302
296
|
"""
|
|
303
297
|
if isinstance(node, ast.Call):
|
|
304
|
-
return _extract_node_name(
|
|
305
|
-
typing.cast(SupportedAstNodes, node.func), assignments
|
|
306
|
-
)
|
|
298
|
+
return _extract_node_name(typing.cast(SupportedAstNodes, node.func), assignments)
|
|
307
299
|
elif isinstance(node, ast.Name):
|
|
308
300
|
var_name = get_node_identifier(node)
|
|
309
301
|
return assignments.get(var_name, var_name)
|
|
@@ -311,9 +303,7 @@ def _extract_node_name(
|
|
|
311
303
|
return _resolve_full_attribute_path(node)
|
|
312
304
|
|
|
313
305
|
elif isinstance(node, ast.Await):
|
|
314
|
-
return _extract_node_name(
|
|
315
|
-
typing.cast(SupportedAstNodes, node.value), assignments
|
|
316
|
-
)
|
|
306
|
+
return _extract_node_name(typing.cast(SupportedAstNodes, node.value), assignments)
|
|
317
307
|
|
|
318
308
|
elif isinstance(
|
|
319
309
|
node,
|
|
@@ -361,9 +351,7 @@ def is_stdlib(module_name: str) -> bool:
|
|
|
361
351
|
class ExceptionFinder(ast.NodeVisitor):
|
|
362
352
|
visited: set[Callable] = set()
|
|
363
353
|
|
|
364
|
-
def __init__(
|
|
365
|
-
self, func: Callable, should_search_module_pred: Callable[[str], bool]
|
|
366
|
-
):
|
|
354
|
+
def __init__(self, func: Callable, should_search_module_pred: Callable[[str], bool]):
|
|
367
355
|
self.exceptions: list[tuple[Optional[type[Exception]], ast.Raise]] = []
|
|
368
356
|
self.assignments: dict[NodeIdentifier, str] = {}
|
|
369
357
|
self.func = func
|
|
@@ -493,10 +481,7 @@ class ExceptionFinder(ast.NodeVisitor):
|
|
|
493
481
|
|
|
494
482
|
founded_func_ast = _find_in_module(
|
|
495
483
|
tree,
|
|
496
|
-
lambda n: (
|
|
497
|
-
isinstance(n, ast.AsyncFunctionDef)
|
|
498
|
-
or isinstance(n, ast.FunctionDef)
|
|
499
|
-
)
|
|
484
|
+
lambda n: (isinstance(n, ast.AsyncFunctionDef) or isinstance(n, ast.FunctionDef))
|
|
500
485
|
and n.name == func_obj.__name__,
|
|
501
486
|
)
|
|
502
487
|
if founded_func_ast:
|
|
@@ -524,9 +509,7 @@ class ExceptionFinder(ast.NodeVisitor):
|
|
|
524
509
|
try:
|
|
525
510
|
caller_type = _exctact_type(caller_node_name, self.func_globals)
|
|
526
511
|
except Exception:
|
|
527
|
-
logger.critical(
|
|
528
|
-
f"Failed to resolve attribute chain: {caller_node_name}"
|
|
529
|
-
)
|
|
512
|
+
logger.critical(f"Failed to resolve attribute chain: {caller_node_name}")
|
|
530
513
|
self.generic_visit(node)
|
|
531
514
|
return
|
|
532
515
|
|
|
@@ -557,8 +540,7 @@ class ExceptionFinder(ast.NodeVisitor):
|
|
|
557
540
|
|
|
558
541
|
cls_ast = _find_in_module(
|
|
559
542
|
tree,
|
|
560
|
-
lambda n: isinstance(n, ast.ClassDef)
|
|
561
|
-
and n.name == caller_type.__name__,
|
|
543
|
+
lambda n: isinstance(n, ast.ClassDef) and n.name == caller_type.__name__,
|
|
562
544
|
)
|
|
563
545
|
if cls_ast:
|
|
564
546
|
casted_cls_ast = typing.cast(ast.ClassDef, cls_ast)
|
|
@@ -44,12 +44,15 @@ def enrich_openapi(
|
|
|
44
44
|
) -> Callable:
|
|
45
45
|
if target_module is None:
|
|
46
46
|
target_module = _find_module_name_where_app_defined_in(app)
|
|
47
|
+
|
|
47
48
|
if target_module is None or target_module == "__main__":
|
|
48
49
|
raise BaseRichAPIException(
|
|
49
50
|
"Could not determine the module where the FastAPI instance was created.\n"
|
|
50
51
|
"Please provide the module name as a string or list of strings."
|
|
51
52
|
)
|
|
52
53
|
|
|
54
|
+
target_module = target_module.split(".")[0] # get the top-level module
|
|
55
|
+
|
|
53
56
|
def _custom_openapi() -> dict:
|
|
54
57
|
if app.openapi_schema: # pragma: no cover
|
|
55
58
|
return app.openapi_schema
|
|
@@ -27,21 +27,25 @@ class BaseHTTPException(HTTPException):
|
|
|
27
27
|
@classmethod
|
|
28
28
|
def get_json_schema(cls) -> HTTPExceptionSchema:
|
|
29
29
|
value = _generic_json_schema_builder(cls)
|
|
30
|
-
assert
|
|
31
|
-
value is not None
|
|
32
|
-
) # should never happen because status code is always set
|
|
30
|
+
assert value is not None # should never happen because status code is always set
|
|
33
31
|
return value
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
def try_to_camel_case(string: str) -> str:
|
|
37
|
-
|
|
35
|
+
final_str = string
|
|
36
|
+
if " " in final_str: # with space
|
|
38
37
|
components = string.split(" ")
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
final_str = components[0] + "".join(x.title() for x in components[1:])
|
|
39
|
+
|
|
40
|
+
if "_" in final_str: # snake case
|
|
41
41
|
components = string.split("_")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
final_str = components[0] + "".join(x.title() for x in components[1:])
|
|
43
|
+
|
|
44
|
+
if "-" in final_str: # kebab case
|
|
45
|
+
components = string.split("-")
|
|
46
|
+
final_str = components[0] + "".join(x.title() for x in components[1:])
|
|
47
|
+
|
|
48
|
+
return final_str
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
def _generic_json_schema_builder(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.0"
|
|
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
|
|
File without changes
|