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
nameis specified, it is used as the key instead.loadis a function with a single argument. If specified,loadis called on read acess on the attribute to convert value in the dictionary.dumpis a function with a single argument. If specified,dumpis called on write acess on the attribute to convert value to be stored to the dictionary. If thedumpis not specified and the value assigned to has__dictattr_get__method, the result of__dictattr_get__()method is stored to the dictionary.defaultis 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/dumpfunction 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/dumpfunction 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]