diff --git a/.coverage b/.coverage deleted file mode 100644 index a5c0d9f86..000000000 Binary files a/.coverage and /dev/null differ diff --git a/.gitignore b/.gitignore index cf175d211..fb0cf92e0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ p38venv .idea .pytest_cache .mypy_cache +.coverage *.pyc *.log test.db diff --git a/docs/fastapi.md b/docs/fastapi.md index 9ae1dadd6..d451feedc 100644 --- a/docs/fastapi.md +++ b/docs/fastapi.md @@ -45,7 +45,7 @@ Those models will be used insted of pydantic ones. Define your desired endpoints, note how `ormar` models are used both as `response_model` and as a requests parameters. -```python hl_lines="50-77" +```python hl_lines="50-79" --8<-- "../docs_src/fastapi/docs001.py" ``` @@ -57,6 +57,23 @@ as `response_model` and as a requests parameters. ## Test the application +### Run fastapi + +If you want to run this script and play with fastapi swagger install uvicorn first + +`pip install uvicorn` + +And launch the fastapi. + +`uvicorn :app --reload` + +Now you can navigate to your browser (by default fastapi address is `127.0.0.1:8000/docs`) and play with the api. + +!!!info + You can read more about running fastapi in [fastapi][fastapi] docs. + +### Test with pytest + Here you have a sample test that will prove that everything works as intended. Be sure to create the tables first. If you are using pytest you can use a fixture. @@ -109,9 +126,13 @@ def test_all_endpoints(): assert len(items) == 0 ``` +!!!tip + If you want to see more test cases and how to test ormar/fastapi see [tests][tests] directory in the github repo + !!!info You can read more on testing fastapi in [fastapi][fastapi] docs. [fastapi]: https://fastapi.tiangolo.com/ [models]: ./models.md -[database initialization]: ../models/#database-initialization-migrations \ No newline at end of file +[database initialization]: ../models/#database-initialization-migrations +[tests]: https://github.com/collerek/ormar/tree/master/tests \ No newline at end of file diff --git a/docs/releases.md b/docs/releases.md index c674fdb4f..75c68ae01 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,3 +1,10 @@ +# 0.3.9 + +* Fix json schema generation as of [#19][#19] +* Fix for not initialized ManyToMany relations in fastapi copies of ormar.Models +* Update docs in regard of fastapi use +* Add tests to verify fastapi/docs proper generation + # 0.3.8 * Added possibility to provide alternative database column names with name parameter to all fields. @@ -42,4 +49,7 @@ Add queryset level methods # 0.3.0 -* Added ManyToMany field and support for many to many relations \ No newline at end of file +* Added ManyToMany field and support for many to many relations + + +[#19]: https://github.com/collerek/ormar/issues/19 \ No newline at end of file diff --git a/docs_src/fastapi/docs001.py b/docs_src/fastapi/docs001.py index 30ca75f5a..475756a48 100644 --- a/docs_src/fastapi/docs001.py +++ b/docs_src/fastapi/docs001.py @@ -72,6 +72,8 @@ async def get_item(item_id: int, item: Item): @app.delete("/items/{item_id}") -async def delete_item(item_id: int, item: Item): +async def delete_item(item_id: int, item: Item = None): + if item: + return {"deleted_rows": await item.delete()} item_db = await Item.objects.get(pk=item_id) return {"deleted_rows": await item_db.delete()} diff --git a/mkdocs.yml b/mkdocs.yml index a7b99e3d3..234f18d07 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,9 +12,9 @@ nav: - Release Notes: releases.md repo_name: collerek/ormar repo_url: https://github.com/collerek/ormar -google_analytics: - - UA-72514911-3 - - auto +#google_analytics: +# - UA-72514911-3 +# - auto theme: name: material highlightjs: true diff --git a/ormar/__init__.py b/ormar/__init__.py index 159fda57f..4aefddcda 100644 --- a/ormar/__init__.py +++ b/ormar/__init__.py @@ -28,7 +28,7 @@ def __repr__(self) -> str: Undefined = UndefinedType() -__version__ = "0.3.8" +__version__ = "0.3.9" __all__ = [ "Integer", "BigInteger", diff --git a/ormar/fields/foreign_key.py b/ormar/fields/foreign_key.py index 2959f328a..854f83dcc 100644 --- a/ormar/fields/foreign_key.py +++ b/ormar/fields/foreign_key.py @@ -58,6 +58,7 @@ def ForeignKey( # noqa CFQ002 pydantic_only=False, default=None, server_default=None, + __pydantic_model__=to, ) return type("ForeignKey", (ForeignKeyField, BaseField), namespace) diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index 0fed9e84f..1f73a0d18 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Type +from typing import Dict, TYPE_CHECKING, Type from ormar.fields import BaseField from ormar.fields.foreign_key import ForeignKeyField @@ -6,6 +6,8 @@ if TYPE_CHECKING: # pragma no cover from ormar.models import Model +REF_PREFIX = "#/components/schemas/" + def ManyToMany( to: Type["Model"], @@ -31,6 +33,9 @@ def ManyToMany( pydantic_only=False, default=None, server_default=None, + __pydantic_model__=to, + # __origin__=List, + # __args__=[Optional[to]] ) return type("ManyToMany", (ManyToManyField, BaseField), namespace) @@ -38,3 +43,10 @@ def ManyToMany( class ManyToManyField(ForeignKeyField): through: Type["Model"] + + @classmethod + def __modify_schema__(cls, field_schema: Dict) -> None: + field_schema["type"] = "array" + field_schema["title"] = cls.name.title() + field_schema["definitions"] = {f"{cls.to.__name__}": cls.to.schema()} + field_schema["items"] = {"$ref": f"{REF_PREFIX}{cls.to.__name__}"} diff --git a/ormar/models/model.py b/ormar/models/model.py index 3805a009f..eaf4935a6 100644 --- a/ormar/models/model.py +++ b/ormar/models/model.py @@ -145,7 +145,7 @@ async def update(self, **kwargs: Any) -> "Model": self_fields = self._extract_model_db_fields() self_fields.pop(self.get_column_name_from_alias(self.Meta.pkname)) - self_fields = self.objects._translate_columns_to_aliases(self_fields) + self_fields = self.translate_columns_to_aliases(self_fields) expr = self.Meta.table.update().values(**self_fields) expr = expr.where(self.pk_column == getattr(self, self.Meta.pkname)) @@ -166,6 +166,6 @@ async def load(self) -> "Model": "Instance was deleted from database and cannot be refreshed" ) kwargs = dict(row) - kwargs = self.objects._translate_aliases_to_columns(kwargs) + kwargs = self.translate_aliases_to_columns(kwargs) self.from_dict(kwargs) return self diff --git a/ormar/models/modelproxy.py b/ormar/models/modelproxy.py index 760232d1c..c9651542d 100644 --- a/ormar/models/modelproxy.py +++ b/ormar/models/modelproxy.py @@ -5,7 +5,7 @@ from ormar.exceptions import RelationshipInstanceError from ormar.fields import BaseField, ManyToManyField from ormar.fields.foreign_key import ForeignKeyField -from ormar.models.metaclass import ModelMeta +from ormar.models.metaclass import ModelMeta, expand_reverse_relationships if TYPE_CHECKING: # pragma no cover from ormar import Model @@ -112,9 +112,10 @@ def _extract_model_db_fields(self) -> Dict: return self_fields @staticmethod - def resolve_relation_name( + def resolve_relation_name( # noqa CCR001 item: Union["NewBaseModel", Type["NewBaseModel"]], related: Union["NewBaseModel", Type["NewBaseModel"]], + register_missing: bool = True, ) -> str: for name, field in item.Meta.model_fields.items(): if issubclass(field, ForeignKeyField): @@ -123,6 +124,13 @@ def resolve_relation_name( # so we need to compare Meta too as this one is copied as is if field.to == related.__class__ or field.to.Meta == related.Meta: return name + # fallback for not registered relation + if register_missing: + expand_reverse_relationships(related.__class__) # type: ignore + return ModelTableProxy.resolve_relation_name( + item, related, register_missing=False + ) + raise ValueError( f"No relation between {item.get_name()} and {related.get_name()}" ) # pragma nocover @@ -140,6 +148,24 @@ def resolve_relation_field( ) return to_field + @classmethod + def translate_columns_to_aliases(cls, new_kwargs: Dict) -> Dict: + for field_name, field in cls.Meta.model_fields.items(): + if ( + field_name in new_kwargs + and field.name is not None + and field.name != field_name + ): + new_kwargs[field.name] = new_kwargs.pop(field_name) + return new_kwargs + + @classmethod + def translate_aliases_to_columns(cls, new_kwargs: Dict) -> Dict: + for field_name, field in cls.Meta.model_fields.items(): + if field.name in new_kwargs and field.name != field_name: + new_kwargs[field_name] = new_kwargs.pop(field.name) + return new_kwargs + @classmethod def merge_instances_list(cls, result_rows: List["Model"]) -> List["Model"]: merged_rows: List["Model"] = [] diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index 88664fb3c..5cf28bcca 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -100,7 +100,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: ) def __setattr__(self, name: str, value: Any) -> None: # noqa CCR001 - if name in self.__slots__: + if name in ("_orm_id", "_orm_saved", "_orm"): object.__setattr__(self, name, value) elif name == "pk": object.__setattr__(self, self.Meta.pkname, value) @@ -137,7 +137,7 @@ def _extract_related_model_instead_of_field( alias = self.get_column_alias(item) if alias in self._orm: return self._orm.get(alias) - return None + return None # pragma no cover def __eq__(self, other: object) -> bool: if isinstance(other, NewBaseModel): diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index df1bc0139..a6c6b26fb 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -74,7 +74,7 @@ def _prepare_model_to_save(self, new_kwargs: dict) -> dict: new_kwargs = self._remove_pk_from_kwargs(new_kwargs) new_kwargs = self.model.substitute_models_with_pks(new_kwargs) new_kwargs = self._populate_default_values(new_kwargs) - new_kwargs = self._translate_columns_to_aliases(new_kwargs) + new_kwargs = self.model.translate_columns_to_aliases(new_kwargs) return new_kwargs def _populate_default_values(self, new_kwargs: dict) -> dict: @@ -83,22 +83,6 @@ def _populate_default_values(self, new_kwargs: dict) -> dict: new_kwargs[field_name] = field.get_default() return new_kwargs - def _translate_columns_to_aliases(self, new_kwargs: dict) -> dict: - for field_name, field in self.model_meta.model_fields.items(): - if ( - field_name in new_kwargs - and field.name is not None - and field.name != field_name - ): - new_kwargs[field.name] = new_kwargs.pop(field_name) - return new_kwargs - - def _translate_aliases_to_columns(self, new_kwargs: dict) -> dict: - for field_name, field in self.model_meta.model_fields.items(): - if field.name in new_kwargs and field.name != field_name: - new_kwargs[field_name] = new_kwargs.pop(field.name) - return new_kwargs - def _remove_pk_from_kwargs(self, new_kwargs: dict) -> dict: pkname = self.model_meta.pkname pk = self.model_meta.model_fields[pkname] @@ -207,7 +191,7 @@ async def count(self) -> int: async def update(self, each: bool = False, **kwargs: Any) -> int: self_fields = self.model.extract_db_own_fields() updates = {k: v for k, v in kwargs.items() if k in self_fields} - updates = self._translate_columns_to_aliases(updates) + updates = self.model.translate_columns_to_aliases(updates) if not each and not self.filter_clauses: raise QueryDefinitionError( "You cannot update without filtering the queryset first. " @@ -353,7 +337,7 @@ async def bulk_update( f"{self.model.__name__} has to have {pk_name} filled." ) new_kwargs = self.model.substitute_models_with_pks(new_kwargs) - new_kwargs = self._translate_columns_to_aliases(new_kwargs) + new_kwargs = self.model.translate_columns_to_aliases(new_kwargs) new_kwargs = {"new_" + k: v for k, v in new_kwargs.items() if k in columns} ready_objects.append(new_kwargs) diff --git a/ormar/relations/relation_manager.py b/ormar/relations/relation_manager.py index a176bec07..6e7eb2491 100644 --- a/ormar/relations/relation_manager.py +++ b/ormar/relations/relation_manager.py @@ -40,6 +40,8 @@ def _add_relation(self, field: Type[BaseField]) -> None: to=field.to, through=getattr(field, "through", None), ) + if field.name not in self._related_names: + self._related_names.append(field.name) def __contains__(self, item: str) -> bool: return item in self._related_names diff --git a/ormar/relations/relation_proxy.py b/ormar/relations/relation_proxy.py index 88130d58a..29e4b97f1 100644 --- a/ormar/relations/relation_proxy.py +++ b/ormar/relations/relation_proxy.py @@ -72,4 +72,6 @@ async def add(self, item: "Model") -> None: if self.relation._type == ormar.RelationType.MULTIPLE: await self.queryset_proxy.create_through_instance(item) rel_name = item.resolve_relation_name(item, self._owner) + if rel_name not in item._orm: + item._orm._add_relation(item.Meta.model_fields[rel_name]) setattr(item, rel_name, self._owner) diff --git a/tests/test_aliases.py b/tests/test_aliases.py index f169c30eb..b48bb3f06 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -15,10 +15,10 @@ class Meta: metadata = metadata database = database - id: ormar.Integer(name='child_id', primary_key=True) - first_name: ormar.String(name='fname', max_length=100) - last_name: ormar.String(name='lname', max_length=100) - born_year: ormar.Integer(name='year_born', nullable=True) + id: ormar.Integer(name="child_id", primary_key=True) + first_name: ormar.String(name="fname", max_length=100) + last_name: ormar.String(name="lname", max_length=100) + born_year: ormar.Integer(name="year_born", nullable=True) class ArtistChildren(ormar.Model): @@ -34,10 +34,10 @@ class Meta: metadata = metadata database = database - id: ormar.Integer(name='artist_id', primary_key=True) - first_name: ormar.String(name='fname', max_length=100) - last_name: ormar.String(name='lname', max_length=100) - born_year: ormar.Integer(name='year') + id: ormar.Integer(name="artist_id", primary_key=True) + first_name: ormar.String(name="fname", max_length=100) + last_name: ormar.String(name="lname", max_length=100) + born_year: ormar.Integer(name="year") children: ormar.ManyToMany(Child, through=ArtistChildren) @@ -47,9 +47,9 @@ class Meta: metadata = metadata database = database - id: ormar.Integer(name='album_id', primary_key=True) - name: ormar.String(name='album_name', max_length=100) - artist: ormar.ForeignKey(Artist, name='artist_id') + id: ormar.Integer(name="album_id", primary_key=True) + name: ormar.String(name="album_name", max_length=100) + artist: ormar.ForeignKey(Artist, name="artist_id") @pytest.fixture(autouse=True, scope="module") @@ -62,70 +62,87 @@ def create_test_database(): def test_table_structure(): - assert 'album_id' in [x.name for x in Album.Meta.table.columns] - assert 'album_name' in [x.name for x in Album.Meta.table.columns] - assert 'fname' in [x.name for x in Artist.Meta.table.columns] - assert 'lname' in [x.name for x in Artist.Meta.table.columns] - assert 'year' in [x.name for x in Artist.Meta.table.columns] + assert "album_id" in [x.name for x in Album.Meta.table.columns] + assert "album_name" in [x.name for x in Album.Meta.table.columns] + assert "fname" in [x.name for x in Artist.Meta.table.columns] + assert "lname" in [x.name for x in Artist.Meta.table.columns] + assert "year" in [x.name for x in Artist.Meta.table.columns] @pytest.mark.asyncio async def test_working_with_aliases(): async with database: async with database.transaction(force_rollback=True): - artist = await Artist.objects.create(first_name='Ted', last_name='Mosbey', born_year=1975) + artist = await Artist.objects.create( + first_name="Ted", last_name="Mosbey", born_year=1975 + ) await Album.objects.create(name="Aunt Robin", artist=artist) - await artist.children.create(first_name='Son', last_name='1', born_year=1990) - await artist.children.create(first_name='Son', last_name='2', born_year=1995) + await artist.children.create( + first_name="Son", last_name="1", born_year=1990 + ) + await artist.children.create( + first_name="Son", last_name="2", born_year=1995 + ) - album = await Album.objects.select_related('artist').first() - assert album.artist.last_name == 'Mosbey' + album = await Album.objects.select_related("artist").first() + assert album.artist.last_name == "Mosbey" assert album.artist.id is not None - assert album.artist.first_name == 'Ted' + assert album.artist.first_name == "Ted" assert album.artist.born_year == 1975 - assert album.name == 'Aunt Robin' + assert album.name == "Aunt Robin" - artist = await Artist.objects.select_related('children').get() + artist = await Artist.objects.select_related("children").get() assert len(artist.children) == 2 - assert artist.children[0].first_name == 'Son' - assert artist.children[1].last_name == '2' + assert artist.children[0].first_name == "Son" + assert artist.children[1].last_name == "2" - await artist.update(last_name='Bundy') + await artist.update(last_name="Bundy") await Artist.objects.filter(pk=artist.pk).update(born_year=1974) - artist = await Artist.objects.select_related('children').get() - assert artist.last_name == 'Bundy' + artist = await Artist.objects.select_related("children").get() + assert artist.last_name == "Bundy" assert artist.born_year == 1974 - artist = await Artist.objects.select_related('children').fields( - ['first_name', 'last_name', 'born_year', 'child__first_name', 'child__last_name']).get() + artist = ( + await Artist.objects.select_related("children") + .fields( + [ + "first_name", + "last_name", + "born_year", + "child__first_name", + "child__last_name", + ] + ) + .get() + ) assert artist.children[0].born_year is None @pytest.mark.asyncio async def test_bulk_operations_and_fields(): async with database: - d1 = Child(first_name='Daughter', last_name='1', born_year=1990) - d2 = Child(first_name='Daughter', last_name='2', born_year=1991) + d1 = Child(first_name="Daughter", last_name="1", born_year=1990) + d2 = Child(first_name="Daughter", last_name="2", born_year=1991) await Child.objects.bulk_create([d1, d2]) - children = await Child.objects.filter(first_name='Daughter').all() + children = await Child.objects.filter(first_name="Daughter").all() assert len(children) == 2 - assert children[0].last_name == '1' + assert children[0].last_name == "1" for child in children: child.born_year = child.born_year - 100 await Child.objects.bulk_update(children) - children = await Child.objects.filter(first_name='Daughter').all() + children = await Child.objects.filter(first_name="Daughter").all() assert len(children) == 2 assert children[0].born_year == 1890 - children = await Child.objects.fields(['first_name', 'last_name']).all() + children = await Child.objects.fields(["first_name", "last_name"]).all() assert len(children) == 2 for child in children: assert child.born_year is None @@ -140,17 +157,21 @@ async def test_bulk_operations_and_fields(): async def test_working_with_aliases_get_or_create(): async with database: async with database.transaction(force_rollback=True): - artist = await Artist.objects.get_or_create(first_name='Teddy', last_name='Bear', born_year=2020) + artist = await Artist.objects.get_or_create( + first_name="Teddy", last_name="Bear", born_year=2020 + ) assert artist.pk is not None - artist2 = await Artist.objects.get_or_create(first_name='Teddy', last_name='Bear', born_year=2020) + artist2 = await Artist.objects.get_or_create( + first_name="Teddy", last_name="Bear", born_year=2020 + ) assert artist == artist2 art3 = artist2.dict() - art3['born_year'] = 2019 + art3["born_year"] = 2019 await Artist.objects.update_or_create(**art3) - artist3 = await Artist.objects.get(last_name='Bear') + artist3 = await Artist.objects.get(last_name="Bear") assert artist3.born_year == 2019 artists = await Artist.objects.all() diff --git a/tests/test_fastapi_docs.py b/tests/test_fastapi_docs.py new file mode 100644 index 000000000..030815f97 --- /dev/null +++ b/tests/test_fastapi_docs.py @@ -0,0 +1,135 @@ +from typing import List + +import databases +import pytest +import sqlalchemy +from fastapi import FastAPI +from starlette.testclient import TestClient + +import ormar +from tests.settings import DATABASE_URL + +app = FastAPI() +metadata = sqlalchemy.MetaData() +database = databases.Database(DATABASE_URL, force_rollback=True) +app.state.database = database + + +@app.on_event("startup") +async def startup() -> None: + database_ = app.state.database + if not database_.is_connected: + await database_.connect() + + +@app.on_event("shutdown") +async def shutdown() -> None: + database_ = app.state.database + if database_.is_connected: + await database_.disconnect() + + +class LocalMeta: + metadata = metadata + database = database + + +class Category(ormar.Model): + class Meta(LocalMeta): + tablename = "categories" + + id: ormar.Integer(primary_key=True) + name: ormar.String(max_length=100) + + +class ItemsXCategories(ormar.Model): + class Meta(LocalMeta): + tablename = "items_x_categories" + + +class Item(ormar.Model): + class Meta(LocalMeta): + pass + + id: ormar.Integer(primary_key=True) + name: ormar.String(max_length=100) + categories: ormar.ManyToMany(Category, through=ItemsXCategories) + + +@pytest.fixture(autouse=True, scope="module") +def create_test_database(): + engine = sqlalchemy.create_engine(DATABASE_URL) + metadata.create_all(engine) + yield + metadata.drop_all(engine) + + +@app.get("/items/", response_model=List[Item]) +async def get_items(): + items = await Item.objects.select_related("categories").all() + return items + + +@app.post("/items/", response_model=Item) +async def create_item(item: Item): + await item.save() + return item + + +@app.post("/items/add_category/", response_model=Item) +async def create_item(item: Item, category: Category): + await item.categories.add(category) + return item + + +@app.post("/categories/", response_model=Category) +async def create_category(category: Category): + await category.save() + return category + + +def test_all_endpoints(): + client = TestClient(app) + with client as client: + response = client.post("/categories/", json={"name": "test cat"}) + category = response.json() + response = client.post("/categories/", json={"name": "test cat2"}) + category2 = response.json() + + response = client.post("/items/", json={"name": "test", "id": 1}) + item = Item(**response.json()) + assert item.pk is not None + + response = client.post( + "/items/add_category/", json={"item": item.dict(), "category": category} + ) + item = Item(**response.json()) + assert len(item.categories) == 1 + assert item.categories[0].name == "test cat" + + client.post( + "/items/add_category/", json={"item": item.dict(), "category": category2} + ) + + response = client.get("/items/") + items = [Item(**item) for item in response.json()] + assert items[0] == item + assert len(items[0].categories) == 2 + assert items[0].categories[0].name == "test cat" + assert items[0].categories[1].name == "test cat2" + + response = client.get("/docs/") + assert response.status_code == 200 + assert b"FastAPI - Swagger UI" in response.content + + +def test_schema_modification(): + schema = Item.schema() + assert schema["properties"]["categories"]["type"] == "array" + assert schema["properties"]["categories"]["title"] == "Categories" + + +def test_schema_gen(): + schema = app.openapi() + assert "Category" in schema["components"]["schemas"] + assert "Item" in schema["components"]["schemas"] diff --git a/tests/test_models.py b/tests/test_models.py index f33d855af..47b5d50e9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,4 +1,5 @@ import asyncio +import uuid from datetime import datetime from typing import List @@ -6,7 +7,6 @@ import pydantic import pytest import sqlalchemy -import uuid import ormar from ormar.exceptions import QueryDefinitionError, NoMatch diff --git a/tests/test_more_reallife_fastapi.py b/tests/test_more_reallife_fastapi.py index f0b9b884e..b538b5c5e 100644 --- a/tests/test_more_reallife_fastapi.py +++ b/tests/test_more_reallife_fastapi.py @@ -77,13 +77,15 @@ async def create_category(category: Category): @app.put("/items/{item_id}") -async def get_item(item_id: int, item: Item): +async def update_item(item_id: int, item: Item): item_db = await Item.objects.get(pk=item_id) return await item_db.update(**item.dict()) @app.delete("/items/{item_id}") -async def delete_item(item_id: int, item: Item): +async def delete_item(item_id: int, item: Item = None): + if item: + return {"deleted_rows": await item.delete()} item_db = await Item.objects.get(pk=item_id) return {"deleted_rows": await item_db.delete()} @@ -111,8 +113,20 @@ def test_all_endpoints(): items = [Item(**item) for item in response.json()] assert items[0].name == "New name" - response = client.delete(f"/items/{item.pk}", json=item.dict()) + response = client.delete(f"/items/{item.pk}") assert response.json().get("deleted_rows", "__UNDEFINED__") != "__UNDEFINED__" response = client.get("/items/") items = response.json() assert len(items) == 0 + + client.post("/items/", json={"name": "test_2", "id": 2, "category": category}) + response = client.get("/items/") + items = response.json() + assert len(items) == 1 + + item = Item(**items[0]) + response = client.delete(f"/items/{item.pk}", json=item.dict()) + assert response.json().get("deleted_rows", "__UNDEFINED__") != "__UNDEFINED__" + + response = client.get("/docs/") + assert response.status_code == 200