From 29bcbae35d5ec63ffef15e8bfdb9f8f66c13755b Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 24 Sep 2020 16:32:34 +0200 Subject: [PATCH] update readme, bump version --- README.md | 46 ++++++++++++++++++++++++++++++++++++++ ormar/__init__.py | 2 +- ormar/models/modelproxy.py | 24 +++++++++++--------- ormar/queryset/queryset.py | 29 +++++++++++++----------- 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 9946b06..4cfb463 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,52 @@ await news.posts.clear() ``` +Since version >=0.3.4 Ormar supports also queryset level delete and update statements +```python +import databases +import ormar +import sqlalchemy + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + +class Book(ormar.Model): + class Meta: + tablename = "books" + metadata = metadata + database = database + + id: ormar.Integer(primary_key=True) + title: ormar.String(max_length=200) + author: ormar.String(max_length=100) + genre: ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + +await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') +await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title='Harry Potter', author="Rowling, J.K.", genre='Fantasy') +await Book.objects.create(title='Lord of the Rings', author="Tolkien, J.R.", genre='Fantasy') + +# update accepts kwargs that are used to update queryset model +# all other arguments are ignored (argument names not in own model table) +await Book.objects.filter(author="Tolstoy, Leo").update(author="Lenin, Vladimir") # update all Tolstoy's books +all_books = await Book.objects.filter(author="Lenin, Vladimir").all() +assert len(all_books) == 2 + +# delete accepts kwargs that will be used in filter +# acting in same way as queryset.filter(**kwargs).delete() +await Book.objects.delete(genre='Fantasy') # delete all fantasy books +all_books = await Book.objects.all() +assert len(all_books) == 3 + +# queryset needs to be filtered before deleting to prevent accidental overwrite +# to update whole database table each=True needs to be provided as a safety switch +await Book.objects.update(each=True, genre='Fiction') +all_books = await Book.objects.filter(genre='Fiction').all() +assert len(all_books) == 3 + +``` + ## Data types The following keyword arguments are supported on all field types. diff --git a/ormar/__init__.py b/ormar/__init__.py index d77460a..0ee9069 100644 --- a/ormar/__init__.py +++ b/ormar/__init__.py @@ -26,7 +26,7 @@ class UndefinedType: # pragma no cover Undefined = UndefinedType() -__version__ = "0.3.3" +__version__ = "0.3.4" __all__ = [ "Integer", "BigInteger", diff --git a/ormar/models/modelproxy.py b/ormar/models/modelproxy.py index 5534dcb..31c0096 100644 --- a/ormar/models/modelproxy.py +++ b/ormar/models/modelproxy.py @@ -28,7 +28,9 @@ class ModelTableProxy: @classmethod def extract_db_own_fields(cls) -> set: related_names = cls._extract_related_names() - self_fields = {name for name in cls.Meta.model_fields.keys() if name not in related_names} + self_fields = { + name for name in cls.Meta.model_fields.keys() if name not in related_names + } return self_fields @classmethod @@ -57,9 +59,9 @@ class ModelTableProxy: related_names = set() for name, field in cls.Meta.model_fields.items(): if ( - inspect.isclass(field) - and issubclass(field, ForeignKeyField) - and not field.virtual + inspect.isclass(field) + and issubclass(field, ForeignKeyField) + and not field.virtual ): related_names.add(name) return related_names @@ -71,9 +73,9 @@ class ModelTableProxy: related_names = set() for name, field in cls.Meta.model_fields.items(): if ( - inspect.isclass(field) - and issubclass(field, ForeignKeyField) - and field.nullable + inspect.isclass(field) + and issubclass(field, ForeignKeyField) + and field.nullable ): related_names.add(name) return related_names @@ -101,7 +103,7 @@ class ModelTableProxy: @staticmethod def resolve_relation_field( - item: Union["Model", Type["Model"]], related: Union["Model", Type["Model"]] + item: Union["Model", Type["Model"]], related: Union["Model", Type["Model"]] ) -> Type[Field]: name = ModelTableProxy.resolve_relation_name(item, related) to_field = item.Meta.model_fields.get(name) @@ -127,12 +129,12 @@ class ModelTableProxy: for field in one.Meta.model_fields.keys(): current_field = getattr(one, field) if isinstance(current_field, list) and not isinstance( - current_field, ormar.Model + current_field, ormar.Model ): setattr(other, field, current_field + getattr(other, field)) elif ( - isinstance(current_field, ormar.Model) - and current_field.pk == getattr(other, field).pk + isinstance(current_field, ormar.Model) + and current_field.pk == getattr(other, field).pk ): setattr( other, diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 639a803..7bbef37 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -16,13 +16,13 @@ if TYPE_CHECKING: # pragma no cover class QuerySet: def __init__( # noqa CFQ002 - self, - model_cls: Type["Model"] = None, - filter_clauses: List = None, - exclude_clauses: List = None, - select_related: List = None, - limit_count: int = None, - offset: int = None, + self, + model_cls: Type["Model"] = None, + filter_clauses: List = None, + exclude_clauses: List = None, + select_related: List = None, + limit_count: int = None, + offset: int = None, ) -> None: self.model_cls = model_cls self.filter_clauses = [] if filter_clauses is None else filter_clauses @@ -53,7 +53,7 @@ class QuerySet: pkname = self.model_cls.Meta.pkname pk = self.model_cls.Meta.model_fields[pkname] if new_kwargs.get(pkname, ormar.Undefined) is None and ( - pk.nullable or pk.autoincrement + pk.nullable or pk.autoincrement ): del new_kwargs[pkname] return new_kwargs @@ -140,20 +140,23 @@ class QuerySet: self_fields = self.model_cls.extract_db_own_fields() updates = {k: v for k, v in kwargs.items() if k in self_fields} if not each and not self.filter_clauses: - raise QueryDefinitionError('You cannot update without filtering the queryset first. ' - 'If you want to update all rows use update(each=True, **kwargs)') + raise QueryDefinitionError( + "You cannot update without filtering the queryset first. " + "If you want to update all rows use update(each=True, **kwargs)" + ) expr = FilterQuery(filter_clauses=self.filter_clauses).apply( self.table.update().values(**updates) ) - # print(expr.compile(compile_kwargs={"literal_binds": True})) return await self.database.execute(expr) async def delete(self, each: bool = False, **kwargs: Any) -> int: if kwargs: return await self.filter(**kwargs).delete() if not each and not self.filter_clauses: - raise QueryDefinitionError('You cannot delete without filtering the queryset first. ' - 'If you want to delete all rows use delete(each=True)') + raise QueryDefinitionError( + "You cannot delete without filtering the queryset first. " + "If you want to delete all rows use delete(each=True)" + ) expr = FilterQuery(filter_clauses=self.filter_clauses).apply( self.table.delete() )