diff --git a/sqlquerybuilder/__init__.py b/sqlquerybuilder/__init__.py index 1abc0f3..6294abc 100644 --- a/sqlquerybuilder/__init__.py +++ b/sqlquerybuilder/__init__.py @@ -8,7 +8,7 @@ PYTHON3 = True if sys.version_info[0] < 3: PYTHON3 = False -VERSION = "0.0.14" +VERSION = "0.0.18" def is_map(obj): @@ -23,13 +23,12 @@ def ensureUtf(s): if isinstance(s, str): return s else: - return s.encode('utf8', 'ignore') + return s.encode("utf8", "ignore") except: return str(s) class classproperty(object): - def __init__(self, getter): self.getter = getter @@ -38,10 +37,10 @@ class classproperty(object): class QMixin(object): - AND = 'AND' - OR = 'OR' - NOT = 'NOT' - UNION = 'UNION' + AND = "AND" + OR = "OR" + NOT = "NOT" + UNION = "UNION" def _combine(self, other, conn): return Operator(conn, self, other) @@ -54,18 +53,21 @@ class QMixin(object): def __and__(self, other): return self._combine(other, self.AND) - def __invert__(self,): + def __invert__( + self, + ): return Operator(self.NOT, self) class Operator(QMixin): - def __init__(self, op=None, left=None, right=None): self.op = op self.left = left self.right = right - def __repr__(self,): + def __repr__( + self, + ): if self.left and self.right: return "(%s %s %s)" % (self.left, self.op, self.right) @@ -86,11 +88,12 @@ class Operator(QMixin): class F(object): - def __init__(self, value): self.value = value - def __repr__(self,): + def __repr__( + self, + ): return "%s" % self.value __str__ = __repr__ @@ -105,16 +108,28 @@ class Q(QMixin): _mode = "MYSQL" lookup_types = [ - 'icontains', 'istartswith', 'iendswith', - 'contains', 'startswith', 'endswith', - 'year', 'month', 'day', 'week_day', 'hour', 'minute', 'second', - 'isnull', 'in'] + "icontains", + "istartswith", + "iendswith", + "contains", + "startswith", + "endswith", + "year", + "month", + "day", + "week_day", + "hour", + "minute", + "second", + "isnull", + "in", + ] op_map = { - 'lte': '<=', - 'gte': '>=', - 'lt': '<', - 'gt': '>', + "lte": "<=", + "gte": ">=", + "lt": "<", + "gt": ">", } def __init__(self, *args, **kwargs): @@ -122,7 +137,9 @@ class Q(QMixin): for arg in args: self.conditions[arg] = None - def __repr__(self,): + def __repr__( + self, + ): return self._compile() def __bool__(self): @@ -132,13 +149,13 @@ class Q(QMixin): @property def date_format(self): - if self._mode == 'SQL_SERVER': + if self._mode == "SQL_SERVER": return "%Y-%d-%m" return "%Y-%m-%d" @property def datetime_format(self): - if self._mode == 'SQL_SERVER': + if self._mode == "SQL_SERVER": return "%Y-%d-%m %H:%M:%S" return "%Y-%m-%d %H:%M:%S" @@ -155,7 +172,11 @@ class Q(QMixin): if isinstance(value, list) or isinstance(value, set) or is_map(value): return ", ".join([self._get_value(item) for item in value]) - if isinstance(value, F) or isinstance(value, QMixin) or isinstance(value, SQLQuery): + if ( + isinstance(value, F) + or isinstance(value, QMixin) + or isinstance(value, SQLQuery) + ): return ensureUtf(value) return "'%s'" % value @@ -163,7 +184,7 @@ class Q(QMixin): def _process(self, compose_column, value): arr = compose_column.split("__") column = arr.pop(0) - if column == '': + if column == "": column += "__" + arr.pop(0) try: @@ -193,28 +214,34 @@ class Q(QMixin): if lookup == "in": return "{0} in ({1})".format(column, self._get_value(value)) - if lookup == 'isnull': + if lookup == "isnull": op = "" if not value: op = "NOT " return "{0} is {1}NULL".format(column, op) - if lookup in ['year', 'month', 'day', 'hour', 'minute', 'second']: + if lookup in ["year", "month", "day", "hour", "minute", "second"]: if arr: - column = "DATEPART({0}, {1})__{2}".format(lookup, column, arr.pop(0)) + column = "DATEPART({0}, {1})__{2}".format( + lookup, column, arr.pop(0) + ) return self._process(column, value) else: return "DATEPART({0}, {1})={2}".format(lookup, column, value) if lookup in self.op_map.keys(): - return "{0}{1}{2}".format(column, self.op_map[lookup], self._get_value(value)) + return "{0}{1}{2}".format( + column, self.op_map[lookup], self._get_value(value) + ) if value is not None: return "{0}{1}{2}".format(column, "=", self._get_value(value)) return column - def _compile(self,): + def _compile( + self, + ): filters = [] for k, v in self.conditions.items(): filters.append(self._process(k, v)) @@ -226,7 +253,6 @@ class Q(QMixin): class SQLQuery(object): - def __init__(self, table=None, sql_mode="MYSQL", sql=None, **kwargs): self.kwargs = kwargs self._table = table @@ -241,11 +267,19 @@ class SQLQuery(object): self._limits = None self._sql = sql self._nolock = False + self._distinct = False - def has_filters(self,): - return self._order_by or self._group_by or self._joins\ - or self._filters or self._excludes or self._extra \ - or self._limits or self._values != ['*'] + def has_filters(self): + return ( + self._order_by + or self._group_by + or self._joins + or self._filters + or self._excludes + or self._extra + or self._limits + or self._values != ["*"] + ) def _q(self, *args, **kwargs): conds = Q() @@ -259,7 +293,7 @@ class SQLQuery(object): _conds._mode = self.sql_mode return conds & _conds - def _clone(self,): + def _clone(self): return copy.deepcopy(self) def values(self, *args): @@ -277,6 +311,11 @@ class SQLQuery(object): clone._filters &= self._q(*args, **kwargs) return clone + def distinct(self, enabled=True): + clone = self._clone() + clone._distinct = enabled + return clone + def exclude(self, *args, **kwargs): clone = self._clone() clone._excludes &= self._q(*args, **kwargs) @@ -299,7 +338,9 @@ class SQLQuery(object): clone = self._clone() if on: on = "ON " + on.format(table=self._table) - clone._joins.append("{how} {table} {on}".format(how=how, table=table, on=on)) + clone._joins.append( + "{how} {table} {on}".format(how=how, table=table, on=on) + ) return clone def extra(self, extra=None, **kwargs): @@ -317,21 +358,22 @@ class SQLQuery(object): class SQLCompiler(object): - - def get_columns(self,): + def get_columns(self): extra_columns = self.get_extra_columns() columns = ", ".join(self._values) return ", ".join([item for item in [columns, extra_columns] if item]) - def get_extra_columns(self,): + def get_extra_columns(self): return self._extra.get("select", "") - def get_extra_where(self,): + def get_extra_where(self): where = self._extra.get("where", []) if where: return " AND ".join(where) - def get_table(self,): + def get_table( + self, + ): return self._table def get_where(self): @@ -339,9 +381,13 @@ class SQLCompiler(object): extra_where = self.get_extra_where() if filters or extra_where: - return "WHERE " + " AND ".join([item for item in [filters, extra_where] if item]) + return "WHERE " + " AND ".join( + [item for item in [filters, extra_where] if item] + ) - def get_order_by(self,): + def get_order_by( + self, + ): conds = [] for cond in self._order_by: order = "" @@ -361,20 +407,22 @@ class SQLCompiler(object): if conds: return "ORDER BY " + ", ".join(conds) - def get_group_by(self,): + def get_group_by( + self, + ): if self._group_by: return "GROUP BY " + ", ".join(self._group_by) - def get_joins(self,): + def get_joins(self): if self._joins: return " ".join(self._joins) - def get_nolock(self,): + def get_nolock(self): if self._nolock: return " WITH (NOLOCK)" return "" - def get_limits(self,): + def get_limits(self): if self._limits and self.sql_mode != "SQL_SERVER": offset = self._limits.start limit = self._limits.stop @@ -385,7 +433,12 @@ class SQLCompiler(object): str += " OFFSET {0}".format(offset) return str - def get_top(self,): + def get_distinct(self): + if self._distinct: + return " DISTINCT " + return "" + + def get_top(self): if self._limits and self.sql_mode == "SQL_SERVER" and not self._limits.start: return "TOP {0}".format(self._limits.stop) @@ -397,15 +450,27 @@ class SQLCompiler(object): else: table = self.get_table() - sql = ["SELECT", self.get_top(), self.get_columns(), - "FROM", table, - self.get_nolock(), - self.get_joins(), self.get_where(), - self.get_group_by(), self.get_order_by(), - self.get_limits()] + sql = [ + "SELECT", + self.get_distinct(), + self.get_top(), + self.get_columns(), + "FROM", + table, + self.get_nolock(), + self.get_joins(), + self.get_where(), + self.get_group_by(), + self.get_order_by(), + self.get_limits(), + ] - if self.sql_mode == "SQL_SERVER" and self._limits and \ - self._limits.start is not None and self._limits.stop is not None: + if ( + self.sql_mode == "SQL_SERVER" + and self._limits + and self._limits.start is not None + and self._limits.stop is not None + ): conds = [] if self._limits.start is not None: conds.append("row_number > %s" % self._limits.start) @@ -416,12 +481,19 @@ class SQLCompiler(object): conds = " AND ".join(conds) paginate = "ROW_NUMBER() OVER (%s) as row_number" % self.get_order_by() - return ["SELECT * FROM (", "SELECT", ",".join([paginate, self.get_columns()]), - "FROM", table, - self.get_joins(), - self.get_where(), - self.get_group_by(), - self.get_limits(), ") as tbl_paginated WHERE ", conds] + return [ + "SELECT * FROM (", + "SELECT", + ",".join([paginate, self.get_columns()]), + "FROM", + table, + self.get_joins(), + self.get_where(), + self.get_group_by(), + self.get_limits(), + ") as tbl_paginated WHERE ", + conds, + ] return sql @@ -434,7 +506,7 @@ class SQLCompiler(object): __str__ = __repr__ @property - def sql(self,): + def sql(self): return self.__str__() def __or__(self, other): @@ -446,7 +518,6 @@ class Queryset(SQLCompiler, SQLQuery): class SQLModel(object): - @classproperty def objects(cls): - return Queryset(cls.table, getattr(cls, 'sql_mode', None)) + return Queryset(cls.table, getattr(cls, "sql_mode", None))