Little python package implementing optional chaining
Project description
safebag
Safebag is a little python package implementing optional chaining.
Installation
pip install safebag
Usage
Code we want to avoid
if (
obj is not None
and obj.attr is not None
and obj.attr.attr is not None
and obj.attr.attr.attr is not None
and obj.attr.attr.attr.attr is not None
):
# Do something useful with obj.attr.attr.attr.attr
...
Pythonic solution
try:
print(obj.attr.attr.attr.attr)
# Do something useful with obj.attr.attr.attr.attr
except(NameError, AttributeError) as e:
# Do something useful with an error
Still it's not very clean way in case of multiple attribute handling in one place
try:
print(obj.attr.attr.attr.attr)
# Do something useful with obj.attr.attr.attr.attr
except(NameError, AttributeError) as e:
...
try:
print(obj.attr.attr)
# Do something useful with obj.attr.attr
except(NameError, AttributeError) as e:
...
try:
print(obj.attr)
# Do something useful with obj.attr
except(NameError, AttributeError) as e:
...
Usage example:
from safebag import chain, get_value
if attr := chain(obj).attr.attr.attr.attr:
# Do something useful with obj.attr.attr.attr.attr
print(get_value(attr))
if attr := chain(obj).attr.attr:
# Do something useful with obj.attr.attr
print(get_value(attr))
if attr := chain(obj).attr:
# Do something useful with obj.attr
print(get_value(attr))
Examples
chain [source]
Optional chain constructor, may be constructed from any object
Chain is used for building sequence of null-safe attribute calls
from __future__ import annotations
import dataclasses as dt
import typing
@dt.dataclass
class Node:
data: int
node: typing.Optional[Node]
nodes = Node(data=1, node=Node(data=2, node=None))
from safebag import chain
third_node_proxy = chain(nodes).node.node.node
print(third_node_proxy) # ChainProxy(data_object=None, bool_hook=False)
get_value [source]
Final value getter for optional chain.
Optional chain constructed from any object. Chain is used for building sequence of null-safe attribute calls.
from __future__ import annotations
import dataclasses as dt
import typing
@dt.dataclass
class Node:
data: int
node: typing.Optional[Node]
nodes = Node(data=1, node=Node(data=2, node=None))
from safebag import chain, get_value
third_node_proxy = chain(nodes).node.node.node
value = get_value(third_node_proxy)
assert value is None
next_node = chain(nodes).node
value = get_value(next_node) # Node(data=2, node=None)
Possible way of getting value
if next_node := chain(nodes).node:
print(next_node.get_value()) # Node(data=2, node=None)
Default can be passed as argument
if next_node := chain(nodes).node.node:
print(next_node.get_value(default='Default')) # 'Default'
Useful in combination with walrus operator:
if next_node := chain(nodes).node.node:
print(get_value(next_node))
if next_node := chain(nodes).node:
print(get_value(next_node)) # Node(data=2, node=None)
ChainProxy [source]
ChainProxy
container:
- stores
data_object
- proxying
data_object
attribute value into newChainProxy
instance when attribute is invoked. If attribute does not exist or attribute value isNone
.ChainProxy
instancedata_object
will beNone
andbool_hook
will beFalse
. ChainProxy
instance always returning when attribute is invoked.
Release: 0.2.0
Performance update
Increase performance by adding empty_proxy instead of real ChainProxy
For case below Numbers: # 0.100 -> 0.046
import timeit
from dataclasses import dataclass
@dataclass
class Node:
data: int
node: typing.Optional[Node]
node = Node(1, None)
executable = lambda: get_value(chain(node).node.node.node.node.node.node.node.node.node.node)
perf = timeit.timeit(executable, number=10000)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.