diff --git a/clickhouse_orm/database.py b/clickhouse_orm/database.py index ec9407c..3e1cf39 100644 --- a/clickhouse_orm/database.py +++ b/clickhouse_orm/database.py @@ -1,7 +1,6 @@ import datetime import logging import re -from collections import namedtuple from math import ceil from string import Template @@ -9,14 +8,11 @@ import pytz import requests from .models import ModelBase -from .utils import import_submodules, parse_tsv +from .utils import Page, import_submodules, parse_tsv logger = logging.getLogger("clickhouse_orm") -Page = namedtuple("Page", "objects number_of_objects pages_total number page_size") - - class DatabaseException(Exception): """ Raised when a database operation fails. diff --git a/clickhouse_orm/query.py b/clickhouse_orm/query.py index e0ab42e..c31e6be 100644 --- a/clickhouse_orm/query.py +++ b/clickhouse_orm/query.py @@ -3,7 +3,8 @@ from math import ceil import pytz -from .utils import arg_to_sql, comma_join, string_or_func +from .engines import CollapsingMergeTree, ReplacingMergeTree +from .utils import Page, arg_to_sql, comma_join, string_or_func # TODO # - check that field names are valid @@ -22,10 +23,10 @@ class Operator(object): raise NotImplementedError # pragma: no cover def _value_to_sql(self, field, value, quote=True): - from clickhouse_orm.funcs import F - - if isinstance(value, F): + if isinstance(value, Cond): + # This is an 'in-database' value, rather than a python one return value.to_sql() + return field.to_db_string(field.to_python(value, pytz.utc), quote) @@ -256,9 +257,15 @@ class Q(object): return sql def __or__(self, other): + if not isinstance(other, Q): + return NotImplemented + return self.__class__._construct_from(self, other, self.OR_MODE) def __and__(self, other): + if not isinstance(other, Q): + return NotImplemented + return self.__class__._construct_from(self, other, self.AND_MODE) def __invert__(self): @@ -456,8 +463,6 @@ class QuerySet(object): return qs def _filter_or_exclude(self, *q, **kwargs): - from .funcs import F - inverse = kwargs.pop("_inverse", False) prewhere = kwargs.pop("prewhere", False) @@ -467,10 +472,10 @@ class QuerySet(object): for arg in q: if isinstance(arg, Q): condition &= arg - elif isinstance(arg, F): + elif isinstance(arg, Cond): condition &= Q(arg) else: - raise TypeError('Invalid argument "%r" to queryset filter' % arg) + raise TypeError(f"Invalid argument '{arg}' of type '{type(arg)}' to filter") if kwargs: condition &= Q(**kwargs) @@ -512,8 +517,6 @@ class QuerySet(object): The result is a namedtuple containing `objects` (list), `number_of_objects`, `pages_total`, `number` (of the current page), and `page_size`. """ - from .database import Page - count = self.count() pages_total = int(ceil(count / float(page_size))) if page_num == -1: @@ -543,8 +546,6 @@ class QuerySet(object): Adds a FINAL modifier to table, meaning data will be collapsed to final version. Can be used with the `CollapsingMergeTree` and `ReplacingMergeTree` engines only. """ - from .engines import CollapsingMergeTree, ReplacingMergeTree - if not isinstance(self._model_cls.engine, (CollapsingMergeTree, ReplacingMergeTree)): raise TypeError( "final() method can be used only with the CollapsingMergeTree and ReplacingMergeTree engines" diff --git a/clickhouse_orm/utils.py b/clickhouse_orm/utils.py index c4526f4..92c7c99 100644 --- a/clickhouse_orm/utils.py +++ b/clickhouse_orm/utils.py @@ -2,12 +2,17 @@ import codecs import importlib import pkgutil import re +from collections import namedtuple from datetime import date, datetime, timedelta, tzinfo from inspect import isclass from types import ModuleType from typing import Any, Dict, Iterable, List, Optional, Type, Union +Page = namedtuple("Page", "objects number_of_objects pages_total number page_size") +Page.__doc__ += "\nA simple data structure for paginated results." + + def escape(value: str, quote: bool = True) -> str: """ If the value is a string, escapes any special characters and optionally