Package attrbox
Attribute-based data structures.
Why?
I have common use cases where I want to improve python's dict and list:
AttrDict: attribute-baseddictwith better merge and deep value accessAttrList:listthat broadcasts operations to its members- Environment: reading environment files
- Configuration: loading command-line arguments and configuration files
JSend: sending JSON responses
Install
python -m pip install attrbox
AttrDict
AttrDict features:
-
Attribute Syntax for
dictsimilar to accessing properties in JavaScript:thing.propmeansthing["prop"]for get / set / delete. -
No
KeyError: if a key is missing, just returnNone(likedict.get()). -
Deep Indexing: use a list of keys and
intto get and set deeply nested values. This is similar to lodash.get except that only the array-like syntax is supported and you must use actualintto index acrosslistobjects. -
Deep Merge: combine two
dictobjects by extending deeply-nested keys where possible. This is different than the newdictunion operator (PEP 584).
from attrbox import AttrDict
items = AttrDict(a=1, b=[{"c": {"d": 5}}], e={"f": {"g": 7}})
items.a
# => 1
items.x is None
# => True
items.x = 10
items['x']
# => 10
items.get(["b", 0, "c", "d"])
# => 5
items <<= {"e": {"f": {"g": 20, "h": [30, 40]}}}
items.e.f.g
# => 20
items[['e', 'f', 'h', 1]]
# => 40
AttrList
AttrList provides member broadcast: performing operations on the list performs the operation on all the items in the list. I typically use this to achieve the Composite design pattern.
from attrbox import AttrDict, AttrList
numbers = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)])
numbers.real
# => [1.0, 3.0, 5.0]
words = AttrList(["Apple", "Bat", "Cat"])
words.lower()
# => ['apple', 'bat', 'cat']
items = AttrList([AttrDict(a=1, b=2), AttrDict(a=5)])
items.a
# => [1, 5]
items.b
# => [2, None]
Environment
attrbox.env is similar to python-dotenv, but uses the AttrDict ability to do deep indexing to allow for things like dotted variable names. Typically, you'll use it by calling load_env() which will find the nearest .env file and load it into os.environ.
Configuration
attrbox supports loading configuration files from .json, .toml, and .attrbox.env files. By default, load_config() looks for a key imports and will recursively import those files (relative to the current file) before loading the rest of the current file (data is merged using AttrDict). This allows you to create templates or smaller configurations that build up to a larger configuration.
For CLI applications, parse_docopt() let's you use the power of docopt with the flexibility of AttrDict. By default, --config and <config> arguments will load the file using the load_config()
"""Usage: prog.py [--help] [--version] [-c CONFIG] --file FILE
Options:
--help show this message and exit
--version show the version number and exit
-c, --config CONFIG load this configuration file (supported: .toml, .json, .env)
--file FILE the file to process
"""
def main():
args = parse_docopt(__doc__, version=__version__)
args.file # has the value of --file
if __name__ == "__main__":
main()
Building on top of docopt we strip off leading dashes and convert them to underscores so that we can access the arguments as AttrDict attributes.
Read more about attrbox.config
JSend
JSend is an approximate implementation of the JSend specification that makes it easy to create standard JSON responses. The main difference is that I added an ok attribute to make it easy to tell if there was a problem (fail or error).
from attrbox import JSend
def some_function(arg1):
result = JSend() # default is "success"
if not is_good(arg1):
# fail = controlled problem
return result.fail(message="You gone messed up.")
try:
result.success(data=process(arg1))
except Exception:
# error = uncontrolled problem
return result.error(message="We have a problem.")
return result
Because the JSend object is an AttrDict, it acts like a dict in every other respect (e.g., it is JSON-serializable).
License
The vendorized version of docopt-ng is also licensed under the MIT License.
Sub-modules
attrbox.attrdict-
dictwith attribute access, deep selectors, and merging. attrbox.attrlist-
listthat broadcasts attribute access to its elements. attrbox.config-
Configuration loading and parsing.
attrbox.env-
Load configuration from environment files …
attrbox.fn-
Generally useful functions.
attrbox.jsend-
Standard JSON responses.
Functions
def load_env(path: Union[pathlib._local.Path, str, ForwardRef(None)] = None) ‑> Dict[str, str]-
Load an environment file.
We recursively search for a
.attrbox.envfile from the path given or the current working directory, if omitted.Args
path:PathStr, optional- starting path. If
None, start from the current working directory. Defaults toNone.
Raises
FileNotFoundError- If not
.attrbox.envfile is found.
Returns
Dict[str, str]- configuration values
Examples
>>> load_env() # our .env doesn't have any values {}If no
.attrbox.envcan be found, aFileNotFoundErroris raised:>>> load_env("/") Traceback (most recent call last): ... FileNotFoundError: Cannot find .env file to load. def load_config(path: pathlib._local.Path, /, *, load_imports: bool = True, loaders: Optional[dict[str, typing.Callable[[str], typing.Any]]] = None, done: Optional[list[pathlib._local.Path]] = None) ‑> dict[str, typing.Any]-
Load a configuration file from
pathusing configurationloaders.Args
path:Path- file to load.
load_imports:bool, optional- If
True, recursively load any files located at theimportskey. Defaults toTrue. loaders:dict[str, LoaderFunc], optional- mapping of file suffixes
to to loader functions. If
None, uses the globalLOADERS. Defaults toNone. done:list[Path], optional- If provided, a list of paths to ignore when
doing recursive loading. Defaults to
None.
Returns
dict[str, Any]- keys/values from the configuration file
Examples
>>> root = Path(__file__).parent.parent.parent >>> expected = {'section': {'key': 'value1', "env": "loaded", ... "json": "loaded", "toml": "loaded"}} >>> load_config(root / "test/config_1.toml") == expected True def parse_docopt(doc: str, /, argv: Optional[Sequence[str]] = None, *, version: str = '1.0.0', options_first: bool = False, read_config: bool = True) ‑> AttrDict-
Parse docopt args and load config.
Args
doc:str- docstring with description of command
argv:Sequence[str], optional- arguments to parse against the
doc. If
None, will default tosys.argv[1:]. Defaults toNone. version:str, optional- program version. Defaults to
"1.0.0". options_first:bool- If
True, options must come before positional arguments. Defaults toFalse. read_config:bool- If
True, a<config>argument or--configoption will be automatically loaded before args are parsed. Defaults toTrue.
Returns
AttrDict[str, Any]- mapping of options to values
Examples
>>> usage = "Usage: test.py [--debug]" >>> parse_docopt(usage, argv=["--debug"]) {'debug': True}>>> root = Path(__file__).parent.parent.parent >>> path = str(root / "test/config_1.toml") >>> usage = "Usage: test.py <config> [--section.key=VAL]" >>> argv = [path, "--section.key=overwrite"] >>> expected = {"section": { ... "key": "overwrite", # overwritten by argv ... "env": "loaded", "json": "loaded", "toml": "loaded" ... }, "config": path} >>> parse_docopt(usage, argv=argv) == expected True
Classes
class AttrDict (*args, **kwargs)-
Like a
dict, but with attribute syntax.NOTE: We do not throw
AttributeErrororKeyErrorbecause accessing a non-existent key or attribute returnsNone.Examples
>>> AttrDict({"a": 1}).a 1Expand source code
class AttrDict(Dict[str, Any]): """Like a `dict`, but with attribute syntax. NOTE: We do not throw `AttributeError` or `KeyError` because accessing a non-existent key or attribute returns `None`. Examples: >>> AttrDict({"a": 1}).a 1 """ def copy(self: Self) -> Self: """Return a shallow copy. Examples: >>> items = AttrDict(a=1) >>> clone = items.copy() >>> items == clone # same contents True >>> items is not clone # different pointers True >>> clone.a = 5 >>> items != clone True """ return self.__class__(super().copy()) def __contains__(self, key: Any) -> bool: """Return `True` if `key` is a key. Args: key (Any): typically a `AnyIndex`. If it's a string, the check proceeds as usual. If it's a `Sequence`, the checks are performed using `.get()`. Returns: bool: `True` if the `key` is a valid key, `False` otherwise. Examples: Normal checking works as expected: >>> items = AttrDict(a=1, b=2) >>> 'a' in items True >>> 'x' in items False Nested checks are also possible: >>> items = AttrDict(a=[{"b": 2}, {"b": 3}]) >>> ['a', 0, 'b'] in items True >>> ['a', 1, 'x'] in items False """ return self.get(key, NOT_FOUND) is not NOT_FOUND def __getattr__(self, name: str) -> Optional[Any]: """Return the value of the attribute or `None`. Args: name (str): attribute name Returns: Any: attribute value or `None` if the attribute is missing Examples: Typically, attributes are the same as key/values: >>> item = AttrDict(a=1) >>> item.a 1 >>> item['a'] 1 However, instance attributes supersede key/values: >>> item = AttrDict(b=1) >>> object.__setattr__(item, 'b', 2) >>> item.b 2 >>> item['b'] 1 Missing attributes return `None`: >>> item = AttrDict(a=1) >>> item.b is None True """ # NOTE: This method is only called when the attribute cannot be found. return self[name] def __setattr__(self, name: str, value: Any) -> None: """Set the value of an attribute. Args: name (str): attribute name value (Any): value to set Examples: Setting an attribute is usually the same as a key/value: >>> item = AttrDict() >>> item.a = 1 >>> item.a 1 >>> item['a'] 1 However, instance attributes supersede key/values: >>> item = AttrDict() >>> object.__setattr__(item, 'b', 2) >>> item.b 2 >>> item.b = 3 # instance attribute updated >>> item.b 3 >>> item['b'] is None True """ try: super().__getattribute__(name) # is real? super().__setattr__(name, value) except AttributeError: # use key/value self[name] = value def __delattr__(self, name: str) -> None: """Delete an attribute. Args: name (str): attribute name Examples: Deleting an attribute usually deletes the underlying key/value: >>> item = AttrDict(a=1) >>> del item.a >>> item {} However, instance attributes supersede key/values: >>> item = AttrDict(b=1) >>> object.__setattr__(item, 'b', 2) >>> item.b # real attribute supersedes key/value 2 >>> del item.b # deletes real attribute >>> object.__getattribute__(item, 'b') # instance attribute gone Traceback (most recent call last): ... AttributeError: 'AttrDict' object has no attribute 'b' >>> item {'b': 1} """ try: super().__getattribute__(name) # is real? super().__delattr__(name) except AttributeError: # use key/value del self[name] def __getitem__(self, key: AnyIndex) -> Optional[Any]: """Return the value of the key. Args: key (AnyIndex): key name or Sequence of the path to a key Returns: Any: value of the key or `None` if it cannot be found Examples: >>> item = AttrDict(a=1) >>> item['a'] 1 >>> item['b'] is None True """ return self.get(key) def __setitem__(self, key: AnyIndex, value: Any) -> None: """Set the value of a key. Args: key (AnyIndex): key name value (Any): key value Examples: >>> item = AttrDict(a=1) >>> item['a'] = 5 >>> item['a'] 5 >>> item[['a', 'b']] = 10 >>> item.a.b 10 """ self.set(key, value) def __delitem__(self, key: str) -> None: """Delete a key. Args: key (str): key name Examples: >>> item = AttrDict(a=1) >>> del item['a'] >>> item {} Deleting missing keys has no effect: >>> item = AttrDict(a=1) >>> del item['b'] >>> item {'a': 1} """ try: super().__delitem__(key) except KeyError: pass def get(self, path: AnyIndex, default: Optional[Any] = None, /) -> Optional[Any]: """Return the value at `path` or `default` if it cannot be found. Args: path (AnyIndex): path to the value default (Any, optional): value to return if `name` is not found. Defaults to `None`. Returns: Optional[Any]: key value or `default` if the key is not found Examples: Normal `.get` functions work: >>> items = AttrDict(a=dict(b=[{"c": 3}, {"c": -10}]), d=4) >>> items.get('d') == 4 True >>> items.get('e') is None True >>> items.get('e', 5) == 5 True But you can also indexing into nested `list` and `dict`: >>> items.get(['a', 'b', 1, 'c']) == -10 True Bad indexing will just return the default: >>> items.get(['e']) is None # key doesn't exist True >>> items.get(['a', 'b', 5]) is None # index unreachable True >>> items.get(['d', 'e']) is None # int is not indexable True """ if isinstance(path, str): return super().get(path, default) return get_path(self, path, default) def set(self: Self, path: AnyIndex, value: Optional[Any] = None, /) -> Self: """Set key at `path` to `value`. Args: path (AnyIndex): path to the key to set. value (Any, optional): value to set. Defaults to `None`. Returns: Self: for chaining Examples: >>> items = AttrDict({"a": 1}) >>> items.set("b", 2) {'a': 1, 'b': 2} You can set values deeper: >>> items.set(["b", "c", "d"], 5) {'a': 1, 'b': {'c': {'d': 5}}} You can also use a `tuple` (or other Sequence): >>> items.set(("b", "c", "d"), 10) {'a': 1, 'b': {'c': {'d': 10}}} An empty `Sequence` performs no action: >>> items.set((), 20) {'a': 1, 'b': {'c': {'d': 10}}} """ if isinstance(path, str): super().__setitem__(path, value) return self set_path(self, path, value, self.__class__) return self def __lshift__(self: Self, other: Mapping[str, Any]) -> Self: """Merge `other` into `self`. NOTE: Any nested dictionaries will be converted to `AttrDict` objects. Args: other (Mapping[str, Any]): other dictionary to merge Returns: AttrDict: merged dictionary Examples: >>> item = AttrDict(a=1, b=2) >>> item <<= {"b": 3} >>> item.b 3 >>> item << {"b": 2, "c": {"d": 4}} << {"c": {"d": {"e": 5}}} {'a': 1, 'b': 2, 'c': {'d': {'e': 5}}} >>> item.c.d.e 5 """ dict_merge(self, other, cls_dict=self.__class__) return selfAncestors
- builtins.dict
- typing.Generic
Subclasses
Methods
def copy(self: Self) ‑> ~Self-
Return a shallow copy.
Examples
>>> items = AttrDict(a=1) >>> clone = items.copy() >>> items == clone # same contents True >>> items is not clone # different pointers True >>> clone.a = 5 >>> items != clone True def __contains__(self, key: Any) ‑> bool-
Return
Trueifkeyis a key.Args
key:Any- typically a
AnyIndex. If it's a string, the check proceeds as usual. If it's aSequence, the checks are performed using.get().
Returns
boolTrueif thekeyis a valid key,Falseotherwise.
Examples
Normal checking works as expected:
>>> items = AttrDict(a=1, b=2) >>> 'a' in items True >>> 'x' in items FalseNested checks are also possible:
>>> items = AttrDict(a=[{"b": 2}, {"b": 3}]) >>> ['a', 0, 'b'] in items True >>> ['a', 1, 'x'] in items False def __getattr__(self, name: str) ‑> Optional[Any]-
Return the value of the attribute or
None.Args
name:str- attribute name
Returns
Any- attribute value or
Noneif the attribute is missing
Examples
Typically, attributes are the same as key/values:
>>> item = AttrDict(a=1) >>> item.a 1 >>> item['a'] 1However, instance attributes supersede key/values:
>>> item = AttrDict(b=1) >>> object.__setattr__(item, 'b', 2) >>> item.b 2 >>> item['b'] 1Missing attributes return
None:>>> item = AttrDict(a=1) >>> item.b is None True def __setattr__(self, name: str, value: Any) ‑> None-
Set the value of an attribute.
Args
name:str- attribute name
value:Any- value to set
Examples
Setting an attribute is usually the same as a key/value:
>>> item = AttrDict() >>> item.a = 1 >>> item.a 1 >>> item['a'] 1However, instance attributes supersede key/values:
>>> item = AttrDict() >>> object.__setattr__(item, 'b', 2) >>> item.b 2 >>> item.b = 3 # instance attribute updated >>> item.b 3 >>> item['b'] is None True def __delattr__(self, name: str) ‑> None-
Delete an attribute.
Args
name:str- attribute name
Examples
Deleting an attribute usually deletes the underlying key/value:
>>> item = AttrDict(a=1) >>> del item.a >>> item {}However, instance attributes supersede key/values:
>>> item = AttrDict(b=1) >>> object.__setattr__(item, 'b', 2) >>> item.b # real attribute supersedes key/value 2 >>> del item.b # deletes real attribute >>> object.__getattribute__(item, 'b') # instance attribute gone Traceback (most recent call last): ... AttributeError: 'AttrDict' object has no attribute 'b' >>> item {'b': 1} def __getitem__(self, key: AnyIndex) ‑> Optional[Any]-
Return the value of the key.
Args
key:AnyIndex- key name or Sequence of the path to a key
Returns
Any- value of the key or
Noneif it cannot be found
Examples
>>> item = AttrDict(a=1) >>> item['a'] 1 >>> item['b'] is None True def __setitem__(self, key: AnyIndex, value: Any) ‑> None-
Set the value of a key.
Args
key:AnyIndex- key name
value:Any- key value
Examples
>>> item = AttrDict(a=1) >>> item['a'] = 5 >>> item['a'] 5>>> item[['a', 'b']] = 10 >>> item.a.b 10 def __delitem__(self, key: str) ‑> None-
Delete a key.
Args
key:str- key name
Examples
>>> item = AttrDict(a=1) >>> del item['a'] >>> item {}Deleting missing keys has no effect:
>>> item = AttrDict(a=1) >>> del item['b'] >>> item {'a': 1} def get(self, path: AnyIndex, default: Optional[Any] = None, /) ‑> Optional[Any]-
Return the value at
pathordefaultif it cannot be found.Args
path:AnyIndex- path to the value
default:Any, optional- value to return if
nameis not found. Defaults toNone.
Returns
Optional[Any]- key value or
defaultif the key is not found
Examples
Normal
.getfunctions work:>>> items = AttrDict(a=dict(b=[{"c": 3}, {"c": -10}]), d=4) >>> items.get('d') == 4 True >>> items.get('e') is None True >>> items.get('e', 5) == 5 TrueBut you can also indexing into nested
listanddict:>>> items.get(['a', 'b', 1, 'c']) == -10 TrueBad indexing will just return the default:
>>> items.get(['e']) is None # key doesn't exist True >>> items.get(['a', 'b', 5]) is None # index unreachable True >>> items.get(['d', 'e']) is None # int is not indexable True def set(self: Self, path: AnyIndex, value: Optional[Any] = None, /) ‑> ~Self-
Set key at
pathtovalue.Args
path:AnyIndex- path to the key to set.
value:Any, optional- value to set. Defaults to
None.
Returns
Self- for chaining
Examples
>>> items = AttrDict({"a": 1}) >>> items.set("b", 2) {'a': 1, 'b': 2}You can set values deeper:
>>> items.set(["b", "c", "d"], 5) {'a': 1, 'b': {'c': {'d': 5}}}You can also use a
tuple(or other Sequence):>>> items.set(("b", "c", "d"), 10) {'a': 1, 'b': {'c': {'d': 10}}}An empty
Sequenceperforms no action:>>> items.set((), 20) {'a': 1, 'b': {'c': {'d': 10}}} def __lshift__(self: Self, other: Mapping[str, Any]) ‑> ~Self-
Merge
otherintoself.NOTE: Any nested dictionaries will be converted to
AttrDictobjects.Args
other:Mapping[str, Any]- other dictionary to merge
Returns
AttrDict- merged dictionary
Examples
>>> item = AttrDict(a=1, b=2) >>> item <<= {"b": 3} >>> item.b 3>>> item << {"b": 2, "c": {"d": 4}} << {"c": {"d": {"e": 5}}} {'a': 1, 'b': 2, 'c': {'d': {'e': 5}}} >>> item.c.d.e 5
class AttrList (*args, **kwargs)-
Collection that broadcasts attribute, index, and function calls to its members.
Attribute access (
.) is broadcast to all members. An exception is an attribute that exists on the list instance itself.>>> nums = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)]) >>> nums.real [1.0, 3.0, 5.0]Array access (
[]) withintandsliceindexes works as usual by returning a portion of the list.stringindexes, however, are broadcast to each member.>>> items = AttrList(["Apple", "Bat", "Cat"]) >>> items[0] 'Apple' >>> items["0"] ['A', 'B', 'C']Calling the list (
()) broadcasts the call to all members. Usually, this is combined with attribute access:>>> items = AttrList(["Apple", "Bat", "Cat"]) >>> items.lower() ['apple', 'bat', 'cat']Expand source code
class AttrList(List[Any]): """Collection that broadcasts attribute, index, and function calls to its members. Attribute access (`.`) is broadcast to all members. An exception is an attribute that exists on the list instance itself. >>> nums = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)]) >>> nums.real [1.0, 3.0, 5.0] Array access (`[]`) with `int` and `slice` indexes works as usual by returning a portion of the list. `string` indexes, however, are broadcast to each member. >>> items = AttrList(["Apple", "Bat", "Cat"]) >>> items[0] 'Apple' >>> items["0"] ['A', 'B', 'C'] Calling the list (`()`) broadcasts the call to all members. Usually, this is combined with attribute access: >>> items = AttrList(["Apple", "Bat", "Cat"]) >>> items.lower() ['apple', 'bat', 'cat'] """ def __getattr__(self, name: str) -> AttrList: """Return an attribute from all members. Args: name (str): attribute name Returns: (list[any]): value of attribute for each member or `None` if missing Examples: >>> from . import AttrDict >>> items = AttrList([AttrDict(a=1), AttrDict(a=2), AttrDict(a=3, b=4)]) >>> items.a [1, 2, 3] >>> items.b [None, None, 4] Note that instance attributes supersede member attributes: >>> object.__setattr__(items, "b", 5) >>> items.b 5 """ # NOTE: This method is only called when the attribute cannot be found. # We delegate this call to every member. result = self.__class__() for member in self: # if isinstance(member, AttrDict): # check if name is defined # result.append(member[name] if name in member else None) # else: result.append(getattr(member, name, None)) return result def __setattr__(self, name: str, value: Any) -> None: """Set an attribute on all members (or the list itself). Args: name (str): attribute name value (any): attribute value Examples: >>> from . import AttrDict >>> items = AttrList([AttrDict(a=1), AttrDict(b=2)]) >>> items.a = 5 >>> items [{'a': 5}, {'b': 2, 'a': 5}] Note that instance attributes supersede member attributes: >>> object.__setattr__(items, "b", 5) >>> items.b = 7 >>> items.b 7 """ try: super().__getattribute__(name) # is real? super().__setattr__(name, value) except AttributeError: # use members for member in self: setattr(member, name, value) def __delattr__(self, name: str) -> None: """Delete an attribute from all members (or the list itself). Args: name (str): attribute name Examples: >>> from . import AttrDict >>> items = AttrList([AttrDict(a=1, b=2), AttrDict(a=2, c=3), dict(d=4)]) >>> del items.a >>> items [{'b': 2}, {'c': 3}, {'d': 4}] Deleting an instance attribute works: >>> object.__setattr__(items, 'b', 5) >>> items.b = 7 >>> items.b 7 >>> del items.b # deletes instance attribute >>> items [{'b': 2}, {'c': 3}, {'d': 4}] """ try: super().__getattribute__(name) # is real? super().__delattr__(name) except AttributeError: # use members for member in self: try: delattr(member, name) except AttributeError: pass def __getitem__(self: Self, index: AttrListKey) -> Union[Self, Any]: """Return an item from all members (or the list itself). Args: index (int|slice|str): item index Returns: (any): the item at the index in each member (`str` index) or the member at the given index (`int` or `slice`) Examples: A string index is applied to all members: >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> items["-1"] [3, 6, 9] Normal indexing works as expected: >>> items = AttrList([1, 2, 3]) >>> items[1] 2 >>> items[0:2] [1, 2] Weird indexing throws a `TypeError`: >>> items = AttrList([1, 2, 3]) >>> items[{"a": 1}] Traceback (most recent call last): ... TypeError: list indices must be integers or slices, not dict """ result = self if isinstance(index, str): index = str2index(index) result = self.__class__(item[index] for item in self) elif isinstance(index, slice): result = self.__class__(super().__getitem__(index)) else: result = super().__getitem__(index) return result def __setitem__(self, index: AttrListKey, value: Any) -> None: """Set an item in all members (or the list itself). Args: index (int|slice|str): item index value (any): item value Examples: A string index is applied to all members: >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> items["0"] = 100 >>> items [[100, 2, 3], [100, 5, 6], [100, 8, 9]] Immutable members are untouched: >>> items = AttrList(["Cat", [1, 2, 3]]) >>> items["0"] = "A" >>> items ['Cat', ['A', 2, 3]] Normal indexing works as expected: >>> items = AttrList([1, 2, 3]) >>> items[1] = 5 >>> items [1, 5, 3] Weird indexing throws a `TypeError`: >>> items = AttrList([1, 2, 3]) >>> items[{"a": 1}] = 100 Traceback (most recent call last): ... TypeError: list indices must be integers or slices, not dict """ if isinstance(index, str): index = str2index(index) for member in self: if hasattr(member, "__setitem__"): member.__setitem__(index, value) else: super().__setitem__(index, value) def __delitem__(self, index: AttrListKey) -> None: """Delete an item from all members (or the list itself). Args: index (int|slice|str): item index Examples: A string index is applied to all members: >>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> del items["0"] >>> items [[2, 3], [5, 6], [8, 9]] Immutable members are untouched: >>> items = AttrList(["Cat", [1, 2, 3]]) >>> del items["-1"] >>> items ['Cat', [1, 2]] Normal indexing works as expected: >>> items = AttrList([1, 2, 3]) >>> del items[1] >>> items [1, 3] Weird indexing throws a `TypeError`: >>> items = AttrList([1, 2, 3]) >>> del items[{"a": 1}] Traceback (most recent call last): ... TypeError: list indices must be integers or slices, not dict """ if isinstance(index, str): index = str2index(index) for member in self: if hasattr(member, "__delitem__"): member.__delitem__(index) else: super().__delitem__(index) def __call__(self: Self, *args: Any, **kwargs: Any) -> Self: """Return a new list with the result of calling all callables in the list. Args: args (*any): arguments to the callable kwargs (**any): keyword arguments to the callable Returns: (Self): new list with results of the callable Examples: >>> items = AttrList(["a", "b", "c"]) >>> items.upper() ['A', 'B', 'C'] >>> items = AttrList([lambda x: x + 2, lambda y: y + 5, "Z"]) >>> items(1) [3, 6, 'Z'] """ return self.__class__( item(*args, **kwargs) if callable(item) else item for item in self )Ancestors
- builtins.list
- typing.Generic
Methods
def __getattr__(self, name: str) ‑> AttrList-
Return an attribute from all members.
Args
name:str- attribute name
Returns
(list[any]): value of attribute for each member or
Noneif missingExamples
>>> from . import AttrDict >>> items = AttrList([AttrDict(a=1), AttrDict(a=2), AttrDict(a=3, b=4)]) >>> items.a [1, 2, 3] >>> items.b [None, None, 4]Note that instance attributes supersede member attributes:
>>> object.__setattr__(items, "b", 5) >>> items.b 5 def __setattr__(self, name: str, value: Any) ‑> None-
Set an attribute on all members (or the list itself).
Args
name:str- attribute name
value:any- attribute value
Examples
>>> from . import AttrDict >>> items = AttrList([AttrDict(a=1), AttrDict(b=2)]) >>> items.a = 5 >>> items [{'a': 5}, {'b': 2, 'a': 5}]Note that instance attributes supersede member attributes:
>>> object.__setattr__(items, "b", 5) >>> items.b = 7 >>> items.b 7 def __delattr__(self, name: str) ‑> None-
Delete an attribute from all members (or the list itself).
Args
name:str- attribute name
Examples
>>> from . import AttrDict >>> items = AttrList([AttrDict(a=1, b=2), AttrDict(a=2, c=3), dict(d=4)]) >>> del items.a >>> items [{'b': 2}, {'c': 3}, {'d': 4}]Deleting an instance attribute works:
>>> object.__setattr__(items, 'b', 5) >>> items.b = 7 >>> items.b 7 >>> del items.b # deletes instance attribute >>> items [{'b': 2}, {'c': 3}, {'d': 4}] def __getitem__(self: Self, index: AttrListKey) ‑> Union[~Self, Any]-
Return an item from all members (or the list itself).
Args
index (int|slice|str): item index
Returns
(any): the item at the index in each member (
strindex) or the member at the given index (intorslice)Examples
A string index is applied to all members:
>>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> items["-1"] [3, 6, 9]Normal indexing works as expected:
>>> items = AttrList([1, 2, 3]) >>> items[1] 2 >>> items[0:2] [1, 2]Weird indexing throws a
TypeError:>>> items = AttrList([1, 2, 3]) >>> items[{"a": 1}] Traceback (most recent call last): ... TypeError: list indices must be integers or slices, not dict def __setitem__(self, index: AttrListKey, value: Any) ‑> None-
Set an item in all members (or the list itself).
Args
- index (int|slice|str): item index
value:any- item value
Examples
A string index is applied to all members:
>>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> items["0"] = 100 >>> items [[100, 2, 3], [100, 5, 6], [100, 8, 9]]Immutable members are untouched:
>>> items = AttrList(["Cat", [1, 2, 3]]) >>> items["0"] = "A" >>> items ['Cat', ['A', 2, 3]]Normal indexing works as expected:
>>> items = AttrList([1, 2, 3]) >>> items[1] = 5 >>> items [1, 5, 3]Weird indexing throws a
TypeError:>>> items = AttrList([1, 2, 3]) >>> items[{"a": 1}] = 100 Traceback (most recent call last): ... TypeError: list indices must be integers or slices, not dict def __delitem__(self, index: AttrListKey) ‑> None-
Delete an item from all members (or the list itself).
Args
index (int|slice|str): item index
Examples
A string index is applied to all members:
>>> items = AttrList([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> del items["0"] >>> items [[2, 3], [5, 6], [8, 9]]Immutable members are untouched:
>>> items = AttrList(["Cat", [1, 2, 3]]) >>> del items["-1"] >>> items ['Cat', [1, 2]]Normal indexing works as expected:
>>> items = AttrList([1, 2, 3]) >>> del items[1] >>> items [1, 3]Weird indexing throws a
TypeError:>>> items = AttrList([1, 2, 3]) >>> del items[{"a": 1}] Traceback (most recent call last): ... TypeError: list indices must be integers or slices, not dict def __call__(self: Self, *args: Any, **kwargs: Any) ‑> ~Self-
Return a new list with the result of calling all callables in the list.
Args
args (any): arguments to the callable kwargs (*any): keyword arguments to the callable
Returns
(Self): new list with results of the callable
Examples
>>> items = AttrList(["a", "b", "c"]) >>> items.upper() ['A', 'B', 'C']>>> items = AttrList([lambda x: x + 2, lambda y: y + 5, "Z"]) >>> items(1) [3, 6, 'Z']
class JSend (*args: Any, **kwargs: Dict[str, Any])-
Service response object.
This object loosely conforms to the JSend specification.
Construct a JSend object.
Examples
>>> result = JSend() >>> result.ok True >>> result.status == 'success' True >>> result.data is None True >>> result == {'ok': True, 'status': 'success', 'data': None} TrueExpand source code
class JSend(AttrDict): """Service response object. This object loosely conforms to the [JSend specification](https://labs.omniti.com/labs/jsend). """ def __init__(self, *args: Any, **kwargs: Dict[str, Any]): """Construct a JSend object. Examples: >>> result = JSend() >>> result.ok True >>> result.status == 'success' True >>> result.data is None True >>> result == {'ok': True, 'status': 'success', 'data': None} True """ self.update(ok=True, status=STATUS_SUCCESS, data=None) super().__init__(*args, **kwargs) def fail(self, message: Msg = None) -> JSend: """Indicate a controlled failure. Args: message (str): human-readable explanation of the failure Returns: JSend: self for chaining Examples: >>> result = JSend() >>> msg = 'Missing a phone number.' >>> result.fail(msg) is result True >>> result.ok is False True >>> result.status == 'fail' True >>> result.message == msg True """ self.update(ok=False, status=STATUS_FAIL, message=message) return self def error(self, message: Msg = None, code: Optional[Any] = None) -> JSend: """Indicate an uncontrolled error. Args: message (str): human-readable explanation of the error code (Any, optional): technical indication of the error Returns: JSend: self for chaining Examples: >>> result = JSend() >>> msg = 'No such file [file.text].' >>> code = 13 >>> result.error(msg, code) is result True >>> result.ok is False True >>> result.status == 'error' True >>> result.message == msg True >>> result.code == code True """ self.update(ok=False, status=STATUS_ERROR, message=message, code=code) return self def success(self, data: Optional[Any] = None) -> JSend: """Indicate a successful response. Args: data (Any, optional): response payload Returns: JSend: self for chaining Examples: >>> data = "Works" >>> result = JSend() >>> result.success(data) is result True >>> result.data == data True """ self.update(ok=True, status=STATUS_SUCCESS, data=data) return selfAncestors
- AttrDict
- builtins.dict
- typing.Generic
Methods
def fail(self, message: Msg = None) ‑> JSend-
Indicate a controlled failure.
Args
message:str- human-readable explanation of the failure
Returns
JSend- self for chaining
Examples
>>> result = JSend() >>> msg = 'Missing a phone number.' >>> result.fail(msg) is result True >>> result.ok is False True >>> result.status == 'fail' True >>> result.message == msg True def error(self, message: Msg = None, code: Optional[Any] = None) ‑> JSend-
Indicate an uncontrolled error.
Args
message:str- human-readable explanation of the error
code:Any, optional- technical indication of the error
Returns
JSend- self for chaining
Examples
>>> result = JSend() >>> msg = 'No such file [file.text].' >>> code = 13 >>> result.error(msg, code) is result True >>> result.ok is False True >>> result.status == 'error' True >>> result.message == msg True >>> result.code == code True def success(self, data: Optional[Any] = None) ‑> JSend-
Indicate a successful response.
Args
data:Any, optional- response payload
Returns
JSend- self for chaining
Examples
>>> data = "Works" >>> result = JSend() >>> result.success(data) is result True >>> result.data == data True def copy(self: Self) ‑> ~Self-
Return a shallow copy …
def __contains__(self, key: Any) ‑> bool-
Inherited from:
AttrDict.__contains__Return
Trueifkeyis a key … def __getattr__(self, name: str) ‑> Optional[Any]-
Inherited from:
AttrDict.__getattr__Return the value of the attribute or
None… def __setattr__(self, name: str, value: Any) ‑> None-
Inherited from:
AttrDict.__setattr__Set the value of an attribute …
def __delattr__(self, name: str) ‑> None-
Inherited from:
AttrDict.__delattr__Delete an attribute …
def __getitem__(self, key: AnyIndex) ‑> Optional[Any]-
Inherited from:
AttrDict.__getitem__Return the value of the key …
def __setitem__(self, key: AnyIndex, value: Any) ‑> None-
Inherited from:
AttrDict.__setitem__Set the value of a key …
def __delitem__(self, key: str) ‑> None-
Inherited from:
AttrDict.__delitem__Delete a key …
def get(self, path: AnyIndex, default: Optional[Any] = None, /) ‑> Optional[Any]-
Return the value at
pathordefaultif it cannot be found … def set(self: Self, path: AnyIndex, value: Optional[Any] = None, /) ‑> ~Self-
Set key at
pathtovalue… def __lshift__(self: Self, other: Mapping[str, Any]) ‑> ~Self-
Inherited from:
AttrDict.__lshift__Merge
otherintoself…
