Examples
The examples collection.
Daily Routine
Let's make a more sophisticated class for daily routine. Here is what it finally will be able to do:
class MyDailyRoutine(DailyRoutine):
breakfast = '9:00'
lunch = '12:00'
dinner = '-1:33' # invalid data
>>> MyDailyRoutine._timepoints
OrderedDict([('breakfast', 9:0), ('lunch', 12:0)])
Let's start with writing a custom class for a time interval (with a modulo of a day):
class ParseError(Exception):
pass
class Time:
def __init__(self, hour, min):
self.hour = hour
self.min = min
@classmethod
def parse(cls, value):
try:
value = strptime(value, '%H:%M')
except ValueError:
raise ParseError(value)
return cls(value.tm_hour, value.tm_min)
def __repr__(self):
return '%s:%s' % (self.hour, self.min)
def __add__(self, other):
if isinstance(other, str):
try:
other = self.parse(other)
except ParseError:
return NotImplemented
elif not isinstance(other, Time):
return NotImplemented
min = self.min + other.min
hour, min = min // 60, min % 60
hour = (self.hour + other.hour + hour) % 24
return self.__class__(hour, min)
As you understand, you can add time intervals:
>>> Time(1, 34) + '23:55'
1:29
Then we are going to define a class for the respective mark. We want it to understand strings and Time
instances as well:
from declared import SkipMark
class time(Mark):
collect_into = '_timepoints'
def __init__(self, value):
self.value = value
@classmethod
def build(cls, mark, marks, owner):
if isinstance(mark, Time):
return mark
if isinstance(mark, str):
try:
return Time.parse(mark)
except ParseError:
raise SkipMark
return Time.parse(mark.value)
time.register(str)
time.register(Time)
That's all, now we can declare daily routines:
class DailyRoutine(Declared, extract=time):
# here go the attributes
We can do that, or just leave this class empty and inherit it.
Lazy declaration
Let's try to write a daily routine with "floating" time of getting up. Here is how we can do it using lazy declarations:
from declared import Declared, lazy
class DailyRoutine(Declared, extract=time):
def __init__(self, get_up):
if isinstance(get_up, str):
get_up = Time.parse(get_up)
self.get_up = get_up
@lazy
def breakfast(self):
return self.get_up + '0:30'
@lazy
def lunch(self):
return self.get_up + '3:30'
>>> routine = MyDailyRoutine('8:30')
>>> routine.process_declared()
>>> routine._timepoints
OrderedDict([('breakfast', 9:0), ('lunch', 12:0)])
Django filters
In this example we will aggregate (declaratively) filters from Q and QuerySet objects.
Let's aggree on what can be considered a filter. Let it be any object, that defines filter
callable that takes iterable as a parameter
(usually, a queryset)
and returns iterable. The Q
objects don't define such interface, also, we probably would want to change it's repr()
behavior. Let's write a wrapper:
class qobj:
def __init__(self, qobj):
self.qobj = qobj
def filter(self, queryset):
return queryset.filter(self.qobj)
def __repr__(self):
pairs = ['%s=%s' % item for item in self.qobj.children]
return 'Q: %s' % ', '.join(pairs)
The string representation got better:
from django.db.models.query import Q, QuerySet
>>> Q(key='value')
<django.db.models.query_utils.Q at 0x7f5771937400>
>>> qobj(Q(key='value'))
Q: key=value
Then we define the class for filter mark:
class qsfilter(Mark):
collect_into = '_declared_filters'
def __init__(self, func):
self.func = func
def __repr__(self):
return self.func.__doc__ or self.func.__name__
def filter(self, queryset):
return self.func(queryset)
@classmethod
def build(cls, mark, *args):
if isinstance(mark, Q):
return qobj(mark)
return mark
qsfilter.register(Q)
Here is how we can declare a container with filters in it:
from declare import Declared
class Filter(Declared, extract=qsfilter):
@qsfilter
def take_one(queryset):
return queryset[:1]
text = Q(question_text__icontains='what')
>>> filters = Filters._declared_filters
>>> filters
OrderedDict([('take_one', take one), ('text', Q: question_text__icontains=what)])
>>> filters['take_one'].filter(Question.objects.all())
[<Question: What's up>]
Question
is a django model taken from the official tutorial.
That was similar to what we've done before and not very interesting. Now let's try to aggregate filters. For example, here is what can be done for combining with OR & AND operations:
class Filter(qor):
filter1 = qsfilter(..)
filter2 = qsfilter(..)
q1 = Q(..)
class Nested(qand):
filter3 = Q(..)
filter4 = qsfilter(..)
# ...
Nested
will combine filter3
and filter4
with AND operation. Filter
will combine filter1
, filter2
, q1
and Nested
with OR.
The nesting depth won't be limited. Looks pretty nice, doesn't it?
Also I think we will need a cascading filter: the one that will just apply filters to queryset one after another.
class filters_sequence(CascadeFilter):
filter1 = qsfilter(..)
filter2 = Q(..)
# ...
For the filter nesting to work we need qor
, qand
and CascadeFilter
to be filters themselves.
But they are classes...
Don't worry, we have metaclasses to the rescue:
from declared import DeclaredMeta
@qsfilter.register
class FiltersDeclaredMeta(DeclaredMeta, abc.ABCMeta):
def __repr__(cls):
return ', '.join(cls._declared_filters.keys())
class DeclaredFilters(metaclass=FiltersDeclaredMeta, extract=qsfilter):
@classmethod
def filter(cls):
raise NotImplementedError()
Now, since FiltersDeclaredMeta
is registered as qsfilter
, DeclaredFilters
will be considered an instance of qsfilter
.
As you can see, DeclaredFilters
defines .filter()
callable.
Let's write the implementations:
from functools import reduce
import operator
class ReducedFilters(DeclaredFilters):
@classmethod
def filter(cls, queryset):
filters = [f.filter(queryset)
for f in cls._declared_filters.values()]
if filters:
return reduce(cls.operation, filters)
return queryset
class qand(ReducedFilters):
operation = operator.and_
class qor(ReducedFilters):
operation = operator.or_
class CascadeFilter(DeclaredFilters):
@classmethod
def filter(cls, objects):
for f in cls._declared_filters.values():
objects = f.filter(objects)
return objects
That's all. You can try it yourself: here is the repository
with some code from the django official tutorial.
The branch declared
contains this example.