|
From: <bg...@us...> - 2018-09-06 10:38:23
|
Revision: 25523
http://sourceforge.net/p/sbml/code/25523
Author: bgoli
Date: 2018-09-06 10:38:15 +0000 (Thu, 06 Sep 2018)
Log Message:
-----------
Added PythonAPI2 function return warning mechanism
Modified Paths:
--------------
branches/libsbml-brett-python-api2/CMakeLists.txt
branches/libsbml-brett-python-api2/src/bindings/python/CMakeLists.txt
branches/libsbml-brett-python-api2/src/bindings/python/local-contrib.i
branches/libsbml-brett-python-api2/src/bindings/python/patch-python.cmake
Modified: branches/libsbml-brett-python-api2/CMakeLists.txt
===================================================================
--- branches/libsbml-brett-python-api2/CMakeLists.txt 2018-09-06 10:34:02 UTC (rev 25522)
+++ branches/libsbml-brett-python-api2/CMakeLists.txt 2018-09-06 10:38:15 UTC (rev 25523)
@@ -325,6 +325,10 @@
(Particularly important on Windows.)" FORCE)
endif()
+if (WITH_PYTHON)
+ # add option to enable Python API2 (with warnings)
+ option(PYTHON_USE_API2_WARNINGS "Enable the new Python API2 with warnings." OFF)
+endif (WITH_PYTHON)
###############################################################################
#
@@ -1371,6 +1375,11 @@
message(STATUS "")
+if(PYTHON_USE_API2_WARNINGS)
+ message(STATUS "")
+ message(STATUS " Using Python API2 warnings = yes")
+endif(PYTHON_USE_API2_WARNINGS)
+
if (LIBSBML_PACKAGE_SUMMARY)
message(STATUS "")
message(STATUS " Support for SBML Level 3 Packages: ")
Modified: branches/libsbml-brett-python-api2/src/bindings/python/CMakeLists.txt
===================================================================
--- branches/libsbml-brett-python-api2/src/bindings/python/CMakeLists.txt 2018-09-06 10:34:02 UTC (rev 25522)
+++ branches/libsbml-brett-python-api2/src/bindings/python/CMakeLists.txt 2018-09-06 10:38:15 UTC (rev 25523)
@@ -252,6 +252,7 @@
COMMAND "${CMAKE_COMMAND}"
ARGS -DBIN_DIRECTORY=\"${CMAKE_CURRENT_BINARY_DIR}\"
-DSRC_DIRECTORY=\"${CMAKE_CURRENT_SOURCE_DIR}\"
+ -DPYTHON_USE_API2_WARNINGS=\"${PYTHON_USE_API2_WARNINGS}\"
-P "${CMAKE_CURRENT_SOURCE_DIR}/patch-python.cmake"
MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/libsbml.i
Modified: branches/libsbml-brett-python-api2/src/bindings/python/local-contrib.i
===================================================================
--- branches/libsbml-brett-python-api2/src/bindings/python/local-contrib.i 2018-09-06 10:34:02 UTC (rev 25522)
+++ branches/libsbml-brett-python-api2/src/bindings/python/local-contrib.i 2018-09-06 10:38:15 UTC (rev 25523)
@@ -3,7 +3,8 @@
* @brief Contributed class that makes using libSBML from python more convenient
* for the time being this only works for python 2.x, but won't break python 3
* @author Gordon Ball
- *
+ * @author Brett G. Olivier
+ *
*<!---------------------------------------------------------------------------
* This file is part of libSBML. Please visit http://sbml.org for more
* information about SBML, and the latest version of libSBML.
@@ -13,17 +14,17 @@
* 2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
* 3. University of Heidelberg, Heidelberg, Germany
*
- * Copyright (C) 2009-2013 jointly by the following organizations:
+ * Copyright (C) 2009-2013 jointly by the following organizations:
* 1. California Institute of Technology, Pasadena, CA, USA
* 2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
- *
+ *
* Copyright (C) 2006-2008 by the California Institute of Technology,
- * Pasadena, CA, USA
- *
- * Copyright (C) 2002-2005 jointly by the following organizations:
+ * Pasadena, CA, USA
+ *
+ * Copyright (C) 2002-2005 jointly by the following organizations:
* 1. California Institute of Technology, Pasadena, CA, USA
* 2. Japan Science and Technology Agency, Japan
- *
+ *
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation. A copy of the license agreement is provided
@@ -47,6 +48,63 @@
%pythoncode %{
+## enable optional new API2 functionality by configure or environment
+if 'USE_LIBSBML_PYTHON_API2_WARNINGS' in os.environ:
+ try:
+ _etest = int(os.environ['USE_LIBSBML_PYTHON_API2_WARNINGS'])
+ except ValueError:
+ _etest = 0
+ print('\nThe environment variable "USE_LIBSBML_PYTHON_API2_WARNINGS" should have the value 1 or 0\n')
+
+ if _etest:
+ USE_LIBSBML_PYTHON_API2_WARNINGS = True
+ else:
+ USE_LIBSBML_PYTHON_API2_WARNINGS = False
+
+# class decorator, from six, that will be used to solve Python2/3 metaclass issues
+# usage is NewClass(with_metaclass(MetaClass, BaseClasses)):
+#def with_metaclass(meta, *bases):
+# """Create a base class with a metaclass.
+#Usage is: NewClass(with_metaclass(MetaClass, BaseClass*)"""
+# 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.
+# class metaclass(type):
+#
+# def __new__(cls, name, this_bases, d):
+# return meta(name, bases, d)
+#
+# @classmethod
+# def __prepare__(cls, name, this_bases):
+# return meta.__prepare__(name, bases)
+# return type.__new__(metaclass, 'temporary_class', (), {})
+
+if USE_LIBSBML_PYTHON_API2_WARNINGS:
+ import warnings
+ class PythonAPI2Warning(UserWarning):
+ """ Custom warning class for warnings generated by libSBML Python API2. """
+
+ def __init__(self, msg):
+ """ x.__init__(...) initializes x; see help(type(x)) for signature """
+ print(msg)
+
+ def p_func_warning_decorator(F):
+ try:
+ incoming_name = F.__name__
+ except:
+ return F
+ from functools import wraps
+ @wraps(F)
+ def warning_wrap(*args, **kwargs):
+ res = msg = None
+ res = F(*args, **kwargs)
+ if type(res) == int and res < 0:
+ msg = '\"{}\" returns an error ({}): {}'.format(incoming_name, res,\
+ OperationReturnValue_toString(res))
+ warnings.warn(msg, category=PythonAPI2Warning)
+ return res
+ return warning_wrap
+
class AutoProperty(type):
"""
Auto-detect Python class getX/setX methods.
@@ -57,7 +115,7 @@
(not at instantiation) and adding corresponding properties (directly
calling C methods where possible) to the class dictionary.
- @note The code should work for python 2.6 upwards, however for python 3 it
+ @note The code should work for python 2.6 upwards, however for python 3 it
needs to be attached via constructors.
"""
def __new__(cls, classname, bases, classdict):
@@ -82,12 +140,19 @@
set_methods = set()
swig_setter = classdict.get('__swig_setmethods__', {})
+ if USE_LIBSBML_PYTHON_API2_WARNINGS:
+ # explicitly collect unsetX methods separately for method decorator
+ unset_methods = set()
+ # collect addX and methods for method decorator
+ re_addfunc = re.compile(r'add[A-Z][A-Za-z0-9_]*')
+ add_methods = set()
+
allowed_methods = [
- 'Annotation',
- 'AnnotationString',
- 'Notes',
- 'NotesString',
- ]
+ 'Annotation',
+ 'AnnotationString',
+ 'Notes',
+ 'NotesString',
+ ]
#only search for get/set methods
#we assume that any unset method also has either get or set
@@ -104,6 +169,10 @@
get_methods.add(name)
elif prefix == 'set':
set_methods.add(name)
+ if USE_LIBSBML_PYTHON_API2_WARNINGS:
+ # find addX methods for decoration
+ if re_addfunc.match(k) is not None:
+ add_methods.add(k)
for name in get_methods | set_methods:
@@ -116,18 +185,18 @@
getter = setter = deleter = None
if name in get_methods:
getter = classdict['get'+name]
-
+
#this is a very dirty way of checking if the get method
#requires extra arguments (and hence cannot be a property)
#it should be possible to do this properly in SWIG?
try:
- argspec = inspect.getargspec(getter)
- numargs = len(argspec.args)
- if numargs > 1 or (numargs == 1 and argspec.args[0] != 'self') \
- or (argspec.varargs!=None and name not in allowed_methods and not name.startswith('ListOf') ):
+ argspec = inspect.getargspec(getter)
+ numargs = len(argspec.args)
+ if numargs > 1 or (numargs == 1 and argspec.args[0] != 'self') \
+ or (argspec.varargs!=None and name not in allowed_methods and not name.startswith('ListOf') ):
+ continue
+ except Exception:
continue
- except:
- continue
#use the c-level get function if the python function
#only consists of a call to it
@@ -134,52 +203,59 @@
cname = classname + '_get' + name
#test if function is "return _libsbml.CLASS_getNAME(__args__)"
try:
- if getter.func_code.co_names == ('_libsbml', cname):
- getter = getattr(_libsbml, cname)
+ if getter.func_code.co_names == ('_libsbml', cname):
+ getter = getattr(_libsbml, cname)
except:
- if getter.__code__.co_names == ('_libsbml', cname):
- getter = getattr(_libsbml, cname)
-
+ if getter.__code__.co_names == ('_libsbml', cname):
+ getter = getattr(_libsbml, cname)
+
if name in set_methods:
setter = classdict['set'+name]
try:
- argspec = inspect.getargspec(getter)
- numargs = len(argspec.args)
- if numargs > 1 and argspec.args[0] == 'self':
- cname = classname + '_set' + name
- try:
- if setter.func_code.co_names == ('_libsbml', cname):
- setter = getattr(_libsbml, cname)
- except:
- if setter.__code__.co_names == ('_libsbml', cname):
- setter = getattr(_libsbml, cname)
-
- #property fget does not get intercepted by __getattr__
- #but fset does, so we implement property setting via
- #the __swig_setmethods__ dict
- swig_setter[mangled] = setter
- continue
+ argspec = inspect.getargspec(getter)
+ numargs = len(argspec.args)
+ if numargs > 1 and argspec.args[0] == 'self':
+ cname = classname + '_set' + name
+ try:
+ if setter.func_code.co_names == ('_libsbml', cname):
+ setter = getattr(_libsbml, cname)
+ except:
+ if setter.__code__.co_names == ('_libsbml', cname):
+ setter = getattr(_libsbml, cname)
+
+ #property fget does not get intercepted by __getattr__
+ #but fset does, so we implement property setting via
+ #the __swig_setmethods__ dict
+ if USE_LIBSBML_PYTHON_API2_WARNINGS:
+ swig_setter[mangled] = p_func_warning_decorator(setter)
+ else:
+ swig_setter[mangled] = setter
+ continue
except:
- pass
-
+ pass
+
if 'unset' + name in classdict:
deleter = classdict['unset'+name]
try:
- argspec = inspect.getargspec(getter)
- numargs = len(argspec.args)
- if numargs == 1 and argspec.args[0] == 'self' and \
- (argspec.varargs==None or name in allowed_methods):
- cname = classname + '_unset' + name
- try:
- if deleter.func_code.co_names == ('_libsbml', cname):
- deleter = getattr(_libsbml, cname)
- except:
- if deleter.__code__.co_names == ('_libsbml', cname):
- deleter = getattr(_libsbml, cname)
+ argspec = inspect.getargspec(getter)
+ numargs = len(argspec.args)
+ if numargs == 1 and argspec.args[0] == 'self' and \
+ (argspec.varargs==None or name in allowed_methods):
+ cname = classname + '_unset' + name
+ try:
+ if deleter.func_code.co_names == ('_libsbml', cname):
+ deleter = getattr(_libsbml, cname)
+ except:
+ if deleter.__code__.co_names == ('_libsbml', cname):
+ deleter = getattr(_libsbml, cname)
except:
- pass
+ pass
+ # wrap mangled deleters
+ if USE_LIBSBML_PYTHON_API2_WARNINGS:
+ unset_methods.add(name)
+
if getter or setter or deleter:
#fset is technically redundant since the method is dispatched
#via _swig_setattr rather than through the property due to that
@@ -187,6 +263,7 @@
#handles properties
classdict[mangled] = property(fget=getter, fset=setter, fdel=deleter)
+
def __repr__(self):
desc = self.__class__.__name__
if hasattr(self, '__len__'):
@@ -196,15 +273,37 @@
if hasattr(self, 'name') and self.name:
desc += ' "%s"' % self.name
return '<' + desc + '>'
-
+
if classdict.get('__repr__', None) in (_swig_repr, None):
classdict['__repr__'] = __repr__
+ if USE_LIBSBML_PYTHON_API2_WARNINGS:
+ # decorate get methods
+ for gm in get_methods:
+ if 'get'+gm in classdict:
+ classdict['get'+gm] = p_func_warning_decorator(classdict['get'+gm])
+ # decorate set methods
+ for sm in set_methods:
+ if 'set'+sm in classdict:
+ classdict['set'+sm] = p_func_warning_decorator(classdict['set'+sm])
+
+ # decorate unset methods
+ for dm in unset_methods:
+ if 'unset'+dm in classdict:
+ classdict['unset'+dm] = p_func_warning_decorator(classdict['unset'+dm])
+
+ # decorate unset methods
+ if len(add_methods) > 0:
+ for am in add_methods:
+ if am in classdict:
+ classdict[am] = p_func_warning_decorator(classdict[am])
+
return type.__new__(cls, classname, bases, classdict)
%}
+
%extend SBase
{
%pythoncode
Modified: branches/libsbml-brett-python-api2/src/bindings/python/patch-python.cmake
===================================================================
--- branches/libsbml-brett-python-api2/src/bindings/python/patch-python.cmake 2018-09-06 10:34:02 UTC (rev 25522)
+++ branches/libsbml-brett-python-api2/src/bindings/python/patch-python.cmake 2018-09-06 10:38:15 UTC (rev 25523)
@@ -27,4 +27,16 @@
import os.path
sys.path.append(os.path.dirname(__file__))
")
+
+if (PYTHON_USE_API2_WARNINGS)
+ file(APPEND "${WRAPPER_FILE}" "
+USE_LIBSBML_PYTHON_API2_WARNINGS = True
+")
+else()
+ file(APPEND "${WRAPPER_FILE}" "
+USE_LIBSBML_PYTHON_API2_WARNINGS = False
+")
+endif()
+
file(APPEND "${WRAPPER_FILE}" "${SOURCECODE}")
+
|