Source code for namedtuplex.abc
# -*- coding: utf-8 -*-
"""The package 'namedtuplex' provides an extended call interface and classes for *namedtuples*.
"""
from __future__ import absolute_import
from __future__ import print_function
from namedtupledefs import namedtuple as _namedtuple
from abc import ABCMeta, abstractproperty
from copy import copy, deepcopy
from pythonids import ISSTR, PYV27X
from namedtuplex import NamedTupleXParameterError
__author__ = 'Arno-Can Uestuensoez'
__license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
__copyright__ = "Copyright (C) 2019 Arno-Can Uestuensoez" \
"@Ingenieurbuero Arno-Can Uestuensoez"
__version__ = '0.1.1'
__uuid__ = "e3590f7b-2a97-4091-9534-d203d49a92ad"
__docformat__ = "restructuredtext en"
if PYV27X:
# avoid nested exceptions for Python2
class ModuleNotFoundError(Exception):
"""For compatibilty with Python3.
"""
pass
# for Jython use native python2 syntax - see later
[docs]class NamedTupleXABCMeta(ABCMeta):
"""The metaclass for named tuples as an abstract base with support
for inheritance and mixin.
For the abstract class refer to :ref:`NamedTupleXABC <RETURN_NamedTupleXABC>`.
"""
[docs] def __new__(cls, name, bases, namespace, **kargs):
"""Adds the symbolic field names for the indices and selected attributes
as member variables.
Args:
cls:
The created class.
name:
The name of the created class.
bases:
List of base classes.
namespace:
The namespace, containing the *_fields* class variable.
kargs:
fields:
The *fields* parameter provides a list of alternate names
for the contained *tuple* members. This in advance defines
the required mandatory number of items for the *tuple*.
.. parsed-literal::
fields := (
'"' <index-names> '"'
| '[' <index-list> ']'
| '(' <index-list> ')'
)
index-names := <index-name>[[ ,] <index-names>]
index-list := <index-name>[, <index-list>]
index-name := "valid name for the field index of the tuple"
fielddefaults:
Defines default values for the *fields* of the tuple. This
allows for the variable length initialization of the created
*namedtuple* class.
.. parsed-literal::
fielddefaults := '(' <default-list> ')'
default-list := '(' <key> ',' <value> ')'
key := (
<field-index>
| <field-name>
)
field-index := "valid integer index of a present field"
field-name := "valid name from the list _fields"
value := "default value"
Default values are supported in accordance to the standard Python
behaviour of call parameters [PYFUNC]_.
module
Sets *__module__* of the created class definition.
Available beginning with *Python-3.6*.
See [namedtuple]_.
tuplefactory:
The class factory to be used for the named tuple.
default := *collections.namedtuple*
rename:
If *True* replaces silently invalid field names by
'_<item-index>'.
default := *False*
Available *Python-2.7* and in *Python-3.x* see manuals "...beginning with Python3.1".
See [namedtuple]_.
useslots:
Creates a member *__slots__*.
default := *True*
See [namedtuple]_.
verbose:
Prints created class definition.
default := *False*
See [namedtuple]_.
Returns:
An abstract class derived from *namedtuple* with alternative
symbolic names for the contained items. In addition predefined
attributes are included.
Raises:
NamedTupleXParameterError
pass-through
"""
#
# parameters by class members
#
#
# defines the name of the created named tuple class
# default is 'namedtuple_<classname>'
#
_typename = namespace.get('_typename')
if _typename != None:
name = _typename
#
# defines the inheritance of _fields and _fielddefaults
#
try:
_merge = kargs.pop('merge')
except KeyError:
_merge = namespace.get('_merge', True)
#
# The presence of the _fields attribute defines the used
# base for each set of attributes. This in particular
# defines the related _fielddefaults
#
#
# fieldnames - superpose parents
#
try:
_fieldnames = kargs.pop('fields')
except KeyError:
_fieldnames = namespace.get('_fields', None)
if isinstance(_fieldnames, abstractproperty):
# it is abstract, so nothing more to do
return ABCMeta.__new__(cls, name, bases, namespace)
try:
_fielddefaults = kargs.pop('fielddefaults')
except KeyError:
_fielddefaults = namespace.get('_fielddefaults', None)
if _merge or (_fieldnames is None and _merge is not False):
# if no fieldnames of merge is forced, merges the parent
# classes fields and fielddefaults,
# else the local definition superposes parent classes
# is not abstract, and has no fieldname, thus possibly inherits
for base in bases:
_f = getattr(base, '_fields', ())
if _f is None:
# check next
continue
if isinstance(_f, abstractproperty):
# it is the ABC
continue
_d = getattr(base, '_fielddefaults', None)
if _d is not None:
if _fielddefaults is not None:
if len(_f) != len(_d):
raise NamedTupleXParameterError(
"default values are right bound and must not be scattered")
_fielddefaults += _d
else:
# is the first set
_fielddefaults = _d
elif _fielddefaults and _f:
# the previous left-hand mixins have defaults, so need for
# all current _fields a default too
raise NamedTupleXParameterError(
"default values are right bound and must not be scattered")
if type(_f) in ISSTR:
# is sstring with space separated field names - normalize
_f = _f.split(' ')
if _fieldnames is None:
# is the first
_fieldnames = _f
else:
# just concat
_fieldnames += _f
if _merge == False:
break
#
# the following attributes are used from current namespace only
#
try:
_tuplefactory = kargs.pop('tuplefactory')
except KeyError:
_tuplefactory = namespace.get('_tuplefactory', None)
try:
_module = kargs.pop('module')
except KeyError:
_module = namespace.get('_module', None)
try:
_rename = kargs.pop('rename')
except KeyError:
_rename = namespace.get('_rename', None)
try:
_verbose = kargs.pop('verbose')
except KeyError:
_verbose = namespace.get('_verbose', None)
if _tuplefactory == None:
_tuplefactory = _namedtuple
namespace['_tuplefactory'] = _tuplefactory
if _module != None:
kargs['module'] = _module
if _rename != None:
kargs['rename'] = _rename
if _verbose != None:
kargs['verbose'] = _verbose
if _fielddefaults != None:
kargs['fielddefaults'] = _fielddefaults
if _fieldnames is not None and not isinstance(_fieldnames, abstractproperty):
# is non-abstract class
mytuple = _tuplefactory("%s_%s" % (_tuplefactory.__name__, name), _fieldnames, **kargs)
bases = (mytuple,) + bases
namespace.setdefault('__doc__', mytuple.__doc__)
namespace.setdefault('__slots__', ())
#
# relies on the *tuplefactory*
#
if hasattr(mytuple, '_fields'):
namespace.pop('_fields', None)
if hasattr(mytuple, '_fielddefaults'):
namespace.pop('_fielddefaults', None)
return ABCMeta.__new__(cls, name, bases, namespace)
[docs]def with_metaclass(meta, *bases):
"""
Function from future/utils.py License: BSD.
Function from jinja2/_compat.py. License: BSD.
Do need a wrapper only for the compilation-breaking
metaclass syntax of Python2/Python3. Require
this on all platforms - including *Jython*.
Original doc-string:
Function from jinja2/_compat.py. License: BSD.
Use it like this::
class BaseForm(object):
pass
class FormType(type):
pass
class Form(with_metaclass(FormType, BaseForm)):
pass
This requires a bit of explanation: the basic idea is to make a
dummy metaclass for one level of class instantiation that replaces
itself with the actual metaclass. Because of internal type checks
we also need to make sure that we downgrade the custom metaclass
for one level to something closer to type (that's why __call__ and
__init__ comes back from type etc.).
This has the advantage over six.with_metaclass of not introducing
dummy classes into the final MRO.
"""
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
[docs]class NamedTupleXABC(with_metaclass(NamedTupleXABCMeta), object):
"""The abstract class for extended *tuple* classes.
The metaclass syntax is based on *future.utils* [future.utils]_ with support for
both *Python* syntax variants of *Python2.7* and *Python3*.
For the metaclass refer to :ref:`NamedTupleXABCMeta <RETURN_NamedTupleXABCMeta>`.
"""
_fields = abstractproperty() #: The abstract property, see abc.abstractproperty [abc]_, and :ref:`param_fieldnames`.
_fielddefaults = None #: Optional default values for _fields in function-paramater style see :ref:`param_fielddefaults`.
[docs] def merge(self, *others, **kargs):
"""Creates a new instance by concatenating the *others* to the copy
of current instance created with standard members. Additional
attributes, properties, and methods have to be processed by
derived classes.
Args:
others:
Instances of tuples to be merged into this instance. The merge
is processed by right-hand concatenation of *others*.
The supported types are:
::
NamedTupleXABC
OrderedDict # same as from '_asdict()'
<namedtuple> # the result of 'namedtuple()'
tuple # when 'rename' is 'True'
kargs:
deep:
If *True* merges a deep copy of all, else a swallow copy only.
default := *False*
module:
Optional parameter to be passed to the *tuplefactory*.
default := *None*
rename:
Optional parameter to be passed to the *tuplefactory*.
default := *None*
tuplefactory:
The *tuplefactory* callable to be used for the new named tuple.
default := *namedtupledefs.namedtuple*
verbose:
Optional parameter to be passed to the *tuplefactory*.
default := *None*
Returns:
A new instance of merged objects.
Raises:
ValueError
pass-through
"""
_kw = {}
if kargs.get('deep'):
_fn = deepcopy(self._fields)
_fd = deepcopy(self._fielddefaults) # spare None-check
else:
_fn = copy(self._fields)
_fd = copy(self._fielddefaults) # spare None-check
if kargs.get('verbose'):
_kw["verbose"] = kargs.get('verbose')
elif hasattr(self, 'verbose'):
_kw["verbose"] = self.verbose
if kargs.get('rename'):
_kw["rename"] = kargs.get('rename')
elif hasattr(self, 'rename'):
_kw["rename"] = self.rename
if kargs.get('module'):
_kw["module"] = kargs.get('module')
elif hasattr(self, 'module'):
_kw["module"] = self.module
_t = ()
for o in others:
if not hasattr(o, '_fields') or not hasattr(o, '_fielddefaults'):
raise ValueError(
"supports only classes from: namedtupledefs.namedtuple " + str(o))
# check for non-scattered function style default values
if _fd and (not o._fielddefaults or len(o._fielddefaults) != len(o._fields)):
raise ValueError(
"default values are right bound and must not be scattered: %s / %s"
%(str(o._fields), str(o._fielddefaults))
)
_fn += o._fields
_fd += o._fielddefaults
_t += o # want the tuple values without fiddling
# resulting names have to be valid - the tuple will check finally
tp = self.__class__(self.__class__.__name__, _fn, fielddefaults=_fd, **_kw)
return tp(*tuple.__add__(self, _t))
#: Reference to the abstract base class.
ABC = NamedTupleXABC #@UndefinedVariable
#: Reference to the metaclass
ABCMETA = NamedTupleXABCMeta