From f26fafe04c234cf344616909d440f75360a2dc80 Mon Sep 17 00:00:00 2001 From: collerek Date: Fri, 25 Feb 2022 10:46:33 +0100 Subject: [PATCH] fix json nullable column --- ormar/fields/model_fields.py | 4 ++-- ormar/fields/parsers.py | 4 ++-- ormar/models/newbasemodel.py | 14 +++---------- tests/test_queries/test_isnull_filter.py | 25 ++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 7a10c25..0b565aa 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -168,7 +168,7 @@ class ModelFieldFactory: unique=kwargs.pop("unique", False), pydantic_only=pydantic_only, autoincrement=autoincrement, - column_type=cls.get_column_type(**kwargs), + column_type=cls.get_column_type(**kwargs, sql_nullable=sql_nullable), choices=choices, encrypt_secret=encrypt_secret, encrypt_backend=encrypt_backend, @@ -516,7 +516,7 @@ class JSON(ModelFieldFactory, pydantic.Json): :return: initialized column with proper options :rtype: sqlalchemy Column """ - return sqlalchemy.JSON() + return sqlalchemy.JSON(none_as_null=kwargs.get("sql_nullable", False)) if TYPE_CHECKING: # pragma: nocover # noqa: C901 diff --git a/ormar/fields/parsers.py b/ormar/fields/parsers.py index e8b7301..9af25d8 100644 --- a/ormar/fields/parsers.py +++ b/ormar/fields/parsers.py @@ -2,7 +2,7 @@ import base64 import datetime import decimal import uuid -from typing import Any, Callable, Dict, Union +from typing import Any, Callable, Dict, Optional, Union import pydantic from pydantic.datetime_parse import parse_date, parse_datetime, parse_time @@ -37,7 +37,7 @@ def encode_bytes(value: Union[str, bytes], represent_as_string: bool = False) -> return value if isinstance(value, bytes) else value.encode("utf-8") -def encode_json(value: Any) -> str: +def encode_json(value: Any) -> Optional[str]: value = json.dumps(value) if not isinstance(value, str) else re_dump_value(value) value = value.decode("utf-8") if isinstance(value, bytes) else value return value diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index d05498e..3a4177f 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -22,14 +22,11 @@ from typing import ( import databases import pydantic import sqlalchemy + +from ormar.fields.parsers import encode_json from ormar.models.utils import Extra from pydantic import BaseModel -try: - import orjson as json -except ImportError: # pragma: no cover - import json # type: ignore - import ormar # noqa I100 from ormar.exceptions import ModelError, ModelPersistenceError @@ -925,12 +922,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass """ if column_name not in self._json_fields: return value - if not isinstance(value, str): - try: - value = json.dumps(value) - except TypeError: # pragma no cover - pass - return value.decode("utf-8") if isinstance(value, bytes) else value + return encode_json(value) def _extract_own_model_fields(self) -> Dict: """ diff --git a/tests/test_queries/test_isnull_filter.py b/tests/test_queries/test_isnull_filter.py index df37d7b..4c368e1 100644 --- a/tests/test_queries/test_isnull_filter.py +++ b/tests/test_queries/test_isnull_filter.py @@ -34,6 +34,18 @@ class Book(ormar.Model): year: int = ormar.Integer(nullable=True) +class JsonModel(ormar.Model): + class Meta(ormar.ModelMeta): + metadata = metadata + database = database + tablename = "jsons" + + id = ormar.Integer(primary_key=True) + text_field = ormar.Text(nullable=True) + json_field = ormar.JSON(nullable=True) + json_not_null = ormar.JSON() + + @pytest.fixture(autouse=True, scope="module") def create_test_database(): engine = sqlalchemy.create_engine(DATABASE_URL) @@ -83,3 +95,16 @@ async def test_is_null(): assert len(tolkien.books) == 2 assert tolkien.books[0].year == 1955 assert tolkien.books[0].title == "The Lord of the Rings" + + +@pytest.mark.asyncio +async def test_isnull_json(): + async with database: + author = await JsonModel.objects.create(json_not_null=None) + assert author.json_field is None + non_null_text_fields = await JsonModel.objects.all(text_field__isnull=False) + assert len(non_null_text_fields) == 0 + non_null_json_fields = await JsonModel.objects.all(json_field__isnull=False) + assert len(non_null_json_fields) == 0 + non_null_json_fields = await JsonModel.objects.all(json_not_null__isnull=False) + assert len(non_null_json_fields) == 1