Created
June 20, 2023 20:07
-
-
Save coordt/a932681c9aea4ca51e9e792b8dd3bfd3 to your computer and use it in GitHub Desktop.
Get a key or attr `name` from obj or default value.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def resolve_name(obj: Any, name: str, default: Any = None) -> Any: # NOQA: C901 | |
""" | |
Get a key or attr ``name`` from obj or default value. | |
Copied and modified from Django Template variable resolutions | |
Resolution methods: | |
- Mapping key lookup | |
- Attribute lookup | |
- Sequence index | |
Args: | |
obj: The object to access | |
name: A dotted name to the value, such as ``mykey.0.name`` | |
default: If the name cannot be resolved from the object, return this value | |
Returns: | |
The value at the resolved name or the default value. | |
# noqa: DAR401 | |
""" | |
lookups = name.split(".") | |
current = obj | |
try: # catch-all for unexpected failures | |
for bit in lookups: | |
try: # dictionary lookup | |
current = current[bit] | |
# ValueError/IndexError are for numpy.array lookup on | |
# numpy < 1.9 and 1.9+ respectively | |
except (TypeError, AttributeError, KeyError, ValueError, IndexError): | |
try: # attribute lookup | |
current = getattr(current, bit) | |
except (TypeError, AttributeError): | |
# Reraise if the exception was raised by a @property | |
if bit in dir(current): | |
raise | |
try: # list-index lookup | |
current = current[int(bit)] | |
except ( | |
IndexError, # list index out of range | |
ValueError, # invalid literal for int() | |
KeyError, # current is a dict without `int(bit)` key | |
TypeError, | |
): # un-subscript-able object | |
return default | |
return current | |
except Exception: # noqa: BLE001 # pragma: no cover | |
return default |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from dataclasses import dataclass | |
import pytest | |
from pytest import param | |
import resolve_name | |
mapping = {"key1": "value1", "dict-key": {"dict-key-key1": "value2-1"}} | |
@dataclass | |
class SimpleObj: | |
"""An object for testing.""" | |
dict_attr: dict | |
list_attr: list | |
@property | |
def exc(self): | |
return self._notavailable | |
test_obj = SimpleObj(dict_attr=mapping, list_attr=["a", "b", "c", "d"]) | |
@pytest.mark.parametrize( | |
["name", "data", "expected"], | |
[ | |
param("key1", mapping, "value1", id="simple mapping"), | |
param("dict-key.dict-key-key1", mapping, "value2-1", id="nested mapping"), | |
param("dict_attr", test_obj, mapping, id="attribute lookup"), | |
param("list_attr.2", test_obj, "c", id="list lookup"), | |
], | |
) | |
def test_resolve_name(name, data, expected): | |
"""Test the resolve_name method gets the correct values.""" | |
assert resolve_name.resolve_name(data, name) == expected | |
def test_resolve_name_default(): | |
"""Test a default value.""" | |
assert resolve_name.resolve_name(mapping, "key3", default="default") == "default" | |
def test_resolve_name_property_error(): | |
"""An error in a property returns default.""" | |
assert resolve_name.resolve_name(test_obj, "exc", default="default") == "default" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment