jashin.dictattr - Encapsulate dictionary with class

jashin.dictattr provides ItemAttr and DictModel to define class that wraps dictionary objects.

Usage

To wrap a dictionary like this,

userdict = {
    "name": "test user",
    "age": 20,
    "registered": "2000-01-01"
}

you can define a class with ItemAttr and DictModel.

from datetime import date
from jashin.dictattr import ItemAttr, DictModel
from dateutil.parser import parse as dateparse

class User(DictModel):
    name = ItemAttr()
    age = ItemAttr()
    registered = ItemAttr()

user = User(userdict)

print(user.name) # prints "test user"

Assignning value to the member updates corresponding item in the source dictionary object.

user.age = 30
print(user.age)          # prints 30
print(userdict["age"])  # prints 30

You can specify function to convert value from/to the source dictionary.

def load_date(s: str) -> date:
    return dateparse(s).date()

def dump_date(d: date) -> str:
    return d.isoformat()

class User(DictModel):
    name = ItemAttr()
    age = ItemAttr()
    registered = ItemAttr(load_date, dump_date)

user = User(userdict)

print(user.name) # prints "test user"
print(repr(user.registered))  # prints "datetime.date(2000, 1, 1)"

# Update registered
user.registered = date(2999, 1, 1)
print(userdict) # prints {'name': 'test user', 'age': 20, 'registered': '2999-01-01'}

Type annotation

ItemAttr is Generic type so you can supply type annotations to the attributes.

class User(DictModel):
    name = ItemAttr[str]()
    age = ItemAttr[int]()
    registered = ItemAttr(load_date, dump_date)

Type checkers like Mypy can check usage of the User object.

Although type is not specified to User.registered attribute, Mypy can infer the type of the attribute from load_date function specified as load function.

Nested item

Child item of the source dictionary can be an another item. For example, following dictionary

bookdict = {
    "title": "A title of book",
    "author": {
        "name": "test user",
        "age": 20,
    }
}

can be wrapped like this.

class Book(DictModel):
    title = ItemAttr[str]()
    author = ItemAttr(User)

The author of the source dictionary is mapped to User class defined above, so we can get name of the author as follows.

book = Book(bookdict)
print(book.author.name) # prints "test user"

Assignment to the nested item also works.

book.author.name = "updated name"
print(bookdict["author"]["name"]) # prints "updated name"

Sequcence of nested child items

Sequence of nested child items are supported with SequenceAttr class.

groupdict = {
    "name": "A group name",
    "members": [
        {"name": "member1"}, {"name": "member2"}
    ]
}

class Group(DictModel):
    name = ItemAttr[str]()
    members = SequenceAttr(User)

group = Group(groupdict)
print(group.members[0].name) # prints "member1"

newmember = User({"name": "member3"})
group.members.append(newmember)
print(groupdict["members"][2])  # prints {'name': 'member3'}

Mapping of nested child items

Mapping of nested child items are supported with MappingAttr class.

teamdict = {
    "name": "A Team name",
    "roles": {
        "manager": {
            "name": "I'm a manager"
        },
        "member1": {
            "name": "I'm a member1"
        },
    }
}

class Team(DictModel):
    name = ItemAttr[str]()
    roles = MappingAttr(User)

team = Team(teamdict)
print(team.roles["manager"].name) # prints "I'm a manager"

newrole = User({"name": "I'm a director"})
team.roles["director"] = newrole
print(teamdict["roles"]["director"])  # prints {'name': 'I'm a director'}

Reference

class jashin.dictattr.DictModel(values)

DictModel can be used to wrap dictionary object.

Parameters

values (Dict[str, Any]) – Dictionary to wrap.

DictModel class is not mandatory to use ItemAttr, but is provied to avoid boilerplate code. ItemAttr works any classes with __dictattr_get__() method.

__dictattr_get__()

Special method called by ItemAttr. Returns dictionary object to wrap.

Return type

Dict[str, Any]

class jashin.dictattr.ItemAttr(load=None, dump=None, *, name=None, default=<OMIT>)

Define an attribute to access an item of the source dictionary.

Parameters
  • load (Optional[Callable[[Any], ~F]]) – Convert value from the source dictionary item.

  • dump (Optional[Callable[[~F], Any]]) – Convert assigned value to store to the source dictionary item.

  • name (Optional[str]) – key in the source dictionary item. Default to attr name in class.

  • default (Any) – Default value is the item is not exit in the source dictionary.

ItemAttr get value from the dictionary obtained from self.__dictattr_get__() method of the class. The key to retrieve value from the dictionary is the name of attribute. If name is specified, it is used as the key instead.

load is a function with a single argument. If specified, load is called on read acess on the attribute to convert value in the dictionary.

dump is a function with a single argument. If specified, dump is called on write acess on the attribute to convert value to be stored to the dictionary. If the dump is not specified and the value assigned to has __dictattr_get__ method, the result of __dictattr_get__() method is stored to the dictionary.

default is the value used if the key is not exist on the source dictionary.

class jashin.dictattr.SequenceAttr(load=None, dump=None, *, name=None, default=<OMIT>)

SequenceAttr is ItemAttr specialized for sequence.

Parameters
  • load (Optional[Callable[[Any], ~F]]) – Convert value from the source dictionary item.

  • dump (Optional[Callable[[~F], Any]]) – Convert assigned value to store to the source dictionary item.

  • name (Optional[str]) – key in the source dictionary item. Default to attr name in class.

  • default (Any) – Default value is the item is not exit in the source dictionary.

Each elements in the sequence is converted by load/dump function on reading/writing the value.

class jashin.dictattr.MappingAttr(load=None, dump=None, *, name=None, default=<OMIT>)

MappingAttr is ItemAttr specialized for mapping.

Parameters
  • load (Optional[Callable[[Any], ~F]]) – Convert value from the source dictionary item.

  • dump (Optional[Callable[[~F], Any]]) – Convert assigned value to store to the source dictionary item.

  • name (Optional[str]) – key in the source dictionary item. Default to attr name in class.

  • default (Any) – Default value is the item is not exit in the source dictionary.

Each item value in the mapping is converted by load/dump function on reading/writing the value.

Unlike ItemAttr, MappingAttr is a generic class with two type parameter for key and value.

class Test(DictModel):
    mappingfield = MappingAttr[str, int]()  # mappingfield is Dict[str, int]