declared
Class based declarations.
Overview
declared a small (< 100 SLOC) python module aiming to solve one simple problem:
extracting from the class declaration instances of the specified type and forming an OrderedDict
out of them.
Note: For those who have used django-rest-framework
: there you can define fields as attributes of Serializer
class.
This package may be regarded as the generalized version of that.
Supports "lazy" processing of the declared marks. To use it, you just declare at least one "lazy" mark
and then call .process_declared()
from the instance or class.
Warning: Only Python 3 is supported yet.
Examples
The simplest one: let's extract all numbers.
from declared import Mark, Declared
class Int(Mark):
collect_into = '_ints'
Int.register(int)
class MyAttrs(Declared, extract=Int):
a = 1
b = a + 1
c = 'not an int'
extract
keyword says the marks are Int
instances (Mark
is default).
collect_into
specifies the attribute name we want to collect the marks into.
>>> MyAttrs._ints
OrderedDict([('a', 1), ('b', 2)])
The second example deals with the declaration of the points of time:
from time import strptime
from declared import Mark
class Time(Mark):
collect_into = '_timepoints'
def __init__(self, value):
self.value = value
@classmethod
def build(cls, mark, *args):
return strptime(mark.value, '%H:%M')
And then use it:
class DailyRoutine(Declared, extract=Time):
breakfast = Time('9:00')
lunch = Time('12:00')
>>> DailyRoutine._timepoints
OrderedDict([('breakfast', time.struct_time(...)), ('lunch', time.struct_time(...))])
>>> DailyRoutine.lunch
time.struct_time(...)
Not that produced struct_time
instances are extra useful, but it demonstrates that a mark can provide a build classmethod
(by default .build()
method returns the mark itself). The passed arguments to .build()
are:
owner
: The marks owner (DailyRoutine
in our example). If the lazy processing is used and.process_declared()
was called from the instance, thenowner
means that instance.marks_dict
: A dict of all marks
Instead of inheriting from Declared
, you can write metaclass=DeclaredMeta
, it means the same.
In django-rest-framework
there is serializers.SerializerMetaclass
used for the same purposes as DeclaredMeta
:
it collects serializers.Field
instances.
Let's imagine that serializers used declared
instead:
from rest_framework import serializers
class field(Mark):
collect_into = '_declared_fields'
@classmethod
def __subclasshook__(cls, C):
if issubclass(C, serializers.Field):
return True
return NotImplemented
class Serializer(Declared, extract=field):
pass
Now you could not discriminate (by it's declaration-parsing capabilities)
between our Serializer
and the original one, from the rest_framework
).
class Person(Serializer):
name = serializers.CharField()
age = serializers.IntegerField()
>>> Person._declared_fields
OrderedDict([('name', CharField()), ('age', IntegerField())])
In the Examples section you will find similar approach used to declare django
filters.
The role of fields there take Q and
QuerySet objects.
Actually, our first "Daily Routine" example can be made more interesting. Check out the Examples section.
Lazy declarations
There are little information usually available at the time of class declaration. So
one time you probably will need lazy declarations. With declared
you can do that,
providing a function that returns a mark or just a value, and decorating in with @lazy
:
class Greeting(Mark):
collect_into = '_greetings'
def __repr__(self):
return self.text
class Greetings(Declared, extract=Greeting):
def __init__(self, name):
self.name = name
@lazy
def in_english(owner):
return Greeting(text='Hello, %s' % owner.name)
If DeclaredMeta
finds at least one mark decorated with @lazy
, it will not process marks. Instead,
it will add process_declared
callable to the owner class. You can call .process_declared()
either from class
or an instance - owner
will be class or instance respectively.
>>> greetings = Greetings('John')
>>> greetings._greetings
AttributeError: 'Greetings' object has no attribute '_greetings'
>>> greetings.process_declared()
>>> greetings._greetings
OrderedDict([('in_english', Hello, John)])
Another example for lazy declarations you can find in the Examples section as a continuation to "Daily Routine".