Package: configable
Installationpip install configableExamplefromconfigableimportConfigable,settingclassCar(Configable):fuel_efficiency=setting(required=true,kind=float)units=setting(default='metric',choices=['metric','english'])defmpg(self):eff=self.fuel_efficiencyreturneffifself.units=='english'else2.35214583*effcar=Car({'fuel_efficiency':30,'units':'metric'})printcar.mpg()TutorialConfigableCreate configable classes by subclassing Configable and addingsettings.
Here’s a very simple example. For introductory purposes, the settings
are specified without options; this means they are optional, and their
values are taken as is from whatever configuration object instantiates
the class.classAnimal(Configable):species=setting()sound=setting()defspeak(self):printself.soundA Configable expects a single argument in its constructor: aconfiguration object. Aconfig objectis just a plain old python
dictionary, probably loaded from a JSON/YAML/etc config file. It should
contain properties that correspond to thesettingsdefined on the Configable it is instantiating.cheetah=Animal({'species':'acinonyx jubatus','sound':'rawr'})InheritanceWhen we subclass a subclass of Configable, we are doing some sort of
specialization of the parent class. This usually means there is a value
for a setting (or settings) defined on the parent class that implies the
specialization. Such special values are specified using theSUBTYPEproperty. For example,classCheetah(Animal):SUBTYPE={'species':'acinonyx jubatus'}defspeak(self):print'rawr'# hard-coded soundSUBTYPEshould be a dictionary with keys/values that correspond to
settings defined on the parent class. The purpose of theSUBTYPEproperty is to identify matching configuration objects (passed into the
parent class constructor) as instances of a special subtype of the
parent class. In other words, we’re injecting an inheritance scheme into
the inheritanceless hash table data structure.It’s way simpler by example; specifying theSUBTYPEproperty allows
this craziness:cheetah=Animal({'species':'acinonyx jubatus'})printisinstance(cheetah,Cheetah)//True!cheetah.speak()//'rawr'The Animal constructor was used to make a Cheetah instance.SUBTYPEcan also be specified as a staticmethod to handle more
general conditions:classCheetah(Animal):@staticmethoddefSUBTYPE(config):returnconfig.get('species')=='acinonyx jubatus'defspeak(self):print'rawr'# hard-coded soundIn this case, theSUBTYPEfunction should return True if its class
should be instantiated instead of the parent class for this
configuration object.Now you can have collections of animals in your config file:{"cheetah":{"species":"acinonyx jubatus"},"grizzly":{"species":"ursus arctos","sound":"roar!"}}and the correct subclass will be instantiated for each. Speaking of
which…ConfigableMapA ConfigableMap is simply a mapping between strings and Configables. The
class is dead simple; have a look at the source if you want to see
what’s goin down. Or, take it all in with this juicy example:fromconfigableimportConfigable,ConfigableMap,settingclassDog(Configable):breed=setting()classDogs(ConfigableMap):TYPE=Dogdogs=Dogs({'gracie':{'breed':'golden'},'spot':{'breed':'terrier'}})printisinstance(dogs.gracie,Dog)//True!Make sure you assign aTypeproperty to a Configable class in the
ConfigableMap prototype! You get all the benefits of subclass
instantiationConfigableArrayGivenConfigableMap,
you should be satisfied with an example,fromconfigableimportConfigable,ConfigableArray,settingclassDog(Configable):breed=setting()classDogs(ConfigableArray):TYPE=Dogdogs=Dogs([{'breed':'golden'},{'breed':'terrier'}])printisinstance(dogs[0],Dog)//True!settingCall this and assign the result to a property on your Configable
subclass (see numerous examples above). Generically (where shown option
values are the defaults),classType(Configable):setting_name=setting(required=False,# Booleandefault=None,# Instance of expected type (see 'kind' below)choices=None,# List of type expected in config objkind=None# Callable)Additionally, you can usesettingas a decorator.classType(Configable):@setting(required=False,# Booleandefault=None,# Instance of expected type (see 'kind' below)choices=None,# List of type expected in config objkind=None# Callable)defsetting_name(self,value):# Do something with valueThe decorated function will be called immediately before the value is
set on the instance (afterkindis
called). You shouldnottry to access other settings from inside this
function as they may not have been loaded yet. If you need to call the
parent class decorated function, you must use the following syntax,classParent(Configable):@setting()defname(self,value):self.capname=value.upper()classChild(Parent):@setting()defname(self,value):Parent.name(self,value)# MUST USE THIS SYNTAXprintself.capnameThe following are short explanations of the setting options.required {bool}If set to true, instantiation of the containing Configable subclass will
fail horribly if the setting is undefined on the configuration object.default {*}Pretty self-explanatory. You probably wantrequiredto
befalseif you are supplying a default setting value. The default
value should be arawvalue, i.e. of a type expected in the
configuration object (fundamental, like int, str, dict, list, etc). The
default, if taken, will be run through all the following setting checks
and ops.choices{iterable<*>}If your settings values are restricted to a small set, list them here.
Configable instantiation will fail if therawvalue is not in this
set.kind {callable}The raw value from the configuration object is run through this
function; therefore, it should accept a single value and return the
transformed value or throw an error. This is often set as a class,
especially when you want nested Configables.