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 ADDED
@@ -0,0 +1,4 @@
1
+ VIRTUAL_ENV="$(pwd)/.venv"
2
+ PATH=$VIRTUAL_ENV/bin:$PATH
3
+ export VIRTUAL_ENV
4
+ export PATH
@@ -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 }}
@@ -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
+ ![Screenshot](docs/images/without-richapi.png)
35
+
36
+ After compiling OpenAPI WITH RichAPI (extended with all possible exceptions coming from your router):
37
+ ![Screenshot](docs/images/with-richapi.png)
@@ -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
+ ![Screenshot](docs/images/without-richapi.png)
21
+
22
+ After compiling OpenAPI WITH RichAPI (extended with all possible exceptions coming from your router):
23
+ ![Screenshot](docs/images/with-richapi.png)
@@ -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.
@@ -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
+ ![Screenshot](./images/without-richapi.png)
105
+
106
+ After compiling OpenAPI WITH RichAPI (extended with all possible exceptions coming from your router):
107
+ ![Screenshot](./images/with-richapi.png)
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