Skip to content

Commit

Permalink
Merge pull request #538 from ponytailer/bugfix-bulk-create
Browse files Browse the repository at this point in the history
Bugfix: dumps the object which has the json fields in bulk create
  • Loading branch information
collerek authored Jan 26, 2022
2 parents 8dc05d5 + 0e167dc commit 4ed267e
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = ANN101, ANN102, W503, S101, CFQ004
ignore = ANN101, ANN102, W503, S101, CFQ004, S311
max-complexity = 8
max-line-length = 88
import-order-style = pycharm
Expand Down
38 changes: 38 additions & 0 deletions docs/models/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,44 @@ But for now you cannot change the ManyToMany column names as they go through oth
--8<-- "../docs_src/models/docs010.py"
```

## Overwriting the default QuerySet

If you want to customize the queries run by ormar you can define your own queryset class (that extends the ormar `QuerySet`) in your model class, default one is simply the `QuerySet`

You can provide a new class in `Meta` configuration of your class as `queryset_class` parameter.

```python
import ormar
from ormar.queryset.queryset import QuerySet
from fastapi import HTTPException


class MyQuerySetClass(QuerySet):

async def first_or_404(self, *args, **kwargs):
entity = await self.get_or_none(*args, **kwargs)
if entity is None:
# in fastapi or starlette
raise HTTPException(404)


class Book(ormar.Model):

class Meta(ormar.ModelMeta):
metadata = metadata
database = database
tablename = "book"
queryset_class = MyQuerySetClass

id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=32)


# when book not found, raise `404` in your view.
book = await Book.objects.first_or_404(name="123")

```

### Type Hints & Legacy

Before version 0.4.0 `ormar` supported only one way of defining `Fields` on a `Model` using python type hints as pydantic.
Expand Down
2 changes: 1 addition & 1 deletion docs/models/inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,4 +570,4 @@ class Category(CreateDateFieldsModel, AuditCreateModel):
code: int = ormar.Integer()
```

That way you can inherit from both create and update classes if needed, and only one of them otherwise.
That way you can inherit from both create and update classes if needed, and only one of them otherwise.
2 changes: 1 addition & 1 deletion docs/signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ Send for `Model.relation_name.remove()` method for `ManyToMany` relations and re

### post_bulk_update

`post_bulk_update(sender: Type["Model"], instances: List["Model"], **kwargs),
`post_bulk_update(sender: Type["Model"], instances: List["Model"], **kwargs)`,
Send for `Model.objects.bulk_update(List[objects])` method.


Expand Down
2 changes: 1 addition & 1 deletion ormar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
from importlib_metadata import version # type: ignore
from ormar.protocols import QuerySetProtocol, RelationProtocol # noqa: I100
from ormar.decorators import ( # noqa: I100
post_bulk_update,
post_delete,
post_relation_add,
post_relation_remove,
post_save,
post_update,
post_bulk_update,
pre_delete,
pre_relation_add,
pre_relation_remove,
Expand Down
2 changes: 1 addition & 1 deletion ormar/decorators/signals.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Callable, List, Type, TYPE_CHECKING, Union
from typing import Callable, List, TYPE_CHECKING, Type, Union

if TYPE_CHECKING: # pragma: no cover
from ormar import Model
Expand Down
9 changes: 2 additions & 7 deletions ormar/models/descriptors/descriptors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import base64
from typing import Any, TYPE_CHECKING, Type

try:
import orjson as json
except ImportError: # pragma: no cover
import json # type: ignore
from ormar.fields.parsers import encode_json

if TYPE_CHECKING: # pragma: no cover
from ormar import Model
Expand Down Expand Up @@ -40,9 +37,7 @@ def __get__(self, instance: "Model", owner: Type["Model"]) -> Any:
return value

def __set__(self, instance: "Model", value: Any) -> None:
if not isinstance(value, str):
value = json.dumps(value)
value = value.decode("utf-8") if isinstance(value, bytes) else value
value = encode_json(value)
instance._internal_set(self.name, value)
instance.set_save_status(False)

Expand Down
24 changes: 12 additions & 12 deletions ormar/models/helpers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,18 @@ def populate_default_options_values( # noqa: CCR001
:param model_fields: dict of model fields
:type model_fields: Union[Dict[str, type], Dict]
"""
if not hasattr(new_model.Meta, "constraints"):
new_model.Meta.constraints = []
if not hasattr(new_model.Meta, "model_fields"):
new_model.Meta.model_fields = model_fields
if not hasattr(new_model.Meta, "abstract"):
new_model.Meta.abstract = False
if not hasattr(new_model.Meta, "extra"):
new_model.Meta.extra = Extra.forbid
if not hasattr(new_model.Meta, "orders_by"):
new_model.Meta.orders_by = []
if not hasattr(new_model.Meta, "exclude_parent_fields"):
new_model.Meta.exclude_parent_fields = []
defaults = {
"queryset_class": ormar.QuerySet,
"constraints": [],
"model_fields": model_fields,
"abstract": False,
"extra": Extra.forbid,
"orders_by": [],
"exclude_parent_fields": [],
}
for key, value in defaults.items():
if not hasattr(new_model.Meta, key):
setattr(new_model.Meta, key, value)

if any(
is_field_an_forward_ref(field) for field in new_model.Meta.model_fields.values()
Expand Down
3 changes: 2 additions & 1 deletion ormar/models/metaclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class ModelMeta:
orders_by: List[str]
exclude_parent_fields: List[str]
extra: Extra
queryset_class: Type[QuerySet]


def add_cached_properties(new_model: Type["Model"]) -> None:
Expand Down Expand Up @@ -622,7 +623,7 @@ def objects(cls: Type["T"]) -> "QuerySet[T]": # type: ignore
f"ForwardRefs. \nBefore using the model you "
f"need to call update_forward_refs()."
)
return QuerySet(model_cls=cls)
return cls.Meta.queryset_class(model_cls=cls)

def __getattr__(self, item: str) -> Any:
"""
Expand Down
10 changes: 3 additions & 7 deletions ormar/models/mixins/save_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@
cast,
)

try:
import orjson as json
except ImportError: # pragma: no cover
import json # type: ignore

import pydantic

import ormar # noqa: I100, I202
from ormar.exceptions import ModelPersistenceError
from ormar.fields.parsers import encode_json
from ormar.models.mixins import AliasMixin
from ormar.models.mixins.relation_mixin import RelationMixin

Expand Down Expand Up @@ -207,8 +203,8 @@ def dump_all_json_fields_to_str(cls, model_dict: Dict) -> Dict:
:rtype: Dict
"""
for key, value in model_dict.items():
if key in cls._json_fields and not isinstance(value, str):
model_dict[key] = json.dumps(value)
if key in cls._json_fields:
model_dict[key] = encode_json(value)
return model_dict

@classmethod
Expand Down
6 changes: 3 additions & 3 deletions ormar/queryset/queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class LegacyRow(dict): # type: ignore
import ormar # noqa I100
from ormar import MultipleMatches, NoMatch
from ormar.exceptions import (
ModelListEmptyError,
ModelPersistenceError,
QueryDefinitionError,
ModelListEmptyError,
)
from ormar.queryset import FieldAccessor, FilterQuery, SelectAction
from ormar.queryset.actions.order_action import OrderAction
Expand Down Expand Up @@ -1065,8 +1065,8 @@ async def bulk_create(self, objects: List["T"]) -> None:
:type objects: List[Model]
"""
ready_objects = [obj.prepare_model_to_save(obj.dict()) for obj in objects]
# don't use execute_many, as in databases it's executed in a loop

# don't use execute_many, as in databases it's executed in a loop
# instead of using execute_many from drivers
expr = self.table.insert().values(ready_objects)
await self.database.execute(expr)
Expand Down
2 changes: 1 addition & 1 deletion ormar/signals/signal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import inspect
from typing import Any, Callable, Dict, Tuple, Type, TYPE_CHECKING, Union
from typing import Any, Callable, Dict, TYPE_CHECKING, Tuple, Type, Union

from ormar.exceptions import SignalDefinitionError

Expand Down
69 changes: 52 additions & 17 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ aiomysql = ">=0.0.21,<0.0.23"
aiosqlite = "^0.17.0"
aiopg = "^1.3.3"
asyncpg = ">=0.24,<0.26"
orjson = "*"

# Sync database drivers for standard tooling around setup/teardown/migrations.
psycopg2-binary = "^2.9.1"
Expand Down
Loading

0 comments on commit 4ed267e

Please sign in to comment.