From 070ff023a0e1e94bbbd0834e8d610f7c84556d39 Mon Sep 17 00:00:00 2001 From: huangsong Date: Tue, 21 Dec 2021 16:33:50 +0800 Subject: [PATCH 1/7] add jsonb in postgresql --- docs/fields/field-types.md | 7 +++++++ ormar/fields/model_fields.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/docs/fields/field-types.md b/docs/fields/field-types.md index 99c5409..484208e 100644 --- a/docs/fields/field-types.md +++ b/docs/fields/field-types.md @@ -140,6 +140,13 @@ You can pass `timezone=True` for timezone aware database column. * Sqlalchemy column: `sqlalchemy.JSON` * Type (used for pydantic): `pydantic.Json` +### JSONB + +`JSONB()` has no required parameters, only support it in the postgresql. + +* Sqlalchemy column: `sqlalchemy.dialects.postgresql.JSONB` +* Type (used for pydantic): `pydantic.Json` + ### LargeBinary `LargeBinary(max_length)` has a required `max_length` parameter. diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index a819b61..2456455 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -520,6 +520,29 @@ class JSON(ModelFieldFactory, pydantic.Json): return sqlalchemy.JSON() +class JSONB(JSON): + """ + JSONB field factory that construct Field classes and populated their values, + only support postgresql + """ + + _type = pydantic.Json + _sample = '{"jsonb": "jsonb"}' + + @classmethod + def get_column_type(cls, **kwargs: Any) -> Any: + """ + Return proper type of db column for given field type. + Accepts required and optional parameters that each column type accepts. + + :param kwargs: key, value pairs of sqlalchemy options + :type kwargs: Any + :return: initialized column with proper options + :rtype: sqlalchemy Column + """ + return sqlalchemy.dialects.postgresql.JSONB() + + if TYPE_CHECKING: # pragma: nocover # noqa: C901 @overload From 94c5b232048a9b34da4740c50df8893eba59ac71 Mon Sep 17 00:00:00 2001 From: huangsong Date: Tue, 21 Dec 2021 17:00:14 +0800 Subject: [PATCH 2/7] use the dict to store the receivers in signal --- ormar/signals/signal.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/ormar/signals/signal.py b/ormar/signals/signal.py index e2c5275..65f8ea0 100644 --- a/ormar/signals/signal.py +++ b/ormar/signals/signal.py @@ -45,7 +45,7 @@ class Signal: """ def __init__(self) -> None: - self._receivers: List[Tuple[Union[int, Tuple[int, int]], Callable]] = [] + self._receivers: Dict[Tuple[Union[int, Tuple[int, int]]], Callable] = {} def connect(self, receiver: Callable) -> None: """ @@ -63,8 +63,8 @@ class Signal: "Signal receivers must accept **kwargs argument." ) new_receiver_key = make_id(receiver) - if not any(rec_id == new_receiver_key for rec_id, _ in self._receivers): - self._receivers.append((new_receiver_key, receiver)) + if new_receiver_key not in self._receivers: + self._receivers[new_receiver_key] = receiver def disconnect(self, receiver: Callable) -> bool: """ @@ -75,15 +75,9 @@ class Signal: :return: flag if receiver was removed :rtype: bool """ - removed = False new_receiver_key = make_id(receiver) - for ind, rec in enumerate(self._receivers): - rec_id, _ = rec - if rec_id == new_receiver_key: - removed = True - del self._receivers[ind] - break - return removed + receiver = self._receivers.pop(new_receiver_key, None) + return True if receiver is not None else False async def send(self, sender: Type["Model"], **kwargs: Any) -> None: """ @@ -93,10 +87,10 @@ class Signal: :param kwargs: arguments passed to receivers :type kwargs: Any """ - receivers = [] - for receiver in self._receivers: - _, receiver_func = receiver - receivers.append(receiver_func(sender=sender, **kwargs)) + receivers = [ + receiver_func(sender=sender, **kwargs) + for receiver_func in self._receivers.values() + ] await asyncio.gather(*receivers) From 15ed00a62f683737bc929040459f25da850f52c3 Mon Sep 17 00:00:00 2001 From: huangsong Date: Tue, 21 Dec 2021 17:35:51 +0800 Subject: [PATCH 3/7] remove jsonb, because the extra lib support --- docs/fields/field-types.md | 7 ------- ormar/fields/model_fields.py | 23 ----------------------- 2 files changed, 30 deletions(-) diff --git a/docs/fields/field-types.md b/docs/fields/field-types.md index 484208e..99c5409 100644 --- a/docs/fields/field-types.md +++ b/docs/fields/field-types.md @@ -140,13 +140,6 @@ You can pass `timezone=True` for timezone aware database column. * Sqlalchemy column: `sqlalchemy.JSON` * Type (used for pydantic): `pydantic.Json` -### JSONB - -`JSONB()` has no required parameters, only support it in the postgresql. - -* Sqlalchemy column: `sqlalchemy.dialects.postgresql.JSONB` -* Type (used for pydantic): `pydantic.Json` - ### LargeBinary `LargeBinary(max_length)` has a required `max_length` parameter. diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 2456455..a819b61 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -520,29 +520,6 @@ class JSON(ModelFieldFactory, pydantic.Json): return sqlalchemy.JSON() -class JSONB(JSON): - """ - JSONB field factory that construct Field classes and populated their values, - only support postgresql - """ - - _type = pydantic.Json - _sample = '{"jsonb": "jsonb"}' - - @classmethod - def get_column_type(cls, **kwargs: Any) -> Any: - """ - Return proper type of db column for given field type. - Accepts required and optional parameters that each column type accepts. - - :param kwargs: key, value pairs of sqlalchemy options - :type kwargs: Any - :return: initialized column with proper options - :rtype: sqlalchemy Column - """ - return sqlalchemy.dialects.postgresql.JSONB() - - if TYPE_CHECKING: # pragma: nocover # noqa: C901 @overload From 0fd533da9ecdc94040c3c424dce2d8b5d2fca701 Mon Sep 17 00:00:00 2001 From: huangsong Date: Tue, 21 Dec 2021 17:49:22 +0800 Subject: [PATCH 4/7] fix mypy check --- ormar/signals/signal.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ormar/signals/signal.py b/ormar/signals/signal.py index 65f8ea0..7568d5a 100644 --- a/ormar/signals/signal.py +++ b/ormar/signals/signal.py @@ -1,6 +1,6 @@ import asyncio import inspect -from typing import Any, Callable, Dict, List, TYPE_CHECKING, Tuple, Type, Union +from typing import Any, Callable, Dict, TYPE_CHECKING, Tuple, Type, Union from ormar.exceptions import SignalDefinitionError @@ -45,7 +45,7 @@ class Signal: """ def __init__(self) -> None: - self._receivers: Dict[Tuple[Union[int, Tuple[int, int]]], Callable] = {} + self._receivers: Dict[Union[int, Tuple[int, int]], Callable] = {} def connect(self, receiver: Callable) -> None: """ @@ -76,8 +76,9 @@ class Signal: :rtype: bool """ new_receiver_key = make_id(receiver) - receiver = self._receivers.pop(new_receiver_key, None) - return True if receiver is not None else False + receiver_func: Union[Callable, None] = self._receivers.pop( + new_receiver_key, None) + return True if receiver_func is not None else False async def send(self, sender: Type["Model"], **kwargs: Any) -> None: """ From 5db4ea966248c938e1c447e127f14fbe396d362b Mon Sep 17 00:00:00 2001 From: huangsong Date: Wed, 22 Dec 2021 10:54:15 +0800 Subject: [PATCH 5/7] SignalEmitter extends dict to save the signals --- ormar/signals/signal.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/ormar/signals/signal.py b/ormar/signals/signal.py index 7568d5a..32d73cd 100644 --- a/ormar/signals/signal.py +++ b/ormar/signals/signal.py @@ -1,6 +1,6 @@ import asyncio import inspect -from typing import Any, Callable, Dict, TYPE_CHECKING, Tuple, Type, Union +from typing import Any, Callable, Dict, Tuple, Type, TYPE_CHECKING, Union from ormar.exceptions import SignalDefinitionError @@ -95,21 +95,15 @@ class Signal: await asyncio.gather(*receivers) -class SignalEmitter: +class SignalEmitter(dict): """ Emitter that registers the signals in internal dictionary. If signal with given name does not exist it's auto added on access. """ - - if TYPE_CHECKING: # pragma: no cover - signals: Dict[str, Signal] - - def __init__(self) -> None: - object.__setattr__(self, "signals", dict()) - def __getattr__(self, item: str) -> Signal: - return self.signals.setdefault(item, Signal()) + return self.setdefault(item, Signal()) - def __setattr__(self, key: str, value: Any) -> None: - signals = object.__getattribute__(self, "signals") - signals[key] = value + def __setattr__(self, key: str, value: Signal) -> None: + if not isinstance(value, Signal): + raise ValueError(f"{value} is not valid signal") + self[key] = value From 5bf3c1a709cb2fe8116a34681ff93670212f0908 Mon Sep 17 00:00:00 2001 From: huangsong Date: Mon, 10 Jan 2022 09:56:48 +0800 Subject: [PATCH 6/7] fix ut coverage --- ormar/decorators/signals.py | 17 +++++++++++------ ormar/signals/signal.py | 2 +- tests/test_signals/test_signals.py | 7 +++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ormar/decorators/signals.py b/ormar/decorators/signals.py index 468fc0c..62abac0 100644 --- a/ormar/decorators/signals.py +++ b/ormar/decorators/signals.py @@ -1,4 +1,4 @@ -from typing import Callable, List, TYPE_CHECKING, Type, Union +from typing import Callable, List, Type, TYPE_CHECKING, Union if TYPE_CHECKING: # pragma: no cover from ormar import Model @@ -54,7 +54,8 @@ def post_save(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: return receiver(signal="post_save", senders=senders) -def post_update(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: +def post_update(senders: Union[Type["Model"], List[Type["Model"]]] +) -> Callable: """ Connect given function to all senders for post_update signal. @@ -67,7 +68,8 @@ def post_update(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: return receiver(signal="post_update", senders=senders) -def post_delete(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: +def post_delete(senders: Union[Type["Model"], List[Type["Model"]]] +) -> Callable: """ Connect given function to all senders for post_delete signal. @@ -119,7 +121,8 @@ def pre_delete(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: return receiver(signal="pre_delete", senders=senders) -def pre_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: +def pre_relation_add(senders: Union[Type["Model"], List[Type["Model"]]] +) -> Callable: """ Connect given function to all senders for pre_relation_add signal. @@ -132,7 +135,8 @@ def pre_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Call return receiver(signal="pre_relation_add", senders=senders) -def post_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: +def post_relation_add(senders: Union[Type["Model"], List[Type["Model"]]] +) -> Callable: """ Connect given function to all senders for post_relation_add signal. @@ -145,7 +149,8 @@ def post_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Cal return receiver(signal="post_relation_add", senders=senders) -def pre_relation_remove(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: +def pre_relation_remove(senders: Union[Type["Model"], List[Type["Model"]]] +) -> Callable: """ Connect given function to all senders for pre_relation_remove signal. diff --git a/ormar/signals/signal.py b/ormar/signals/signal.py index 32d73cd..d5b4a5c 100644 --- a/ormar/signals/signal.py +++ b/ormar/signals/signal.py @@ -105,5 +105,5 @@ class SignalEmitter(dict): def __setattr__(self, key: str, value: Signal) -> None: if not isinstance(value, Signal): - raise ValueError(f"{value} is not valid signal") + raise SignalDefinitionError(f"{value} is not valid signal") self[key] = value diff --git a/tests/test_signals/test_signals.py b/tests/test_signals/test_signals.py index 4832682..3dbcfa6 100644 --- a/tests/test_signals/test_signals.py +++ b/tests/test_signals/test_signals.py @@ -7,6 +7,7 @@ import sqlalchemy import ormar from ormar import post_delete, post_save, post_update, pre_delete, pre_save, pre_update +from ormar.signals import SignalEmitter from ormar.exceptions import SignalDefinitionError from tests.settings import DATABASE_URL @@ -77,6 +78,12 @@ def test_passing_callable_without_kwargs(): pass +def test_invalid_signal(): + emitter = SignalEmitter() + with pytest.raises(SignalDefinitionError): + emitter.save = 1 + + @pytest.mark.asyncio async def test_signal_functions(cleanup): async with database: From c1d42498b91cf5a5c3fbfcbf45ec9d18083bb619 Mon Sep 17 00:00:00 2001 From: huangsong Date: Mon, 10 Jan 2022 09:58:17 +0800 Subject: [PATCH 7/7] fix indent --- ormar/decorators/signals.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/ormar/decorators/signals.py b/ormar/decorators/signals.py index 62abac0..54e97e5 100644 --- a/ormar/decorators/signals.py +++ b/ormar/decorators/signals.py @@ -54,8 +54,7 @@ def post_save(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: return receiver(signal="post_save", senders=senders) -def post_update(senders: Union[Type["Model"], List[Type["Model"]]] -) -> Callable: +def post_update(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: """ Connect given function to all senders for post_update signal. @@ -68,8 +67,7 @@ def post_update(senders: Union[Type["Model"], List[Type["Model"]]] return receiver(signal="post_update", senders=senders) -def post_delete(senders: Union[Type["Model"], List[Type["Model"]]] -) -> Callable: +def post_delete(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: """ Connect given function to all senders for post_delete signal. @@ -121,8 +119,7 @@ def pre_delete(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: return receiver(signal="pre_delete", senders=senders) -def pre_relation_add(senders: Union[Type["Model"], List[Type["Model"]]] -) -> Callable: +def pre_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: """ Connect given function to all senders for pre_relation_add signal. @@ -135,8 +132,7 @@ def pre_relation_add(senders: Union[Type["Model"], List[Type["Model"]]] return receiver(signal="pre_relation_add", senders=senders) -def post_relation_add(senders: Union[Type["Model"], List[Type["Model"]]] -) -> Callable: +def post_relation_add(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: """ Connect given function to all senders for post_relation_add signal. @@ -149,8 +145,7 @@ def post_relation_add(senders: Union[Type["Model"], List[Type["Model"]]] return receiver(signal="post_relation_add", senders=senders) -def pre_relation_remove(senders: Union[Type["Model"], List[Type["Model"]]] -) -> Callable: +def pre_relation_remove(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: """ Connect given function to all senders for pre_relation_remove signal.