The dictobj class
- class delfick_project.norms.obj.dictobj(*args, **kwargs)
This is an object that behaves like both an object (dot notation access to attributes) and like a dictionary (square bracket notation access to attrs).
It is a subclass of
dict
and hasis_dict
set to True.It will also generate an
__init__
for you based on what you specify as thefields
attribute.class MyAmazingKls(dictobj): fields = ["one", "two", ("three", 4)]
Creates an
__init__
that behaves like:def __init__(self, one, two, three=4): self.one = one self.two = two self.three = three
- fields
Must be an iterable of strings where each string is a valid variable name.
Or a tuple of
(<variable_name>, <dflt_value>)
where the<dflt_value>
is used if that variable is not passed into__init__
.Because it must be an iterable, it can also be a dictionary where the values are docstrings for the attributes!
class MyAmazingKls(dictobj): fields = { "one": "The first argument" , "two": "The second argument" , ("three", 4): "Optional third argument" }
Is a perfectly valid example.
Note that if you specify fields as a dictionary, you must specify those arguments as keyword arguments when you instantiate the class.
Also, if fields doesn’t make sense, an exception will be raised when you define your subclass of dictobj
Once an instance of
dictobj
is created you may access the attributes however you wish!instance = MyAmazingKls(one=1, two=2) instance.one == 1 instance["one"] == 1 instance.three == 4 list(instance.items()) == [("one", 1), ("two", 2), ("three", 4)] instance.as_dict() == {"one": 1, "two": 2, "three": 4}
The dictobj.Spec helper
We provide an attribute based DSL for converting attributes into the fields dictionary, with a classmethod for creating a create_spec object for the class.
So we can define something like:
class MyAmazingKls(dictobj.Spec):
one = dictobj.Field(string_spec)
two = dictobj.Field(string_spec, wrapper=listof)
three = dictobj.NullableField(string_spec)
Which is equivalent to
class MyAmazingKls(dictobj, metaclass=Field.metaclass):
one = Field(string_spec)
two = Field(string_spec, wrapper=listof)
three = Field(string_spec, nullable=True)
Which is equivalent to:
class MyAmazingKls(dictobj, Field.mixin):
fields = {
"one": string_spec
, "two": listof(string_spec())
, "three": defaulted(or_spec(none_spec(), string_spec()), None)
}
You then use these specs by normalising an instance of MyAmazingKls.FieldSpec.
For example:
from delfick_project.norms import dictobj, Meta
class D(dictobj.Spec):
one = dictobj.Field(sb.string_spec, wrapper=sb.required)
two = dictobj.Field(sb.integer_spec, default=20)
three = dictobj.NullableField(sb.boolean)
spec = D.FieldSpec()
d = spec.normalise(Meta.empty(), {"one": "hello"})
# Which is equivalent to
d = spec.empty_normalise(one="hello")
# and then you'll have
assert d.one == "hello"
assert d.two == 20
assert d.three is None
- class delfick_project.norms.field_spec.Field(spec=<class 'delfick_project.norms.spec_base.NotSpecified'>, help=None, formatted=False, wrapper=<class 'delfick_project.norms.spec_base.NotSpecified'>, default=<class 'delfick_project.norms.spec_base.NotSpecified'>, nullable=False, format_into=<class 'delfick_project.norms.spec_base.NotSpecified'>, after_format=<class 'delfick_project.norms.spec_base.NotSpecified'>)
Representation of a single Field
This has a reference to the mixin and metaclass in this file.
It also let’s you define things like:
Whether this is a formatted field
has a default
Is wrapped by some other spec class
- make_spec(meta, formatter)
Create the spec for this Field:
If callable, then call it
If is nullable
or the spec with none_spec
if we have an after format, do the same with that
if it has a default, wrap in defaulted
If it can be formatted, wrap in formatted
If it has a wrapper, wrap it with that
Return the result!
You can also have a form of dependency injection by combining this from objects from option_merge
from delfick_project.option_merge import MergedOptionStringFormatter, MergedOptions
from delfick_project.norms import dictobj, Meta
class D(dictobj.Spec):
thing = dictobj.Field(format_into=sb.any_spec, default="{things.thing}")
one = dictobj.Field(sb.overridden("{one}"), formatted=True)
two = dictobj.Field(sb.integer_spec)
class Thing:
_merged_options_formattable = True
thing = Thing()
everything = MergedOptions.using(
{ "things": {"thing": thing}
, "one": "three"
}
)
meta = Meta(everything, [])
spec = D.FieldSpec(formatter=MergedOptionStringFormatter)
d = spec.normalise(meta, {"two": 20})
assert d.thing is thing
assert d.one == "three"
assert d.two == 20