teams-bots/write-message/venv/lib/python3.10/site-packages/PyObjCTools/KeyValueCoding.py

385 lines
10 KiB
Python

"""
Support for Key-Value Coding in Python. This provides a simple functional
interface to Cocoa's Key-Value coding that also works for regular Python
objects.
Public API:
setKey(obj, key, value) -> None
setKeyPath (obj, keypath, value) -> None
getKey(obj, key) -> value
getKeyPath (obj, keypath) -> value
A keypath is a string containing a sequence of keys separated by dots. The
path is followed by repeated calls to 'getKey'. This can be used to easily
access nested attributes.
This API is mirroring the 'getattr' and 'setattr' APIs in Python, this makes
it more natural to work with Key-Value coding from Python. It also doesn't
require changes to existing Python classes to make use of Key-Value coding,
making it easier to build applications as a platform independent core with
a Cocoa GUI layer.
See the Cocoa documentation on the Apple developer website for more
information on Key-Value coding. The protocol is basically used to enable
weaker coupling between the view and model layers.
"""
import collections.abc
import types
import objc
__all__ = ("getKey", "setKey", "getKeyPath", "setKeyPath")
_null = objc.lookUpClass("NSNull").null()
def keyCaps(s):
return s[:1].capitalize() + s[1:]
# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/393090
# Title: Binary floating point summation accurate to full precision
# Version no: 2.2
def msum(iterable):
"""
Full precision summation using multiple floats for intermediate values
"""
# sorted, non-overlapping partial sums
partials = []
for x in iterable:
i = 0
for y in partials:
if abs(x) < abs(y):
x, y = y, x
hi = x + y
lo = y - (hi - x)
if lo:
partials[i] = lo
i += 1
x = hi
partials[i:] = [x]
return sum(partials, 0.0)
class _ArrayOperators:
@staticmethod
def avg(obj, segments):
path = ".".join(segments)
lst = getKeyPath(obj, path)
count = len(lst)
if count == 0:
return 0.0
return msum(float(x) if x is not _null else 0.0 for x in lst) / count
@staticmethod
def count(obj, segments):
return len(obj)
@staticmethod
def distinctUnionOfArrays(obj, segments):
path = ".".join(segments)
rval = []
s = set()
r = []
for lst in obj:
for item in (getKeyPath(item, path) for item in lst):
try:
if item in s or item in r:
continue
rval.append(item)
s.add(item)
except TypeError:
if item in rval:
continue
rval.append(item)
r.append(item)
return rval
@staticmethod
def distinctUnionOfSets(obj, segments):
path = ".".join(segments)
rval = set()
for lst in obj:
for item in (getKeyPath(item, path) for item in lst):
rval.add(item)
return rval
@staticmethod
def distinctUnionOfObjects(obj, segments):
path = ".".join(segments)
rval = []
s = set()
r = []
for item in (getKeyPath(item, path) for item in obj):
try:
if item in s or item in r:
continue
rval.append(item)
s.add(item)
except TypeError:
if item in rval:
continue
rval.append(item)
r.append(item)
return rval
@staticmethod
def max(obj, segments): # noqa: A003
path = ".".join(segments)
return max(x for x in getKeyPath(obj, path) if x is not _null)
@staticmethod
def min(obj, segments): # noqa: A003
path = ".".join(segments)
return min(x for x in getKeyPath(obj, path) if x is not _null)
@staticmethod
def sum(obj, segments): # noqa: A003
path = ".".join(segments)
lst = getKeyPath(obj, path)
return msum(float(x) if x is not _null else 0.0 for x in lst)
@staticmethod
def unionOfArrays(obj, segments):
path = ".".join(segments)
rval = []
for lst in obj:
rval.extend(getKeyPath(item, path) for item in lst)
return rval
@staticmethod
def unionOfObjects(obj, segments):
path = ".".join(segments)
return [getKeyPath(item, path) for item in obj]
def getKey(obj, key):
"""
Get the attribute referenced by 'key'. The key is used
to build the name of an attribute, or attribute accessor method.
The following attributes and accessors are tried (in this order):
- Accessor 'getKey'
- Accesoor 'get_key'
- Accessor or attribute 'key'
- Accessor or attribute 'isKey'
- Attribute '_key'
If none of these exist, raise KeyError
"""
if obj is None:
return None
if isinstance(obj, (objc.objc_object, objc.objc_class)):
return obj.valueForKey_(key)
# check for dict-like objects
getitem = getattr(obj, "__getitem__", None)
if getitem is not None:
try:
return getitem(key)
except (KeyError, IndexError, TypeError):
pass
# check for array-like objects
if isinstance(
obj, (collections.abc.Sequence, collections.abc.Set)
) and not isinstance(obj, (str, collections.abc.Mapping)):
def maybe_get(obj, key):
try:
return getKey(obj, key)
except KeyError:
return _null
return [maybe_get(obj, key) for obj in iter(obj)]
try:
m = getattr(obj, "get" + keyCaps(key))
except AttributeError:
pass
else:
return m()
try:
m = getattr(obj, "get_" + key)
except AttributeError:
pass
else:
return m()
for keyName in (key, "is" + keyCaps(key)):
try:
m = getattr(obj, keyName)
except AttributeError:
continue
if isinstance(m, types.MethodType) and m.__self__ is obj:
return m()
elif isinstance(m, types.BuiltinMethodType):
# Can't access the bound self of methods of builtin classes :-(
return m()
elif isinstance(m, objc.selector) and m.self is obj:
return m()
else:
return m
try:
return getattr(obj, "_" + key)
except AttributeError:
raise KeyError(f"Key {key} does not exist")
def setKey(obj, key, value):
"""
Set the attribute referenced by 'key' to 'value'. The key is used
to build the name of an attribute, or attribute accessor method.
The following attributes and accessors are tried (in this order):
- Mapping access (that is __setitem__ for collection.Mapping instances)
- Accessor 'setKey_'
- Accessor 'setKey'
- Accessor 'set_key'
- Attribute '_key'
- Attribute 'key'
Raises KeyError if the key doesn't exist.
"""
if obj is None:
return
if isinstance(obj, (objc.objc_object, objc.objc_class)):
obj.setValue_forKey_(value, key)
return
if isinstance(obj, collections.abc.Mapping):
obj[key] = value
return
aBase = "set" + keyCaps(key)
for accessor in (aBase + "_", aBase, "set_" + key):
m = getattr(obj, accessor, None)
if m is None:
continue
try:
m(value)
return
except TypeError:
pass
try:
m = getattr(obj, key)
except AttributeError:
pass
else:
if isinstance(m, types.MethodType) and m.__self__ is obj:
# This looks like a getter method, don't call setattr
pass
else:
try:
setattr(obj, key, value)
return
except AttributeError:
raise KeyError(f"Key {key} does not exist")
try:
getattr(obj, "_" + key)
except AttributeError:
pass
else:
setattr(obj, "_" + key, value)
return
try:
setattr(obj, key, value)
except AttributeError:
raise KeyError(f"Key {key} does not exist")
def getKeyPath(obj, keypath):
"""
Get the value for the keypath. Keypath is a string containing a
path of keys, path elements are separated by dots.
"""
if not keypath:
raise KeyError
if obj is None:
return None
if isinstance(obj, (objc.objc_object, objc.objc_class)):
return obj.valueForKeyPath_(keypath)
elements = keypath.split(".")
cur = obj
elemiter = iter(elements)
for e in elemiter:
if e[:1] == "@":
try:
oper = getattr(_ArrayOperators, e[1:])
except AttributeError:
raise KeyError(f"Array operator {e} not implemented")
return oper(cur, elemiter)
cur = getKey(cur, e)
return cur
def setKeyPath(obj, keypath, value):
"""
Set the value at 'keypath'. The keypath is a string containing a
path of keys, separated by dots.
"""
if obj is None:
return
if isinstance(obj, (objc.objc_object, objc.objc_class)):
return obj.setValue_forKeyPath_(value, keypath)
elements = keypath.split(".")
cur = obj
for e in elements[:-1]:
cur = getKey(cur, e)
return setKey(cur, elements[-1], value)
class kvc:
def __init__(self, obj):
self.__pyobjc_object__ = obj
def __getattr__(self, attr):
return getKey(self.__pyobjc_object__, attr)
def __repr__(self):
return repr(self.__pyobjc_object__)
def __setattr__(self, attr, value):
if not attr.startswith("_"):
setKey(self.__pyobjc_object__, attr, value)
else:
object.__setattr__(self, attr, value)
def __getitem__(self, item):
if not isinstance(item, str):
raise TypeError("Keys must be strings")
return getKeyPath(self.__pyobjc_object__, item)
def __setitem__(self, item, value):
if not isinstance(item, str):
raise TypeError("Keys must be strings")
setKeyPath(self.__pyobjc_object__, item, value)