Your IP : 3.12.71.166
from ._common import *
from ._constant import NamedConstant
import sys as _sys
__all__ = [
'TupleSize', 'NamedTuple',
]
# NamedTuple
class NamedTupleDict(OrderedDict):
"""Track field order and ensure field names are not reused.
NamedTupleMeta will use the names found in self._field_names to translate
to indices.
"""
def __init__(self, *args, **kwds):
self._field_names = []
super(NamedTupleDict, self).__init__(*args, **kwds)
def __setitem__(self, key, value):
"""Records anything not dundered or not a descriptor.
If a field name is used twice, an error is raised.
Single underscore (sunder) names are reserved.
"""
if is_sunder(key):
if key not in ('_size_', '_order_', '_fields_', '_review_'):
raise ValueError(
'_sunder_ names, such as %r, are reserved for future NamedTuple use'
% (key, )
)
elif is_dunder(key):
if key == '__order__':
key = '_order_'
elif key in self._field_names:
# overwriting a field?
raise TypeError('attempt to reuse field name: %r' % (key, ))
elif not is_descriptor(value):
if key in self:
# field overwriting a descriptor?
raise TypeError('%s already defined as: %r' % (key, self[key]))
self._field_names.append(key)
super(NamedTupleDict, self).__setitem__(key, value)
class _TupleAttributeAtIndex(object):
def __init__(self, name, index, doc, default):
self.name = name
self.index = index
if doc is undefined:
doc = None
self.__doc__ = doc
self.default = default
def __get__(self, instance, owner):
if instance is None:
return self
if len(instance) <= self.index:
raise AttributeError('%s instance has no value for %s' % (instance.__class__.__name__, self.name))
return instance[self.index]
def __repr__(self):
return '%s(%d)' % (self.__class__.__name__, self.index)
class undefined(object):
def __repr__(self):
return 'undefined'
def __bool__(self):
return False
__nonzero__ = __bool__
undefined = undefined()
class TupleSize(NamedConstant):
fixed = constant('fixed', 'tuple length is static')
minimum = constant('minimum', 'tuple must be at least x long (x is calculated during creation')
variable = constant('variable', 'tuple length can be anything')
class NamedTupleMeta(type):
"""Metaclass for NamedTuple"""
@classmethod
def __prepare__(metacls, cls, bases, size=undefined, **kwds):
return NamedTupleDict()
def __init__(cls, *args , **kwds):
super(NamedTupleMeta, cls).__init__(*args)
def __new__(metacls, cls, bases, clsdict, size=undefined, **kwds):
if bases == (object, ):
bases = (tuple, object)
elif tuple not in bases:
if object in bases:
index = bases.index(object)
bases = bases[:index] + (tuple, ) + bases[index:]
else:
bases = bases + (tuple, )
# include any fields from base classes
base_dict = NamedTupleDict()
namedtuple_bases = []
for base in bases:
if isinstance(base, NamedTupleMeta):
namedtuple_bases.append(base)
i = 0
if namedtuple_bases:
for name, index, doc, default in metacls._convert_fields(*namedtuple_bases):
base_dict[name] = index, doc, default
i = max(i, index)
# construct properly ordered dict with normalized indexes
for k, v in clsdict.items():
base_dict[k] = v
original_dict = base_dict
if size is not undefined and '_size_' in original_dict:
raise TypeError('_size_ cannot be set if "size" is passed in header')
add_order = isinstance(clsdict, NamedTupleDict)
clsdict = NamedTupleDict()
clsdict.setdefault('_size_', size or TupleSize.fixed)
unnumbered = OrderedDict()
numbered = OrderedDict()
_order_ = original_dict.pop('_order_', [])
if _order_ :
_order_ = _order_.replace(',',' ').split()
add_order = False
# and process this class
for k, v in original_dict.items():
if k not in original_dict._field_names:
clsdict[k] = v
else:
# TODO:normalize v here
if isinstance(v, baseinteger):
# assume an offset
v = v, undefined, undefined
i = v[0] + 1
target = numbered
elif isinstance(v, basestring):
# assume a docstring
if add_order:
v = i, v, undefined
i += 1
target = numbered
else:
v = undefined, v, undefined
target = unnumbered
elif isinstance(v, tuple) and len(v) in (2, 3) and isinstance(v[0], baseinteger) and isinstance(v[1], (basestring, NoneType)):
# assume an offset, a docstring, and (maybe) a default
if len(v) == 2:
v = v + (undefined, )
v = v
i = v[0] + 1
target = numbered
elif isinstance(v, tuple) and len(v) in (1, 2) and isinstance(v[0], (basestring, NoneType)):
# assume a docstring, and (maybe) a default
if len(v) == 1:
v = v + (undefined, )
if add_order:
v = (i, ) + v
i += 1
target = numbered
else:
v = (undefined, ) + v
target = unnumbered
else:
# refuse to guess further
raise ValueError('not sure what to do with %s=%r (should be OFFSET [, DOC [, DEFAULT]])' % (k, v))
target[k] = v
# all index values have been normalized
# deal with _order_ (or lack thereof)
fields = []
aliases = []
seen = set()
max_len = 0
if not _order_:
if unnumbered:
raise ValueError("_order_ not specified and OFFSETs not declared for %r" % (unnumbered.keys(), ))
for name, (index, doc, default) in sorted(numbered.items(), key=lambda nv: (nv[1][0], nv[0])):
if index in seen:
aliases.append(name)
else:
fields.append(name)
seen.add(index)
max_len = max(max_len, index + 1)
offsets = numbered
else:
# check if any unnumbered not in _order_
missing = set(unnumbered) - set(_order_)
if missing:
raise ValueError("unable to order fields: %s (use _order_ or specify OFFSET" % missing)
offsets = OrderedDict()
# if any unnumbered, number them from their position in _order_
i = 0
for k in _order_:
try:
index, doc, default = unnumbered.pop(k, None) or numbered.pop(k)
except IndexError:
raise ValueError('%s (from _order_) not found in %s' % (k, cls))
if index is not undefined:
i = index
if i in seen:
aliases.append(k)
else:
fields.append(k)
seen.add(i)
offsets[k] = i, doc, default
i += 1
max_len = max(max_len, i)
# now handle anything in numbered
for k, (index, doc, default) in sorted(numbered.items(), key=lambda nv: (nv[1][0], nv[0])):
if index in seen:
aliases.append(k)
else:
fields.append(k)
seen.add(index)
offsets[k] = index, doc, default
max_len = max(max_len, index+1)
# at this point fields and aliases should be ordered lists, offsets should be an
# OrdededDict with each value an int, str or None or undefined, default or None or undefined
assert len(fields) + len(aliases) == len(offsets), "number of fields + aliases != number of offsets"
assert set(fields) & set(offsets) == set(fields), "some fields are not in offsets: %s" % set(fields) & set(offsets)
assert set(aliases) & set(offsets) == set(aliases), "some aliases are not in offsets: %s" % set(aliases) & set(offsets)
for name, (index, doc, default) in offsets.items():
assert isinstance(index, baseinteger), "index for %s is not an int (%s:%r)" % (name, type(index), index)
assert isinstance(doc, (basestring, NoneType)) or doc is undefined, "doc is not a str, None, nor undefined (%s:%r)" % (name, type(doc), doc)
# create descriptors for fields
for name, (index, doc, default) in offsets.items():
clsdict[name] = _TupleAttributeAtIndex(name, index, doc, default)
clsdict['__slots__'] = ()
# create our new NamedTuple type
namedtuple_class = super(NamedTupleMeta, metacls).__new__(metacls, cls, bases, clsdict)
namedtuple_class._fields_ = fields
namedtuple_class._aliases_ = aliases
namedtuple_class._defined_len_ = max_len
return namedtuple_class
@staticmethod
def _convert_fields(*namedtuples):
"create list of index, doc, default triplets for cls in namedtuples"
all_fields = []
for cls in namedtuples:
base = len(all_fields)
for field in cls._fields_:
desc = getattr(cls, field)
all_fields.append((field, base+desc.index, desc.__doc__, desc.default))
return all_fields
def __add__(cls, other):
"A new NamedTuple is created by concatenating the _fields_ and adjusting the descriptors"
if not isinstance(other, NamedTupleMeta):
return NotImplemented
return NamedTupleMeta('%s%s' % (cls.__name__, other.__name__), (cls, other), {})
def __call__(cls, *args, **kwds):
"""Creates a new NamedTuple class or an instance of a NamedTuple subclass.
NamedTuple should have args of (class_name, names, module)
`names` can be:
* A string containing member names, separated either with spaces or
commas. Values are auto-numbered from 1.
* An iterable of member names. Values are auto-numbered from 1.
* An iterable of (member name, value) pairs.
* A mapping of member name -> value.
`module`, if set, will be stored in the new class' __module__ attribute;
Note: if `module` is not set this routine will attempt to discover the
calling module by walking the frame stack; if this is unsuccessful
the resulting class will not be pickleable.
subclass should have whatever arguments and/or keywords will be used to create an
instance of the subclass
"""
if cls is NamedTuple or cls._defined_len_ == 0:
original_args = args
original_kwds = kwds.copy()
# create a new subclass
try:
if 'class_name' in kwds:
class_name = kwds.pop('class_name')
else:
class_name, args = args[0], args[1:]
if 'names' in kwds:
names = kwds.pop('names')
else:
names, args = args[0], args[1:]
if 'module' in kwds:
module = kwds.pop('module')
elif args:
module, args = args[0], args[1:]
else:
module = None
if 'type' in kwds:
type = kwds.pop('type')
elif args:
type, args = args[0], args[1:]
else:
type = None
except IndexError:
raise TypeError('too few arguments to NamedTuple: %s, %s' % (original_args, original_kwds))
if args or kwds:
raise TypeError('too many arguments to NamedTuple: %s, %s' % (original_args, original_kwds))
if PY2:
# if class_name is unicode, attempt a conversion to ASCII
if isinstance(class_name, unicode):
try:
class_name = class_name.encode('ascii')
except UnicodeEncodeError:
raise TypeError('%r is not representable in ASCII' % (class_name, ))
# quick exit if names is a NamedTuple
if isinstance(names, NamedTupleMeta):
names.__name__ = class_name
if type is not None and type not in names.__bases__:
names.__bases__ = (type, ) + names.__bases__
return names
metacls = cls.__class__
bases = (cls, )
clsdict = metacls.__prepare__(class_name, bases)
# special processing needed for names?
if isinstance(names, basestring):
names = names.replace(',', ' ').split()
if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):
names = [(e, i) for (i, e) in enumerate(names)]
# Here, names is either an iterable of (name, index) or (name, index, doc, default) or a mapping.
item = None # in case names is empty
for item in names:
if isinstance(item, basestring):
# mapping
field_name, field_index = item, names[item]
else:
# non-mapping
if len(item) == 2:
field_name, field_index = item
else:
field_name, field_index = item[0], item[1:]
clsdict[field_name] = field_index
if type is not None:
if not isinstance(type, tuple):
type = (type, )
bases = type + bases
namedtuple_class = metacls.__new__(metacls, class_name, bases, clsdict)
# TODO: replace the frame hack if a blessed way to know the calling
# module is ever developed
if module is None:
try:
module = _sys._getframe(1).f_globals['__name__']
except (AttributeError, ValueError, KeyError):
pass
if module is None:
make_class_unpicklable(namedtuple_class)
else:
namedtuple_class.__module__ = module
return namedtuple_class
else:
# instantiate a subclass
namedtuple_instance = cls.__new__(cls, *args, **kwds)
if isinstance(namedtuple_instance, cls):
namedtuple_instance.__init__(*args, **kwds)
return namedtuple_instance
@bltin_property
def __fields__(cls):
return list(cls._fields_)
# collections.namedtuple compatibility
_fields = __fields__
@bltin_property
def __aliases__(cls):
return list(cls._aliases_)
def __repr__(cls):
return "<NamedTuple %r>" % (cls.__name__, )
namedtuple_dict = _Addendum(
dict=NamedTupleMeta.__prepare__('NamedTuple', (object, )),
doc="NamedTuple base class.\n\n Derive from this class to define new NamedTuples.\n\n",
ns=globals(),
)
@namedtuple_dict
def __new__(cls, *args, **kwds):
if cls._size_ is TupleSize.fixed and len(args) > cls._defined_len_:
raise TypeError('%d fields expected, %d received' % (cls._defined_len_, len(args)))
unknown = set(kwds) - set(cls._fields_) - set(cls._aliases_)
if unknown:
raise TypeError('unknown fields: %r' % (unknown, ))
final_args = list(args) + [undefined] * (len(cls.__fields__) - len(args))
for field, value in kwds.items():
index = getattr(cls, field).index
if final_args[index] != undefined:
raise TypeError('field %s specified more than once' % field)
final_args[index] = value
cls._review_(final_args)
missing = []
for index, value in enumerate(final_args):
if value is undefined:
# look for default values
name = cls.__fields__[index]
default = getattr(cls, name).default
if default is undefined:
missing.append(name)
else:
final_args[index] = default
if missing:
if cls._size_ in (TupleSize.fixed, TupleSize.minimum):
raise TypeError('values not provided for field(s): %s' % ', '.join(missing))
while final_args and final_args[-1] is undefined:
final_args.pop()
missing.pop()
if cls._size_ is not TupleSize.variable or undefined in final_args:
raise TypeError('values not provided for field(s): %s' % ', '.join(missing))
return tuple.__new__(cls, tuple(final_args))
@namedtuple_dict
def __reduce_ex__(self, proto):
return self.__class__, tuple(getattr(self, f) for f in self._fields_)
@namedtuple_dict
def __repr__(self):
if len(self) == len(self._fields_):
return "%s(%s)" % (
self.__class__.__name__, ', '.join(['%s=%r' % (f, o) for f, o in zip(self._fields_, self)])
)
else:
return '%s(%s)' % (self.__class__.__name__, ', '.join([repr(o) for o in self]))
@namedtuple_dict
def __str__(self):
return "%s(%s)" % (
self.__class__.__name__, ', '.join(['%r' % (getattr(self, f), ) for f in self._fields_])
)
@namedtuple_dict
@bltin_property
def _fields_(self):
return list(self.__class__._fields_)
# compatibility methods with stdlib namedtuple
@namedtuple_dict
@bltin_property
def __aliases__(self):
return list(self.__class__._aliases_)
@namedtuple_dict
@bltin_property
def _fields(self):
return list(self.__class__._fields_)
@namedtuple_dict
@classmethod
def _make(cls, iterable, new=None, len=None):
return cls.__new__(cls, *iterable)
@namedtuple_dict
def _asdict(self):
return OrderedDict(zip(self._fields_, self))
@namedtuple_dict
def _replace(self, **kwds):
current = self._asdict()
current.update(kwds)
return self.__class__(**current)
@namedtuple_dict
@classmethod
def _review_(cls, final_args):
pass
NamedTuple = NamedTupleMeta('NamedTuple', (object, ), namedtuple_dict.resolve())
del namedtuple_dict