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 has is_dict set to True.

It will also generate an __init__ for you based on what you specify as the fields 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