richapi 0.1.0__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.1.0/.envrc +4 -0
- richapi-0.1.0/.github/workflows/deploy-docs-on-demand.yml +24 -0
- richapi-0.1.0/.github/workflows/main.yml +41 -0
- richapi-0.1.0/.github/workflows/on-release-main.yml +40 -0
- richapi-0.1.0/.gitignore +168 -0
- richapi-0.1.0/.python-version +1 -0
- richapi-0.1.0/PKG-INFO +37 -0
- richapi-0.1.0/README.md +23 -0
- richapi-0.1.0/deploy_docs.py +27 -0
- richapi-0.1.0/docs/advance.md +36 -0
- richapi-0.1.0/docs/conventions.md +72 -0
- richapi-0.1.0/docs/images/with-richapi.png +0 -0
- richapi-0.1.0/docs/images/without-richapi.png +0 -0
- richapi-0.1.0/docs/index.md +111 -0
- richapi-0.1.0/makefile +37 -0
- richapi-0.1.0/mkdocs.yml +105 -0
- richapi-0.1.0/pyproject.toml +60 -0
- richapi-0.1.0/richapi/__init__.py +4 -0
- richapi-0.1.0/richapi/__version__.py +1 -0
- richapi-0.1.0/richapi/cli.py +87 -0
- richapi-0.1.0/richapi/exc_parser/__init__.py +4 -0
- richapi-0.1.0/richapi/exc_parser/compiler.py +495 -0
- richapi-0.1.0/richapi/exc_parser/openapi.py +309 -0
- richapi-0.1.0/richapi/exc_parser/protocol.py +64 -0
- richapi-0.1.0/richapi/exceptions.py +2 -0
- richapi-0.1.0/tests/__init__.py +0 -0
- richapi-0.1.0/tests/test_exc_raising_cases.py +115 -0
- richapi-0.1.0/tests/test_exception_edge_cases.py +58 -0
- richapi-0.1.0/tox.ini +38 -0
- richapi-0.1.0/uv.lock +1345 -0
richapi-0.1.0/.envrc
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Deploy Docs On Demand
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
deploy-docs-on-demand:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: ${{ matrix.python-version }}
|
|
15
|
+
|
|
16
|
+
- uses: yezz123/setup-uv@v4
|
|
17
|
+
|
|
18
|
+
- name: Setup python ${{ matrix.python-version }} and uv
|
|
19
|
+
shell: bash
|
|
20
|
+
run: |
|
|
21
|
+
uv sync --frozen
|
|
22
|
+
|
|
23
|
+
- name: Deploy documentation
|
|
24
|
+
run: uv run python deploy_docs.py
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Main
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
types: [opened, synchronize, reopened]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
tox:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.9", "3.10", "3.11"]
|
|
16
|
+
fail-fast: false
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- uses: yezz123/setup-uv@v4
|
|
25
|
+
|
|
26
|
+
- name: Pin python-version ${{ matrix.python-version }}
|
|
27
|
+
run: uv python pin ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Setup python ${{ matrix.python-version }} and uv
|
|
30
|
+
shell: bash
|
|
31
|
+
run: |
|
|
32
|
+
uv sync --frozen
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: make install
|
|
36
|
+
|
|
37
|
+
- name: Run tests
|
|
38
|
+
run: make test
|
|
39
|
+
|
|
40
|
+
- name: Run check
|
|
41
|
+
run: make check
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: release-main
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Check out
|
|
13
|
+
uses: actions/checkout@v3
|
|
14
|
+
|
|
15
|
+
- name: Export tag
|
|
16
|
+
id: vars
|
|
17
|
+
run: echo tag=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT
|
|
18
|
+
|
|
19
|
+
- name: Set up python
|
|
20
|
+
uses: actions/setup-python@v4
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- uses: yezz123/setup-uv@v4
|
|
25
|
+
|
|
26
|
+
- name: Setup python ${{ matrix.python-version }} and uv
|
|
27
|
+
shell: bash
|
|
28
|
+
run: |
|
|
29
|
+
uv sync --frozen
|
|
30
|
+
|
|
31
|
+
- name: Install the latest version of rye
|
|
32
|
+
uses: eifinger/setup-rye@v4
|
|
33
|
+
|
|
34
|
+
- name: Build and publish
|
|
35
|
+
run: |
|
|
36
|
+
rye build
|
|
37
|
+
rye publish --token $PYPI_TOKEN --yes
|
|
38
|
+
env:
|
|
39
|
+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
40
|
+
RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
|
richapi-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
docs/source
|
|
2
|
+
|
|
3
|
+
# From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
|
|
4
|
+
|
|
5
|
+
# Byte-compiled / optimized / DLL files
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
|
|
10
|
+
# C extensions
|
|
11
|
+
*.so
|
|
12
|
+
.DS_Store
|
|
13
|
+
|
|
14
|
+
# Distribution / packaging
|
|
15
|
+
.Python
|
|
16
|
+
build/
|
|
17
|
+
develop-eggs/
|
|
18
|
+
dist/
|
|
19
|
+
downloads/
|
|
20
|
+
eggs/
|
|
21
|
+
.eggs/
|
|
22
|
+
lib/
|
|
23
|
+
lib64/
|
|
24
|
+
parts/
|
|
25
|
+
sdist/
|
|
26
|
+
var/
|
|
27
|
+
wheels/
|
|
28
|
+
share/python-wheels/
|
|
29
|
+
*.egg-info/
|
|
30
|
+
.installed.cfg
|
|
31
|
+
*.egg
|
|
32
|
+
MANIFEST
|
|
33
|
+
|
|
34
|
+
# PyInstaller
|
|
35
|
+
# Usually these files are written by a python script from a template
|
|
36
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
37
|
+
*.manifest
|
|
38
|
+
*.spec
|
|
39
|
+
|
|
40
|
+
# Installer logs
|
|
41
|
+
pip-log.txt
|
|
42
|
+
pip-delete-this-directory.txt
|
|
43
|
+
|
|
44
|
+
# Unit test / coverage reports
|
|
45
|
+
htmlcov/
|
|
46
|
+
.tox/
|
|
47
|
+
.nox/
|
|
48
|
+
.coverage
|
|
49
|
+
.coverage.*
|
|
50
|
+
.cache
|
|
51
|
+
nosetests.xml
|
|
52
|
+
coverage.xml
|
|
53
|
+
*.cover
|
|
54
|
+
*.py,cover
|
|
55
|
+
.hypothesis/
|
|
56
|
+
.pytest_cache/
|
|
57
|
+
cover/
|
|
58
|
+
|
|
59
|
+
# Translations
|
|
60
|
+
*.mo
|
|
61
|
+
*.pot
|
|
62
|
+
|
|
63
|
+
# Django stuff:
|
|
64
|
+
*.log
|
|
65
|
+
local_settings.py
|
|
66
|
+
db.sqlite3
|
|
67
|
+
db.sqlite3-journal
|
|
68
|
+
|
|
69
|
+
# Flask stuff:
|
|
70
|
+
instance/
|
|
71
|
+
.webassets-cache
|
|
72
|
+
|
|
73
|
+
# Scrapy stuff:
|
|
74
|
+
.scrapy
|
|
75
|
+
|
|
76
|
+
# Sphinx documentation
|
|
77
|
+
docs/_build/
|
|
78
|
+
|
|
79
|
+
# PyBuilder
|
|
80
|
+
.pybuilder/
|
|
81
|
+
target/
|
|
82
|
+
|
|
83
|
+
# Jupyter Notebook
|
|
84
|
+
.ipynb_checkpoints
|
|
85
|
+
|
|
86
|
+
# IPython
|
|
87
|
+
profile_default/
|
|
88
|
+
ipython_config.py
|
|
89
|
+
|
|
90
|
+
# pyenv
|
|
91
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
92
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
93
|
+
# .python-version
|
|
94
|
+
|
|
95
|
+
# pipenv
|
|
96
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
97
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
98
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
99
|
+
# install all needed dependencies.
|
|
100
|
+
#Pipfile.lock
|
|
101
|
+
|
|
102
|
+
# poetry
|
|
103
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
104
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
105
|
+
# commonly ignored for libraries.
|
|
106
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
107
|
+
#poetry.lock
|
|
108
|
+
|
|
109
|
+
# pdm
|
|
110
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
111
|
+
#pdm.lock
|
|
112
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
113
|
+
# in version control.
|
|
114
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
115
|
+
.pdm.toml
|
|
116
|
+
|
|
117
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
118
|
+
__pypackages__/
|
|
119
|
+
|
|
120
|
+
# Celery stuff
|
|
121
|
+
celerybeat-schedule
|
|
122
|
+
celerybeat.pid
|
|
123
|
+
|
|
124
|
+
# SageMath parsed files
|
|
125
|
+
*.sage.py
|
|
126
|
+
|
|
127
|
+
# Environments
|
|
128
|
+
.env
|
|
129
|
+
.venv
|
|
130
|
+
env/
|
|
131
|
+
venv/
|
|
132
|
+
ENV/
|
|
133
|
+
env.bak/
|
|
134
|
+
venv.bak/
|
|
135
|
+
|
|
136
|
+
# Spyder project settings
|
|
137
|
+
.spyderproject
|
|
138
|
+
.spyproject
|
|
139
|
+
|
|
140
|
+
# Rope project settings
|
|
141
|
+
.ropeproject
|
|
142
|
+
|
|
143
|
+
# mkdocs documentation
|
|
144
|
+
/site
|
|
145
|
+
|
|
146
|
+
# mypy
|
|
147
|
+
.mypy_cache/
|
|
148
|
+
.dmypy.json
|
|
149
|
+
dmypy.json
|
|
150
|
+
|
|
151
|
+
# Pyre type checker
|
|
152
|
+
.pyre/
|
|
153
|
+
|
|
154
|
+
# pytype static type analyzer
|
|
155
|
+
.pytype/
|
|
156
|
+
|
|
157
|
+
# Cython debug symbols
|
|
158
|
+
cython_debug/
|
|
159
|
+
|
|
160
|
+
# Vscode config files
|
|
161
|
+
.vscode/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.9.19
|
richapi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: richapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find HTTPExceptions and turn them into documented responses!
|
|
5
|
+
Project-URL: repository, https://github.com/ManiMozaffar/richapi
|
|
6
|
+
Project-URL: Homepage, https://github.com/ManiMozaffar/richapi
|
|
7
|
+
Project-URL: Documentation, https://github.com/ManiMozaffar/richapi
|
|
8
|
+
Project-URL: Source, https://github.com/ManiMozaffar/richapi
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: loguru>=0.7.2
|
|
12
|
+
Requires-Dist: typer>=0.12.5
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
### Installation
|
|
16
|
+
|
|
17
|
+
To install the package, run the following command:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install richapi
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Help
|
|
24
|
+
|
|
25
|
+
See [documentation](https://manimozaffar.github.io/richapi/latest/) for more details.
|
|
26
|
+
|
|
27
|
+
### Use Cases
|
|
28
|
+
|
|
29
|
+
RichAPI is designed to simplify FastAPI projects by automatically generating documented HTTP responses from HTTPException without extra effort! 🎉
|
|
30
|
+
|
|
31
|
+
This package is planned to have more features for now that might be essential for any FastAPI project later.
|
|
32
|
+
|
|
33
|
+
What would you see WITHOUT RichAPI:
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
After compiling OpenAPI WITH RichAPI (extended with all possible exceptions coming from your router):
|
|
37
|
+

|
richapi-0.1.0/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
### Installation
|
|
2
|
+
|
|
3
|
+
To install the package, run the following command:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install richapi
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Help
|
|
10
|
+
|
|
11
|
+
See [documentation](https://manimozaffar.github.io/richapi/latest/) for more details.
|
|
12
|
+
|
|
13
|
+
### Use Cases
|
|
14
|
+
|
|
15
|
+
RichAPI is designed to simplify FastAPI projects by automatically generating documented HTTP responses from HTTPException without extra effort! 🎉
|
|
16
|
+
|
|
17
|
+
This package is planned to have more features for now that might be essential for any FastAPI project later.
|
|
18
|
+
|
|
19
|
+
What would you see WITHOUT RichAPI:
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
After compiling OpenAPI WITH RichAPI (extended with all possible exceptions coming from your router):
|
|
23
|
+

|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from richapi.__version__ import __version__
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_command(command: str) -> None:
|
|
11
|
+
try:
|
|
12
|
+
result = subprocess.run(
|
|
13
|
+
command, shell=True, check=True, capture_output=True, text=True
|
|
14
|
+
)
|
|
15
|
+
print(result.stdout)
|
|
16
|
+
except subprocess.CalledProcessError:
|
|
17
|
+
logger.exception("Error executing command")
|
|
18
|
+
sys.exit(1)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
command = "mike set-default latest"
|
|
23
|
+
run_command(command)
|
|
24
|
+
|
|
25
|
+
command = f"mike deploy --push --update-aliases {__version__} latest"
|
|
26
|
+
|
|
27
|
+
run_command(command)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
## Advance
|
|
2
|
+
|
|
3
|
+
This is guideline for how to use the library in more advanced ways.
|
|
4
|
+
|
|
5
|
+
### Customization of the OpenAPI schema
|
|
6
|
+
|
|
7
|
+
Sometimes, your exceptions is more than 'details'.
|
|
8
|
+
In that case, you can very explicitly define the exception JSON schema inside the exception class. You may even use Pydantic to define the schema dynamically so you don't need to write the schema manually.
|
|
9
|
+
|
|
10
|
+
All you need to do, is to return type HTTPExceptionSchema correctly on the method `get_json_schema` of your exception class which you inherited from `BaseHTTPException`.
|
|
11
|
+
|
|
12
|
+
Imagine you are writing an exception that is returning a user balance and a time to retry again a failed bank transaction, you can define the schema like this:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from richapi import BaseHTTPException
|
|
16
|
+
from richapi.exc_parser import HTTPExceptionSchema
|
|
17
|
+
from pydantic import create_model
|
|
18
|
+
|
|
19
|
+
class YourException(BaseHTTPException):
|
|
20
|
+
user_balance: int
|
|
21
|
+
retry_again_in: int
|
|
22
|
+
status_code: int = 400
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def get_json_schema(cls) -> HTTPExceptionSchema:
|
|
26
|
+
schema = create_model(name, user_balance=(int, ...), retry_again_in=(int, ...))
|
|
27
|
+
return HTTPExceptionSchema(
|
|
28
|
+
schema_name="YourException",
|
|
29
|
+
schema_desc="This is a custom exception",
|
|
30
|
+
status_code=cls.status_code,
|
|
31
|
+
response_schema_json=schema.model_json_schema(),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Remember that the responsbility of raising and returning the correct data on FastAPI is not library responsibility, but the responsibility of the developer. The library is only responsible for generating the OpenAPI schema by looking at the functions and classes that are raising exceptions.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
## Conventions
|
|
2
|
+
|
|
3
|
+
This is guideline for how to raise exceptions so that the library can generate the OpenAPI schema from them.
|
|
4
|
+
|
|
5
|
+
Since the library is a mini compiler using Abstract Syntax Tree and functions global to extract exceptions and their responses, there are some conventions that you need to follow to get the best out of it.
|
|
6
|
+
|
|
7
|
+
Keep in mind that if you don't follow these conventions, there would be alot false positive and false negative cases, and the library may not be able to extract the exceptions and their responses correctly.
|
|
8
|
+
|
|
9
|
+
### Inherit from `richapi.BaseHTTPException`
|
|
10
|
+
|
|
11
|
+
Exceptions are better to be very concrete, without having any dynamic data in them.
|
|
12
|
+
So that using `raise Class` is enough to raise them.
|
|
13
|
+
|
|
14
|
+
Exceptions ideally should inherit from `richapi.BaseHTTPException`. Check the ideal case below:
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from richapi import BaseHTTPException
|
|
18
|
+
|
|
19
|
+
class UserNotFound(BaseHTTPException):
|
|
20
|
+
status_code = 404
|
|
21
|
+
details = "User not found"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
And then, only writing `raise UserNotFound` is enough to raise the exception. This is most ideal case.
|
|
25
|
+
|
|
26
|
+
### Avoiding dynamic data in the constructor of the exceptions
|
|
27
|
+
|
|
28
|
+
Exceptions should not have any dynamic data in them.
|
|
29
|
+
For example the code below is NOT ideal:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
|
|
33
|
+
user_id = 1 # imaginary dynamic data
|
|
34
|
+
error_message = f"User {user_id} is not allowed to perform this action"
|
|
35
|
+
raise FooException(detail=error_message)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Since the library is using static analyzers to extract the exceptions, it is not possible to extract the dynamic data from the exceptions. (or rather very hard to do so)
|
|
39
|
+
|
|
40
|
+
### Avoiding usage of magic methods
|
|
41
|
+
|
|
42
|
+
Magic methods are implicit, therefore extracting them from the abstract syntax tree is exteremly hard and equal to rewriting the Python interpreter.
|
|
43
|
+
|
|
44
|
+
One example that you should not do in your code:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
class Foo:
|
|
48
|
+
def __init__(self, bar):
|
|
49
|
+
self.bar = bar
|
|
50
|
+
|
|
51
|
+
def __eq__(self, other):
|
|
52
|
+
raise HTTPException(status_code=400, detail="This is not allowed")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Keep it simple
|
|
56
|
+
|
|
57
|
+
The ideal way to raise exceptions is to keep it simple from syntax point of view;
|
|
58
|
+
|
|
59
|
+
This is very ideal and can be easily parsed and compiled.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
raise HTTPException(status_code=400, detail="This is not allowed")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
But the below example MAY not be parsed correctly:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
exc = lambda: HTTPException
|
|
69
|
+
raise exc()(status_code=400, detail="This is not allowed")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Keep it simple, keep the exception type next to raise keyword; without any dynamic data in the constructor. This help the library to extract the exceptions and their responses and guarantee that they are correctly compiled into the OpenAPI schema.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
### Installation
|
|
2
|
+
|
|
3
|
+
To install the package, run the following command:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install richapi
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Usage
|
|
10
|
+
|
|
11
|
+
**RichAPI is designed to simplify FastAPI projects by automatically generating documented HTTP responses from HTTPException without extra effort! 🎉
|
|
12
|
+
**
|
|
13
|
+
This package is planned to have more features for now that might be essential for any FastAPI project later.
|
|
14
|
+
|
|
15
|
+
There are multiple way you can do that,
|
|
16
|
+
first way is simply to regenerate the OpenAPI schema by running the following command:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from richapi import enrich_openapi
|
|
20
|
+
from fastapi import FastAPI
|
|
21
|
+
|
|
22
|
+
app = FastAPI()
|
|
23
|
+
# add your routers ....
|
|
24
|
+
app.openapi = enrich_openapi(app)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
You can also compile the OpenAPI schema and save it to a file, by running this command from CLI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
richapi compile module.app:app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
And then, you can run the following code to load the compiled OpenAPI schema:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from richapi import load_openapi
|
|
37
|
+
from fastapi import FastAPI
|
|
38
|
+
|
|
39
|
+
app = FastAPI()
|
|
40
|
+
|
|
41
|
+
with open("openapi.json") as open_api_json:
|
|
42
|
+
app.openapi = load_openapi(app, json.loads(open_api_json.read()))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
!!! hint "My recommended way"
|
|
46
|
+
|
|
47
|
+
I personally recommend the second way, because it is better to have it compiled and saved to file, because there are static analysis running on the OpanAPI generator and your source code which isn't very ideal to be running in production.
|
|
48
|
+
|
|
49
|
+
### Example
|
|
50
|
+
|
|
51
|
+
Imagine this application;
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import random
|
|
55
|
+
|
|
56
|
+
from fastapi import FastAPI
|
|
57
|
+
|
|
58
|
+
from richapi.exc_parser.openapi import compile_openapi_from_fastapi, enrich_openapi
|
|
59
|
+
from richapi.exc_parser.protocol import BaseHTTPException
|
|
60
|
+
|
|
61
|
+
app = FastAPI()
|
|
62
|
+
app.openapi = enrich_openapi(app)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class InteralServer(BaseHTTPException):
|
|
66
|
+
status_code = 500
|
|
67
|
+
detail = "Internal server error"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class NotFound(BaseHTTPException):
|
|
71
|
+
status_code = 501
|
|
72
|
+
detail = "Not found"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class GatewayError(BaseHTTPException):
|
|
76
|
+
status_code = 502
|
|
77
|
+
detail = "Gateway error"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def very_nested():
|
|
81
|
+
value = InteralServer
|
|
82
|
+
raise value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def nested():
|
|
86
|
+
try:
|
|
87
|
+
very_nested()
|
|
88
|
+
except InteralServer:
|
|
89
|
+
raise NotFound
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.get("/home")
|
|
93
|
+
def read_root() -> dict[str, str]:
|
|
94
|
+
if random.choice([True, False]):
|
|
95
|
+
nested()
|
|
96
|
+
|
|
97
|
+
elif random.choice([True, False]):
|
|
98
|
+
raise GatewayError()
|
|
99
|
+
|
|
100
|
+
return {"Hello": "World"}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
What would you see WITHOUT RichAPI:
|
|
104
|
+

|
|
105
|
+
|
|
106
|
+
After compiling OpenAPI WITH RichAPI (extended with all possible exceptions coming from your router):
|
|
107
|
+

|
|
108
|
+
|
|
109
|
+
### Conventions (VERY IMPORTANT)
|
|
110
|
+
|
|
111
|
+
There are some conventions about how to raise exceptions and etc. You may read the [conventions](conventions.md) to know more about them. The way library works is that it is a mini compiler using Abstract Syntax Tree and functions global to extract exceptions and their responses, and then it generates the OpenAPI schema from them. So there are limitations to what it can do, and you may need to follow the conventions to get the best out of it. Anything that is not following the conventions MAY be ignored by the compiler, and there is no plan to support them in the future, because it is not possible to support all the ways you can raise exceptions in Python... To give you an example, you may download a source code from internet and then use 'eval' function to run it dynamically, and there is no way to know what exceptions you may raise in that code, so it is impossible to support all the ways you can raise exceptions in Python.
|
richapi-0.1.0/makefile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.PHONY: install
|
|
2
|
+
install: ## Install the uv environment
|
|
3
|
+
@echo "🚀 Creating virtual environment using uv"
|
|
4
|
+
uv sync
|
|
5
|
+
|
|
6
|
+
.PHONY: check
|
|
7
|
+
check: ## Run the quality checks on the code
|
|
8
|
+
@echo "🚀 Running quality checks"
|
|
9
|
+
uv run ruff check .
|
|
10
|
+
uv run pyright .
|
|
11
|
+
|
|
12
|
+
.PHONY: test
|
|
13
|
+
test: ## Test the code with pytest
|
|
14
|
+
@echo "🚀 Testing code: Running pytest"
|
|
15
|
+
uv run pytest
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
.PHONY: docs
|
|
19
|
+
docs: ## Build and serve the documentation
|
|
20
|
+
@echo "🚀 Testing documentation: Building and testing"
|
|
21
|
+
uv run mkdocs serve
|
|
22
|
+
|
|
23
|
+
.PHONY: deploy-docs
|
|
24
|
+
deploy-docs: ## Build and serve the documentation
|
|
25
|
+
@echo "🚀 Deploying documentation"
|
|
26
|
+
uv run python deploy_docs.py
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
.PHONY: docs-test
|
|
30
|
+
docs-test: ## Test if documentation can be built without warnings or errors
|
|
31
|
+
@uv run mkdocs build -s
|
|
32
|
+
|
|
33
|
+
.PHONY: help
|
|
34
|
+
help:
|
|
35
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
|
36
|
+
|
|
37
|
+
.DEFAULT_GOAL := help
|