Specs¶
spec_base is the core of input_algorithms and implements many specification
classes based off the Spec class.
A specification is an object with a normalise method and is used to validate
and transform data.
NotSpecified¶
-
class
input_algorithms.spec_base.NotSpecified¶ Tell the difference between None and not specified
Specifications¶
-
class
input_algorithms.spec_base.Spec(*pargs, **kwargs)¶ Default shape for a spec (specification, not test!)
- __init__
passes all *args and **kwargs to a
self.setupif that exists on the class- normalise
Calls one of the following functions on the class, choosing in this order:
- normalise_either
If defined, then this is meant to work with both a specified value as well as
NotSpecified.If this function returns
NotSpecified, then we continue looking for a function to handle this value.- normalise_empty
Called if the value is
NotSpecified.- default
Called if the value is
NotSpecified.- return NotSpecified
If the value is NotSpecified and the above don’t return, then the value is returned as is.
- normalise_filled
The value is not
NotSpecified
If none of those options are defined, then an error is raised complaining that we couldn’t work out what to do with the value.
Note that any validation errors should be an subclass of
input_algorithms.errors.BadSpec. This is because the default specs only catch such exceptions. Anything else is assumed to be a ProgrammerError and worthy of a traceback.- fake_filled
If
fakeis on the class that is used, otherwise ifdefaultis on the class that is used, otherwise we returnNotSpecified.This is used to generate fake data from a specification.
The idea is that a Spec is an object with a
normalisemethod that takes in two objects:metaandval.metaShould be an instance of
input_algorithms.meta.Metaand is used to indicate where we are. This should be passed to any children specifications.For example, if we are normalising a dictionary where a child specification is used on a particular value at a key called
a_key, we should do something similar to:child_spec().normalise(meta.at("a_key"), val["a_key"])
valShould be the value we are normalising. The normalising process should validate and transform the value to be what we are specifying.
For example listof(string_spec()) will transform a single string to be a list of one string.
When you create a subclass of
Specyou either implement one of thenormalise_*methods ornormaliseitself.
apply_validators¶
-
input_algorithms.spec_base.apply_validators(meta, val, validators, chain_value=True)¶ Apply a number of validators to a value.
- chain_value
Sets whether to pass the validated value into the next validator or whether to use the original value each time
All validators are tried and errors are collected.
If any fails, an error is raised, otherwise the value is returned.
Where value is the original value, or the result of the last validator depending on
chain_value.
Available Specs¶
pass_through_spec¶
- Usage
pass_through_spec().normalise(meta, val)Will not touch the value in any way and just return it.
always_same_spec¶
- Usage
always_same_spec(result).normalise(meta, val)Will ignore value and just return
result.
dictionary_spec¶
- Usage
dictionary_spec().normalise(meta, val)Will normalise
NotSpecifiedinto{}Specified values are valid if
type(val) is dictorval.is_dict() is True. If either those conditions are true, then the dictionary is returned as is.
dictof¶
- Usage
dictof(name_spec, value_spec).normalise(meta, val) # or dictof(name_spec, value_spec, nested=True).normalise(meta, val)This will first use
dictionary_speclogic on the value to ensure we are normalising a dictionary.It will then use
name_specandvalue_specon the items in the value to produce a resulting dictionary.For example if we have
{"a": 1, "b": 2}, using dictof is equivalent to:{ name_spec.normalise(meta.at("a"), "a"): value_spec.normalise(meta.at("a"), 1) , name_spec.normalise(meta.at("b"), "b"): value_spec.normalise(meta.at("b"), 2) }This specification will also do the same to any
dictionaryvalues it has ifnestedis set toTrue(defaults toFalse)It will also collect any errors and raise a collection of all errors it comes across.
tupleof¶
- Usage
tupleof(spec).normalise(meta, val)This specification will transform
NotSpecifiedinto()If we don’t have a tuple, we turn the value into a tuple of that value. Except for lists which are turned in a tuple.
The resulting tuple of items is returned.
listof¶
- Usage
listof(spec).normalise(meta, val) # or listof(spec, expect=typ).normalise(meta, val)This specification will transform
NotSpecifiedinto[]If we don’t have a list, we turn the value into a list of that value.
If
expectis specified, any item already passingisinstance(item, expect)is left alone, otherwisespecis used to normalise the value.The resulting list of items is returned.
set_options¶
- Usage
set_options(<key1>=<spec1>, ..., <keyn>=<specn>).normalise(meta, val) # For example set_options(key_1=integer_spec(), key_2=string_spec()).normalise(meta, val)This specification transforms
NotSpecifiedinto{}.Specified values are validated using
dictionary_specand then for each keyword argument we use either that key in thevalorNotspecifiedand normalise it with thespecfor that key.Finally, we assemble the result from all these keys in a dictionary and return that.
Errors are collected and raised in a group.
Extra keys in
valare ignored.
defaulted¶
- Usage
defaulted(spec, dflt).normalise(meta, val)This specification will return
dfltifvalisNotSpecified.Otherwise, it merely proxies
specand doesspec.normalise(meta, val)
required¶
- Usage
required(spec).normalise(meta, val)This specification will raise an error if
valisNotSpecified.Otherwise, it merely proxies
specand doesspec.normalise(meta, val)
boolean¶
- Usage
boolean().normalise(meta, val)This complains if the value is not
isintance(val, bool).Otherwise it just returns the
val.Note
This specification does not handle
NotSpecified. This is a deliberate decision. Use defaulted(boolean(), <dflt>) if you want to handle that.
directory_spec¶
- Usage
directory_spec().normalise(meta, val) # or directory_spec(spec).normalise(meta, val)This specification will first normalise
valwithspecifspecis specified.It then makes sure that
valis a string, exists, and is a directory.If it isn’t, an error is raised, otherwise the
valis returned.
filename_spec¶
- Usage
directory_spec().normalise(meta, val) # or filename_spec(spec).normalise(meta, val)This specification will first normalise
valwithspecifspecis specified.It then makes sure that
valis a string, exists, and is a file.If it isn’t, an error is raised, otherwise the
valis returned.
file_spec¶
- Usage
file_spec().normalise(meta, val)This will complain if
valis not a file object, otherwise it just returnsval.
string_spec¶
- Usage
string_spec().normalise(meta, val)This transforms
NotSpecifiedinto""If
valis specified, it will complain if notisinstance(val, six.string_types), otherwise it just returnsval.
integer_spec¶
- Usage
integer_spec().normalise(meta, val)This will complain if
valis not an integer, unless it hasisdigitand this function returnsTrue.We return
int(val)regardless.Note
This specification does not handle
NotSpecified. This is a deliberate decision. Use defaulted(integer_spec(), <dflt>) if you want to handle that.
float_spec¶
- Usage
float_spec().normalise(meta, val)If the
valis not aboolthen we dofloat(val)and return the result.Otherwise, or if that fails, an error is raised.
string_or_int_as_string_spec¶
- Usage
string_or_int_as_string_spec().normalise(meta, val)This transforms
NotSpecifiedinto""If the
valis not an integer or string, it will complain, otherwise it returnsstr(val).
valid_string_spec¶
- Usage
valid_string_spec(validator1, ..., validatorn).normalise(meta, val)This takes in a number of validator specifications and applies them to
valafter passing throughvalid_string_speclogic.Validators are just objects with
normalisemethods that happen to raise errors and return thevalas is.If none of the validators raise an error, the original
valis returned.
integer_choice_spec¶
- Usage
integer_choice_spec([1, 2, 3]).normalise(meta, val) # or integer_choice_spec([1, 2, 3], reason="Choose one of the first three numbers!").normalise(meta, val)This absurdly specific specification will make sure
valis an integer before making sure it’s one of thechoicesthat are provided.It defaults to complaining
Expected one of the available choicesunless you providereason, which it will use instead if it doesn’t match one of the choices.
string_choice_spec¶
- Usage
string_choice_spec(["a", "b", "c"]).normalise(meta, val) # or string_choice_spec(["a", "b", "c"], reason="Choose one of the first three characters in the alphabet!").normalise(meta, val)This absurdly specific specification will make sure
valis a string before making sure it’s one of thechoicesthat are provided.It defaults to complaining
Expected one of the available choicesunless you providereason, which it will use instead if it doesn’t match one of the choices.
create_spec¶
- Usage
create_spec( kls , validator1, ..., validatorn , key1=spec1, ..., keyn=specn ).normalise(meta, val)This specification will return
valas is if it’s already an instance ofkls.Otherwise, it will run
valthrough all thevalidator``s before using the ``key->``spec`` keyword arguments in aset_optionsspecification to create the arguments used to instantiate an instance ofkls.
or_spec¶
- Usage
or_spec(spec1, ..., specn).normalise(meta, val)This will keep trying
spec.normalise(meta, val)until it finds one that doesn’t raise aBadSpecerror.If it can’t find one, then it raises all the errors as a group.
match_spec¶
- Usage
match_spec((typ1, spec1), ..., (typn, specn)).normalise(meta, val) # or match_spec((typ1, spec1), ..., (typn, specn), fallback=fspec).normalise(meta, val)This will find the
specassociated with the firsttypthat succeedsisinstance(val, typ).Note
If
specis callable, we dospec().normalise(meta, val).If fallback is specified and none of the
typ``s match the ``valthenfpsecis used as thespec. It is also called first if it’s a callable object.If we can’t find a match for the
val, an error is raised.
and_spec¶
- Usage
and_spec(spec1, ..., specn).normalise(meta, val)This will do
val = spec.normalise(meta, val)for eachspecthat is provided and returns the finalval.If any of the ``spec``s fail, then an error is raised.
optional_spec¶
- Usage
optional_spec(spec).normalise(meta, val)This will return
NotSpecifiedif thevalisNotSpecified.Otherwise it merely acts as a proxy for
spec.
dict_from_bool_spec¶
- Usage
dict_from_bool_spec(dict_maker, spec).normalise(meta, val)If
valisNotSpecifiedthen we dospec.normalise(meta, {})If
valis a boolean, we first doval = dict_maker(meta, val)and then dospec.normalise(meta, val)to return the value.Example:
A good example is setting enabled on a dictionary:
spec = dict_from_bool_spec(lambda b: {"enabled": b}, set_options(enabled=boolean())) spec.normalise(meta, False) == {"enabled": False} spec.normalise(meta, {"enabled": True}) == {"enabled": True}
formatted¶
- Usage
formatted(spec, formatter).normalise(meta, val) # or formatted(spec, formatter, expected_type=typ).normalise(meta, val) # or formatted(spec, formatter, after_format=some_spec()).normlise(meta, val)This specification is a bit special and is designed to be used with
MergedOptionStringFormatterfrom theoption_mergelibrary (http://option-merge.readthedocs.org/en/latest/docs/api/formatter.html).The idea is that
meta.everythingis an instance ofMergedOptionsand it will create a new instance ofmeta.everything.__class__usingmeta.everything.convertersandmeta.everything.dont_prefixif they exist. Note that this should work with normal dictionaries as well.We then update our copy of
meta.everythingwithmeta.key_names()and create an instance offormatterusing this copy of themeta.everything,meta.pathandspec.normalise(meta, val)as the value.We call
formaton theformatterinstance, check that it’s an instance ofexpected_typeif that has been specified.Once we have our formatted value, we normalise it with after_format if that was specified.
And finally, return a value!
many_format¶
- Usage
many_format(spec, formatter).normalise(meta, val) # or many_format(spec, formatter, expected_type=typ).normalise(meta, val)This is a fun specification!
It essentially does
formatted(spec, formatter, expected_type=typ).normalise(meta, val)until the result doesn’t change anymore.Before doing the same thing on
"{{{val}}}".format(val)Example:
Let’s say we’re at
images.my_image.persistence.image_namein the configuration.This means
{_key_name_2}(which is frommeta.key_names()) is equal tomy_image.many_format(overridden("images.{_key_name_2}.image_name"), formatter=MergedOptionStringFormatter) # Is equivalent to formatted(overridden("{images.my_image.image_name}"), formatter=MergedOptionStringFormatter)This essentially means we can format a key in the options using other keys from the options!
overridden¶
- Usage
overridden(value).normalise(meta, val)This will return
valueregardless of whatvalis!
any_spec¶
- Usage
any_spec().normalise(meta, val)Will return
valregardless of whatvalis.
container_spec¶
- Usage
container_spec(kls, spec).normalise(meta, val)This will apply
spec.normalise(meta, val)and callklswith the result of that as the one argument.Note
if the
valis alreadyisinstance(val, kls)then it will just returnval.
delayed¶
- Usage
delayed(spec).normalise(meta, val)This returns a function that when called will do
spec.normalise(meta, val)
typed¶
- Usage
typed(kls).normalise(meta, val)This will return the value as is as long as it’s isinstance of
klsOtherwise it complains that it’s the wrong type
has¶
- Usage
has(prop1, prop2, ..., propn).normalise(meta, val)This will complain if the value does not have any of the specified properties (using hasattr)
tuple_spec¶
- Usage
tuple_spec(spec1, spec2, ..., specn).normalise(meta, val)Will complain if the value is not a tuple or doesn’t have the same number of items as specified specs.
Will complain if any of the specs fail for their respective part of val.
Returns the result of running all the values through the specs as a tuple.
none_spec¶
- Usage
none_spec().normalise(meta, val)Will complain if the value is not None. Otherwise returns None.
Defaults to None.
many_item_formatted_spec¶
- Usage
class FinalKls(dictobj): fields = ["one", "two", ("three", None)] class my_special_spec(many_item_formatted_spec): specs = [integer_spec(), string_spec()] def create_result(self, one, two, three, meta, val, dividers): if three is NotSpecified: return FinalKls(one, two) else: return FinalKls(one, two, three) # The rest of the options are optional creates = FinalKls value_name = "special" seperators = "^" optional_specs = [boolean()] spec = my_special_spec() spec.normalise(meta, "1^tree") == FinalKls(1, "tree") spec.normalise(meta, [1, "tree"]) == FinalKls(1, "tree") spec.normalise(meta, [1, tree, False]) == FinalKls(1, "tree", False)We can also define modification hooks for each part of the spec:
class my_special_spec(many_item_formatted_spec): specs = [integer_spec(), integer_spec(), integer_spec()] def spec_wrapper_2(self, spec, one, two, meta, val, dividers): return defaulted(spec, one + two) def determine_2(self, meta, val): return 42 def alter_2(self, one, meta, original_two, val): if one < 10: return original_two else: return original_two * 10 def alter_3(self, one, two, meta, original_three, val): if two < 100: return original_three else: return original_three * 100 def create_result(self, one, two, three, meta, val, dividers): return FinalKls(one, two, three)A spec for something that is many items Either a list or a string split by “:”
If it’s a string it will split by ‘:’ Otherwise if it’s a list, then it will use as is and will complain if it has two many values
It will use determine_<num> on any value that is still NotSpecified after splitting the val.
And will use alter_<num> on all values after they have been formatted.
Where <num> is 1 indexed index of the value in the spec specifications.
Finally, create_result is called at the end to create the final result from the determined/formatted/altered values.