Files
sqlquerybuilder/__init__.py

279 lines
6.6 KiB
Python

import datetime
def is_number(s):
try:
float(s) # for int, long and float
except ValueError:
try:
complex(s) # for complex
except ValueError:
return False
return True
class classproperty(object):
def __init__(self, getter):
self.getter = getter
def __get__(self, instance, owner):
return self.getter(owner)
class QMixin(object):
AND = 'AND'
OR = 'OR'
NOT = 'NOT'
def _combine(self, other, conn):
return Operator(conn, self, other)
def __or__(self, other):
return self._combine(other, self.OR)
def __and__(self, other):
return self._combine(other, self.AND)
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,):
if self.left and self.right:
return "(%s %s %s)" % (self.left, self.op, self.right)
if self.right:
return "%s" % (self.right)
if self.left and self.op == "NOT":
return "%s %s" % (self.op, self.left)
return "%s" % self.left
__str__ = __repr__
def __bool__(self):
return bool(self.right or self.left)
__nonzero__ = __bool__
class Q(QMixin):
lookup_types = [
'iexact', 'contains', 'icontains',
'startswith', 'istartswith', 'endswith', 'iendswith', 'year',
'month', 'day', 'week_day', 'hour', 'minute', 'second',
'isnull', 'search', 'regex', 'iregex']
op_map = {
'lte': '<=',
'gte': '>=',
'lt': '<',
'gt': '>',
}
def __init__(self, *args, **kwargs):
self.conditions = kwargs
def __repr__(self,):
return self._compile()
def __bool__(self):
return bool(self.conditions)
__nonzero__ = __bool__
def _get_value(self, value):
if isinstance(value, int) or isinstance(value, float):
return value
if isinstance(value, datetime.datetime):
return "'%s'" % value.strftime("%Y-%m-%d %H:%M:%S")
if isinstance(value, datetime.date):
return "'%s'" % value.strftime("%Y-%m-%d")
return "'%s'" % value
def _process(self, compose_column, value):
arr = compose_column.split("__")
column = arr.pop(0)
try:
lookup = arr.pop(0)
except:
lookup = None
if lookup in self.lookup_types:
if lookup == "icontains":
return "{0} like '%{1}%'".format(column, value)
if lookup == "iendswith":
return "{0} like '%{1}'".format(column, value)
if lookup == "istartwith":
return "{0} like '{1}%'".format(column, value)
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 arr:
column = "DATEPART('{0}')__{1}".format(lookup, arr.pop(0))
return self._process(column, value)
else:
return "DATEPART('{0}')={1}".format(lookup, 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._get_value(value))
def _compile(self,):
filters = []
for k, v in self.conditions.items():
filters.append(self._process(k, v))
if filters:
return "(%s)" % " AND ".join(filters)
return ""
class SQLQuery(object):
def __init__(self, table=None):
if table:
self._table = table
self._values = []
self._order_by = []
self._group_by = []
self._joins = []
self._filters = Q()
self._excludes = Q()
def values(self, *args):
self._values = args
return self
def filter(self, *args, **kwargs):
for arg in args:
if issubclass(arg.__class__, QMixin):
self._filters &= arg
self._filters &= Q(**kwargs)
return self
def exclude(self, *args, **kwargs):
for arg in args:
if issubclass(arg.__class__, QMixin):
self._excludes &= arg
self._excludes &= Q(**kwargs)
return self
def order_by(self, *args):
self._order_by = args
return self
def group_by(self, *args):
self._group_by = args
return self
def join(self, table, on="", how="inner join"):
if on:
on = "ON " + on
self._joins.append("{how} {table} {on}".format(how=how, table=table, on=on))
return self
class SQLCompiler(object):
def get_columns(self,):
if self._values:
return ", ".join(self._values)
return "*"
def get_table(self,):
return self._table
def get_where(self):
filters = self._filters & ~self._excludes
if filters:
return "WHERE " + str(filters)
return ""
def get_order_by(self,):
conds = []
for cond in self._order_by:
order = ""
column = cond
try:
if cond[0] == "-":
order = "desc"
column = cond[1:]
except:
pass
conds.append("{0} {1}".format(column, order))
if conds:
return "ORDER BY " + ", ".join(conds)
return ""
def get_group_by(self,):
if self._group_by:
return "GROUP BY " + ", ".join(self._group_by)
return ""
def get_joins(self,):
if self._joins:
return " ".join(self._joins)
return ""
def _compile(self):
sql = """
SELECT {columns}
FROM {table}
{joins}
{where}
{group_by}
{order_by}
""".format(
columns=self.get_columns(),
table=self.get_table(),
joins=self.get_joins(),
where=self.get_where(),
group_by=self.get_group_by(),
order_by=self.get_order_by()
)
return sql
def __repr__(self):
return self._compile()
__str__ = __repr__
class Queryset(SQLCompiler, SQLQuery):
pass
class SQLModel(object):
@classproperty
def objects(cls):
return Queryset(cls.table)