https://github.com/alixlahuec/fastapi-checks
Utility package to run checks against FastAPI applications
https://github.com/alixlahuec/fastapi-checks
cli fastapi pytest-plugin python
Last synced: 2 months ago
JSON representation
Utility package to run checks against FastAPI applications
- Host: GitHub
- URL: https://github.com/alixlahuec/fastapi-checks
- Owner: alixlahuec
- License: mit
- Created: 2023-12-26T05:16:15.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-01-28T01:20:54.000Z (over 2 years ago)
- Last Synced: 2024-05-13T00:46:37.364Z (about 2 years ago)
- Topics: cli, fastapi, pytest-plugin, python
- Language: Python
- Homepage:
- Size: 69.3 KB
- Stars: 1
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
A small library that provides tooling to inspect and run tests against [FastAPI](https://github.com/tiangolo/fastapi) applications.
[](https://codecov.io/github/alixlahuec/fastapi-checks)
## Installation
```bash
poetry add fastapi-checks # with poetry
pip install fastapi-checks # with pip
```
### For CLI usage
```bash
poetry add fastapi-checks --extras "cli" # with poetry
pip install fastapi-checks[cli] # with pip
```
## Usage
### CLI
#### General options
| Parameter | Description |
| --------- | ----------- |
| `--app` | (Required) The location of the FastAPI instance to use. Must be provided before any commands. |
| `--config` | (Optional) The location of the config file to use. Must be provided before any commands |
```bash
fastapi-checks --app "app_example.main:app" routes
fastapi-checks --config "myconfigfile.json" debug
```
#### Using a config file
The library uses [`pycosmiconfig`](https://github.com/JuroOravec/pycosmiconfig/) to automatically detect and load configuration files.
Among other formats, it supports the following:
- a `[tool.fastapi_checks]` property in pyproject.toml
- a `fastapi_checks.json`, `fastapi_checks.yaml`, `fastapi_checks.toml` file inside a `.config` subdirectory
- a `fastapi_checks.config.py` file
See the project's homepage for the full list of supported formats.
#### Available commands
**`routes`**
View a summary of API routes. Available options:
- `--html`: render the results as an interactive table in the browser (uses [Dash](https://dash.plotly.com/)).
- `--match`: only show routes whose path matches a specific pattern.
+ This will be parsed as a regular expression (e.g., `--match "0.1"` will return all routes whose path includes `"0.1"`; `--match "^/0.1"` will return all routes whose path starts with `"/0.1"`).
```bash
fastapi-checks routes
fastapi-checks routes --html
fastapi-checks routes --match "0.1"
```
**`debug`**
Show debug information (e.g. config file, FastAPI application details).
```bash
fastapi-checks debug
```
### Test runners
The library can also be used to write tests asserting against a FastAPI application.
An example (using Pytest):
```python
from fastapi_checks.testing import FastApiChecks
from pytest import mark
from app_example.main import app # the FastAPI app to assert against
# This is an alias for the App parser
FAC = FastApiChecks(app=app)
# Are all routes under /0.1 marked as deprecated?
@mark.parametrize(
"api_route",
FAC.api_routes(path_regex="^/0.1"),
)
def test_01_routes_are_deprecated(api_route):
assert api_route.route.deprecated is True
# Are all generated operation IDs unique?
def test_unique_operation_ids():
ids = [r.operation_id for r in FAC.api_routes()]
ids_set = set(ids)
assert len(ids_set) == len(ids)
```
For Pytest users, the library also exposes a `fastapi_checks` fixture. It requires declaring a fixture called `fastapi_app`, which should return a FastAPI instance.
The `fastapi_checks` fixture will return the equivalent of `FastApiChecks(app=fastapi_app)` from the example above.
```python
from pytest import fixture
@fixture
def fastapi_app():
from app_example.main import app # the FastAPI app to assert against
return app
def test_01_routes_are_deprecated(fastapi_checks):
routes = fastapi_checks.api_routes(path_regex="0.1")
for r in routes:
assert len(r.security) > 0
```
## Concepts
The library's core functionalities are exposed through a few Pydantic models, which can be used to interact with FastAPI concepts (e.g. dependencies, routes). Each model exposes some useful methods, as well as the original object where applicable.
### `App`
Wraps `fastapi.FastAPI`. The original FastAPI instance is available under the `app` property.
```python
from fastapi_checks.models import App
from app_example.main import app
app_instance = App(app=app)
# Get the original FastAPI object
# A lot of data is directly available, through methods like .openapi()
# See the FastAPI documentation for more information
app_instance.app
# Get all API routes
# They are returned as ApiRoute instances
app_instance.api_routes()
# Get all API routes that match a RegExp
app_instance.api_routes(path_regex="^/0.1")
# Get all dependencies (recursively), by route path
# They are returned as Dependency instances
app_instance.dependencies()
# Get all dependencies (recursively), for API routes that match a RegExp
app_instance.dependencies(path_regex="^/0.1")
```
### `ApiRoute`
Wraps `fastapi.routing.APIRoute`. The original APIRoute instance is available under the `route` property.
```python
from fastapi_checks.models import App
from app_example.main import app
app_instance = App(app=app)
api_route = app_instance.api_routes()[0]
# Get the original APIRoute object
api_route.route
# Get the generated OpenAPI spec for the route
api_route.openapi
# Get the route's operation ID
api_route.operation_id
# Get all dependencies (recursively)
api_route.dependencies
# Get all unique dependencies (recursively)
# This filters out dependencies on the same callable
api_route.unique_dependencies
# Get the names of all (unique) dependencies (recursively)
# Each value will be either a method name or a class name
api_route.dependency_names
# Get all Mark instances attached to the route's (unique) dependencies
api_route.dependency_marks
# Get the security requirements for the route
# At the moment, only the HTTPBearer scheme is supported
api_route.security()
api_route.security(scheme="HTTPBearer")
# Check if the route's patch matches a RegExp
# This is used internally in App.api_routes() or App.dependencies()
api_route.predicate(path_regex="^/0.1")
```
### `Dependency`
Wraps `fastapi.Depends`. The original callable is available under the `callable` property.
```python
from fastapi_checks.models import App
from app_example.main import app
app_instance = App(app=app)
api_route = app_instance.api_routes()[0]
dependency = api_route.dependencies()[0]
# Get the callable
dependency.callable
# Get the name of the callable
# This is used internally in ApiRoute.dependency_names()
dependency.humanized
# Get the name of the argument where the dependency is injected, if applicable
# For app-, router-, and route-level dependencies, this will be null
dependency.argname
# Get the route associated with the dependency
# assert dependency.route == api_route
dependency.route
# Get the context in which the dependency is called (directly or through nested dependencies)
# This will be either:
# - "path", for app-, router-, and route-level dependencies
# - "handler", for route handler dependencies
dependency.origin
# Get the Mark instance attached to the dependency, if it exists
# This is used internally in ApiRoute.dependency_marks()
dependency.mark
# Check if the dependency has / doesn't have certain tags
# NB: the tags must have been provided using the `mark` decorator
dependency.tags_include_all(tags=["tag1", "tag2"])
dependency.tags_exclude_all(tags=["tag3", "tag4"])
dependency.tags_include(tags=["tag1", "tag3"])
```
### `Mark`
This model facilitates working with dependencies; it doesn't map to a FastAPI concept. It enables adding markup to callables being used as FastAPI dependencies, to help answer questions like "How many endpoints rely on a dependency with [tag]?". `Mark` annotations should be created using the `mark` decorator, and are available on `Dependency` objects.
**Decorator usage:**
```python
# app_example/services/auth.py
from fastapi_checks.decorators import mark
@mark("auth")
def some_auth_dependency():
pass
@mark("auth")
class AnotherAuthDependency:
def __init__(*args, **kwargs):
...
def __call__(*args, **kwargs):
...
```
**Assertions on Mark instances:**
```python
# tests/app.py
from fastapi_checks.testing import FastApiChecks
from app_example.main import app
FAC = FastApiChecks(app=app)
def test_all_routes_use_auth():
api_routes = FAC.api_routes()
for r in api_routes:
assert r.dependency_marks.includes("auth"), f"{r.route.path} uses an auth dependency"
```
**Model properties:**
```python
from fastapi_checks.models import App
from app_example.main import app
app_instance = App(app=app)
api_route = app_instance.api_routes()[0]
dependency = api_route.dependencies()[0]
mark = dependency.mark
# Get the "marked" callable
mark.marked
# Get the tags attached to the callable
mark.tags
```
## Development
- Clone the repository
- Install dependencies with `poetry install`
- The following commands are available:
+ `make fixes`: lint files with Ruff
+ `make checks`: check files with Ruff
+ `make typecheck`: typecheck files with mypy
+ `make test`: run tests with Pytest
+ `make test:cov`: run tests with Pytest & report on coverage
For rapid development, you can run CLI commands against the sample FastAPI application located in `app_example`. In the root directory:
```bash
poetry run fastapi-checks routes
```
## License
This project is licensed under the terms of the MIT license.