-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathNamedValueAccess.py
117 lines (92 loc) · 3.55 KB
/
NamedValueAccess.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"""NamedValueAccess.py
NamedValueAccess provides functions for accessing Python objects by keys and
named attributes. A 'key' is a single identifier such as 'foo'. A 'name' could
be a key, or a qualified key, such as 'foo.bar.boo'. Names are generally more
convenient and powerful, while the key-oriented function is more efficient and
provide the atomic functionality that the name-oriented function is built upon.
CREDIT
Chuck Esterbrook <echuck@mindspring.com>
Tavis Rudd <tavis@calrudd.com>
"""
from MiscUtils import NoDefault
class NamedValueAccessError(LookupError):
"""General named value access error."""
class ValueForKeyError(NamedValueAccessError):
"""No value for key found error."""
def valueForKey(obj, key, default=NoDefault):
"""Get the value of the object named by the given key.
This method returns the value with the following precedence:
1. Methods before non-methods
2. Attributes before keys (__getitem__)
3. Public things before private things
(private being denoted by a preceding underscore)
Suppose key is 'foo', then this method returns one of these:
* obj.foo()
* obj._foo()
* obj.foo
* obj._foo
* obj['foo']
* default # only if specified
If all of these fail, a ValueForKeyError is raised.
NOTES
* valueForKey() works on dictionaries and dictionary-like objects.
* See valueForName() which is a more advanced version of this
function that allows multiple, qualified keys.
"""
if obj is None:
raise TypeError('We do not accept None as object')
if not isinstance(key, str):
raise TypeError('We only accept strings for keys')
attr = None
unknown = False
if isinstance(obj, dict):
if default is NoDefault:
try:
return obj[key]
except KeyError:
raise ValueForKeyError(key) from None
else:
return obj.get(key, default)
else:
try:
cls = obj.__class__
except AttributeError:
# happens for classes themselves
cls = method = None
else:
method = getattr(cls, key, None)
if not method:
underKey = '_' + key
method = getattr(cls, underKey, None) if cls else None
if not method:
attr = getattr(obj, key, NoDefault)
if attr is NoDefault:
attr = getattr(obj, underKey, NoDefault)
if attr is NoDefault and cls is not None:
getitem = getattr(cls, '__getitem__', None)
if getitem:
try:
getitem(obj, key)
except KeyError:
unknown = True
if not unknown:
if method:
return method(obj)
if attr is not NoDefault:
return attr
if default is not NoDefault:
return default
raise ValueForKeyError(key)
def valueForName(obj, name, default=NoDefault):
"""Get the value of the object that is named.
The name can use dotted notation to traverse through a network/graph
of objects. Since this function relies on valueForKey() for each
individual component of the name, you should be familiar with the
semantics of that notation.
Example: valueForName(obj, 'department.manager.salary')
"""
for name in name.split('.'):
obj = valueForKey(obj, name, default)
if obj is default:
break
return obj