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 thedump
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]