Skip to content

Instantly share code, notes, and snippets.

@coordt
Created June 20, 2023 20:07
Show Gist options
  • Save coordt/a932681c9aea4ca51e9e792b8dd3bfd3 to your computer and use it in GitHub Desktop.
Save coordt/a932681c9aea4ca51e9e792b8dd3bfd3 to your computer and use it in GitHub Desktop.
Get a key or attr `name` from obj or default value.
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
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