candv: Constants & Values¶
candv
allows to create complex enum-like constants.
In contrast to other methods of defining constants, candv
helps to organize and to document classes of constants.
This is done by providing iteration and lookup facilities for containers of constants.
Additionally, candv
provides an ability to attach human-readable names, descriptions, arbitrary values, and methods to constants.
Inspired by Constants from Twisted and Form Fields from Django.
Contents¶
Brief Overview¶
The most basic definition of simple constants:
1 2 3 4 5 6 7 | from candv import Constants
from candv import SimpleConstant
class TEAM(Constants):
RED = SimpleConstant()
BLUE = SimpleConstant()
|
And usage:
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | TEAM.RED # <constant 'TEAM.RED'>
TEAM['RED'] # <constant 'TEAM.RED'>
TEAM.get('RED') # <constant 'TEAM.RED'>
TEAM.get('GREEN') # None
TEAM.RED.name # 'RED'
TEAM.RED.full_name # 'TEAM.RED'
TEAM.RED.to_primitive() # {'name': 'RED'}
TEAM.RED.container # <constants container 'TEAM'>
TEAM # <constants container 'TEAM'>
TEAM.name # 'TEAM'
TEAM.full_name # 'TEAM'
len(TEAM) # 2
TEAM.has_name('RED') # True
TEAM.names() # ['RED', 'BLUE']
TEAM.iternames() # <odict_iterator object at 0x7f451013e0e0>
TEAM.constants() # [<constant 'TEAM.RED'>, <constant 'TEAM.BLUE'>]
TEAM.iterconstants() # <odict_iterator object at 0x7f45100f3450>
TEAM.items() # [('RED', <constant 'TEAM.RED'>), ('BLUE', <constant 'TEAM.BLUE'>)]
TEAM.iteritems() # <odict_iterator object at 0x7f451013bdb0>
TEAM.to_primitive() # {'name': 'TEAM', 'items': [{'name': 'RED'}, {'name': 'BLUE'}]}
|
Using with values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from candv import Values
from candv import ValueConstant
class TEAM(Values):
RED = ValueConstant(1)
BLUE = ValueConstant(2)
TEAM.values() # [1, 2]
TEAM.itervalues() # <map object at 0x7f450ffdb1c0>
TEAM.get_by_value(1) # <constant 'TEAM.RED'>
TEAM.filter_by_value(1) # [<constant 'TEAM.RED'>]
TEAM.RED.value # 1
TEAM.RED.to_primitive() # {'name': 'RED', 'value': 1}
|
Using with human-readable names:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from candv import Constants
from candv import VerboseConstant
class Countries(Constants):
au = VerboseConstant("Australia")
uk = VerboseConstant("United Kingdom")
us = VerboseConstant("United States")
Countries.au.name # 'au'
Countries.au.verbose_name # 'Australia'
Countries.au.help_text # None
Countries.au.to_primitive() # {'name': 'au', 'verbose_name': 'Australia', 'help_text': None}
|
With values and names:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from candv import Values
from candv import VerboseValueConstant
class SkillLevel(Values):
rki = VerboseValueConstant(0, "rookie")
avg = VerboseValueConstant(1, "average")
vtn = VerboseValueConstant(2, "veteran")
ace = VerboseValueConstant(3, "ace")
SkillLevel.avg.value # 1
SkillLevel.avg.name # 'avg'
SkillLevel.avg.full_name # 'SkillLevel.avg'
SkillLevel.avg.verbose_name # 'average'
SkillLevel.avg.help_text # None
SkillLevel.avg.to_primitive() # {'name': 'avg', 'value': 1, 'verbose_name': 'average', 'help_text': None}
|
Plays well with verboselib or, say, Django translation strings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from candv import Constants
from candv import VerboseConstant
from verboselib import Translations
translations = Translations(
domain="the_app",
locale_dir_path="locale",
)
_ = translations.gettext_lazy
class UnitType(Constants):
aircraft = VerboseConstant(_("aircraft"))
ship = VerboseConstant(_("ship"))
train = VerboseConstant(_("train"))
vehicle = VerboseConstant(_("vehicle"))
|
Supports custom methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from candv import Constants
from candv import SimpleConstant
class SupportedLanguages(Constants):
en = SimpleConstant()
ru = SimpleConstant()
@classmethod
def get_default(cls):
return cls.en
SupportedLanguages.get_default() # <constant 'SupportedLanguages.en'>
|
And custom types of constants:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from candv import Constants
from candv import SimpleConstant
from candv import with_constant_class
class MissionStatus(SimpleConstant):
...
class MissionStatuses(with_constant_class(MissionStatus), Constants):
not_loaded = MissionStatus()
loaded = MissionStatus()
playing = MissionStatus()
|
It’s also possible to define hierarchies:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | from candv import Constants
from candv import SimpleConstant
class STATUS(Constants):
SUCCESS = SimpleConstant()
ERROR = SimpleConstant().to_group(Constants,
INVALID = SimpleConstant(),
NOT_FOUND = SimpleConstant(),
INTERNAL = SimpleConstant(),
)
STATUS.names() # ['SUCCESS', 'ERROR']
STATUS.ERROR # <constants group 'STATUS.ERROR'>
STATUS.ERROR.full_name # 'STATUS.ERROR'
STATUS.ERROR.INTERNAL # <constant 'STATUS.ERROR.INTERNAL'>
STATUS.ERROR.INTERNAL.full_name # 'STATUS.ERROR.INTERNAL'
STATUS.ERROR.names() # ['INVALID', 'NOT_FOUND', 'INTERNAL']
|
Usage¶
The concept behind candv
is that constants are grouped together into containers.
Those containers are special classes having constants as class attributes. All containers are created by subclassing Constants
. Containers cannot be instantiated.
In their turn, constants are special objects, which are instances of SimpleConstant
or of its derivatives.
As containers are classes and constants are instances of classes, they can and actually have own methods and attributes.
It is possible to add custom functionality simply by subclassing Constants
and SimpleConstant
respectively.
In addition to the basic constants and basic containers, candv
also provides extended ones like VerboseConstant
, ValueConstant
, Values
, etc.
Simple constants¶
Simple constants are really simple. They do not have any particular values attached and resemble enum.Enum
used with enum.auto
:
1 2 3 4 5 6 7 | from candv import Constants
from candv import SimpleConstant
class STATUS(Constants):
SUCCESS = SimpleConstant()
FAILURE = SimpleConstant()
|
Here, STATUS
is a subclass of Constants
. It acts as a container:
8 9 | >>> STATUS
<constants container 'STATUS'>
|
All containers have the following attributes:
name
full_name
By default they are equal to the name of the class itself:
10 11 12 13 14 | >>> STATUS.name
'STATUS'
>>> STATUS.full_name
'STATUS'
|
Note
If there is a reason on the Earth to define custom names, it can be done:
class STATUS(Constants):
name = "foo"
full_name = f"package.{name}"
SUCCESS = SimpleConstant()
FAILURE = SimpleConstant()
The same attributes are available to all constants as well:
15 16 17 18 19 | >>> STATUS.SUCCESS.name
'SUCCESS'
>>> STATUS.SUCCESS.full_name
'STATUS.SUCCESS'
|
As can be seen from the above, names of constants are equal to the names of container’s attributes. And full names combine names of constants with full names of their containers. Custom values are not allowed.
Next, all containers have a member-access API similar to the API of Python’s dict
:
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | >>> STATUS.names()
['SUCCESS', 'FAILURE']
>>> STATUS.iternames()
<odict_iterator object at 0x7f289fa6e680>
>>> STATUS.constants()
[<constant 'STATUS.SUCCESS'>, <constant 'STATUS.FAILURE'>]
>>> STATUS.iterconstants()
<odict_iterator object at 0x7f289fa6ecc0>
>>> STATUS.items()
[('SUCCESS', <constant 'STATUS.SUCCESS'>), ('FAILURE', <constant 'STATUS.FAILURE'>)]
>>> STATUS.iteritems()
<odict_iterator object at 0x7f289fa1e360>
>>> list(STATUS)
['SUCCESS', 'FAILURE']
>>> len(STATUS)
2
>>> STATUS['SUCCESS']
<constant 'STATUS.SUCCESS'>
>>> 'SUCCESS' in STATUS
True
>>> STATUS.has_name('PENDING')
False
>>> STATUS.get('XXX')
None
>>> STATUS.get('XXX', default=999)
999
|
Note
Since 1.1.2 it is possible to list constants and get the same result by calling values()
and
itervalues()
:
>>> STATUS.values()
[<constant 'STATUS.SUCCESS'>, <constant 'STATUS.FAILURE'>]
>>> STATUS.itervalues()
<odict_iterator object at 0x7f289fa17b30>
These methods are overridden in Values
(see the section below).
In addition to the item-access, containers also provide a dot-access for their constants:
58 59 | >>> STATUS.SUCCESS
<constant 'STATUS.SUCCESS'>
|
Finally, every constant has access to own containers via the container
attribute:
60 61 | >>> STATUS.SUCCESS.container
<constants container 'STATUS'>
|
Constants with values¶
Constants with values are created via ValueConstant
and can have arbitrary values attached to them.
Such constants have to be contained by derivatives of Values
class. This enables additional functionality like inverse lookups, i.e. lookups of constants by their values.
1 2 3 4 5 6 7 8 | from candv import ValueConstant
from candv import Values
class TEAMS(Values):
NONE = ValueConstant('#EEE')
RED = ValueConstant('#F00')
BLUE = ValueConstant('#00F')
|
Here, TEAMS
is a subclass of Values
, which is a specialized version of Constants
. And ValueConstant
is a specialized version of SimpleConstant
:
9 10 11 12 13 | >>> Values.mro()
[<constants container 'Values'>, <constants container 'Constants'>, <class 'object'>]
>>> ValueConstant.mro()
[<class 'candv.ext.ValueConstant'>, <class 'candv.core.SimpleConstant'>, <class 'object'>]
|
So, TEAMS
has all of the attributes and methods described above. Its values()
method returns actual values of its constants:
14 15 16 17 18 | >>> TEAMS.values()
['#EEE', '#F00', '#00F']
>>> TEAMS.itervalues()
<map object at 0x7f289fa54ac0>
|
Values of constants themselves are also accessible:
19 20 | >>> TEAMS.RED.value
'#F00'
|
In addition to the previously mentioned get()
method, Values
provides get_by_value()
method:
21 22 | >>> TEAMS.get_by_value('#F00')
<constant 'TEAMS.RED'>
|
It is allowed for constants to have multiple constants with same values. However, in such case the get_by_value()
method will return the first matching constant considering the order constants are defined:
1 2 3 4 | class FOO(Values):
ATTR1 = ValueConstant('one')
ATTR2 = ValueConstant('two')
ATTR1_DUB = ValueConstant('one')
|
5 6 | >>> FOO.get_by_value('one')
<constant 'FOO.ATTR1'>
|
If there is a real need to have multiple constants with same values, it’s possible to get all of them by their value using filter_by_value()
method:
7 8 | >>> FOO.filter_by_value('one')
[<constant 'FOO.ATTR1'>, <constant 'FOO.ATTR1_DUB'>]
|
Verbose constants¶
Verbose constants are special constants with human-readable names and help messages.
They can be useful when there’s a need to present constants as possible choices to a user.
Usually, this is achieved by defining each constant literal as a separate global variable, followed by construction of a lookup dictionary or tuple:
1 2 3 4 5 6 7 8 9 | COUNTRY_AU = 'au'
COUNTRY_UK = 'uk'
COUNTRY_US = 'us'
COUNTRIES_NAMES = (
(COUNTRY_AU, "Australia"),
(COUNTRY_UK, "United States"),
(COUNTRY_US, "United Kingdom"),
)
|
This is hard to use and to maintain already. And its very common for names to come with descriptions or help texts, which means additional complexity.
In the contrast, it’s possible to use VerboseConstant
to keep definitions coupled and concise:
1 2 3 4 5 6 7 8 9 10 11 | from candv import Constants
from candv import VerboseConstant
class Countries(Constants):
au = VerboseConstant("Australia")
uk = VerboseConstant("United Kingdom")
us = VerboseConstant(
verbose_name="United States",
help_text="optional description",
)
|
Verbose constants are derived from SimpleConstant
in their nature:
12 13 | >>> VerboseConstant.mro()
[<class 'candv.ext.VerboseConstant'>, <class 'candv.ext.VerboseMixin'>, <class 'candv.core.SimpleConstant'>, <class 'object'>]
|
And in addition to the basic attributes of SimpleConstant
, instances of VerboseConstant
have extra optional attributes:
verbose_name
help_text
14 15 16 17 18 19 20 21 22 23 24 | >>> Countries.au.name
'au'
>>> Countries.au.verbose_name
'Australia'
>>> Countries.au.help_text
None
>>> Countries.us.help_text
'optional description'
|
Attributes of verbose constants can be lazy translations, for example, provided by verboselib or, say, Django translation strings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from candv import Constants
from candv import VerboseConstant
from verboselib import Translations
translations = Translations(
domain="the_app",
locale_dir_path="locale",
)
_ = translations.gettext_lazy
class UnitType(Constants):
aircraft = VerboseConstant(_("aircraft"))
ship = VerboseConstant(_("ship"))
train = VerboseConstant(_("train"))
vehicle = VerboseConstant(_("vehicle"))
|
Verbose constants with values¶
Another type of constants supported by candv
out of the box are verbose constants with values.
Intuitively, the constant class which allows that is VerboseValueConstant
:
Obviously, it needs to be contained by Values
or by its derivatives:
1 2 3 4 5 6 7 8 9 | from candv import Values
from candv import VerboseValueConstant
class SkillLevel(Values):
rki = VerboseValueConstant(0, "rookie")
avg = VerboseValueConstant(1, "average")
vtn = VerboseValueConstant(2, "veteran")
ace = VerboseValueConstant(3, "ace")
|
Here, constants have attributes of both ValueConstant
and VerboseConstant
:
10 11 12 13 14 15 16 17 18 19 20 | >>> VerboseValueConstant.mro()
[<class 'candv.ext.VerboseValueConstant'>, <class 'candv.ext.VerboseMixin'>, <class 'candv.ext.ValueConstant'>, <class 'candv.core.SimpleConstant'>, <class 'object'>]
>>> SkillLevel.avg.name
'avg'
>>> SkillLevel.avg.full_name
'SkillLevel.avg'
>>> SkillLevel.avg.value
1
|
Hierarchies¶
candv
library supports an exotic feature of constants hierarchies. This enables creation of subconstants:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from candv import Constants
from candv import SimpleConstant
class TREE(Constants):
LEFT = SimpleConstant().to_group(Constants,
LEFT = SimpleConstant(),
RIGHT = SimpleConstant(),
)
RIGHT = SimpleConstant().to_group(Constants,
LEFT = SimpleConstant(),
RIGHT = SimpleConstant(),
)
|
Here, the key point is to_group()
method. It turns a constant into a group
, which is both a constant and a container.
As for the arguments, the to_group()
method accepts a class that will be used to construct new container and instances of constants passed as keywords.
Groups can be created from any constant and any container can be used to store subconstants.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | >>> TREE.LEFT
<constants group 'TREE.LEFT'>
>>> TREE.LEFT.name
'LEFT'
>>> TREE.LEFT.full_name
'TREE.LEFT'
>>> TREE.LEFT.constant_class
<class 'candv.base.Constant'>
>>> TREE.LEFT.names()
['LEFT', 'RIGHT']
>>> TREE.LEFT.LEFT
<constant 'TREE.LEFT.LEFT'>
>>> TREE.LEFT.LEFT.full_name
'TREE.LEFT.LEFT'
>>> TREE.LEFT.LEFT.container
<constants group 'TREE.LEFT'>
|
Serialization¶
There are several ways to serialize candv
constants:
- Using
pickle
. - Converting to a primitive and then to a JSON or similar.
Pickling¶
Usually, pickling should be avoided. However, there are situations, when it cannot be avoided, e.g., when passing data to and from subprocesses, etc. If pickled objects really can be trusted, they are good to go.
candv
constants are pickle
-able. For example, there’s a definition of STATUS
in a constants.py
module:
1 2 3 4 5 6 7 8 | # constants.py
from candv import Constants
from candv import SimpleConstant
class STATUS(Constants):
SUCCESS = SimpleConstant()
FAILURE = SimpleConstant()
|
One process can create a variable and pickle it into a file:
1 2 3 4 5 6 7 8 9 | import pickle
from constants import STATUS
status = STATUS.SUCCESS
with open('foo.pkl', 'wb') as f:
pickle.dump(status, f)
|
And another process can restore the value:
1 2 3 4 | import pickle
with open('foo.pkl', 'rb') as f:
status = pickle.load(f)
|
5 6 | >>> status
<constant 'STATUS.SUCCESS'>
|
Converting to primitives¶
New in version 1.3.0.
Constants and containers can be converted into Python primitives for further serialization, for example, into JSONs.
This is done via to_primitive()
method.
For example, for simple constants defined previously:
>>> STATUS.to_primitive()
{'name': 'STATUS', 'items': [{'name': 'SUCCESS'}, {'name': 'FAILURE'}]}
>>> STATUS.SUCCESS.to_primitive()
{'name': 'SUCCESS'}
Same for constants with values:
>>> TEAMS.RED.to_primitive()
{'name': 'RED', 'value': '#F00'}
Note
Actual values of constants are out of scope of this library.
Any value can be used as a value of constants, but converting values into primitives is almost up to the user.
If a given value is a callable
(e.g., it’s a lazy translation string), candv
will call it to get it’s value.
If it has to_primitive(*args, **kwargs)
method, again, candv
will call it.
If it has isoformat()
method (it’s a date
, time
, etc.), candv
will call it either.
Everything else is expected to be a primitive by itself. Otherwise, it’s recommended to implement a custom constant class with custom conversion to primitives.
For verbose constants:
>>> Countries.au.to_primitive()
{'name': 'au', 'verbose_name': 'Australia', 'help_text': None}
For verbose constants with values:
>>> SkillLevel.ace.to_primitive()
{'name': 'ace', 'value': 3, 'verbose_name': 'ace', 'help_text': None}
And for hierarchies:
>>> TREE.to_primitive()
{'name': 'TREE', 'items': [{'name': 'LEFT', 'items': [{'name': 'LEFT'}, {'name': 'RIGHT'}]}, {'name': 'RIGHT', 'items': [{'name': 'LEFT'}, {'name': 'RIGHT'}]}]}
>>> TREE.LEFT.to_primitive()
{'name': 'LEFT', 'items': [{'name': 'LEFT'}, {'name': 'RIGHT'}]}
>>> TREE.LEFT.LEFT.to_primitive()
{'name': 'LEFT'}
Using with django¶
It’s possible to use verbose constants and verbose constants with values as choices
in djnago
models. See django-candv-choices details.
Additionally, see django-rf-candv-choices for using as choices
in django-rest-framework
.
Customization¶
It is possible to create custom classes of constant and of containers if standard functionality is not enough.
Custom definitions¶
There are several reasons why one would need to create a custom class of constants. For example:
- A need to vividly define a type of constants tracked by a certain container.
- A need to add extra methods to constants.
- A need to add extra attributes to constants.
Custom constants can be created simply by subclassing one of existing classes of constants, e.g.:
1 2 3 4 | from candv import SimpleConstant
class SupportedLanguage(SimpleConstant):
...
|
Here, SupportedLanguage
is quite ready to be used, e.g.:
5 6 7 8 9 10 | from candv import Constants
class SupportedLanguages(Constants):
en = SupportedLanguage()
fr = SupportedLanguage()
|
Despite SupportedLanguages
is a valid container, it does not enforce which constants are its valid members. For example, it’s still possible to use other constants:
11 12 13 14 15 | class SupportedLanguages(Constants):
en = SupportedLanguage()
fr = SupportedLanguage()
xx = SimpleConstant()
|
Here, all constants will be visible to the container:
16 17 | >>> SupportedLanguages.names()
['en', 'fr', 'xx']
|
If a container has methods relying on custom attributes of its members, such behavior might become troublesome.
One should specify constant_class
attribute in order to explicitly define constants supported by a container. So, a bit more correct definition would be:
18 19 20 21 22 | class SupportedLanguages(Constants):
constant_class = SupportedLanguage
en = SupportedLanguage()
fr = SupportedLanguage()
|
As a result, any constants except SupportedLanguage
and its derivatives will be ignored:
23 24 25 26 27 28 29 | class SupportedLanguages(Constants):
constant_class = SupportedLanguage
en = SupportedLanguage()
fr = SupportedLanguage()
xx = SimpleConstant()
|
30 31 | >>> SupportedLanguages.names()
['en', 'fr']
|
As definitions of the constant_class
attribute may clutter definitions of classes, it’s possible to lift them out of class bodies using a helper with_constant_class()
:
32 33 34 35 36 37 | from candv import with_constant_class
class SupportedLanguages(with_constant_class(SupportedLanguage), Constants):
en = SupportedLanguage()
fr = SupportedLanguage()
|
Of course, it’s possible to add custom methods and attributes to both constants and containers.
For example, the following constants allow formatting and parsing of operations having opcodes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | from candv import ValueConstant
from candv import Values
from candv import with_constant_class
class Opcode(ValueConstant):
def compose(self, *args):
chunks = [self.value, ]
chunks.extend(args)
return '/'.join(map(str, chunks))
class OPERATIONS(with_constant_class(Opcode), Values):
REQ = Opcode(100)
ACK = Opcode(200)
@classmethod
def decompose(cls, value):
chunks = value.split('/')
opcode = int(chunks.pop(0))
constant = cls.get_by_value(opcode)
return constant, chunks
|
Example usage of such constants is defined as follows.
24 25 26 27 28 | >>> OPERATIONS.ACK.compose(1, 2, 'foo')
'200/1/2/foo'
>>> OPERATIONS.decompose('200/1/2/foo')
(<constant 'OPERATIONS.ACK'>, ['1', '2', 'foo'])
|
The point here is to show that it is possible to add arbitrary attributes and logic to constants if really needed.
Adding verbosity¶
If custom constants need to have human-friendly attributes provided by VerboseConstant
, they can be added by VerboseMixin
:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from candv import SimpleConstant
from candv import VerboseMixin
class CustomConstant(VerboseMixin, SimpleConstant):
def __init__(self, arg1, agr2, verbose_name=None, help_text=None):
super().__init__(
verbose_name=verbose_name,
help_text=help_text,
)
self.arg1 = arg1
self.arg2 = arg2
|
Note
Here, verbose_name
and help_text
attributes must be passed as keyword arguments during super().__init__()
call.
Custom conversion to primitives¶
Custom constants which have complex attributes may need to define custom logic for converting their attributes into primitives. This is primarily needed for serialization, say, into JSON.
One has to override to_primitive()
method to define custom conversion logic. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from fractions import Fraction
from pprint import pprint
from candv import Constants
from candv import SimpleConstant
from candv import with_constant_class
class FractionConstant(SimpleConstant):
def __init__(self, value):
super().__init__()
self.value = value
def to_primitive(self, context=None):
primitive = super().to_primitive(context)
primitive.update({
'numerator': self.value.numerator,
'denominator': self.value.denominator
})
return primitive
class Fractions(with_constant_class(FractionConstant), Constants):
one_half = FractionConstant(Fraction(1, 2))
one_third = FractionConstant(Fraction(1, 3))
|
26 27 28 29 30 31 32 | >>> Fractions.one_half.to_primitive()
{'name': 'one_half', 'numerator': 1, 'denominator': 2}
>>> pprint(Fractions.to_primitive())
{'items': [{'denominator': 2, 'name': 'one_half', 'numerator': 1},
{'denominator': 3, 'name': 'one_third', 'numerator': 1}],
'name': 'Fractions'}
|
The plot in a nutshell:
- Define
to_primitive()
method which accepts an optionalcontext
argument.- Call parent’s method and get a primitive.
- Update that primitive with custom data which may depend on the context.
- Return the updated primitive.
The same can be applied to custom constant containers as well.
Hierarchies¶
Hierarchies are made by creating groups from constants objects. Since groups are created dynamically, original attributes and methods of constants have to be supplied to groups.
This can be done by overriding merge_into_group()
method. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from candv import Values
from candv import ValueConstant
class Opcode(ValueConstant):
# custom method that also needs to be available in groups
def compose(self, *args):
chunks = [self.value, ]
chunks.extend(args)
return '/'.join(map(str, chunks))
def merge_into_group(self, group):
super().merge_into_group(group)
group.compose = self.compose
|
16 17 18 19 | class FOO(Values):
BAR = Opcode(300).to_group(Values,
BAZ = Opcode(301),
)
|
20 21 22 23 24 | >>> FOO.BAR.compose(1, 2, 3)
'300/1/2/3'
>>> FOO.BAR.BAZ.compose(5, 6)
'301/5/6'
|
Here, the overridden method merge_into_group()
calls the original method of the
base class and adds a new compose
attribute to the group.
In this simple case the attribute is a reference to the compose()
method of the custom Opcode
class.
Warning
Attaching methods of existing objects to another objects can be not a good idea.
Consider using method factories or at least lambdas.
Changelog¶
1.5.0 (Nov 18, 2020)
API changes:
to_primitive()
methods use*args
and**kwargs
instead of thecontext
param.
1.4.0 (Oct 30, 2020)
API changes: public API is not changed, however, the following internal changes are introduced:
candv.base
is moved tocandv.core
.- Package-level definitions in
candv
are moved tocandv.ext
. candv.version
module is added.- Package-specific exceptions are defined in
candv.exceptions
. Work as before, but now exceptions can be caught more precisely if needed. candv.SimpleConstant
is a direct reference tocandv.core.SimpleConstant
now. Previously it was an alias tocandv.base.Constant
.candv.Constants
is a direct reference tocandv.core.Constants
now. Previously it was an alias tocandv.base.ConstantsContainer
.
Python support:
- Support of all Python versions below
3.7
is dropped.
Other:
- All external dependencies are removed.
- The license is switched from
LGPLv3
toMIT
. - The documentation is reworked to be more explanatory and concise.
1.3.1 (Aug 1, 2015)
- Comparison of constants is fixed: now it is based on constant’s
full_name
attribute (issue #11).
- Comparison of constants is fixed: now it is based on constant’s
1.3.0 (Dec 31, 2014)
to_primitive()
method is implemented. This can be used for serialization, for example, into JSON (issue #1). See usage and customization for more info.
1.2.0 (Oct 11, 2014)
- Core classes are significantly refactored.
constant_class
now usescandv.SimpleConstant
as the default value (instead ofNone
, see Customization for more info).- Support of groups is reimplemented: now they are classes just as other constants containers (previously groups were instances of patched containers). So, groups automatically gain all of those attributes and methods which usual containers have.
- Constant’s
container
attribute is public now. Groups of constants have it too (see Hierarchies). - API of containers is made really close to API of Python’s
dict
(see usage for more info):__getitem__
,__contains__
,__len__
and__iter__
magic methods are implemented.contains
method is renamed tohas_name
.get_by_name
method is removed in favor of__getitem__
method.get
method with support of default value is introduced.
- All objects (containers, groups and constants) have
name
andfull_name
attributes now. This may be useful if names of constants are used as key values (e.g. for Redis). - Also, all objects have good
repr
now. - Mixin factory
candv.with_constant_class()
is introduced. It may help to define containers in a more compact way. - A potential bug of uninitialized unbounded constants is fixed. Unbounded constant is an instance of a class which differs from container’s
constant_class
or its subclasses. This is unnatural case, but if this is really needed, it will not break now. - Exception messages are more informative now.
- Tests are moved out the package.
- Introductory documentation is improved. Other docs are updated too.
1.1.2 (Jul 6, 2014)
values
anditervalues
attributes are added toConstants
.
1.1.1 (Jun 21, 2014)
- switch license from
GPLv2
toLGPLv3
.
- switch license from
1.1.0 (Jun 21, 2014)
Choices
container is moved to django-candv-choices library.- Docs are updated and typos are fixed.
- Utils are stripped from requirements.
1.0.0 (Apr 15, 2014)
- Initial version.
Sources¶
Feel free to explore, fork or contribute: https://github.com/oblalex/candv
API¶
candv package¶
Submodules¶
candv.core module¶
Defines base constant and base container for constants.
-
class
candv.core.
SimpleConstant
[source]¶ Bases:
object
Base class for all constants.
Variables: name (str) – constant’s name: set up automatically and is equal to the name of the container’s attribute -
full_name
¶
-
merge_into_group
(group)[source]¶ Called automatically by the container after group construction.
Note
Redefine this method in all derived classes. Attach all custom attributes and methods to the group here.
Parameters: group – an instance of Constants
or of its subclass into which this constant will be mergedReturns: None
-
to_group
(group_class, **group_members)[source]¶ Convert a constant into a constants group.
Parameters: - group_class (class) – a class of group container which will be created
- group_members – unpacked dict which defines group members
Returns: a lazy constants group which will be evaluated by the container. Method
merge_into_group()
will be called during evaluation of the groupExample:
from candv import Constants from candv import SimpleConstant class FOO(Constants): A = SimpleConstant() B = SimpleConstant().to_group(Constants, B2 = SimpleConstant(), B0 = SimpleConstant(), B1 = SimpleConstant(), )
-
-
class
candv.core.
Constants
[source]¶ Bases:
object
Base class for creating constants containers.
Each constant defined within the container will remember its creation order.
See an example in
constants()
.Variables: constant_class – defines a class of constants which a container will store. This attribute MUST be set up manually when you define a new container type. Otherwise container will not be initialized. Default: None
Raises: CandvContainerMisusedError – if you try to create an instance of container. Containers are singletons and they cannot be instantiated. Their attributes must be used directly. -
constant_class
¶ Defines a top-level class of constants which can be stored by container
alias of
SimpleConstant
-
full_name
= 'Constants'¶
-
name
= 'Constants'¶
-
-
candv.core.
with_constant_class
(the_class)[source]¶ Create a mixin class with
constant_class
attribute.Allows to set a constant class for constants container outside container itself. This may help to create more readable container definition, e.g.:
from candv import Constants from candv import SimpleConstant from candv import with_constant_class class CustomConstant(SimpleConstant): ... class FOO(with_constant_class(CustomConstant), Constants): A = CustomConstant() B = CustomConstant()
>>> FOO.constant_class <class '__main__.CustomConstant'>
candv.exceptions module¶
-
exception
candv.exceptions.
CandvException
[source]¶ Bases:
Exception
Base exception
New in version 1.4.0.
-
exception
candv.exceptions.
CandvTypeError
[source]¶ Bases:
TypeError
,candv.exceptions.CandvException
New in version 1.4.0.
-
exception
candv.exceptions.
CandvInvalidGroupMemberError
[source]¶ Bases:
candv.exceptions.CandvTypeError
New in version 1.4.0.
-
exception
candv.exceptions.
CandvInvalidConstantClass
[source]¶ Bases:
candv.exceptions.CandvTypeError
New in version 1.4.0.
-
exception
candv.exceptions.
CandvContainerMisusedError
[source]¶ Bases:
candv.exceptions.CandvTypeError
New in version 1.4.0.
-
exception
candv.exceptions.
CandvConstantAlreadyBoundError
[source]¶ Bases:
ValueError
,candv.exceptions.CandvException
New in version 1.4.0.
-
exception
candv.exceptions.
CandvMissingConstantError
[source]¶ Bases:
KeyError
,candv.exceptions.CandvException
New in version 1.4.0.
-
exception
candv.exceptions.
CandvValueNotFoundError
[source]¶ Bases:
ValueError
,candv.exceptions.CandvException
New in version 1.4.0.
candv.ext module¶
Provides extra ready-to-use classes for constructing custom constants.
-
class
candv.ext.
VerboseMixin
(*args, **kwargs)[source]¶ Bases:
object
Adds
verbose_name
andhelp_text
attributes to constants.Arguments must be passed as kwargs.
Parameters: Example:
class CustomConstant(object): def __init__(self, arg1, arg2, kwarg1=None): pass class VerboseCustomConstant(VerboseMixin, CustomConstant): def __init__(self, arg1, arg2, kwarg1=None, verbose_name=None, help_text=None): super().__init__( arg1, arg2, kwarg1=kwarg1, verbose_name=verbose_name, help_text=help_text, )
-
class
candv.ext.
VerboseConstant
(verbose_name=None, help_text=None)[source]¶ Bases:
candv.ext.VerboseMixin
,candv.core.SimpleConstant
A constant with optional verbose name and optional help text.
Parameters: Variables:
-
class
candv.ext.
ValueConstant
(value)[source]¶ Bases:
candv.core.SimpleConstant
A constant with ability to hold arbitrary values.
Parameters: value – a value to attach to constant Variables: value – constant’s value
-
class
candv.ext.
VerboseValueConstant
(value, verbose_name=None, help_text=None)[source]¶ Bases:
candv.ext.VerboseMixin
,candv.ext.ValueConstant
A constant which can have both verbose name, help text, and a value.
Parameters: Variables:
-
class
candv.ext.
Values
[source]¶ Bases:
candv.core.Constants
A container for
ValueConstant
and its derivatives.Supports getting and filtering constants by their values plus listing values of all constants in container.
-
constant_class
¶ alias of
ValueConstant
-
classmethod
filter_by_value
(value)[source]¶ Get all constants which have given value.
Parameters: value – value of the constants to look for Returns: list of all found constants with given value
-
full_name
= 'Values'¶
-
classmethod
get_by_value
(value)[source]¶ Get a constant by its value.
Parameters: value – value of the constant to look for Returns: first found constant with given value Raises: CandvValueNotFoundError – if no constant in container has given value
-
classmethod
itervalues
()[source]¶ Get an iterator over values of all constants in the order they were defined.
Same as
values()
but returns an interator.Note
Overrides
itervalues()
since 1.1.2.
-
name
= 'Values'¶
-
classmethod
values
()[source]¶ List values of all constants in the order they were defined.
Returns: list
of valuesExample:
from candv import Values from candv import ValueConstant class FOO(Values): TWO = ValueConstant(2) ONE = ValueConstant(1) SOME = ValueConstant("some string")
>>> FOO.values() [2, 1, 'some string']
Note
Overrides
values()
since 1.1.2.
-