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.
Files changed (32) hide show
  1. {richapi-0.2.0 → richapi-0.2.1}/PKG-INFO +1 -1
  2. {richapi-0.2.0 → richapi-0.2.1}/pyproject.toml +1 -1
  3. richapi-0.2.1/richapi/__version__.py +1 -0
  4. {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/compiler.py +18 -36
  5. {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/openapi.py +3 -0
  6. {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/protocol.py +13 -9
  7. richapi-0.2.0/richapi/__version__.py +0 -1
  8. {richapi-0.2.0 → richapi-0.2.1}/.envrc +0 -0
  9. {richapi-0.2.0 → richapi-0.2.1}/.github/workflows/deploy-docs-on-demand.yml +0 -0
  10. {richapi-0.2.0 → richapi-0.2.1}/.github/workflows/main.yml +0 -0
  11. {richapi-0.2.0 → richapi-0.2.1}/.github/workflows/on-release-main.yml +0 -0
  12. {richapi-0.2.0 → richapi-0.2.1}/.gitignore +0 -0
  13. {richapi-0.2.0 → richapi-0.2.1}/README.md +0 -0
  14. {richapi-0.2.0 → richapi-0.2.1}/deploy_docs.py +0 -0
  15. {richapi-0.2.0 → richapi-0.2.1}/docs/advance.md +0 -0
  16. {richapi-0.2.0 → richapi-0.2.1}/docs/conventions.md +0 -0
  17. {richapi-0.2.0 → richapi-0.2.1}/docs/images/with-richapi.png +0 -0
  18. {richapi-0.2.0 → richapi-0.2.1}/docs/images/without-richapi.png +0 -0
  19. {richapi-0.2.0 → richapi-0.2.1}/docs/implementation.md +0 -0
  20. {richapi-0.2.0 → richapi-0.2.1}/docs/index.md +0 -0
  21. {richapi-0.2.0 → richapi-0.2.1}/makefile +0 -0
  22. {richapi-0.2.0 → richapi-0.2.1}/mkdocs.yml +0 -0
  23. {richapi-0.2.0 → richapi-0.2.1}/richapi/__init__.py +0 -0
  24. {richapi-0.2.0 → richapi-0.2.1}/richapi/cli.py +0 -0
  25. {richapi-0.2.0 → richapi-0.2.1}/richapi/exc_parser/__init__.py +0 -0
  26. {richapi-0.2.0 → richapi-0.2.1}/richapi/exceptions.py +0 -0
  27. {richapi-0.2.0 → richapi-0.2.1}/tests/__init__.py +0 -0
  28. {richapi-0.2.0 → richapi-0.2.1}/tests/test_exc_raising_cases.py +0 -0
  29. {richapi-0.2.0 → richapi-0.2.1}/tests/test_exception_edge_cases.py +0 -0
  30. {richapi-0.2.0 → richapi-0.2.1}/tests/test_fastapi_auth_functions.py +0 -0
  31. {richapi-0.2.0 → richapi-0.2.1}/tox.ini +0 -0
  32. {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.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "richapi"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Find HTTPExceptions and turn them into documented responses!"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -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
- if " " in string: # with space
35
+ final_str = string
36
+ if " " in final_str: # with space
38
37
  components = string.split(" ")
39
- return components[0] + "".join(x.title() for x in components[1:])
40
- elif "_" in string: # snake case
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
- return components[0] + "".join(x.title() for x in components[1:])
43
- else:
44
- return string # not known
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