-
-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from Turall/master
add python-gino support
- Loading branch information
Showing
21 changed files
with
438 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ __pycache__/ | |
|
||
# C extensions | ||
*.so | ||
|
||
.vscode | ||
# Distribution / packaging | ||
.Python | ||
build/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
Asynchronous routes will be automatically generated when using the `GinoCRUDRouter`. To use it, you must pass a | ||
[pydantic](https://pydantic-docs.helpmanual.io/) model, your SQLAlchemy Table, and the databases database. | ||
This CRUDRouter is intended to be used with the python [Gino](https://python-gino.org/) library. An example | ||
of how to use [Gino](https://python-gino.org/) with FastAPI can be found both | ||
[here](https://python-gino.org/docs/en/1.0/tutorials/fastapi.html) and below. | ||
|
||
!!! warning | ||
To use the `GinoCRUDRouter`, Databases **and** SQLAlchemy must be first installed. | ||
|
||
## Minimal Example | ||
Below is a minimal example assuming that you have already imported and created | ||
all the required models and database connections. | ||
|
||
```python | ||
router = GinoCRUDRouter( | ||
schema=MyPydanticModel, | ||
db=db, | ||
db_model=MyModel | ||
) | ||
app.include_router(router) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
from typing import Any, Callable, List, Optional, Type, Union, Coroutine | ||
|
||
from fastapi import HTTPException | ||
|
||
from . import NOT_FOUND, CRUDGenerator, _utils | ||
from ._types import DEPENDENCIES, PAGINATION | ||
from ._types import PYDANTIC_SCHEMA as SCHEMA | ||
|
||
try: | ||
from asyncpg.exceptions import UniqueViolationError | ||
from gino import Gino | ||
from sqlalchemy.exc import IntegrityError | ||
from sqlalchemy.ext.declarative import DeclarativeMeta as Model | ||
except ImportError: | ||
Model: Any = None # type: ignore | ||
gino_installed = False | ||
else: | ||
gino_installed = True | ||
|
||
CALLABLE = Callable[..., Coroutine[Any, Any, Model]] | ||
CALLABLE_LIST = Callable[..., Coroutine[Any, Any, List[Model]]] | ||
|
||
|
||
class GinoCRUDRouter(CRUDGenerator[SCHEMA]): | ||
def __init__( | ||
self, | ||
schema: Type[SCHEMA], | ||
db_model: Model, | ||
db: "Gino", | ||
create_schema: Optional[Type[SCHEMA]] = None, | ||
update_schema: Optional[Type[SCHEMA]] = None, | ||
prefix: Optional[str] = None, | ||
tags: Optional[List[str]] = None, | ||
paginate: Optional[int] = None, | ||
get_all_route: Union[bool, DEPENDENCIES] = True, | ||
get_one_route: Union[bool, DEPENDENCIES] = True, | ||
create_route: Union[bool, DEPENDENCIES] = True, | ||
update_route: Union[bool, DEPENDENCIES] = True, | ||
delete_one_route: Union[bool, DEPENDENCIES] = True, | ||
delete_all_route: Union[bool, DEPENDENCIES] = True, | ||
**kwargs: Any | ||
) -> None: | ||
assert gino_installed, "Gino must be installed to use the GinoCRUDRouter." | ||
|
||
self.db_model = db_model | ||
self.db = db | ||
self._pk: str = db_model.__table__.primary_key.columns.keys()[0] | ||
self._pk_type: type = _utils.get_pk_type(schema, self._pk) | ||
|
||
super().__init__( | ||
schema=schema, | ||
create_schema=create_schema, | ||
update_schema=update_schema, | ||
prefix=prefix or db_model.__tablename__, | ||
tags=tags, | ||
paginate=paginate, | ||
get_all_route=get_all_route, | ||
get_one_route=get_one_route, | ||
create_route=create_route, | ||
update_route=update_route, | ||
delete_one_route=delete_one_route, | ||
delete_all_route=delete_all_route, | ||
**kwargs | ||
) | ||
|
||
def _get_all(self, *args: Any, **kwargs: Any) -> CALLABLE_LIST: | ||
async def route( | ||
pagination: PAGINATION = self.pagination, | ||
) -> List[Model]: | ||
skip, limit = pagination.get("skip"), pagination.get("limit") | ||
|
||
db_models: List[Model] = ( | ||
await self.db_model.query.limit(limit).offset(skip).gino.all() | ||
) | ||
return db_models | ||
|
||
return route | ||
|
||
def _get_one(self, *args: Any, **kwargs: Any) -> CALLABLE: | ||
async def route(item_id: self._pk_type) -> Model: # type: ignore | ||
model: Model = await self.db_model.get(item_id) | ||
|
||
if model: | ||
return model | ||
else: | ||
raise NOT_FOUND | ||
|
||
return route | ||
|
||
def _create(self, *args: Any, **kwargs: Any) -> CALLABLE: | ||
async def route( | ||
model: self.create_schema, # type: ignore | ||
) -> Model: | ||
try: | ||
async with self.db.transaction(): | ||
db_model: Model = await self.db_model.create(**model.dict()) | ||
return db_model | ||
except (IntegrityError, UniqueViolationError): | ||
raise HTTPException(422, "Key already exists") | ||
|
||
return route | ||
|
||
def _update(self, *args: Any, **kwargs: Any) -> CALLABLE: | ||
async def route( | ||
item_id: self._pk_type, # type: ignore | ||
model: self.update_schema, # type: ignore | ||
) -> Model: | ||
try: | ||
db_model: Model = await self._get_one()(item_id) | ||
async with self.db.transaction(): | ||
model = model.dict(exclude={self._pk}) | ||
await db_model.update(**model).apply() | ||
|
||
return db_model | ||
except (IntegrityError, UniqueViolationError) as e: | ||
raise HTTPException(422, ", ".join(e.args)) | ||
|
||
return route | ||
|
||
def _delete_all(self, *args: Any, **kwargs: Any) -> CALLABLE_LIST: | ||
async def route() -> List[Model]: | ||
await self.db_model.delete.gino.status() | ||
return await self._get_all()(pagination={"skip": 0, "limit": None}) | ||
|
||
return route | ||
|
||
def _delete_one(self, *args: Any, **kwargs: Any) -> CALLABLE: | ||
async def route(item_id: self._pk_type) -> Model: # type: ignore | ||
db_model: Model = await self._get_one()(item_id) | ||
await db_model.delete() | ||
|
||
return db_model | ||
|
||
return route |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
from pydantic import BaseModel | ||
|
||
from .conf import config | ||
|
||
PAGINATION_SIZE = 10 | ||
CUSTOM_TAGS = ["Tag1", "Tag2"] | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .config import BaseConfig | ||
|
||
config = BaseConfig() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import os | ||
import pathlib | ||
|
||
|
||
ENV_FILE_PATH = pathlib.Path(__file__).parent / "dev.env" | ||
assert ENV_FILE_PATH.exists() | ||
|
||
|
||
class BaseConfig: | ||
POSTGRES_HOST = "" | ||
POSTGRES_USER = "" | ||
POSTGRES_PASSWORD = "" | ||
POSTGRES_DB = "" | ||
POSTGRES_PORT = "" | ||
|
||
def __init__(self): | ||
self._apply_dot_env() | ||
self._apply_env_vars() | ||
self.POSTGRES_URI = f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}" | ||
print(self.POSTGRES_URI) | ||
|
||
def _apply_dot_env(self): | ||
with open(ENV_FILE_PATH) as fp: | ||
for line in fp.readlines(): | ||
line = line.strip(" \n") | ||
|
||
if not line.startswith("#"): | ||
k, v = line.split("=", 1) | ||
|
||
if hasattr(self, k) and not getattr(self, k): | ||
setattr(self, k, v) | ||
|
||
def _apply_env_vars(self): | ||
for k, v in os.environ.items(): | ||
if hasattr(self, k): | ||
setattr(self, k, v) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
version: "3.9" | ||
|
||
services: | ||
db: | ||
image: postgres | ||
restart: always | ||
env_file: | ||
- dev.env | ||
ports: | ||
- 5432:5432 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
POSTGRES_HOST=localhost | ||
POSTGRES_DB=test | ||
POSTGRES_USER=postgres | ||
POSTGRES_PASSWORD=password | ||
POSTGRES_PORT=5432 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
1711d00
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs: