From 1ff06fd2e019ef085c95074d1358366cf2144474 Mon Sep 17 00:00:00 2001 From: Vitor Santos Costa Date: Mon, 19 Mar 2018 15:41:53 +0000 Subject: [PATCH] integrate backcall --- CMakeLists.txt | 1 - info/meta.yaml | 1 - packages/python/yap_kernel/setup.py | 2 +- .../yap_kernel/yap_ipython/core/backcall.py | 109 ++++++++++++++++++ .../yap_ipython/core/tests/test_events.py | 2 +- 5 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 packages/python/yap_kernel/yap_ipython/core/backcall.py diff --git a/CMakeLists.txt b/CMakeLists.txt index d3f950bf7..27d15849c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -688,7 +688,6 @@ if (PYTHONLIBS_FOUND AND SWIG_FOUND) find_python_module(jupyter) find_python_module(wheel) find_python_module(setuptools) - find_python_module(backcall) if (PY_JUPYTER AND PY_WHEEL AND PY_SETUPTOOLS AND PY_BACKCALL) add_subdirectory(packages/python/yap_kernel) ENDIF () diff --git a/info/meta.yaml b/info/meta.yaml index 24b367100..cd9f197cd 100644 --- a/info/meta.yaml +++ b/info/meta.yaml @@ -19,7 +19,6 @@ requirements: - libxml2 - backcall run: - - backcall - jupyterlab - python - readline diff --git a/packages/python/yap_kernel/setup.py b/packages/python/yap_kernel/setup.py index 9e0b36d09..dd108a73e 100644 --- a/packages/python/yap_kernel/setup.py +++ b/packages/python/yap_kernel/setup.py @@ -67,7 +67,7 @@ setup_args = dict( version = version_ns['__version__'], scripts = glob(pjoin('scripts', '*')), packages = packages, - py_modules = ['yap_kernel_launcher'], + py_modules = ['yap_kernel_launcher','backcall'], package_data = package_data, #package_dir = {'':here}, description = "YAP Kernel for Jupyter", diff --git a/packages/python/yap_kernel/yap_ipython/core/backcall.py b/packages/python/yap_kernel/yap_ipython/core/backcall.py new file mode 100644 index 000000000..fe1fdb547 --- /dev/null +++ b/packages/python/yap_kernel/yap_ipython/core/backcall.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Jan 13 18:17:15 2014 + +@author: takluyver +""" +import sys +PY3 = (sys.version_info[0] >= 3) + +try: + from inspect import signature, Parameter # Python >= 3.3 +except ImportError: + from ._signatures import signature, Parameter + +if PY3: + from functools import wraps +else: + from functools import wraps as _wraps + def wraps(f): + def dec(func): + _wraps(f)(func) + func.__wrapped__ = f + return func + + return dec + +def callback_prototype(prototype): + """Decorator to process a callback prototype. + + A callback prototype is a function whose signature includes all the values + that will be passed by the callback API in question. + + The original function will be returned, with a ``prototype.adapt`` attribute + which can be used to prepare third party callbacks. + """ + protosig = signature(prototype) + positional, keyword = [], [] + for name, param in protosig.parameters.items(): + if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): + raise TypeError("*args/**kwargs not supported in prototypes") + + if (param.default is not Parameter.empty) \ + or (param.kind == Parameter.KEYWORD_ONLY): + keyword.append(name) + else: + positional.append(name) + + kwargs = dict.fromkeys(keyword) + def adapt(callback): + """Introspect and prepare a third party callback.""" + sig = signature(callback) + try: + # XXX: callback can have extra optional parameters - OK? + sig.bind(*positional, **kwargs) + return callback + except TypeError: + pass + + # Match up arguments + unmatched_pos = positional[:] + unmatched_kw = kwargs.copy() + unrecognised = [] + # TODO: unrecognised parameters with default values - OK? + for name, param in sig.parameters.items(): + # print(name, param.kind) #DBG + if param.kind == Parameter.POSITIONAL_ONLY: + if len(unmatched_pos) > 0: + unmatched_pos.pop(0) + else: + unrecognised.append(name) + elif param.kind == Parameter.POSITIONAL_OR_KEYWORD: + if (param.default is not Parameter.empty) and (name in unmatched_kw): + unmatched_kw.pop(name) + elif len(unmatched_pos) > 0: + unmatched_pos.pop(0) + else: + unrecognised.append(name) + elif param.kind == Parameter.VAR_POSITIONAL: + unmatched_pos = [] + elif param.kind == Parameter.KEYWORD_ONLY: + if name in unmatched_kw: + unmatched_kw.pop(name) + else: + unrecognised.append(name) + else: # VAR_KEYWORD + unmatched_kw = {} + + # print(unmatched_pos, unmatched_kw, unrecognised) #DBG + + if unrecognised: + raise TypeError("Function {!r} had unmatched arguments: {}".format(callback, unrecognised)) + + n_positional = len(positional) - len(unmatched_pos) + + @wraps(callback) + def adapted(*args, **kwargs): + """Wrapper for third party callbacks that discards excess arguments""" +# print(args, kwargs) + args = args[:n_positional] + for name in unmatched_kw: + # XXX: Could name not be in kwargs? + kwargs.pop(name) +# print(args, kwargs, unmatched_pos, cut_positional, unmatched_kw) + return callback(*args, **kwargs) + + return adapted + + prototype.adapt = adapt + return prototype \ No newline at end of file diff --git a/packages/python/yap_kernel/yap_ipython/core/tests/test_events.py b/packages/python/yap_kernel/yap_ipython/core/tests/test_events.py index d95024cf9..fa2ec60e0 100644 --- a/packages/python/yap_kernel/yap_ipython/core/tests/test_events.py +++ b/packages/python/yap_kernel/yap_ipython/core/tests/test_events.py @@ -1,4 +1,4 @@ -from backcall import callback_prototype +from yap_ipython.core.backcall import callback_prototype import unittest from unittest.mock import Mock import nose.tools as nt