Author: Wim Lavrijsen <wlavrij...@lbl.gov>
Branch: cppyy-packaging
Changeset: r94596:10c6393b2cd5
Date: 2018-05-15 20:59 -0700
http://bitbucket.org/pypy/pypy/changeset/10c6393b2cd5/

Log:    pythonization improvements

diff --git a/pypy/module/_cppyy/__init__.py b/pypy/module/_cppyy/__init__.py
--- a/pypy/module/_cppyy/__init__.py
+++ b/pypy/module/_cppyy/__init__.py
@@ -19,12 +19,14 @@
         '_bind_object'           : 'interp_cppyy._bind_object',
         'bind_object'            : 'interp_cppyy.bind_object',
         'move'                   : 'interp_cppyy.move',
+        '_pin_type'              : 'interp_cppyy._pin_type',
     }
 
     appleveldefs = {
         '_post_import_startup'   : 'pythonify._post_import_startup',
+        'Template'               : 'pythonify.CPPTemplate',
         'add_pythonization'      : 'pythonify.add_pythonization',
-        'Template'               : 'pythonify.CPPTemplate',
+        'remove_pythonization'   : 'pythonify.remove_pythonization',
     }
 
     def __init__(self, space, *args):
diff --git a/pypy/module/_cppyy/capi/loadable_capi.py 
b/pypy/module/_cppyy/capi/loadable_capi.py
--- a/pypy/module/_cppyy/capi/loadable_capi.py
+++ b/pypy/module/_cppyy/capi/loadable_capi.py
@@ -676,7 +676,7 @@
     space.setattr(w_pycppclass, space.newtext(m1),
                   space.getattr(w_pycppclass, space.newtext(m2)))
 
-def pythonize(space, name, w_pycppclass):
+def pythonize(space, w_pycppclass, name):
     if name == "string":
         space.setattr(w_pycppclass, space.newtext("c_str"), 
_pythonizations["stdstring_c_str"])
         _method_alias(space, w_pycppclass, "_cppyy_as_builtin", "c_str")
diff --git a/pypy/module/_cppyy/helper.py b/pypy/module/_cppyy/helper.py
--- a/pypy/module/_cppyy/helper.py
+++ b/pypy/module/_cppyy/helper.py
@@ -59,26 +59,6 @@
         name = name[:_find_qualifier_index(name)]
     return name.strip(' ')
 
-def extract_namespace(name):
-    # find the namespace the named class lives in, take care of templates
-    tpl_open = 0
-    for pos in xrange(len(name)-1, 1, -1):
-        c = name[pos]
-
-        # count '<' and '>' to be able to skip template contents
-        if c == '>':
-            tpl_open += 1
-        elif c == '<':
-            tpl_open -= 1
-
-        # collect name up to "::"
-        elif tpl_open == 0 and c == ':' and name[pos-1] == ':':
-            # found the extend of the scope ... done
-            return name[0:pos-1]
-
-    # no namespace; assume outer scope
-    return ""
-
 
 #- operator mappings --------------------------------------------------------
 _operator_mappings = {}
diff --git a/pypy/module/_cppyy/interp_cppyy.py 
b/pypy/module/_cppyy/interp_cppyy.py
--- a/pypy/module/_cppyy/interp_cppyy.py
+++ b/pypy/module/_cppyy/interp_cppyy.py
@@ -14,6 +14,7 @@
 from pypy.module._cffi_backend import ctypefunc
 from pypy.module._cppyy import converter, executor, ffitypes, helper
 
+CLASS_FLAGS_IS_PINNED      = 0x0001
 
 INSTANCE_FLAGS_PYTHON_OWNS = 0x0001
 INSTANCE_FLAGS_IS_REF      = 0x0002
@@ -131,7 +132,7 @@
     cppclass = space.interp_w(W_CPPClassDecl, w_cppclass)
     # add back-end specific method pythonizations (doing this on the wrapped
     # class allows simple aliasing of methods)
-    capi.pythonize(space, cppclass.name, w_pycppclass)
+    capi.pythonize(space, w_pycppclass, cppclass.name)
     state = space.fromcache(State)
     state.cppclass_registry[rffi.cast(rffi.LONG, cppclass.handle)] = 
w_pycppclass
 
@@ -816,14 +817,15 @@
 
 
 class W_CPPScopeDecl(W_Root):
-    _attrs_ = ['space', 'handle', 'name', 'methods', 'datamembers']
+    _attrs_ = ['space', 'handle', 'flags', 'name', 'methods', 'datamembers']
     _immutable_fields_ = ['handle', 'name']
 
     def __init__(self, space, opaque_handle, final_scoped_name):
         self.space = space
-        self.name = final_scoped_name
         assert lltype.typeOf(opaque_handle) == capi.C_SCOPE
         self.handle = opaque_handle
+        self.flags = 0
+        self.name = final_scoped_name
         self.methods = {}
         # Do not call "self._build_methods()" here, so that a distinction can
         #  be made between testing for existence (i.e. existence in the cache
@@ -1316,7 +1318,7 @@
 
     # cast to actual if requested and possible
     w_pycppclass = None
-    if do_cast and rawobject:
+    if do_cast and rawobject and not (clsdecl.flags & CLASS_FLAGS_IS_PINNED):
         actual = capi.c_actual_class(space, clsdecl, rawobject)
         if actual != clsdecl.handle:
             try:
@@ -1390,3 +1392,13 @@
     if obj:
         obj.flags |= INSTANCE_FLAGS_IS_R_VALUE
     return w_obj
+
+
+# pythonization interface ---------------------------------------------------
+
+# do not auto-cast to given type
+@unwrap_spec(w_pycppclass=W_Root)
+def _pin_type(space, w_pycppclass):
+    w_clsdecl = space.findattr(w_pycppclass, space.newtext("__cppdecl__"))
+    decl = space.interp_w(W_CPPClassDecl, w_clsdecl)
+    decl.flags |= CLASS_FLAGS_IS_PINNED
diff --git a/pypy/module/_cppyy/pythonify.py b/pypy/module/_cppyy/pythonify.py
--- a/pypy/module/_cppyy/pythonify.py
+++ b/pypy/module/_cppyy/pythonify.py
@@ -227,7 +227,7 @@
     # needs to run first, so that the generic pythonizations can use them
     import _cppyy
     _cppyy._register_class(pycls)
-    _pythonize(pycls)
+    _pythonize(pycls, pycls.__cppname__)
     return pycls
 
 def make_cpptemplatetype(scope, template_name):
@@ -291,6 +291,27 @@
     raise AttributeError("'%s' has no attribute '%s'" % (str(scope), name))
 
 
+# helper for pythonization API
+def extract_namespace(name):
+    # find the namespace the named class lives in, take care of templates
+    tpl_open = 0
+    for pos in xrange(len(name)-1, 1, -1):
+        c = name[pos]
+
+        # count '<' and '>' to be able to skip template contents
+        if c == '>':
+            tpl_open += 1
+        elif c == '<':
+            tpl_open -= 1
+
+        # collect name up to "::"
+        elif tpl_open == 0 and c == ':' and name[pos-1] == ':':
+            # found the extend of the scope ... done
+            return name[:pos-1], name[pos+1:]
+
+    # no namespace; assume outer scope
+    return '', name
+
 # pythonization by decoration (move to their own file?)
 def python_style_getitem(self, idx):
     # python-style indexing: check for size and allow indexing from the back
@@ -314,15 +335,7 @@
     else:
         return python_style_getitem(self, slice_or_idx)
 
-
-_pythonizations = {}
-def _pythonize(pyclass):
-
-    try:
-        _pythonizations[pyclass.__name__](pyclass)
-    except KeyError:
-        pass
-
+def _pythonize(pyclass, name):
     # general note: use 'in pyclass.__dict__' rather than 'hasattr' to prevent
     # adding pythonizations multiple times in derived classes
 
@@ -363,10 +376,10 @@
     # map begin()/end() protocol to iter protocol on STL(-like) classes, but
     # not on vector, which is pythonized in the capi (interp-level; there is
     # also the fallback on the indexed __getitem__, but that is slower)
-    if not 'vector' in pyclass.__name__[:11] and \
+    if not 'vector' in name[:11] and \
             ('begin' in pyclass.__dict__ and 'end' in pyclass.__dict__):
-        if _cppyy._scope_byname(pyclass.__cppname__+'::iterator') or \
-                _cppyy._scope_byname(pyclass.__cppname__+'::const_iterator'):
+        if _cppyy._scope_byname(name+'::iterator') or \
+                _cppyy._scope_byname(name+'::const_iterator'):
             def __iter__(self):
                 i = self.begin()
                 while i != self.end():
@@ -386,7 +399,7 @@
             pyclass.__getitem__ = python_style_getitem
 
     # string comparisons
-    if pyclass.__name__ == _cppyy._std_string_name():
+    if name == _cppyy._std_string_name():
         def eq(self, other):
             if type(other) == pyclass:
                 return self.c_str() == other.c_str()
@@ -396,7 +409,7 @@
         pyclass.__str__ = pyclass.c_str
 
     # std::pair unpacking through iteration
-    if 'std::pair' == pyclass.__name__[:9] or 'pair' == pyclass.__name__[:4]:
+    if 'std::pair' == name[:9]:
         def getitem(self, idx):
             if idx == 0: return self.first
             if idx == 1: return self.second
@@ -406,6 +419,16 @@
         pyclass.__getitem__ = getitem
         pyclass.__len__     = return2
 
+    # user provided, custom pythonizations
+    try:
+        ns_name, cl_name = extract_namespace(name)
+        pythonizors = _pythonizations[ns_name]
+        name = cl_name
+    except KeyError:
+        pythonizors = _pythonizations['']   # global scope
+
+    for p in pythonizors:
+        p(pyclass, name)
 
 def _post_import_startup():
     # _cppyy should not be loaded at the module level, as that will trigger a
@@ -450,11 +473,26 @@
 
 
 # user-defined pythonizations interface
-_pythonizations = {}
-def add_pythonization(class_name, callback):
-    """Takes a class name and a callback. The callback should take a single
-    argument, the class proxy, and is called the first time the named class
-    is bound."""
-    if not callable(callback):
-        raise TypeError("given '%s' object is not callable" % str(callback))
-    _pythonizations[class_name] = callback
+_pythonizations = {'' : list()}
+def add_pythonization(pythonizor, scope = ''):
+    """<pythonizor> should be a callable taking two arguments: a class proxy,
+    and its C++ name. It is called on each time a named class from <scope>
+    (the global one by default, but a relevant C++ namespace is recommended)
+    is bound.
+    """
+    if not callable(pythonizor):
+        raise TypeError("given '%s' object is not callable" % str(pythonizor))
+    try:
+        _pythonizations[scope].append(pythonizor)
+    except KeyError:
+        _pythonizations[scope] = list()
+        _pythonizations[scope].append(pythonizor)
+
+def remove_pythonization(pythonizor, scope = ''):
+    """Remove previously registered <pythonizor> from <scope>.
+    """
+    try:
+        _pythonizations[scope].remove(pythonizor)
+        return True
+    except (KeyError, ValueError):
+        return False
diff --git a/pypy/module/_cppyy/test/Makefile b/pypy/module/_cppyy/test/Makefile
--- a/pypy/module/_cppyy/test/Makefile
+++ b/pypy/module/_cppyy/test/Makefile
@@ -7,6 +7,7 @@
         fragileDict.so \
         operatorsDict.so \
         overloadsDict.so \
+        pythonizablesDict.so \
         stltypesDict.so \
         templatesDict.so
 
diff --git a/pypy/module/_cppyy/test/conftest.py 
b/pypy/module/_cppyy/test/conftest.py
--- a/pypy/module/_cppyy/test/conftest.py
+++ b/pypy/module/_cppyy/test/conftest.py
@@ -10,7 +10,7 @@
             import os
             tst = os.path.basename(item.location[0])
             if not tst in ('test_helper.py', 'test_cppyy.py', 
'test_pythonify.py',
-                           'test_datatypes.py'):
+                           'test_datatypes.py', 'test_pythonization.py'):
                 py.test.skip("genreflex is not installed")
             import re
             if tst == 'test_pythonify.py' and \
@@ -19,6 +19,9 @@
             elif tst == 'test_datatypes.py' and \
                 not re.search("AppTestDATATYPES.test0[1-7]", item.location[2]):
                 py.test.skip("genreflex is not installed")
+            elif tst == 'test_pythonization.py' and \
+                not re.search("AppTestPYTHONIZATION.test0[0]", 
item.location[2]):
+                py.test.skip("genreflex is not installed")
 
 def pytest_ignore_collect(path, config):
     if py.path.local.sysfind('genreflex') is None and 
config.option.runappdirect:
diff --git a/pypy/module/_cppyy/test/test_helper.py 
b/pypy/module/_cppyy/test/test_helper.py
--- a/pypy/module/_cppyy/test/test_helper.py
+++ b/pypy/module/_cppyy/test/test_helper.py
@@ -53,11 +53,12 @@
 
 
 def test_namespace_extraction():
-    assert helper.extract_namespace("vector")                        == ""
-    assert helper.extract_namespace("std::vector")                   == "std"
-    assert helper.extract_namespace("std::vector<double>")           == "std"
-    assert helper.extract_namespace("std::vector<std::vector>")      == "std"
-    assert helper.extract_namespace("vector<double>")                == ""
-    assert helper.extract_namespace("vector<std::vector>")           == ""
-    assert helper.extract_namespace("aap::noot::mies::zus")          == 
"aap::noot::mies"
+    from pypy.module._cppyy import pythonify
 
+    assert pythonify.extract_namespace("vector")[0]                     == ""
+    assert pythonify.extract_namespace("std::vector")[0]                == 
"std"
+    assert pythonify.extract_namespace("std::vector<double>")[0]        == 
"std"
+    assert pythonify.extract_namespace("std::vector<std::vector>")[0]   == 
"std"
+    assert pythonify.extract_namespace("vector<double>")[0]             == ""
+    assert pythonify.extract_namespace("vector<std::vector>")[0]        == ""
+    assert pythonify.extract_namespace("aap::noot::mies::zus")[0]       == 
"aap::noot::mies"
diff --git a/pypy/module/_cppyy/test/test_pythonify.py 
b/pypy/module/_cppyy/test/test_pythonify.py
--- a/pypy/module/_cppyy/test/test_pythonify.py
+++ b/pypy/module/_cppyy/test/test_pythonify.py
@@ -378,13 +378,13 @@
 
         import _cppyy
 
-        def example01a_pythonize(pyclass):
-            assert pyclass.__name__ == 'example01a'
-            def getitem(self, idx):
-                return self.addDataToInt(idx)
-            pyclass.__getitem__ = getitem
+        def example01a_pythonize(pyclass, name):
+            if name == 'example01a':
+                def getitem(self, idx):
+                    return self.addDataToInt(idx)
+                pyclass.__getitem__ = getitem
 
-        _cppyy.add_pythonization('example01a', example01a_pythonize)
+        _cppyy.add_pythonization(example01a_pythonize)
 
         e = _cppyy.gbl.example01a(1)
 
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to