Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/poetry #157

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/en/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ on the feature and potential help on where to start implementation wise.

## Development

### Using [poetry](https://python-poetry.org/)

FastAPI-Crudrouter uses [poetry](https://python-poetry.org/) as depenency management system. To install all of the required dependecies simply run:

poetry install

Both dev-requirements and project-requirements will be installed with this command.

To enter the virtual-environment created with poetry use:

poetry shell

If you are not familiar with poetry, please read their [usage guide](https://python-poetry.org/docs/basic-usage/).

### Installing the Dev Requirements
FastAPI-Crudrouter requires as set of development requirements that can installed with `pip` be found in `tests/dev.requirements.txt`

Expand Down
2 changes: 2 additions & 0 deletions fastapi_crudrouter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
OrmarCRUDRouter,
SQLAlchemyCRUDRouter,
TortoiseCRUDRouter,
BeanieCRUDRouter,
)

from ._version import __version__ # noqa: F401
Expand All @@ -16,4 +17,5 @@
"TortoiseCRUDRouter",
"OrmarCRUDRouter",
"GinoCRUDRouter",
"BeanieCRUDRouter",
]
2 changes: 2 additions & 0 deletions fastapi_crudrouter/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .ormar import OrmarCRUDRouter
from .sqlalchemy import SQLAlchemyCRUDRouter
from .tortoise import TortoiseCRUDRouter
from .beanie import BeanieCRUDRouter

__all__ = [
"_utils",
Expand All @@ -17,4 +18,5 @@
"TortoiseCRUDRouter",
"OrmarCRUDRouter",
"GinoCRUDRouter",
"BeanieCRUDRouter",
]
142 changes: 142 additions & 0 deletions fastapi_crudrouter/core/beanie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from typing import Any, Callable, Dict, List, Type, cast, Coroutine, Optional, Union

from fastapi import HTTPException

from . import CRUDGenerator, NOT_FOUND, _utils
from ._types import DEPENDENCIES, PAGINATION

try:
from beanie import Document
from beanie.odm.fields import PydanticObjectId
except ImportError:
Document = None # type: ignore
beanie_installed = False
else:
beanie_installed = True


CALLABLE = Callable[..., Coroutine[Any, Any, Document]]
CALLABLE_LIST = Callable[..., Coroutine[Any, Any, List[Optional[Document]]]]


class BeanieCRUDRouter(CRUDGenerator[Document]):
def __init__(
self,
schema: Type[Document],
create_schema: Optional[Type[Document]] = None,
update_schema: Optional[Type[Document]] = 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 (
beanie_installed
), "Beanie ODM must be installed to use the BeanieCRUDRouter."

# TODO: Beanie only supports `id` as the primary field, when other
# fields get supported, this part needs to be updated
self._pk: str = 'id'
self._pk_type: type = _utils.get_pk_type(schema, self._pk)

super().__init__(
schema=schema,
create_schema=create_schema or schema,
update_schema=update_schema or schema,
prefix=prefix or schema.Settings.name,
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
)

self._INTEGRITY_ERROR = self._get_integrity_error_type()

def _get_all(self, *args: Any, **kwargs: Any) -> CALLABLE_LIST:
async def route(
pagination: PAGINATION = self.pagination,
) -> List[Optional[Document]]:
skip, limit = pagination.get("skip"), pagination.get("limit")
query = await self.schema.all(
skip=cast(int, skip),
limit=limit
).to_list()
return query # type: ignore

return route

def _get_one(self, *args: Any, **kwargs: Any) -> CALLABLE:
async def route(item_id: self._pk_type) -> Document: # type: ignore
model = await self.schema.get(
item_id
)
if model is None:
raise NOT_FOUND from None
return model

return route

def _create(self, *args: Any, **kwargs: Any) -> CALLABLE:
async def route(model: self.create_schema) -> Document: # type: ignore
model_dict = model.dict()
if self._pk_type == PydanticObjectId:
model_dict.pop(self._pk, None)
try:
document: Type[Document] = self.schema(**model_dict)
return await self.schema.insert_one(document) # TODO: Test
except self._INTEGRITY_ERROR:
raise HTTPException(422, "Key already exists") from None

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
) -> Document:
try:
update_query: Dict = {
"$set": {
field: value
for field, value in model.dict(exclude_unset=True).items()
}
}
await self.schema.get(item_id).update( update_query) # TODO: Test
except self._INTEGRITY_ERROR as e:
self._raise(e)
return await self._get_one()(item_id)

return route

def _delete_all(self, *args: Any, **kwargs: Any) -> CALLABLE_LIST:
async def route() -> List[Optional[Document]]:
await self.schema.delete_all()
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) -> Document: # type: ignore
model = await self._get_one()(item_id)
await model.delete()
return model

return route

def _get_integrity_error_type(self) -> Type[Exception]:
"""Imports the Integrity exception based on the used backend"""
# TODO: All Beanie exceptions are children of Exception class, so
# plain `Exception` should work. Although a more definite exception
# might be needed.
return Exception
Loading