aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython-genutils/py3/ipython_genutils/ipstruct.py
diff options
context:
space:
mode:
authornkozlovskiy <nmk@ydb.tech>2023-09-29 12:24:06 +0300
committernkozlovskiy <nmk@ydb.tech>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/ipython-genutils/py3/ipython_genutils/ipstruct.py
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
downloadydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz
add ydb deps
Diffstat (limited to 'contrib/python/ipython-genutils/py3/ipython_genutils/ipstruct.py')
-rw-r--r--contrib/python/ipython-genutils/py3/ipython_genutils/ipstruct.py376
1 files changed, 376 insertions, 0 deletions
diff --git a/contrib/python/ipython-genutils/py3/ipython_genutils/ipstruct.py b/contrib/python/ipython-genutils/py3/ipython_genutils/ipstruct.py
new file mode 100644
index 0000000000..31511ecdf1
--- /dev/null
+++ b/contrib/python/ipython-genutils/py3/ipython_genutils/ipstruct.py
@@ -0,0 +1,376 @@
+# encoding: utf-8
+"""A dict subclass that supports attribute style access.
+
+Can probably be replaced by types.SimpleNamespace from Python 3.3
+"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+__all__ = ['Struct']
+
+
+class Struct(dict):
+ """A dict subclass with attribute style access.
+
+ This dict subclass has a a few extra features:
+
+ * Attribute style access.
+ * Protection of class members (like keys, items) when using attribute
+ style access.
+ * The ability to restrict assignment to only existing keys.
+ * Intelligent merging.
+ * Overloaded operators.
+ """
+ _allownew = True
+ def __init__(self, *args, **kw):
+ """Initialize with a dictionary, another Struct, or data.
+
+ Parameters
+ ----------
+ args : dict, Struct
+ Initialize with one dict or Struct
+ kw : dict
+ Initialize with key, value pairs.
+
+ Examples
+ --------
+
+ >>> s = Struct(a=10,b=30)
+ >>> s.a
+ 10
+ >>> s.b
+ 30
+ >>> s2 = Struct(s,c=30)
+ >>> sorted(s2.keys())
+ ['a', 'b', 'c']
+ """
+ object.__setattr__(self, '_allownew', True)
+ dict.__init__(self, *args, **kw)
+
+ def __setitem__(self, key, value):
+ """Set an item with check for allownew.
+
+ Examples
+ --------
+
+ >>> s = Struct()
+ >>> s['a'] = 10
+ >>> s.allow_new_attr(False)
+ >>> s['a'] = 10
+ >>> s['a']
+ 10
+ >>> try:
+ ... s['b'] = 20
+ ... except KeyError:
+ ... print('this is not allowed')
+ ...
+ this is not allowed
+ """
+ if not self._allownew and key not in self:
+ raise KeyError(
+ "can't create new attribute %s when allow_new_attr(False)" % key)
+ dict.__setitem__(self, key, value)
+
+ def __setattr__(self, key, value):
+ """Set an attr with protection of class members.
+
+ This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
+ :exc:`AttributeError`.
+
+ Examples
+ --------
+
+ >>> s = Struct()
+ >>> s.a = 10
+ >>> s.a
+ 10
+ >>> try:
+ ... s.get = 10
+ ... except AttributeError:
+ ... print("you can't set a class member")
+ ...
+ you can't set a class member
+ """
+ # If key is an str it might be a class member or instance var
+ if isinstance(key, str):
+ # I can't simply call hasattr here because it calls getattr, which
+ # calls self.__getattr__, which returns True for keys in
+ # self._data. But I only want keys in the class and in
+ # self.__dict__
+ if key in self.__dict__ or hasattr(Struct, key):
+ raise AttributeError(
+ 'attr %s is a protected member of class Struct.' % key
+ )
+ try:
+ self.__setitem__(key, value)
+ except KeyError as e:
+ raise AttributeError(e)
+
+ def __getattr__(self, key):
+ """Get an attr by calling :meth:`dict.__getitem__`.
+
+ Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
+ :exc:`AttributeError`.
+
+ Examples
+ --------
+
+ >>> s = Struct(a=10)
+ >>> s.a
+ 10
+ >>> type(s.get)
+ <... 'builtin_function_or_method'>
+ >>> try:
+ ... s.b
+ ... except AttributeError:
+ ... print("I don't have that key")
+ ...
+ I don't have that key
+ """
+ try:
+ result = self[key]
+ except KeyError:
+ raise AttributeError(key)
+ else:
+ return result
+
+ def __iadd__(self, other):
+ """s += s2 is a shorthand for s.merge(s2).
+
+ Examples
+ --------
+
+ >>> s = Struct(a=10,b=30)
+ >>> s2 = Struct(a=20,c=40)
+ >>> s += s2
+ >>> sorted(s.keys())
+ ['a', 'b', 'c']
+ """
+ self.merge(other)
+ return self
+
+ def __add__(self,other):
+ """s + s2 -> New Struct made from s.merge(s2).
+
+ Examples
+ --------
+
+ >>> s1 = Struct(a=10,b=30)
+ >>> s2 = Struct(a=20,c=40)
+ >>> s = s1 + s2
+ >>> sorted(s.keys())
+ ['a', 'b', 'c']
+ """
+ sout = self.copy()
+ sout.merge(other)
+ return sout
+
+ def __sub__(self,other):
+ """s1 - s2 -> remove keys in s2 from s1.
+
+ Examples
+ --------
+
+ >>> s1 = Struct(a=10,b=30)
+ >>> s2 = Struct(a=40)
+ >>> s = s1 - s2
+ >>> s
+ {'b': 30}
+ """
+ sout = self.copy()
+ sout -= other
+ return sout
+
+ def __isub__(self,other):
+ """Inplace remove keys from self that are in other.
+
+ Examples
+ --------
+
+ >>> s1 = Struct(a=10,b=30)
+ >>> s2 = Struct(a=40)
+ >>> s1 -= s2
+ >>> s1
+ {'b': 30}
+ """
+ for k in other.keys():
+ if k in self:
+ del self[k]
+ return self
+
+ def __dict_invert(self, data):
+ """Helper function for merge.
+
+ Takes a dictionary whose values are lists and returns a dict with
+ the elements of each list as keys and the original keys as values.
+ """
+ outdict = {}
+ for k,lst in data.items():
+ if isinstance(lst, str):
+ lst = lst.split()
+ for entry in lst:
+ outdict[entry] = k
+ return outdict
+
+ def dict(self):
+ return self
+
+ def copy(self):
+ """Return a copy as a Struct.
+
+ Examples
+ --------
+
+ >>> s = Struct(a=10,b=30)
+ >>> s2 = s.copy()
+ >>> type(s2) is Struct
+ True
+ """
+ return Struct(dict.copy(self))
+
+ def hasattr(self, key):
+ """hasattr function available as a method.
+
+ Implemented like has_key.
+
+ Examples
+ --------
+
+ >>> s = Struct(a=10)
+ >>> s.hasattr('a')
+ True
+ >>> s.hasattr('b')
+ False
+ >>> s.hasattr('get')
+ False
+ """
+ return key in self
+
+ def allow_new_attr(self, allow = True):
+ """Set whether new attributes can be created in this Struct.
+
+ This can be used to catch typos by verifying that the attribute user
+ tries to change already exists in this Struct.
+ """
+ object.__setattr__(self, '_allownew', allow)
+
+ def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
+ """Merge two Structs with customizable conflict resolution.
+
+ This is similar to :meth:`update`, but much more flexible. First, a
+ dict is made from data+key=value pairs. When merging this dict with
+ the Struct S, the optional dictionary 'conflict' is used to decide
+ what to do.
+
+ If conflict is not given, the default behavior is to preserve any keys
+ with their current value (the opposite of the :meth:`update` method's
+ behavior).
+
+ Parameters
+ ----------
+ __loc_data : dict, Struct
+ The data to merge into self
+ __conflict_solve : dict
+ The conflict policy dict. The keys are binary functions used to
+ resolve the conflict and the values are lists of strings naming
+ the keys the conflict resolution function applies to. Instead of
+ a list of strings a space separated string can be used, like
+ 'a b c'.
+ kw : dict
+ Additional key, value pairs to merge in
+
+ Notes
+ -----
+
+ The `__conflict_solve` dict is a dictionary of binary functions which will be used to
+ solve key conflicts. Here is an example::
+
+ __conflict_solve = dict(
+ func1=['a','b','c'],
+ func2=['d','e']
+ )
+
+ In this case, the function :func:`func1` will be used to resolve
+ keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
+ keys 'd' and 'e'. This could also be written as::
+
+ __conflict_solve = dict(func1='a b c',func2='d e')
+
+ These functions will be called for each key they apply to with the
+ form::
+
+ func1(self['a'], other['a'])
+
+ The return value is used as the final merged value.
+
+ As a convenience, merge() provides five (the most commonly needed)
+ pre-defined policies: preserve, update, add, add_flip and add_s. The
+ easiest explanation is their implementation::
+
+ preserve = lambda old,new: old
+ update = lambda old,new: new
+ add = lambda old,new: old + new
+ add_flip = lambda old,new: new + old # note change of order!
+ add_s = lambda old,new: old + ' ' + new # only for str!
+
+ You can use those four words (as strings) as keys instead
+ of defining them as functions, and the merge method will substitute
+ the appropriate functions for you.
+
+ For more complicated conflict resolution policies, you still need to
+ construct your own functions.
+
+ Examples
+ --------
+
+ This show the default policy:
+
+ >>> s = Struct(a=10,b=30)
+ >>> s2 = Struct(a=20,c=40)
+ >>> s.merge(s2)
+ >>> sorted(s.items())
+ [('a', 10), ('b', 30), ('c', 40)]
+
+ Now, show how to specify a conflict dict:
+
+ >>> s = Struct(a=10,b=30)
+ >>> s2 = Struct(a=20,b=40)
+ >>> conflict = {'update':'a','add':'b'}
+ >>> s.merge(s2,conflict)
+ >>> sorted(s.items())
+ [('a', 20), ('b', 70)]
+ """
+
+ data_dict = dict(__loc_data__,**kw)
+
+ # policies for conflict resolution: two argument functions which return
+ # the value that will go in the new struct
+ preserve = lambda old,new: old
+ update = lambda old,new: new
+ add = lambda old,new: old + new
+ add_flip = lambda old,new: new + old # note change of order!
+ add_s = lambda old,new: old + ' ' + new
+
+ # default policy is to keep current keys when there's a conflict
+ conflict_solve = dict.fromkeys(self, preserve)
+
+ # the conflict_solve dictionary is given by the user 'inverted': we
+ # need a name-function mapping, it comes as a function -> names
+ # dict. Make a local copy (b/c we'll make changes), replace user
+ # strings for the three builtin policies and invert it.
+ if __conflict_solve:
+ inv_conflict_solve_user = __conflict_solve.copy()
+ for name, func in [('preserve',preserve), ('update',update),
+ ('add',add), ('add_flip',add_flip),
+ ('add_s',add_s)]:
+ if name in inv_conflict_solve_user.keys():
+ inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
+ del inv_conflict_solve_user[name]
+ conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
+ for key in data_dict:
+ if key not in self:
+ self[key] = data_dict[key]
+ else:
+ self[key] = conflict_solve[key](self[key],data_dict[key])
+