diff --git a/MANIFEST.in b/MANIFEST.in index aa665ab91..9f474bfef 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -global-include *.h *.S *.c *.cu +global-include *.h *.S *.c *.cu LICENSE include buildbot.json include LICENSE include README.rst diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 937fa9cf8..bccabb12c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,57 +9,57 @@ jobs: - job: 'Test' strategy: matrix: - Python27Linux: - imageName: 'ubuntu-16.04' - python.version: '2.7' - Python27Mac: - imageName: 'macos-10.15' - python.version: '2.7' - Python35Linux: - imageName: 'ubuntu-16.04' - python.version: '3.5' - Python35Windows: - imageName: 'vs2017-win2016' - python.version: '3.5' - Python35Mac: - imageName: 'macos-10.15' - python.version: '3.5' Python36Linux: - imageName: 'ubuntu-16.04' + imageName: 'ubuntu-latest' python.version: '3.6' Python36Windows: - imageName: 'vs2017-win2016' - python.version: '3.6' - Python36Mac: - imageName: 'macos-10.15' + imageName: 'windows-2019' python.version: '3.6' Python37Linux: - imageName: 'ubuntu-16.04' + imageName: 'ubuntu-latest' python.version: '3.7' Python37Windows: - imageName: 'vs2017-win2016' + imageName: 'windows-latest' python.version: '3.7' Python37Mac: - imageName: 'macos-10.15' + imageName: 'macos-latest' python.version: '3.7' Python38Linux: - imageName: 'ubuntu-16.04' + imageName: 'ubuntu-latest' python.version: '3.8' Python38Windows: - imageName: 'vs2017-win2016' + imageName: 'windows-latest' python.version: '3.8' Python38Mac: - imageName: 'macos-10.15' + imageName: 'macos-latest' python.version: '3.8' Python39Linux: - imageName: 'ubuntu-16.04' + imageName: 'ubuntu-latest' python.version: '3.9' Python39Windows: - imageName: 'vs2017-win2016' + imageName: 'windows-latest' python.version: '3.9' Python39Mac: - imageName: 'macos-10.15' + imageName: 'macos-latest' python.version: '3.9' + Python310Linux: + imageName: 'ubuntu-latest' + python.version: '3.10' + Python310Windows: + imageName: 'windows-latest' + python.version: '3.10' + Python310Mac: + imageName: 'macos-latest' + python.version: '3.10' + Python311Linux: + imageName: 'ubuntu-latest' + python.version: '3.11.0-rc.2' + Python311Windows: + imageName: 'windows-latest' + python.version: '3.11.0-rc.2' + Python311Mac: + imageName: 'macos-latest' + python.version: '3.11.0-rc.2' maxParallel: 4 pool: vmImage: $(imageName) @@ -69,6 +69,7 @@ jobs: inputs: versionSpec: '$(python.version)' architecture: 'x64' + allowUnstable: true - script: | python -m pip install --upgrade pip setuptools diff --git a/build-constraints.txt b/build-constraints.txt index 23e660096..c1e82f1b0 100644 --- a/build-constraints.txt +++ b/build-constraints.txt @@ -1,5 +1,9 @@ # build version constraints for use with wheelwright + multibuild -numpy==1.15.0; python_version<='3.7' -numpy==1.17.3; python_version=='3.8' +numpy==1.15.0; python_version<='3.7' and platform_machine!='aarch64' +numpy==1.19.2; python_version<='3.7' and platform_machine=='aarch64' +numpy==1.17.3; python_version=='3.8' and platform_machine!='aarch64' +numpy==1.19.2; python_version=='3.8' and platform_machine=='aarch64' numpy==1.19.3; python_version=='3.9' -numpy; python_version>='3.10' +numpy==1.21.3; python_version=='3.10' +numpy==1.23.2; python_version=='3.11' +numpy; python_version>='3.12' diff --git a/requirements.txt b/requirements.txt index 95e91abfa..8e46c9589 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ tqdm>=4.10.0,<5.0.0 pathlib==1.0.1; python_version < "3.4" # Development dependencies cython>=0.25 -hypothesis>=4.0.0,<6.0.0 +hypothesis>=4.0.0,<7.0.0 pytest mock>=2.0.0,<3.0.0 flake8>=3.5.0,<3.6.0 diff --git a/setup.py b/setup.py index 353d27548..06e9ba10a 100644 --- a/setup.py +++ b/setup.py @@ -195,7 +195,7 @@ def setup_package(): ext_modules=cythonize(ext_modules, language_level=2), setup_requires=[ "numpy>=1.15.0", - "cython>=0.25", + "cython>=0.25,<3.0", "murmurhash>=0.28.0,<1.1.0", "cymem>=2.0.2,<2.1.0", "preshed>=1.0.1,<3.1.0", @@ -247,6 +247,8 @@ def setup_package(): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Scientific/Engineering", ], cmdclass={"build_ext": build_ext_subclass}, diff --git a/thinc/about.py b/thinc/about.py index 07dee2a74..c8ba958be 100644 --- a/thinc/about.py +++ b/thinc/about.py @@ -4,7 +4,7 @@ # https://github.com/pypa/warehouse/blob/master/warehouse/__about__.py __name__ = "thinc" -__version__ = "7.4.4" +__version__ = "7.4.6" __summary__ = "Practical Machine Learning for NLP" __uri__ = "https://github.com/explosion/thinc" __author__ = "Matthew Honnibal" diff --git a/thinc/extra/wrapt/LICENSE b/thinc/extra/wrapt/LICENSE new file mode 100644 index 000000000..1cc511724 --- /dev/null +++ b/thinc/extra/wrapt/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2013-2022, Graham Dumpleton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/thinc/extra/wrapt/__init__.py b/thinc/extra/wrapt/__init__.py index cdde5c68a..ee6539b77 100644 --- a/thinc/extra/wrapt/__init__.py +++ b/thinc/extra/wrapt/__init__.py @@ -1,30 +1,27 @@ -__version_info__ = ("1", "11", "1") -__version__ = ".".join(__version_info__) +__version_info__ = ('1', '14', '1') +__version__ = '.'.join(__version_info__) -from .wrappers import ( - ObjectProxy, - CallableObjectProxy, - FunctionWrapper, - BoundFunctionWrapper, - WeakFunctionProxy, - PartialCallableObjectProxy, - resolve_path, - apply_patch, - wrap_object, - wrap_object_attribute, - function_wrapper, - wrap_function_wrapper, - patch_function_wrapper, - transient_function_wrapper, -) +from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper, + BoundFunctionWrapper, WeakFunctionProxy, PartialCallableObjectProxy, + resolve_path, apply_patch, wrap_object, wrap_object_attribute, + function_wrapper, wrap_function_wrapper, patch_function_wrapper, + transient_function_wrapper) -from .decorators import adapter_factory, AdapterFactory, decorator, synchronized +from .decorators import (adapter_factory, AdapterFactory, decorator, + synchronized) -from .importer import ( - register_post_import_hook, - when_imported, - notify_module_loaded, - discover_post_import_hooks, -) +from .importer import (register_post_import_hook, when_imported, + notify_module_loaded, discover_post_import_hooks) + +# Import of inspect.getcallargs() included for backward compatibility. An +# implementation of this was previously bundled and made available here for +# Python <2.7. Avoid using this in future. from inspect import getcallargs + +# Variant of inspect.formatargspec() included here for forward compatibility. +# This is being done because Python 3.11 dropped inspect.formatargspec() but +# code for handling signature changing decorators relied on it. Exposing the +# bundled implementation here in case any user of wrapt was also needing it. + +from .arguments import formatargspec diff --git a/thinc/extra/wrapt/_wrappers.c b/thinc/extra/wrapt/_wrappers.c index 0569a5a7f..67c5d5e1a 100644 --- a/thinc/extra/wrapt/_wrappers.c +++ b/thinc/extra/wrapt/_wrappers.c @@ -1328,6 +1328,19 @@ static PyObject *WraptObjectProxy_complex( /* ------------------------------------------------------------------------- */ +static PyObject *WraptObjectProxy_mro_entries( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) +{ + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + return Py_BuildValue("(O)", self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + static PyObject *WraptObjectProxy_get_name( WraptObjectProxyObject *self) { @@ -1746,6 +1759,10 @@ static PyMethodDef WraptObjectProxy_methods[] = { { "__round__", (PyCFunction)WraptObjectProxy_round, METH_NOARGS, 0 }, #endif { "__complex__", (PyCFunction)WraptObjectProxy_complex, METH_NOARGS, 0 }, +#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7) + { "__mro_entries__", (PyCFunction)WraptObjectProxy_mro_entries, + METH_VARARGS | METH_KEYWORDS, 0 }, +#endif { NULL, NULL }, }; @@ -1944,13 +1961,13 @@ static int WraptPartialCallableObjectProxy_init( if (!PyObject_Length(args)) { PyErr_SetString(PyExc_TypeError, - "__init__ of partial needs an argument"); + "__init__ of partial needs an argument"); return -1; } if (PyObject_Length(args) < 1) { PyErr_SetString(PyExc_TypeError, - "partial type takes at least one argument"); + "partial type takes at least one argument"); return -1; } @@ -1958,7 +1975,7 @@ static int WraptPartialCallableObjectProxy_init( if (!PyCallable_Check(wrapped)) { PyErr_SetString(PyExc_TypeError, - "the first argument must be callable"); + "the first argument must be callable"); return -1; } @@ -1968,7 +1985,7 @@ static int WraptPartialCallableObjectProxy_init( return -1; result = WraptPartialCallableObjectProxy_raw_init(self, wrapped, - fnargs, kwds); + fnargs, kwds); Py_DECREF(fnargs); @@ -2007,6 +2024,8 @@ static int WraptPartialCallableObjectProxy_clear( static void WraptPartialCallableObjectProxy_dealloc( WraptPartialCallableObjectProxyObject *self) { + PyObject_GC_UnTrack(self); + WraptPartialCallableObjectProxy_clear(self); WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); @@ -2263,6 +2282,8 @@ static int WraptFunctionWrapperBase_clear(WraptFunctionWrapperObject *self) static void WraptFunctionWrapperBase_dealloc(WraptFunctionWrapperObject *self) { + PyObject_GC_UnTrack(self); + WraptFunctionWrapperBase_clear(self); WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); @@ -2278,12 +2299,15 @@ static PyObject *WraptFunctionWrapperBase_call( PyObject *result = NULL; static PyObject *function_str = NULL; + static PyObject *classmethod_str = NULL; if (!function_str) { #if PY_MAJOR_VERSION >= 3 function_str = PyUnicode_InternFromString("function"); + classmethod_str = PyUnicode_InternFromString("classmethod"); #else function_str = PyString_InternFromString("function"); + classmethod_str = PyString_InternFromString("classmethod"); #endif } @@ -2313,8 +2337,10 @@ static PyObject *WraptFunctionWrapperBase_call( kwds = param_kwds; } - if (self->instance == Py_None && (self->binding == function_str || + if ((self->instance == Py_None) && (self->binding == function_str || PyObject_RichCompareBool(self->binding, function_str, + Py_EQ) == 1 || self->binding == classmethod_str || + PyObject_RichCompareBool(self->binding, classmethod_str, Py_EQ) == 1)) { PyObject *instance = NULL; @@ -2489,6 +2515,101 @@ static PyObject *WraptFunctionWrapperBase_descr_get( /* ------------------------------------------------------------------------- */ +static PyObject *WraptFunctionWrapperBase_set_name( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + method = PyObject_GetAttrString(self->object_proxy.wrapped, + "__set_name__"); + + if (!method) { + PyErr_Clear(); + Py_INCREF(Py_None); + return Py_None; + } + + result = PyObject_Call(method, args, kwds); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_instancecheck( + WraptFunctionWrapperObject *self, PyObject *instance) +{ + PyObject *object = NULL; + PyObject *result = NULL; + + int check = 0; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + check = PyObject_IsInstance(instance, self->object_proxy.wrapped); + + if (check < 0) { + return NULL; + } + + result = check ? Py_True : Py_False; + + Py_INCREF(result); + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_subclasscheck( + WraptFunctionWrapperObject *self, PyObject *args) +{ + PyObject *subclass = NULL; + PyObject *object = NULL; + PyObject *result = NULL; + + int check = 0; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O", &subclass)) + return NULL; + + object = PyObject_GetAttrString(subclass, "__wrapped__"); + + if (!object) + PyErr_Clear(); + + check = PyObject_IsSubclass(object ? object: subclass, + self->object_proxy.wrapped); + + Py_XDECREF(object); + + if (check == -1) + return NULL; + + result = check ? Py_True : Py_False; + + Py_INCREF(result); + + return result; +} + +/* ------------------------------------------------------------------------- */ + static PyObject *WraptFunctionWrapperBase_get_self_instance( WraptFunctionWrapperObject *self, void *closure) { @@ -2559,6 +2680,18 @@ static PyObject *WraptFunctionWrapperBase_get_self_parent( /* ------------------------------------------------------------------------- */; +static PyMethodDef WraptFunctionWrapperBase_methods[] = { + { "__set_name__", (PyCFunction)WraptFunctionWrapperBase_set_name, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__instancecheck__", (PyCFunction)WraptFunctionWrapperBase_instancecheck, + METH_O, 0}, + { "__subclasscheck__", (PyCFunction)WraptFunctionWrapperBase_subclasscheck, + METH_VARARGS, 0 }, + { NULL, NULL }, +}; + +/* ------------------------------------------------------------------------- */; + static PyGetSetDef WraptFunctionWrapperBase_getset[] = { { "__module__", (getter)WraptObjectProxy_get_module, (setter)WraptObjectProxy_set_module, 0 }, @@ -2612,7 +2745,7 @@ PyTypeObject WraptFunctionWrapperBase_Type = { offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ - 0, /*tp_methods*/ + WraptFunctionWrapperBase_methods, /*tp_methods*/ 0, /*tp_members*/ WraptFunctionWrapperBase_getset, /*tp_getset*/ 0, /*tp_base*/ diff --git a/thinc/extra/wrapt/arguments.py b/thinc/extra/wrapt/arguments.py new file mode 100644 index 000000000..032bc059e --- /dev/null +++ b/thinc/extra/wrapt/arguments.py @@ -0,0 +1,38 @@ +# The inspect.formatargspec() function was dropped in Python 3.11 but we need +# need it for when constructing signature changing decorators based on result of +# inspect.getargspec() or inspect.getfullargspec(). The code here implements +# inspect.formatargspec() base on Parameter and Signature from inspect module, +# which were added in Python 3.6. Thanks to Cyril Jouve for the implementation. + +try: + from inspect import Parameter, Signature +except ImportError: + from inspect import formatargspec +else: + def formatargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}): + if kwonlydefaults is None: + kwonlydefaults = {} + ndefaults = len(defaults) if defaults else 0 + parameters = [ + Parameter( + arg, + Parameter.POSITIONAL_OR_KEYWORD, + default=defaults[i] if i >= 0 else Parameter.empty, + annotation=annotations.get(arg, Parameter.empty), + ) for i, arg in enumerate(args, ndefaults - len(args)) + ] + if varargs: + parameters.append(Parameter(varargs, Parameter.VAR_POSITIONAL)) + parameters.extend( + Parameter( + kwonlyarg, + Parameter.KEYWORD_ONLY, + default=kwonlydefaults.get(kwonlyarg, Parameter.empty), + annotation=annotations.get(kwonlyarg, Parameter.empty), + ) for kwonlyarg in kwonlyargs + ) + if varkw: + parameters.append(Parameter(varkw, Parameter.VAR_KEYWORD)) + return_annotation = annotations.get('return', Signature.empty) + return str(Signature(parameters, return_annotation=return_annotation)) \ No newline at end of file diff --git a/thinc/extra/wrapt/decorators.py b/thinc/extra/wrapt/decorators.py index d5dd9538e..c3f254729 100644 --- a/thinc/extra/wrapt/decorators.py +++ b/thinc/extra/wrapt/decorators.py @@ -6,18 +6,9 @@ import sys PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -if PY3: - string_types = (str,) - - import builtins - - exec_ = getattr(builtins, "exec") - del builtins - -else: - string_types = (basestring,) +if PY2: + string_types = basestring, def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" @@ -31,23 +22,27 @@ def exec_(_code_, _globs_=None, _locs_=None): _locs_ = _globs_ exec("""exec _code_ in _globs_, _locs_""") +else: + string_types = str, + + import builtins + + exec_ = getattr(builtins, "exec") + del builtins from functools import partial -from inspect import ismethod, isclass, formatargspec -from collections import namedtuple +from inspect import isclass from threading import Lock, RLock +from .arguments import formatargspec + try: from inspect import signature except ImportError: pass -from .wrappers import ( - FunctionWrapper, - BoundFunctionWrapper, - ObjectProxy, - CallableObjectProxy, -) +from .wrappers import (FunctionWrapper, BoundFunctionWrapper, ObjectProxy, + CallableObjectProxy) # Adapter wrapper for the wrapped function which will overlay certain # properties from the adapter function onto the wrapped function so that @@ -55,8 +50,8 @@ def exec_(_code_, _globs_=None, _locs_=None): # inspect.signature() and inspect.getsource() return the correct results # one would expect. - class _AdapterFunctionCode(CallableObjectProxy): + def __init__(self, wrapped_code, adapter_code): super(_AdapterFunctionCode, self).__init__(wrapped_code) self._self_adapter_code = adapter_code @@ -81,17 +76,16 @@ def co_kwonlyargcount(self): def co_varnames(self): return self._self_adapter_code.co_varnames - class _AdapterFunctionSurrogate(CallableObjectProxy): + def __init__(self, wrapped, adapter): super(_AdapterFunctionSurrogate, self).__init__(wrapped) self._self_adapter = adapter @property def __code__(self): - return _AdapterFunctionCode( - self.__wrapped__.__code__, self._self_adapter.__code__ - ) + return _AdapterFunctionCode(self.__wrapped__.__code__, + self._self_adapter.__code__) @property def __defaults__(self): @@ -103,40 +97,41 @@ def __kwdefaults__(self): @property def __signature__(self): - if "signature" not in globals(): + if 'signature' not in globals(): return self._self_adapter.__signature__ else: - # Can't allow this to fail on Python 3 else it falls - # through to using __wrapped__, but that will be the - # wrong function we want to derive the signature - # from. Thus generate the signature ourselves. - return signature(self._self_adapter) if PY2: func_code = __code__ func_defaults = __defaults__ - class _BoundAdapterWrapper(BoundFunctionWrapper): + @property def __func__(self): - return _AdapterFunctionSurrogate( - self.__wrapped__.__func__, self._self_parent._self_adapter - ) + return _AdapterFunctionSurrogate(self.__wrapped__.__func__, + self._self_parent._self_adapter) + + @property + def __signature__(self): + if 'signature' not in globals(): + return self.__wrapped__.__signature__ + else: + return signature(self._self_parent._self_adapter) if PY2: im_func = __func__ - class AdapterWrapper(FunctionWrapper): __bound_function_wrapper__ = _BoundAdapterWrapper def __init__(self, *args, **kwargs): - adapter = kwargs.pop("adapter") + adapter = kwargs.pop('adapter') super(AdapterWrapper, self).__init__(*args, **kwargs) - self._self_surrogate = _AdapterFunctionSurrogate(self.__wrapped__, adapter) + self._self_surrogate = _AdapterFunctionSurrogate( + self.__wrapped__, adapter) self._self_adapter = adapter @property @@ -159,21 +154,17 @@ def __kwdefaults__(self): def __signature__(self): return self._self_surrogate.__signature__ - class AdapterFactory(object): def __call__(self, wrapped): raise NotImplementedError() - class DelegatedAdapterFactory(AdapterFactory): def __init__(self, factory): super(DelegatedAdapterFactory, self).__init__() self.factory = factory - def __call__(self, wrapped): return self.factory(wrapped) - adapter_factory = DelegatedAdapterFactory # Decorator for creating other decorators. This decorator and the @@ -183,8 +174,7 @@ def __call__(self, wrapped): # function so the wrapper is effectively indistinguishable from the # original wrapped function. - -def decorator(wrapper=None, enabled=None, adapter=None): +def decorator(wrapper=None, enabled=None, adapter=None, proxy=FunctionWrapper): # The decorator should be supplied with a single positional argument # which is the wrapper function to be used to implement the # decorator. This may be preceded by a step whereby the keyword @@ -194,7 +184,7 @@ def decorator(wrapper=None, enabled=None, adapter=None): # decorator. In that case parts of the function '__code__' and # '__defaults__' attributes are used from the adapter function # rather than those of the wrapped function. This allows for the - # argument specification from inspect.getargspec() and similar + # argument specification from inspect.getfullargspec() and similar # functions to be overridden with a prototype for a different # function than what was wrapped. The 'enabled' argument provides a # way to enable/disable the use of the decorator. If the type of @@ -205,6 +195,8 @@ def decorator(wrapper=None, enabled=None, adapter=None): # if 'enabled' is callable it will be called to obtain the value to # be checked. If False, the wrapper will not be called and instead # the original wrapped function will be called directly instead. + # The 'proxy' argument provides a way of passing a custom version of + # the FunctionWrapper class used in decorating the function. if wrapper is not None: # Helper function for creating wrapper of the appropriate @@ -217,16 +209,37 @@ def _build(wrapped, wrapper, enabled=None, adapter=None): if not callable(adapter): ns = {} + + # Check if the signature argument specification has + # annotations. If it does then we need to remember + # it but also drop it when attempting to manufacture + # a standin adapter function. This is necessary else + # it will try and look up any types referenced in + # the annotations in the empty namespace we use, + # which will fail. + + annotations = {} + if not isinstance(adapter, string_types): + if len(adapter) == 7: + annotations = adapter[-1] + adapter = adapter[:-1] adapter = formatargspec(*adapter) - exec_("def adapter{}: pass".format(adapter), ns, ns) - adapter = ns["adapter"] - return AdapterWrapper( - wrapped=wrapped, wrapper=wrapper, enabled=enabled, adapter=adapter - ) + exec_('def adapter{}: pass'.format(adapter), ns, ns) + adapter = ns['adapter'] + + # Override the annotations for the manufactured + # adapter function so they match the original + # adapter signature argument specification. + + if annotations: + adapter.__annotations__ = annotations + + return AdapterWrapper(wrapped=wrapped, wrapper=wrapper, + enabled=enabled, adapter=adapter) - return FunctionWrapper(wrapped=wrapped, wrapper=wrapper, enabled=enabled) + return proxy(wrapped=wrapped, wrapper=wrapper, enabled=enabled) # The wrapper has been provided so return the final decorator. # The decorator is itself one of our function wrappers so we @@ -284,7 +297,8 @@ def _capture(target_wrapped): # Finally build the wrapper itself and return it. - return _build(target_wrapped, target_wrapper, _enabled, adapter) + return _build(target_wrapped, target_wrapper, + _enabled, adapter) return _capture @@ -370,7 +384,7 @@ def _capture(target_wrapped): # This one is a bit strange because binding was actually # performed on the wrapper created by our decorator # factory. We need to apply that binding to the decorator - # wrapper function which which the decorator factory + # wrapper function that the decorator factory # was applied to. target_wrapper = wrapper.__get__(None, instance) @@ -394,7 +408,7 @@ def _capture(target_wrapped): # This one is a bit strange because binding was actually # performed on the wrapper created by our decorator # factory. We need to apply that binding to the decorator - # wrapper function which which the decorator factory + # wrapper function that the decorator factory # was applied to. target_wrapper = wrapper.__get__(instance, type(instance)) @@ -405,9 +419,12 @@ def _capture(target_wrapped): # We first return our magic function wrapper here so we can # determine in what context the decorator factory was used. In - # other words, it is itself a universal decorator. + # other words, it is itself a universal decorator. The decorator + # function is used as the adapter so that linters see a signature + # corresponding to the decorator and not the wrapper it is being + # applied to. - return _build(wrapper, _wrapper) + return _build(wrapper, _wrapper, adapter=decorator) else: # The wrapper still has not been provided, so we are just @@ -415,8 +432,8 @@ def _capture(target_wrapped): # decorator again wrapped in a partial using the collected # arguments. - return partial(decorator, enabled=enabled, adapter=adapter) - + return partial(decorator, enabled=enabled, adapter=adapter, + proxy=proxy) # Decorator for implementing thread synchronization. It can be used as a # decorator, in which case the synchronization context is determined by @@ -428,7 +445,6 @@ def _capture(target_wrapped): # synchronization primitive without creating a separate lock against the # derived or supplied context. - def synchronized(wrapped): # Determine if being passed an object which is a synchronization # primitive. We can't check by type for Lock, RLock, Semaphore etc, @@ -436,7 +452,7 @@ def synchronized(wrapped): # existence of acquire() and release() methods. This is more # extensible anyway as it allows custom synchronization mechanisms. - if hasattr(wrapped, "acquire") and hasattr(wrapped, "release"): + if hasattr(wrapped, 'acquire') and hasattr(wrapped, 'release'): # We remember what the original lock is and then return a new # decorator which accesses and locks it. When returning the new # decorator we wrap it with an object proxy so we can override @@ -454,6 +470,7 @@ def _synchronized(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) class _PartialDecorator(CallableObjectProxy): + def __enter__(self): lock.acquire() return lock @@ -472,7 +489,7 @@ def __exit__(self, *args): def _synchronized_lock(context): # Attempt to retrieve the lock for the specific context. - lock = vars(context).get("_synchronized_lock", None) + lock = vars(context).get('_synchronized_lock', None) if lock is None: # There is no existing lock defined for the context we @@ -493,11 +510,11 @@ def _synchronized_lock(context): # at the same time and were competing to create the # meta lock. - lock = vars(context).get("_synchronized_lock", None) + lock = vars(context).get('_synchronized_lock', None) if lock is None: lock = RLock() - setattr(context, "_synchronized_lock", lock) + setattr(context, '_synchronized_lock', lock) return lock @@ -506,10 +523,11 @@ def _synchronized_wrapper(wrapped, instance, args, kwargs): # desired context is held. If instance is None then the # wrapped function is used as the context. - with _synchronized_lock(instance or wrapped): + with _synchronized_lock(instance if instance is not None else wrapped): return wrapped(*args, **kwargs) class _FinalDecorator(FunctionWrapper): + def __enter__(self): self._self_lock = _synchronized_lock(self.__wrapped__) self._self_lock.acquire() @@ -520,5 +538,4 @@ def __exit__(self, *args): return _FinalDecorator(wrapped=wrapped, wrapper=_synchronized_wrapper) - synchronized._synchronized_meta_lock = Lock() diff --git a/thinc/extra/wrapt/importer.py b/thinc/extra/wrapt/importer.py index c408367e9..5c4d4cc66 100644 --- a/thinc/extra/wrapt/importer.py +++ b/thinc/extra/wrapt/importer.py @@ -7,21 +7,20 @@ import threading PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -if PY3: - import importlib - - string_types = (str,) +if PY2: + string_types = basestring, + find_spec = None else: - string_types = (basestring,) + string_types = str, + from importlib.util import find_spec from .decorators import synchronized # The dictionary registering any post import hooks to be triggered once # the target module has been imported. Once a module has been imported # and the hooks fired, the list of hooks recorded against the target -# module will be truncacted but the list left in the dictionary. This +# module will be truncated but the list left in the dictionary. This # acts as a flag to indicate that the module had already been imported. _post_import_hooks = {} @@ -35,20 +34,17 @@ # proxy callback being registered which will defer loading of the # specified module containing the callback function until required. - def _create_import_hook_from_string(name): def import_hook(module): - module_name, function = name.split(":") - attrs = function.split(".") + module_name, function = name.split(':') + attrs = function.split('.') __import__(module_name) callback = sys.modules[module_name] for attr in attrs: callback = getattr(callback, attr) return callback(module) - return import_hook - @synchronized(_post_import_hooks_lock) def register_post_import_hook(hook, name): # Create a deferred import hook if hook is a string name rather than @@ -102,10 +98,8 @@ def register_post_import_hook(hook, name): _post_import_hooks[name].append(hook) - # Register post import hooks defined as package entry points. - def _create_import_hook_from_entrypoint(entrypoint): def import_hook(module): __import__(entrypoint.module_name) @@ -113,10 +107,8 @@ def import_hook(module): for attr in entrypoint.attrs: callback = getattr(callback, attr) return callback(module) - return import_hook - def discover_post_import_hooks(group): try: import pkg_resources @@ -127,16 +119,14 @@ def discover_post_import_hooks(group): callback = _create_import_hook_from_entrypoint(entrypoint) register_post_import_hook(callback, entrypoint.name) - # Indicate that a module has been loaded. Any post import hooks which # were registered against the target module will be invoked. If an # exception is raised in any of the post import hooks, that will cause # the import of the target module to fail. - @synchronized(_post_import_hooks_lock) def notify_module_loaded(module): - name = getattr(module, "__name__", None) + name = getattr(module, '__name__', None) hooks = _post_import_hooks.get(name, None) if hooks: @@ -145,33 +135,49 @@ def notify_module_loaded(module): for hook in hooks: hook(module) - # A custom module import finder. This intercepts attempts to import # modules and watches out for attempts to import target modules of # interest. When a module of interest is imported, then any post import # hooks which are registered will be invoked. - class _ImportHookLoader: + def load_module(self, fullname): module = sys.modules[fullname] notify_module_loaded(module) return module - class _ImportHookChainedLoader: + def __init__(self, loader): self.loader = loader - def load_module(self, fullname): + if hasattr(loader, "load_module"): + self.load_module = self._load_module + if hasattr(loader, "create_module"): + self.create_module = self._create_module + if hasattr(loader, "exec_module"): + self.exec_module = self._exec_module + + def _load_module(self, fullname): module = self.loader.load_module(fullname) notify_module_loaded(module) return module + # Python 3.4 introduced create_module() and exec_module() instead of + # load_module() alone. Splitting the two steps. + + def _create_module(self, spec): + return self.loader.create_module(spec) + + def _exec_module(self, module): + self.loader.exec_module(module) + notify_module_loaded(module) class ImportHookFinder: + def __init__(self): self.in_progress = {} @@ -199,24 +205,7 @@ def find_module(self, fullname, path=None): # Now call back into the import system again. try: - if PY3: - # For Python 3 we need to use find_spec().loader - # from the importlib.util module. It doesn't actually - # import the target module and only finds the - # loader. If a loader is found, we need to return - # our own loader which will then in turn call the - # real loader to import the module and invoke the - # post import hooks. - try: - import importlib.util - - loader = importlib.util.find_spec(fullname).loader - except (ImportError, AttributeError): - loader = importlib.find_loader(fullname, path) - if loader: - return _ImportHookChainedLoader(loader) - - else: + if not find_spec: # For Python 2 we don't have much choice but to # call back in to __import__(). This will # actually cause the module to be imported. If no @@ -229,17 +218,69 @@ def find_module(self, fullname, path=None): return _ImportHookLoader() + else: + # For Python 3 we need to use find_spec().loader + # from the importlib.util module. It doesn't actually + # import the target module and only finds the + # loader. If a loader is found, we need to return + # our own loader which will then in turn call the + # real loader to import the module and invoke the + # post import hooks. + + loader = getattr(find_spec(fullname), "loader", None) + + if loader and not isinstance(loader, _ImportHookChainedLoader): + return _ImportHookChainedLoader(loader) + finally: del self.in_progress[fullname] + def find_spec(self, fullname, path=None, target=None): + # Since Python 3.4, you are meant to implement find_spec() method + # instead of find_module() and since Python 3.10 you get deprecation + # warnings if you don't define find_spec(). + + # If the module being imported is not one we have registered + # post import hooks for, we can return immediately. We will + # take no further part in the importing of this module. + + if not fullname in _post_import_hooks: + return None + + # When we are interested in a specific module, we will call back + # into the import system a second time to defer to the import + # finder that is supposed to handle the importing of the module. + # We set an in progress flag for the target module so that on + # the second time through we don't trigger another call back + # into the import system and cause a infinite loop. + + if fullname in self.in_progress: + return None + + self.in_progress[fullname] = True + + # Now call back into the import system again. + + try: + # This should only be Python 3 so find_spec() should always + # exist so don't need to check. + + spec = find_spec(fullname) + loader = getattr(spec, "loader", None) + + if loader and not isinstance(loader, _ImportHookChainedLoader): + spec.loader = _ImportHookChainedLoader(loader) + + return spec + + finally: + del self.in_progress[fullname] # Decorator for marking that a function should be called as a post # import hook when the target module is imported. - def when_imported(name): def register(hook): register_post_import_hook(hook, name) return hook - return register diff --git a/thinc/extra/wrapt/wrappers.py b/thinc/extra/wrapt/wrappers.py index 9c20948b6..2716cd1da 100644 --- a/thinc/extra/wrapt/wrappers.py +++ b/thinc/extra/wrapt/wrappers.py @@ -6,19 +6,16 @@ import inspect PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -if PY3: - string_types = (str,) +if PY2: + string_types = basestring, else: - string_types = (basestring,) - + string_types = str, def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" return meta("NewBase", bases, {}) - class _ObjectProxyMethods(object): # We use properties to override the values of __module__ and @@ -62,7 +59,6 @@ def __dict__(self): def __weakref__(self): return self.__wrapped__.__weakref__ - class _ObjectProxyMetaType(type): def __new__(cls, name, bases, dictionary): # Copy our special properties into the class so that they @@ -74,20 +70,27 @@ def __new__(cls, name, bases, dictionary): return type.__new__(cls, name, bases, dictionary) - class ObjectProxy(with_metaclass(_ObjectProxyMetaType)): - __slots__ = "__wrapped__" + __slots__ = '__wrapped__' def __init__(self, wrapped): - object.__setattr__(self, "__wrapped__", wrapped) + object.__setattr__(self, '__wrapped__', wrapped) # Python 3.2+ has the __qualname__ attribute, but it does not # allow it to be overridden using a property and it must instead # be an actual string object instead. try: - object.__setattr__(self, "__qualname__", wrapped.__qualname__) + object.__setattr__(self, '__qualname__', wrapped.__qualname__) + except AttributeError: + pass + + # Python 3.10 onwards also does not allow itself to be overridden + # using a property and it must instead be set explicitly. + + try: + object.__setattr__(self, '__annotations__', wrapped.__annotations__) except AttributeError: pass @@ -107,41 +110,33 @@ def __class__(self): def __class__(self, value): self.__wrapped__.__class__ = value - @property - def __annotations__(self): - return self.__wrapped__.__annotations__ - - @__annotations__.setter - def __annotations__(self, value): - self.__wrapped__.__annotations__ = value - def __dir__(self): return dir(self.__wrapped__) def __str__(self): return str(self.__wrapped__) - if PY3: - + if not PY2: def __bytes__(self): return bytes(self.__wrapped__) def __repr__(self): - return "<{} at 0x{:x} for {} at 0x{:x}>".format( - type(self).__name__, - id(self), - type(self.__wrapped__).__name__, - id(self.__wrapped__), - ) + return '<{} at 0x{:x} for {} at 0x{:x}>'.format( + type(self).__name__, id(self), + type(self.__wrapped__).__name__, + id(self.__wrapped__)) def __reversed__(self): return reversed(self.__wrapped__) - if PY3: - + if not PY2: def __round__(self): return round(self.__wrapped__) + if sys.hexversion >= 0x03070000: + def __mro_entries__(self, bases): + return (self.__wrapped__,) + def __lt__(self, other): return self.__wrapped__ < other @@ -170,21 +165,33 @@ def __bool__(self): return bool(self.__wrapped__) def __setattr__(self, name, value): - if name.startswith("_self_"): + if name.startswith('_self_'): object.__setattr__(self, name, value) - elif name == "__wrapped__": + elif name == '__wrapped__': object.__setattr__(self, name, value) try: - object.__delattr__(self, "__qualname__") + object.__delattr__(self, '__qualname__') + except AttributeError: + pass + try: + object.__setattr__(self, '__qualname__', value.__qualname__) + except AttributeError: + pass + try: + object.__delattr__(self, '__annotations__') except AttributeError: pass try: - object.__setattr__(self, "__qualname__", value.__qualname__) + object.__setattr__(self, '__annotations__', value.__annotations__) except AttributeError: pass - elif name == "__qualname__": + elif name == '__qualname__': + setattr(self.__wrapped__, name, value) + object.__setattr__(self, name, value) + + elif name == '__annotations__': setattr(self.__wrapped__, name, value) object.__setattr__(self, name, value) @@ -198,19 +205,19 @@ def __getattr__(self, name): # If we are being to lookup '__wrapped__' then the # '__init__()' method cannot have been called. - if name == "__wrapped__": - raise ValueError("wrapper has not been initialised") + if name == '__wrapped__': + raise ValueError('wrapper has not been initialised') return getattr(self.__wrapped__, name) def __delattr__(self, name): - if name.startswith("_self_"): + if name.startswith('_self_'): object.__delattr__(self, name) - elif name == "__wrapped__": - raise TypeError("__wrapped__ must be an object") + elif name == '__wrapped__': + raise TypeError('__wrapped__ must be an object') - elif name == "__qualname__": + elif name == '__qualname__': object.__delattr__(self, name) delattr(self.__wrapped__, name) @@ -423,32 +430,34 @@ def __iter__(self): return iter(self.__wrapped__) def __copy__(self): - raise NotImplementedError("object proxy must define __copy__()") + raise NotImplementedError('object proxy must define __copy__()') def __deepcopy__(self, memo): - raise NotImplementedError("object proxy must define __deepcopy__()") + raise NotImplementedError('object proxy must define __deepcopy__()') def __reduce__(self): - raise NotImplementedError("object proxy must define __reduce_ex__()") + raise NotImplementedError( + 'object proxy must define __reduce_ex__()') def __reduce_ex__(self, protocol): - raise NotImplementedError("object proxy must define __reduce_ex__()") - + raise NotImplementedError( + 'object proxy must define __reduce_ex__()') class CallableObjectProxy(ObjectProxy): + def __call__(self, *args, **kwargs): return self.__wrapped__(*args, **kwargs) - class PartialCallableObjectProxy(ObjectProxy): + def __init__(self, *args, **kwargs): if len(args) < 1: - raise TypeError("partial type takes at least one argument") + raise TypeError('partial type takes at least one argument') wrapped, args = args[0], args[1:] if not callable(wrapped): - raise TypeError("the first argument must be callable") + raise TypeError('the first argument must be callable') super(PartialCallableObjectProxy, self).__init__(wrapped) @@ -463,28 +472,21 @@ def __call__(self, *args, **kwargs): return self.__wrapped__(*_args, **_kwargs) - class _FunctionWrapperBase(ObjectProxy): - __slots__ = ( - "_self_instance", - "_self_wrapper", - "_self_enabled", - "_self_binding", - "_self_parent", - ) + __slots__ = ('_self_instance', '_self_wrapper', '_self_enabled', + '_self_binding', '_self_parent') - def __init__( - self, wrapped, instance, wrapper, enabled=None, binding="function", parent=None - ): + def __init__(self, wrapped, instance, wrapper, enabled=None, + binding='function', parent=None): super(_FunctionWrapperBase, self).__init__(wrapped) - object.__setattr__(self, "_self_instance", instance) - object.__setattr__(self, "_self_wrapper", wrapper) - object.__setattr__(self, "_self_enabled", enabled) - object.__setattr__(self, "_self_binding", binding) - object.__setattr__(self, "_self_parent", parent) + object.__setattr__(self, '_self_instance', instance) + object.__setattr__(self, '_self_wrapper', wrapper) + object.__setattr__(self, '_self_enabled', enabled) + object.__setattr__(self, '_self_binding', binding) + object.__setattr__(self, '_self_parent', parent) def __get__(self, instance, owner): # This method is actually doing double duty for both unbound and @@ -516,14 +518,9 @@ def __get__(self, instance, owner): if not inspect.isclass(self.__wrapped__): descriptor = self.__wrapped__.__get__(instance, owner) - return self.__bound_function_wrapper__( - descriptor, - instance, - self._self_wrapper, - self._self_enabled, - self._self_binding, - self, - ) + return self.__bound_function_wrapper__(descriptor, instance, + self._self_wrapper, self._self_enabled, + self._self_binding, self) return self @@ -536,17 +533,14 @@ def __get__(self, instance, owner): # method. In that case we rebind against the original wrapped # function from the parent again. - if self._self_instance is None and self._self_binding == "function": - descriptor = self._self_parent.__wrapped__.__get__(instance, owner) + if self._self_instance is None and self._self_binding == 'function': + descriptor = self._self_parent.__wrapped__.__get__( + instance, owner) return self._self_parent.__bound_function_wrapper__( - descriptor, - instance, - self._self_wrapper, - self._self_enabled, - self._self_binding, - self._self_parent, - ) + descriptor, instance, self._self_wrapper, + self._self_enabled, self._self_binding, + self._self_parent) return self @@ -568,11 +562,12 @@ def __call__(self, *args, **kwargs): # a function that was already bound to an instance. In that case # we want to extract the instance from the function and use it. - if self._self_binding == "function": + if self._self_binding in ('function', 'classmethod'): if self._self_instance is None: - instance = getattr(self.__wrapped__, "__self__", None) + instance = getattr(self.__wrapped__, '__self__', None) if instance is not None: - return self._self_wrapper(self.__wrapped__, instance, args, kwargs) + return self._self_wrapper(self.__wrapped__, instance, + args, kwargs) # This is generally invoked when the wrapped function is being # called as a normal function and is not bound to a class as an @@ -580,10 +575,38 @@ def __call__(self, *args, **kwargs): # wrapped function was a method, but this wrapper was in turn # wrapped using the staticmethod decorator. - return self._self_wrapper(self.__wrapped__, self._self_instance, args, kwargs) - + return self._self_wrapper(self.__wrapped__, self._self_instance, + args, kwargs) + + def __set_name__(self, owner, name): + # This is a special method use to supply information to + # descriptors about what the name of variable in a class + # definition is. Not wanting to add this to ObjectProxy as not + # sure of broader implications of doing that. Thus restrict to + # FunctionWrapper used by decorators. + + if hasattr(self.__wrapped__, "__set_name__"): + self.__wrapped__.__set_name__(owner, name) + + def __instancecheck__(self, instance): + # This is a special method used by isinstance() to make checks + # instance of the `__wrapped__`. + return isinstance(instance, self.__wrapped__) + + def __subclasscheck__(self, subclass): + # This is a special method used by issubclass() to make checks + # about inheritance of classes. We need to upwrap any object + # proxy. Not wanting to add this to ObjectProxy as not sure of + # broader implications of doing that. Thus restrict to + # FunctionWrapper used by decorators. + + if hasattr(subclass, "__wrapped__"): + return issubclass(subclass.__wrapped__, self.__wrapped__) + else: + return issubclass(subclass, self.__wrapped__) class BoundFunctionWrapper(_FunctionWrapperBase): + def __call__(self, *args, **kwargs): # If enabled has been specified, then evaluate it at this point # and if the wrapper is not to be executed, then simply return @@ -602,7 +625,7 @@ def __call__(self, *args, **kwargs): # likely wrapping an instance method vs a static method or class # method. - if self._self_binding == "function": + if self._self_binding == 'function': if self._self_instance is None: # This situation can occur where someone is calling the # instancemethod via the class type and passing the instance @@ -612,15 +635,14 @@ def __call__(self, *args, **kwargs): # wrapper doesn't see anything as being different. if not args: - raise TypeError("missing 1 required positional argument") + raise TypeError('missing 1 required positional argument') instance, args = args[0], args[1:] wrapped = PartialCallableObjectProxy(self.__wrapped__, instance) return self._self_wrapper(wrapped, instance, args, kwargs) - return self._self_wrapper( - self.__wrapped__, self._self_instance, args, kwargs - ) + return self._self_wrapper(self.__wrapped__, self._self_instance, + args, kwargs) else: # As in this case we would be dealing with a classmethod or @@ -636,10 +658,10 @@ def __call__(self, *args, **kwargs): # class type, as it reflects what they have available in the # decoratored function. - instance = getattr(self.__wrapped__, "__self__", None) - - return self._self_wrapper(self.__wrapped__, instance, args, kwargs) + instance = getattr(self.__wrapped__, '__self__', None) + return self._self_wrapper(self.__wrapped__, instance, args, + kwargs) class FunctionWrapper(_FunctionWrapperBase): @@ -719,39 +741,33 @@ def __init__(self, wrapped, wrapper, enabled=None): # the arguments around to extract 'self' for use as the instance. if isinstance(wrapped, classmethod): - binding = "classmethod" + binding = 'classmethod' elif isinstance(wrapped, staticmethod): - binding = "staticmethod" + binding = 'staticmethod' - elif hasattr(wrapped, "__self__"): + elif hasattr(wrapped, '__self__'): if inspect.isclass(wrapped.__self__): - binding = "classmethod" + binding = 'classmethod' else: - binding = "function" + binding = 'function' else: - binding = "function" - - super(FunctionWrapper, self).__init__(wrapped, None, wrapper, enabled, binding) + binding = 'function' + super(FunctionWrapper, self).__init__(wrapped, None, wrapper, + enabled, binding) try: - if not os.environ.get("WRAPT_DISABLE_EXTENSIONS"): - from ._wrappers import ( - ObjectProxy, - CallableObjectProxy, - PartialCallableObjectProxy, - FunctionWrapper, - BoundFunctionWrapper, - _FunctionWrapperBase, - ) + if not os.environ.get('WRAPT_DISABLE_EXTENSIONS'): + from ._wrappers import (ObjectProxy, CallableObjectProxy, + PartialCallableObjectProxy, FunctionWrapper, + BoundFunctionWrapper, _FunctionWrapperBase) except ImportError: pass # Helper functions for applying wrappers to existing functions. - def resolve_path(module, name): if isinstance(module, string_types): __import__(module) @@ -759,59 +775,57 @@ def resolve_path(module, name): parent = module - path = name.split(".") + path = name.split('.') attribute = path[0] - original = getattr(parent, attribute) - for attribute in path[1:]: - parent = original - - # We can't just always use getattr() because in doing - # that on a class it will cause binding to occur which - # will complicate things later and cause some things not - # to work. For the case of a class we therefore access - # the __dict__ directly. To cope though with the wrong - # class being given to us, or a method being moved into - # a base class, we need to walk the class hierarchy to - # work out exactly which __dict__ the method was defined - # in, as accessing it from __dict__ will fail if it was - # not actually on the class given. Fallback to using - # getattr() if we can't find it. If it truly doesn't - # exist, then that will fail. - - if inspect.isclass(original): - for cls in inspect.getmro(original): + # We can't just always use getattr() because in doing + # that on a class it will cause binding to occur which + # will complicate things later and cause some things not + # to work. For the case of a class we therefore access + # the __dict__ directly. To cope though with the wrong + # class being given to us, or a method being moved into + # a base class, we need to walk the class hierarchy to + # work out exactly which __dict__ the method was defined + # in, as accessing it from __dict__ will fail if it was + # not actually on the class given. Fallback to using + # getattr() if we can't find it. If it truly doesn't + # exist, then that will fail. + + def lookup_attribute(parent, attribute): + if inspect.isclass(parent): + for cls in inspect.getmro(parent): if attribute in vars(cls): - original = vars(cls)[attribute] - break + return vars(cls)[attribute] else: - original = getattr(original, attribute) - + return getattr(parent, attribute) else: - original = getattr(original, attribute) + return getattr(parent, attribute) - return (parent, attribute, original) + original = lookup_attribute(parent, attribute) + for attribute in path[1:]: + parent = original + original = lookup_attribute(parent, attribute) + + return (parent, attribute, original) def apply_patch(parent, attribute, replacement): setattr(parent, attribute, replacement) - def wrap_object(module, name, factory, args=(), kwargs={}): (parent, attribute, original) = resolve_path(module, name) wrapper = factory(original, *args, **kwargs) apply_patch(parent, attribute, wrapper) return wrapper - # Function for applying a proxy object to an attribute of a class # instance. The wrapper works by defining an attribute of the same name # on the class which is a descriptor and which intercepts access to the # instance attribute. Note that this cannot be used on attributes which # are themselves defined by a property object. - class AttributeWrapper(object): + def __init__(self, attribute, factory, args, kwargs): self.attribute = attribute self.factory = factory @@ -828,21 +842,18 @@ def __set__(self, instance, value): def __delete__(self, instance): del instance.__dict__[self.attribute] - def wrap_object_attribute(module, name, factory, args=(), kwargs={}): - path, attribute = name.rsplit(".", 1) + path, attribute = name.rsplit('.', 1) parent = resolve_path(module, path)[2] wrapper = AttributeWrapper(attribute, factory, args, kwargs) apply_patch(parent, attribute, wrapper) return wrapper - # Functions for creating a simple decorator using a FunctionWrapper, # plus short cut functions for applying wrappers to functions. These are # for use when doing monkey patching. For a more featured way of # creating decorators see the decorator decorator instead. - def function_wrapper(wrapper): def _wrapper(wrapped, instance, args, kwargs): target_wrapped = args[0] @@ -853,21 +864,16 @@ def _wrapper(wrapped, instance, args, kwargs): else: target_wrapper = wrapper.__get__(instance, type(instance)) return FunctionWrapper(target_wrapped, target_wrapper) - return FunctionWrapper(wrapper, _wrapper) - def wrap_function_wrapper(module, name, wrapper): return wrap_object(module, name, FunctionWrapper, (wrapper,)) - def patch_function_wrapper(module, name): def _wrapper(wrapper): return wrap_object(module, name, FunctionWrapper, (wrapper,)) - return _wrapper - def transient_function_wrapper(module, name): def _decorator(wrapper): def _wrapper(wrapped, instance, args, kwargs): @@ -878,7 +884,6 @@ def _wrapper(wrapped, instance, args, kwargs): target_wrapper = wrapper.__get__(None, instance) else: target_wrapper = wrapper.__get__(instance, type(instance)) - def _execute(wrapped, instance, args, kwargs): (parent, attribute, original) = resolve_path(module, name) replacement = FunctionWrapper(original, target_wrapper) @@ -887,14 +892,10 @@ def _execute(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) finally: setattr(parent, attribute, original) - return FunctionWrapper(target_wrapped, _execute) - return FunctionWrapper(wrapper, _wrapper) - return _decorator - # A weak function proxy. This will work on instance methods, class # methods, static methods and regular functions. Special treatment is # needed for the method types because the bound method is effectively a @@ -904,7 +905,6 @@ def _execute(wrapped, instance, args, kwargs): # and the original function. The function is then rebound at the point # of a call via the weak function proxy. - def _weak_function_proxy_callback(ref, proxy, callback): if proxy._self_expired: return @@ -918,10 +918,9 @@ def _weak_function_proxy_callback(ref, proxy, callback): if callback is not None: callback(proxy) - class WeakFunctionProxy(ObjectProxy): - __slots__ = ("_self_expired", "_self_instance") + __slots__ = ('_self_expired', '_self_instance') def __init__(self, wrapped, callback=None): # We need to determine if the wrapped function is actually a @@ -937,23 +936,22 @@ def __init__(self, wrapped, callback=None): # the callback here so as not to cause any odd reference cycles. _callback = callback and functools.partial( - _weak_function_proxy_callback, proxy=self, callback=callback - ) + _weak_function_proxy_callback, proxy=self, + callback=callback) self._self_expired = False if isinstance(wrapped, _FunctionWrapperBase): - self._self_instance = weakref.ref(wrapped._self_instance, _callback) + self._self_instance = weakref.ref(wrapped._self_instance, + _callback) if wrapped._self_parent is not None: super(WeakFunctionProxy, self).__init__( - weakref.proxy(wrapped._self_parent, _callback) - ) + weakref.proxy(wrapped._self_parent, _callback)) else: super(WeakFunctionProxy, self).__init__( - weakref.proxy(wrapped, _callback) - ) + weakref.proxy(wrapped, _callback)) return @@ -961,13 +959,13 @@ def __init__(self, wrapped, callback=None): self._self_instance = weakref.ref(wrapped.__self__, _callback) super(WeakFunctionProxy, self).__init__( - weakref.proxy(wrapped.__func__, _callback) - ) + weakref.proxy(wrapped.__func__, _callback)) except AttributeError: self._self_instance = None - super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped, _callback)) + super(WeakFunctionProxy, self).__init__( + weakref.proxy(wrapped, _callback)) def __call__(self, *args, **kwargs): # We perform a boolean check here on the instance and wrapped