fastapi-crudrouter-v2 0.1.3__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Adam Watkins
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-crudrouter-v2
3
+ Version: 0.1.3
4
+ Summary: A dynamic FastAPI router that automatically creates CRUD routes for your models
5
+ License-File: LICENSE
6
+ Author: pasha_danilevich
7
+ Author-email: danilevitch.pasha@yandex.ru
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Description-Content-Type: text/markdown
17
+
18
+ <p align="center">
19
+ <img src="https://raw.githubusercontent.com/awtkns/fastapi-crudrouter/master/docs/en/docs/assets/logo.png" height="200" />
20
+ </p>
21
+ <p align="center">
22
+ <em>⚡ Create CRUD routes with lighting speed</em> ⚡</br>
23
+ <sub>A dynamic FastAPI router that automatically creates CRUD routes for your models</sub>
24
+ </p>
25
+ <p align="center">
26
+ <img alt="Tests" src="https://img.shields.io/github/actions/workflow/status/awtkns/fastapi-crudrouter/.github/workflows/pytest.yml?color=%2334D058" />
27
+ <img alt="Downloads" src="https://img.shields.io/pypi/dm/fastapi-crudrouter?color=%2334D058" />
28
+ <a href="https://pypi.org/project/fastapi-crudrouter" target="_blank">
29
+ <img src="https://img.shields.io/pypi/v/fastapi-crudrouter?color=%2334D058&label=pypi%20package" alt="Package version">
30
+ </a>
31
+ <img alt="License" src="https://img.shields.io/github/license/awtkns/fastapi-crudrouter?color=%2334D058" />
32
+ </p>
33
+ <p align="center">
34
+ <img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/fastapi-crudrouter">
35
+ </p>
36
+
37
+ ---
38
+
39
+ **Documentation**: <a href="https://fastapi-crudrouter.awtkns.com" target="_blank">https://fastapi-crudrouter.awtkns.com</a>
40
+
41
+ **Source Code**: <a href="https://github.com/awtkns/fastapi-crudrouter" target="_blank">https://github.com/awtkns/fastapi-crudrouter</a>
42
+
43
+ ---
44
+ Tired of rewriting generic CRUD routes? Need to rapidly prototype a feature for a presentation
45
+ or a hackathon? Thankfully, [fastapi-crudrouter](https://github.com/awtkns/fastapi-crudrouter) has your back. As an
46
+ extension to the APIRouter included with [FastAPI](https://fastapi.tiangolo.com/), the FastAPI CRUDRouter will automatically
47
+ generate and document your CRUD routes for you, all you have to do is pass your model and maybe your database connection.
48
+
49
+ FastAPI-CRUDRouter is **lighting fast**, well tested, and **production ready**.
50
+
51
+
52
+ ## Installation
53
+ ```bash
54
+ pip install fastapi-crudrouter
55
+ ```
56
+
57
+ ## Basic Usage
58
+ Below is a simple example of what the CRUDRouter can do. In just ten lines of code, you can generate all
59
+ the crud routes you need for any model. A full list of the routes generated can be found [here](https://fastapi-crudrouter.awtkns.com/routing).
60
+
61
+ ```python
62
+ from pydantic import BaseModel
63
+ from fastapi import FastAPI
64
+ from fastapi_crudrouter import MemoryCRUDRouter as CRUDRouter
65
+
66
+ class Potato(BaseModel):
67
+ id: int
68
+ color: str
69
+ mass: float
70
+
71
+ app = FastAPI()
72
+ app.include_router(CRUDRouter(schema=Potato))
73
+ ```
74
+
75
+ ## Advanced Usage
76
+ fastapi-crudrouter provides a number of features that allow you to get the most out of your automatically generated CRUD
77
+ routes. Listed below are some highlights.
78
+
79
+ - Automatic Pagination ([docs](https://fastapi-crudrouter.awtkns.com/pagination/))
80
+ - Ability to Provide Custom Create and Update Schemas ([docs](https://fastapi-crudrouter.awtkns.com/schemas/))
81
+ - Dynamic Generation of Create and Update Schemas ([docs](https://fastapi-crudrouter.awtkns.com/schemas/))
82
+ - Ability to Add, Customize, or Disable Specific Routes ([docs](https://fastapi-crudrouter.awtkns.com/routing/))
83
+ - Native Support for FastAPI Dependency Injection ([docs](https://fastapi-crudrouter.awtkns.com/dependencies/))
84
+
85
+ ## Supported Backends / ORMs
86
+ fastapi-crudrouter currently supports a number of backends / ORMs. Listed below are the backends currently supported. This list will
87
+ likely grow in future releases.
88
+
89
+ - In Memory ([docs](https://fastapi-crudrouter.awtkns.com/backends/memory/))
90
+ - SQLAlchemy ([docs](https://fastapi-crudrouter.awtkns.com/backends/sqlalchemy/))
91
+ - Databases (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/async/))
92
+ - Gino (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/gino.html))
93
+ - Ormar (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/ormar/))
94
+ - Tortoise ORM (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/tortoise/))
95
+
96
+ ## OpenAPI Support
97
+ By default, all routes generated by the CRUDRouter will be documented according to OpenAPI spec.
98
+
99
+ Below are the default routes created by the CRUDRouter shown in the generated OpenAPI documentation.
100
+
101
+ ![OpenAPI Route Overview](https://raw.githubusercontent.com/awtkns/fastapi-crudrouter/master/docs/en/docs/assets/RouteOverview.png)
102
+
103
+ The CRUDRouter is able to dynamically generate detailed documentation based on the models given to it.
104
+
105
+ ![OpenAPI Route Detail](https://raw.githubusercontent.com/awtkns/fastapi-crudrouter/master/docs/en/docs/assets/RouteDetail.png)
106
+
@@ -0,0 +1,88 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/awtkns/fastapi-crudrouter/master/docs/en/docs/assets/logo.png" height="200" />
3
+ </p>
4
+ <p align="center">
5
+ <em>⚡ Create CRUD routes with lighting speed</em> ⚡</br>
6
+ <sub>A dynamic FastAPI router that automatically creates CRUD routes for your models</sub>
7
+ </p>
8
+ <p align="center">
9
+ <img alt="Tests" src="https://img.shields.io/github/actions/workflow/status/awtkns/fastapi-crudrouter/.github/workflows/pytest.yml?color=%2334D058" />
10
+ <img alt="Downloads" src="https://img.shields.io/pypi/dm/fastapi-crudrouter?color=%2334D058" />
11
+ <a href="https://pypi.org/project/fastapi-crudrouter" target="_blank">
12
+ <img src="https://img.shields.io/pypi/v/fastapi-crudrouter?color=%2334D058&label=pypi%20package" alt="Package version">
13
+ </a>
14
+ <img alt="License" src="https://img.shields.io/github/license/awtkns/fastapi-crudrouter?color=%2334D058" />
15
+ </p>
16
+ <p align="center">
17
+ <img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/fastapi-crudrouter">
18
+ </p>
19
+
20
+ ---
21
+
22
+ **Documentation**: <a href="https://fastapi-crudrouter.awtkns.com" target="_blank">https://fastapi-crudrouter.awtkns.com</a>
23
+
24
+ **Source Code**: <a href="https://github.com/awtkns/fastapi-crudrouter" target="_blank">https://github.com/awtkns/fastapi-crudrouter</a>
25
+
26
+ ---
27
+ Tired of rewriting generic CRUD routes? Need to rapidly prototype a feature for a presentation
28
+ or a hackathon? Thankfully, [fastapi-crudrouter](https://github.com/awtkns/fastapi-crudrouter) has your back. As an
29
+ extension to the APIRouter included with [FastAPI](https://fastapi.tiangolo.com/), the FastAPI CRUDRouter will automatically
30
+ generate and document your CRUD routes for you, all you have to do is pass your model and maybe your database connection.
31
+
32
+ FastAPI-CRUDRouter is **lighting fast**, well tested, and **production ready**.
33
+
34
+
35
+ ## Installation
36
+ ```bash
37
+ pip install fastapi-crudrouter
38
+ ```
39
+
40
+ ## Basic Usage
41
+ Below is a simple example of what the CRUDRouter can do. In just ten lines of code, you can generate all
42
+ the crud routes you need for any model. A full list of the routes generated can be found [here](https://fastapi-crudrouter.awtkns.com/routing).
43
+
44
+ ```python
45
+ from pydantic import BaseModel
46
+ from fastapi import FastAPI
47
+ from fastapi_crudrouter import MemoryCRUDRouter as CRUDRouter
48
+
49
+ class Potato(BaseModel):
50
+ id: int
51
+ color: str
52
+ mass: float
53
+
54
+ app = FastAPI()
55
+ app.include_router(CRUDRouter(schema=Potato))
56
+ ```
57
+
58
+ ## Advanced Usage
59
+ fastapi-crudrouter provides a number of features that allow you to get the most out of your automatically generated CRUD
60
+ routes. Listed below are some highlights.
61
+
62
+ - Automatic Pagination ([docs](https://fastapi-crudrouter.awtkns.com/pagination/))
63
+ - Ability to Provide Custom Create and Update Schemas ([docs](https://fastapi-crudrouter.awtkns.com/schemas/))
64
+ - Dynamic Generation of Create and Update Schemas ([docs](https://fastapi-crudrouter.awtkns.com/schemas/))
65
+ - Ability to Add, Customize, or Disable Specific Routes ([docs](https://fastapi-crudrouter.awtkns.com/routing/))
66
+ - Native Support for FastAPI Dependency Injection ([docs](https://fastapi-crudrouter.awtkns.com/dependencies/))
67
+
68
+ ## Supported Backends / ORMs
69
+ fastapi-crudrouter currently supports a number of backends / ORMs. Listed below are the backends currently supported. This list will
70
+ likely grow in future releases.
71
+
72
+ - In Memory ([docs](https://fastapi-crudrouter.awtkns.com/backends/memory/))
73
+ - SQLAlchemy ([docs](https://fastapi-crudrouter.awtkns.com/backends/sqlalchemy/))
74
+ - Databases (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/async/))
75
+ - Gino (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/gino.html))
76
+ - Ormar (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/ormar/))
77
+ - Tortoise ORM (async) ([docs](https://fastapi-crudrouter.awtkns.com/backends/tortoise/))
78
+
79
+ ## OpenAPI Support
80
+ By default, all routes generated by the CRUDRouter will be documented according to OpenAPI spec.
81
+
82
+ Below are the default routes created by the CRUDRouter shown in the generated OpenAPI documentation.
83
+
84
+ ![OpenAPI Route Overview](https://raw.githubusercontent.com/awtkns/fastapi-crudrouter/master/docs/en/docs/assets/RouteOverview.png)
85
+
86
+ The CRUDRouter is able to dynamically generate detailed documentation based on the models given to it.
87
+
88
+ ![OpenAPI Route Detail](https://raw.githubusercontent.com/awtkns/fastapi-crudrouter/master/docs/en/docs/assets/RouteDetail.png)
@@ -0,0 +1,9 @@
1
+ from .core import (
2
+ TortoiseCRUDRouter,
3
+ )
4
+
5
+ from ._version import __version__ # noqa: F401
6
+
7
+ __all__ = [
8
+ "TortoiseCRUDRouter",
9
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from ._base import NOT_FOUND, CRUDGenerator
2
+ from ._router import TortoiseCRUDRouter
3
+
4
+ __all__ = ["TortoiseCRUDRouter", "CRUDGenerator", "NOT_FOUND"]
@@ -0,0 +1,212 @@
1
+ # mypy: ignore-errors
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any, Callable, Generic, List, Optional, Type, Union
4
+
5
+ from fastapi import APIRouter, HTTPException
6
+ from fastapi.types import DecoratedCallable
7
+
8
+ from ._types import DEPENDENCIES, T
9
+ from ._utils import pagination_factory, schema_factory
10
+
11
+ NOT_FOUND = HTTPException(404, "Item not found")
12
+
13
+
14
+ class CRUDGenerator(Generic[T], APIRouter, ABC):
15
+ schema: Type[T]
16
+ create_schema: Type[T]
17
+ update_schema: Type[T]
18
+ _base_path: str = "/"
19
+
20
+ def __init__(
21
+ self,
22
+ schema: Type[T],
23
+ create_schema: Optional[Type[T]] = None,
24
+ update_schema: Optional[Type[T]] = None,
25
+ prefix: Optional[str] = None,
26
+ tags: Optional[List[str]] = None,
27
+ paginate: Optional[int] = None,
28
+ get_all_route: Union[bool, DEPENDENCIES] = True,
29
+ get_one_route: Union[bool, DEPENDENCIES] = True,
30
+ create_route: Union[bool, DEPENDENCIES] = True,
31
+ update_route: Union[bool, DEPENDENCIES] = True,
32
+ delete_one_route: Union[bool, DEPENDENCIES] = True,
33
+ delete_all_route: Union[bool, DEPENDENCIES] = True,
34
+ trailing_slash: bool = True,
35
+ **kwargs: Any,
36
+ ) -> None:
37
+ self.schema = schema
38
+ self.pagination = pagination_factory(max_limit=paginate)
39
+ self._pk: str = self._pk if hasattr(self, "_pk") else "id"
40
+ self.create_schema = (
41
+ create_schema
42
+ if create_schema
43
+ else schema_factory(self.schema, pk_field_name=self._pk, name="Create")
44
+ )
45
+ self.update_schema = (
46
+ update_schema
47
+ if update_schema
48
+ else schema_factory(self.schema, pk_field_name=self._pk, name="Update")
49
+ )
50
+
51
+ prefix = str(prefix if prefix else self.schema.__name__).lower()
52
+ prefix = self._base_path + prefix.strip("/")
53
+ tags = tags or [prefix.strip("/").capitalize()]
54
+ path = "/" if trailing_slash else ""
55
+ path_param = "/{item_id}" + path
56
+
57
+ super().__init__(prefix=prefix, tags=tags, **kwargs)
58
+
59
+ if get_all_route:
60
+ self._add_api_route(
61
+ path,
62
+ self._get_all(),
63
+ methods=["GET"],
64
+ response_model=Optional[List[self.schema]], # type: ignore
65
+ summary="Get All",
66
+ dependencies=get_all_route,
67
+ )
68
+
69
+ if create_route:
70
+ self._add_api_route(
71
+ path,
72
+ self._create(),
73
+ methods=["POST"],
74
+ response_model=self.schema,
75
+ summary="Create One",
76
+ dependencies=create_route,
77
+ )
78
+
79
+ if delete_all_route:
80
+ self._add_api_route(
81
+ path,
82
+ self._delete_all(),
83
+ methods=["DELETE"],
84
+ response_model=Optional[List[self.schema]], # type: ignore
85
+ summary="Delete All",
86
+ dependencies=delete_all_route,
87
+ )
88
+
89
+ if get_one_route:
90
+ self._add_api_route(
91
+ path_param,
92
+ self._get_one(),
93
+ methods=["GET"],
94
+ response_model=self.schema,
95
+ summary="Get One",
96
+ dependencies=get_one_route,
97
+ error_responses=[NOT_FOUND],
98
+ )
99
+
100
+ if update_route:
101
+ self._add_api_route(
102
+ path_param,
103
+ self._update(),
104
+ methods=["PUT"],
105
+ response_model=self.schema,
106
+ summary="Update One",
107
+ dependencies=update_route,
108
+ error_responses=[NOT_FOUND],
109
+ )
110
+
111
+ if delete_one_route:
112
+ self._add_api_route(
113
+ path_param,
114
+ self._delete_one(),
115
+ methods=["DELETE"],
116
+ response_model=self.schema,
117
+ summary="Delete One",
118
+ dependencies=delete_one_route,
119
+ error_responses=[NOT_FOUND],
120
+ )
121
+
122
+ def _add_api_route(
123
+ self,
124
+ path: str,
125
+ endpoint: Callable[..., Any],
126
+ dependencies: Union[bool, DEPENDENCIES],
127
+ error_responses: Optional[List[HTTPException]] = None,
128
+ **kwargs: Any,
129
+ ) -> None:
130
+ dependencies = [] if isinstance(dependencies, bool) else dependencies
131
+ responses: Any = (
132
+ {err.status_code: {"detail": err.detail} for err in error_responses}
133
+ if error_responses
134
+ else None
135
+ )
136
+
137
+ super().add_api_route(
138
+ path, endpoint, dependencies=dependencies, responses=responses, **kwargs
139
+ )
140
+
141
+ def api_route(
142
+ self, path: str, *args: Any, **kwargs: Any
143
+ ) -> Callable[[DecoratedCallable], DecoratedCallable]:
144
+ """Overrides and exiting route if it exists"""
145
+ methods = kwargs["methods"] if "methods" in kwargs else ["GET"]
146
+ self.remove_api_route(path, methods)
147
+ return super().api_route(path, *args, **kwargs)
148
+
149
+ def get(
150
+ self, path: str, *args: Any, **kwargs: Any
151
+ ) -> Callable[[DecoratedCallable], DecoratedCallable]:
152
+ self.remove_api_route(path, ["Get"])
153
+ return super().get(path, *args, **kwargs)
154
+
155
+ def post(
156
+ self, path: str, *args: Any, **kwargs: Any
157
+ ) -> Callable[[DecoratedCallable], DecoratedCallable]:
158
+ self.remove_api_route(path, ["POST"])
159
+ return super().post(path, *args, **kwargs)
160
+
161
+ def put(
162
+ self, path: str, *args: Any, **kwargs: Any
163
+ ) -> Callable[[DecoratedCallable], DecoratedCallable]:
164
+ self.remove_api_route(path, ["PUT"])
165
+ return super().put(path, *args, **kwargs)
166
+
167
+ def delete(
168
+ self, path: str, *args: Any, **kwargs: Any
169
+ ) -> Callable[[DecoratedCallable], DecoratedCallable]:
170
+ self.remove_api_route(path, ["DELETE"])
171
+ return super().delete(path, *args, **kwargs)
172
+
173
+ def remove_api_route(self, path: str, methods: List[str]) -> None:
174
+ methods_ = set(methods)
175
+
176
+ for route in self.routes:
177
+ if (
178
+ route.path == f"{self.prefix}{path}" # type: ignore
179
+ and route.methods == methods_ # type: ignore
180
+ ):
181
+ self.routes.remove(route)
182
+
183
+ @abstractmethod
184
+ def _get_all(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
185
+ raise NotImplementedError
186
+
187
+ @abstractmethod
188
+ def _get_one(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
189
+ raise NotImplementedError
190
+
191
+ @abstractmethod
192
+ def _create(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
193
+ raise NotImplementedError
194
+
195
+ @abstractmethod
196
+ def _update(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
197
+ raise NotImplementedError
198
+
199
+ @abstractmethod
200
+ def _delete_one(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
201
+ raise NotImplementedError
202
+
203
+ @abstractmethod
204
+ def _delete_all(self, *args: Any, **kwargs: Any) -> Callable[..., Any]:
205
+ raise NotImplementedError
206
+
207
+ def _raise(self, e: Exception, status_code: int = 422) -> HTTPException:
208
+ raise HTTPException(422, ", ".join(e.args)) from e
209
+
210
+ @staticmethod
211
+ def get_routes() -> List[str]:
212
+ return ["get_all", "create", "delete_all", "get_one", "update", "delete_one"]
@@ -0,0 +1,114 @@
1
+ # mypy: ignore-errors
2
+
3
+ from typing import Any, Callable, Coroutine, List, Optional, Type, Union, cast
4
+
5
+ from tortoise.models import Model
6
+
7
+ from . import NOT_FOUND, CRUDGenerator
8
+ from ._types import DEPENDENCIES, PAGINATION
9
+ from ._types import PYDANTIC_SCHEMA as SCHEMA
10
+
11
+ tortoise_installed = True
12
+
13
+
14
+ CALLABLE = Callable[..., Coroutine[Any, Any, Model]]
15
+ CALLABLE_LIST = Callable[..., Coroutine[Any, Any, List[Model]]]
16
+
17
+
18
+ class TortoiseCRUDRouter(CRUDGenerator[SCHEMA]):
19
+ def __init__(
20
+ self,
21
+ schema: Type[SCHEMA],
22
+ db_model: Type[Model],
23
+ create_schema: Optional[Type[SCHEMA]] = None,
24
+ update_schema: Optional[Type[SCHEMA]] = None,
25
+ prefix: Optional[str] = None,
26
+ tags: Optional[List[str]] = None,
27
+ paginate: Optional[int] = None,
28
+ get_all_route: Union[bool, DEPENDENCIES] = True,
29
+ get_one_route: Union[bool, DEPENDENCIES] = True,
30
+ create_route: Union[bool, DEPENDENCIES] = True,
31
+ update_route: Union[bool, DEPENDENCIES] = True,
32
+ delete_one_route: Union[bool, DEPENDENCIES] = True,
33
+ delete_all_route: Union[bool, DEPENDENCIES] = True,
34
+ **kwargs: Any
35
+ ) -> None:
36
+ assert (
37
+ tortoise_installed
38
+ ), "Tortoise ORM must be installed to use the TortoiseCRUDRouter."
39
+
40
+ self.db_model = db_model
41
+ self._pk: str = db_model.describe()["pk_field"]["db_column"]
42
+
43
+ super().__init__(
44
+ schema=schema,
45
+ create_schema=create_schema,
46
+ update_schema=update_schema,
47
+ prefix=prefix or db_model.describe()["name"].replace("None.", ""),
48
+ tags=tags,
49
+ paginate=paginate,
50
+ get_all_route=get_all_route,
51
+ get_one_route=get_one_route,
52
+ create_route=create_route,
53
+ update_route=update_route,
54
+ delete_one_route=delete_one_route,
55
+ delete_all_route=delete_all_route,
56
+ **kwargs
57
+ )
58
+
59
+ def _get_all(self, *args: Any, **kwargs: Any) -> CALLABLE_LIST:
60
+ async def get_all(pagination: PAGINATION = self.pagination) -> List[Model]:
61
+ skip, limit = pagination.get("skip"), pagination.get("limit")
62
+ query = self.db_model.all().offset(cast(int, skip))
63
+ if limit:
64
+ query = query.limit(limit)
65
+ return await query
66
+
67
+ return get_all
68
+
69
+ def _get_one(self, *args: Any, **kwargs: Any) -> CALLABLE:
70
+ async def get_one(item_id: int) -> Model:
71
+ model = await self.db_model.filter(id=item_id).first()
72
+
73
+ if model:
74
+ return model
75
+ else:
76
+ raise NOT_FOUND
77
+
78
+ return get_one
79
+
80
+ def _create(self, *args: Any, **kwargs: Any) -> CALLABLE:
81
+ async def create(model: self.create_schema) -> Model: # type: ignore
82
+ db_model = self.db_model(**model.dict())
83
+ await db_model.save()
84
+
85
+ return db_model
86
+
87
+ return create
88
+
89
+ def _update(self, *args: Any, **kwargs: Any) -> CALLABLE:
90
+ async def update(
91
+ item_id: int, model: self.update_schema # type: ignore
92
+ ) -> Model:
93
+ await self.db_model.filter(id=item_id).update(
94
+ **model.dict(exclude_unset=True)
95
+ )
96
+ return await self._get_one()(item_id)
97
+
98
+ return update
99
+
100
+ def _delete_all(self, *args: Any, **kwargs: Any) -> CALLABLE_LIST:
101
+ async def delete_all() -> List[Model]:
102
+ await self.db_model.all().delete()
103
+ return await self._get_all()(pagination={"skip": 0, "limit": None})
104
+
105
+ return delete_all
106
+
107
+ def _delete_one(self, *args: Any, **kwargs: Any) -> CALLABLE:
108
+ async def delete_one(item_id: int) -> Model:
109
+ model: Model = await self._get_one()(item_id)
110
+ await self.db_model.filter(id=item_id).delete()
111
+
112
+ return model
113
+
114
+ return delete_one
@@ -0,0 +1,12 @@
1
+ # mypy: ignore-errors
2
+
3
+ from typing import Dict, Optional, Sequence, TypeVar
4
+
5
+ from fastapi.params import Depends
6
+ from pydantic import BaseModel
7
+
8
+ PAGINATION = Dict[str, Optional[int]]
9
+ PYDANTIC_SCHEMA = BaseModel
10
+
11
+ T = TypeVar("T", bound=BaseModel)
12
+ DEPENDENCIES = Optional[Sequence[Depends]]
@@ -0,0 +1,74 @@
1
+ # mypy: ignore-errors
2
+
3
+ from typing import Any, Optional, Type
4
+
5
+ from fastapi import Depends, HTTPException
6
+ from pydantic import create_model
7
+
8
+ from ._types import PAGINATION, T
9
+
10
+
11
+ def schema_factory(
12
+ schema_cls: Type[T], pk_field_name: str = "id", name: str = "Create"
13
+ ) -> Type[T]:
14
+ """
15
+ Is used to create a CreateSchema which does not contain pk
16
+ """
17
+
18
+ fields = {}
19
+
20
+ for field_name, field_info in schema_cls.model_fields.items():
21
+ if field_name != pk_field_name:
22
+
23
+ annotation = field_info.annotation
24
+
25
+ if field_info.is_required():
26
+ fields[field_name] = (annotation, ...)
27
+ else:
28
+
29
+ default = field_info.default if field_info.default is not ... else ...
30
+ fields[field_name] = (annotation, default)
31
+
32
+ name = schema_cls.__name__ + name
33
+
34
+ schema: Type[T] = create_model(name, **fields)
35
+ return schema
36
+
37
+
38
+ def create_query_validation_exception(field: str, msg: str) -> HTTPException:
39
+ return HTTPException(
40
+ 422,
41
+ detail={
42
+ "detail": [
43
+ {"loc": ["query", field], "msg": msg, "type": "type_error.integer"}
44
+ ]
45
+ },
46
+ )
47
+
48
+
49
+ def pagination_factory(max_limit: Optional[int] = None) -> Any:
50
+ """
51
+ Created the pagination dependency to be used in the router
52
+ """
53
+
54
+ def pagination(skip: int = 0, limit: Optional[int] = max_limit) -> PAGINATION:
55
+ if skip < 0:
56
+ raise create_query_validation_exception(
57
+ field="skip",
58
+ msg="skip query parameter must be greater or equal to zero",
59
+ )
60
+
61
+ if limit is not None:
62
+ if limit <= 0:
63
+ raise create_query_validation_exception(
64
+ field="limit", msg="limit query parameter must be greater than zero"
65
+ )
66
+ elif max_limit and max_limit < limit:
67
+ raise create_query_validation_exception(
68
+ field="limit",
69
+ msg=f"limit query parameter must be less than {max_limit}",
70
+ )
71
+
72
+ return {"skip": skip, "limit": limit}
73
+
74
+ return Depends(pagination)
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "fastapi-crudrouter-v2"
3
+ version = "0.1.3"
4
+ description = "A dynamic FastAPI router that automatically creates CRUD routes for your models"
5
+ authors = [
6
+ {name = "pasha_danilevich",email = "danilevitch.pasha@yandex.ru"}
7
+ ]
8
+ readme = "README.md"
9
+
10
+ [tool.poetry.dependencies]
11
+ python = ">=3.9, <4.0"
12
+
13
+
14
+
15
+ [build-system]
16
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
17
+ build-backend = "poetry.core.masonry.api"