# -*- coding: utf-8 -*-
# Copyright 2017-TODAY LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).
import properties
from datetime import datetime, date
from ..base_model import BaseModel
[docs]class Domain(properties.HasProperties):
"""This represents a full search query."""
OR = 'OR'
AND = 'AND'
def __init__(self, queries=None, join_with=AND):
"""Initialize a domain, with optional queries."""
self.query = []
if queries is not None:
for query in queries:
self.add_query(query, join_with)
[docs] @classmethod
def from_tuple(cls, queries):
"""Create a ``Domain`` given a set of complex query tuples.
Args:
queries (iter): An iterator of complex queries. Each iteration
should contain either:
* A data-set compatible with :func:`~domain.Domain.add_query`
* A string to switch the join type
Example::
[('subject', 'Test1'),
'OR',
('subject', 'Test2')',
('subject', 'Test3')',
]
# The above is equivalent to:
# subject:'Test1' OR subject:'Test2' OR subject:'Test3'
[('modified_at', datetime(2017, 01, 01)),
('status', 'active'),
]
# The above is equivalent to:
# modified_at:[2017-01-01T00:00:00Z TO *]
# AND status:"active"
Returns:
Domain: A domain representing the input queries.
"""
domain = cls()
join_with = cls.AND
for query in queries:
if query in [cls.OR, cls.AND]:
join_with = query
else:
domain.add_query(query, join_with)
return domain
[docs] def add_query(self, query, join_with=AND):
"""Join a new query to existing queries on the stack.
Args:
query (tuple or list or DomainCondition): The condition for the
query. If a ``DomainCondition`` object is not provided, the
input should conform to the interface defined in
:func:`~.domain.DomainCondition.from_tuple`.
join_with (str): The join string to apply, if other queries are
already on the stack.
"""
if not isinstance(query, DomainCondition):
query = DomainCondition.from_tuple(query)
if len(self.query):
self.query.append(join_with)
self.query.append(query)
def __str__(self):
"""Return a string usable as the query in an API request."""
if not self.query:
return '*'
return '(%s)' % ' '.join([str(q) for q in self.query])
[docs]class DomainCondition(properties.HasProperties):
"""This represents one condition of a domain query."""
field = properties.String(
'Field to search on',
required=True,
)
value = properties.String(
'String Value',
required=True,
)
@property
def field_name(self):
"""Return the name of the API field."""
return BaseModel._to_camel_case(self.field)
def __init__(self, field, value, **kwargs):
"""Initialize a new generic query condition.
Args:
field (str): Field name to search on. This should be the
Pythonified name as in the internal models, not the
name as provided in the API e.g. ``first_name`` for
the Customer's first name instead of ``firstName``.
value (mixed): The value of the field.
"""
return super(DomainCondition, self).__init__(
field=field, value=value, **kwargs
)
[docs] @classmethod
def from_tuple(cls, query):
"""Create a condition from a query tuple.
Args:
query (tuple or list): Tuple or list that contains a query domain
in the format of ``(field_name, field_value,
field_value_to)``. ``field_value_to`` is only applicable in
the case of a date search.
Returns:
DomainCondition: An instance of a domain condition. The specific
type will depend on the data type of the first value provided
in ``query``.
"""
field, query = query[0], query[1:]
try:
cls = TYPES[type(query[0])]
except KeyError:
# We just fallback to the base class if unknown type.
pass
return cls(field, *query)
def __str__(self):
"""Return a string usable as a query part in an API request."""
return '%s:"%s"' % (self.field_name, self.value)
[docs]class DomainConditionBoolean(DomainCondition):
"""This represents an integer query."""
value = properties.Bool(
'Boolean Value',
required=True,
)
def __str__(self):
"""Return a string usable as a query part in an API request."""
value = 'true' if self.value else 'false'
return '%s:%s' % (self.field_name, value)
[docs]class DomainConditionInteger(DomainCondition):
"""This represents an integer query."""
value = properties.Integer(
'Integer Value',
required=True,
)
def __str__(self):
"""Return a string usable as a query part in an API request."""
return '%s:%d' % (self.field_name, self.value)
[docs]class DomainConditionDateTime(DomainCondition):
"""This represents a date time query."""
value = properties.DateTime(
'Date From',
required=True,
)
value_to = properties.DateTime(
'Date To',
)
def __init__(self, field, value_from, value_to=None):
"""Initialize a new datetime query condition.
Args:
field (str): Field name to search on. This should be the
Pythonified name as in the internal models, not the
name as provided in the API e.g. ``first_name`` for
the Customer's first name instead of ``firstName``.
value_from (date or datetime): The start value of the field.
value_to (date or datetime, optional): The ending value for
the field. If omitted, will search to now.
"""
return super(DomainConditionDateTime, self).__init__(
field=field, value=value_from, value_to=value_to,
)
def __str__(self):
"""Return a string usable as a query part in an API request."""
value_to = self.value_to.isoformat() if self.value_to else '*'
return '%s:[%sZ TO %sZ]' % (
self.field_name,
self.value.isoformat(),
value_to,
)
TYPES = {
bool: DomainConditionBoolean,
int: DomainConditionInteger,
date: DomainConditionDateTime,
datetime: DomainConditionDateTime,
}
__all__ = [
'Domain',
'DomainCondition',
'DomainConditionBoolean',
'DomainConditionDateTime',
'DomainConditionInteger',
]