Getting started#
Simply import easytree
and create nested nodes on the fly using the dot notation.
>>> import easytree
>>> tree = easytree.dict()
>>> tree.foo.bar.baz = "Hello world!"
>>> tree
{
"foo": {
"bar": {
"baz": "Hello world!"
}
}
}
Inheritence#
easytree.dict
inherits from the native python dict
class.
>>> tree = easytree.dict({"foo":"bar"})
>>> isinstance(tree, dict)
True
easytree.list
also inherits from the native python list
class.
>>> numbers = easytree.list([1,3,5])
>>> isinstance(numbers, list)
True
Setting or assigning values#
Instead of raising an AttributeError
, reading a new attribute on an easytree.dict
creates and returns a new child Node
.
>>> tree = easytree.dict()
>>> tree.address # new undefined node
<Node 'address'>
Reading or setting an attribute on such child node dynamically casts it as an easytree.dict
.
>>> tree.address.country = "United States"
>>> tree.address # now a dict
{"country": "United States"}
Alternatively, using a list method such as append
dynamically casts the new node as an easytree.list
>>> tree = easytree.dict()
>>> tree.address
<Node 'address'>
>>> tree.address.country.append("United States")
>>> tree.address
{"country": ["United States"]}
Note
Technically, casting replaces the node with an appropriate class instance (e.g. dict or list) in the parent object
Of course, you can use the dot or bracket notation interchangeably, both to read and assign nested values
>>> tree = easytree.dict({"foo":"bar"})
>>> tree["foo"]
"bar"
>>> tree.foo
"bar"
Note
The bracket notation remains necessary if the key is not a valid attribute identifier name.
>>> tree = easytree.dict()
>>> tree["attribute with space"] = True
>>> tree
{"attribute with space": True}
Nested assignment#
Dictionaries assigned to an easytree.dict
or added to an easytree.list
are themselves cast as easytree.dict
instances, allowing you to use the dot notation on nested dictionaries.
>>> friends = easytree.list()
>>> friends.append({"firstname": "Alice"})
>>> isinstance(friends[0], easytree.dict)
True
>>> friends[0].address.country = "Netherlands"
>>> friends
[
{
"firstname": "Alice",
"address": {
"country": "Netherlands"
}
}
]
Lists assigned to an easytree.dict
are cast as easytree.list
instances.
>>> tree = easytree.dict({"numbers": [1,3,5]})
>>> isinstance(tree.numbers, easytree.list)
True
Tuple values assigned to an easytree.dict
are also cast.
>>> tree = easytree.dict({"country": ("France", {"capital": "Paris"})})
>>> isinstance(tree.country, tuple)
True
>>> tree.country[0]
'France'
>>> tree.country[0].capital
'Paris'
Getter#
The get
method of easytree.dict
is supercharged to query deeply-nested trees.
>>> profile = easytree.dict()
>>> profile.friends.append({"name":"Bob", "address":{"country":"France"}})
>>> profile.get(["friends", 0, "address", "country"])
France
>>> profile.get(["friends", 0, "address", "street"])
None
Hint
Normally, this would raise an error, as a list is not hashable. This means no collisions are possible between keys and such list queries.
Context manager#
The context manager returns the node, such that writing deeply-nested trees is easier:
>>> order = easytree.dict()
>>> with order.customer.delivery.address as a:
... a.country = "United States"
... a.city = "New York"
... a.street = "5th avenue"
>>> order
{
"order": {
"customer": {
"delivery": {
"address": {
"country": "United States",
"city": "New York",
"street": "5th avenue"
}
}
}
}
}
Because the append
method returns a reference to the last appended item, writing deeply-nested trees which combine easytree.dict
and easytree.list
nodes is also easy:
>>> profile = easytree.dict()
>>> with profile.friends.append({"firstname":"Flora"}) as friend:
... friend.birthday = "25/02"
... friend.address.country = "France"
>>> profile
{
"friends": [
{
"firstname": "Flora",
"birthday": "25/02",
"address": {
"country": "France"
}
}
]
}
The undefined node#
An undefined
node object created when an undefined attribute is read from an easytree.dict
node.
>>> person = easytree.dict()
>>> person.address
<Node 'address'>
Assigning or reading an attribute from an undefined
node casts it as a dictionary.
>>> person = easytree.dict()
>>> person.address.country = "Nigeria"
>>> person.address
{"country": "Nigeria"}
Using the bracket notation works identically.
>>> person = easytree.dict()
>>> person["address"].country = "Nigeria"
>>> person.address
{"country": "Nigeria"}
An undefined
node evaluates to False
.
>>> person = easytree.dict()
>>> if not person.address:
... print("address is missing")
address is missing
Pitfalls#
By definition, and unless an easytree is sealed or frozen, reading an undefined attribute will not raise an exception.
>>> profile = easytree.dict({"firstname":"David"})
>>> profile.firstnam #typo
<Node 'firstnam'>
Using a numeric key on an undefined node will cast the node as a dictionary, not a list.
>>> profile = easytree.dict({"firstname":"David"})
>>> profile.friends[0].name = "Flora"
>>> profile
{
"friends": {
0: "Flora"
}
}
Dictionary and lists added to an easytree are cast to an easytree.dict
or easytree.list
instance. This means identity is not preserved.
>>> point = {"x":1, "y":1}
>>> graph = easytree.list([point])
>>> point in graph
True
>>> graph[0] is point
False