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 (DailyRoutinein our example). If the lazy processing is used and.process_declared()was called from the instance, thenownermeans 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".