Source code for pytyp.spec.dispatch
# The contents of this file are subject to the Mozilla Public License
# (MPL) Version 1.1 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License
# at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is Pytyp (http://www.acooke.org/pytyp)
# The Initial Developer of the Original Code is Andrew Cooke.
# Portions created by the Initial Developer are Copyright (C) 2011
# Andrew Cooke. All Rights Reserved.
#
# Alternatively, the contents of this file may be used under the terms
# of the LGPL license (the GNU Lesser General Public License,
# http://www.gnu.org/licenses/lgpl.html), in which case the provisions
# of the LGPL License are applicable instead of those above.
#
# If you wish to allow use of your version of this file only under the
# terms of the LGPL License and not to allow others to use your version
# of this file under the MPL, indicate your decision by deleting the
# provisions above and replace them with the notice and other provisions
# required by the LGPL License. If you do not delete the provisions
# above, a recipient may use your version of this file under either the
# MPL or the LGPL License.
from collections import OrderedDict
from functools import wraps
from inspect import getcallargs
from pytyp.spec.check import unpack, verify_all
class DispatchError(TypeError): pass
[docs]class Overload:
'''
This class implements the ``overload`` decorator via the constructor. It
implements a data descriptor that returns the wrapper method (and the
``.intercept()`` method, used to add additional methods).
'''
def __init__(self, default):
self.__name__ = default.__name__
if unpack(default)[0]:
raise DispatchError('Default method {} has type specifications.'
.format(default.__name__))
self._final = None
self.intercept(default)
def intercept(self, method):
self._final = self.wrap(method, self._final)
return self._final
def __get__(self, obj, objtype=None):
if obj:
return lambda *args, **kargs: self._final(obj, *args, **kargs)
else:
return self
@staticmethod
def wrap(method, previous):
annotation = unpack(method)[0]
@wraps(method)
def wrapper(obj, *args, **kargs):
callargs = getcallargs(method, obj, *args, **kargs)
try:
verify_all(callargs, annotation)
except TypeError:
#print('Failed {} with {} {}'.format(method.__name__, args, kargs))
if previous:
return previous(obj, *args, **kargs)
else:
raise
wrapper.obj = obj # for previous
return method(obj, *args, **kargs)
wrapper.previous = lambda *args, **kargs: previous(wrapper.obj, *args, **kargs)
return wrapper
overload = Overload
'''
This is the decorator for dynamic dispatch by type. It should be placed on the
default method - it is that method whose name will be called for *all* the
overloaded methods.
Additional methods are then marked by a decorator that is ``.intercept`` on the
default. For example::
class MyClass:
@overload
def default_method(self, foo, bar):
# code here runs if foo is not a sequence (or list)
@default_method.intercept
def foo_seq(self, foo:Sequence, bar):
# code here runs if foo is a sequence (but not a string!)
@default_method.intercept
def foo_list(self, foo:list, bar):
# code here runs if foo is a list
In the example above, when ``default_method()`` is called, any of the three
methods might be used, depending on the type of ``foo``.
The order in which methods are checked is "bottom to top" and a method can
pass the call "up" by calling ``.previous()`` on itself. So, for example,
code in ``foo_list()`` can call ``foo_seq()`` via ``self.foo_list.previous()``.
'''