Patchwork [02,of,10] policy: add helper to import cext/pure module

login
register
mail settings
Submitter Yuya Nishihara
Date May 20, 2017, 8:41 a.m.
Message ID <76ed34b377b3f46c54d9.1495269669@mimosa>
Download mbox | patch
Permalink /patch/20762/
State Accepted
Headers show

Comments

Yuya Nishihara - May 20, 2017, 8:41 a.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1470969017 -32400
#      Fri Aug 12 11:30:17 2016 +0900
# Node ID 76ed34b377b3f46c54d9c8e68f8f851a2cfc641c
# Parent  f8ff1fc7f71f1a5956f300050014a1ebb74fdacb
policy: add helper to import cext/pure module

These functions are sysstr API since __import__() and getattr() hate byte
strings on Python 3.

There's a minor BC, which is ImportError will be raised if invalid
HGMODULEPOLICY is specified. I think this is more desired behavior.

We're planning to add strict checking for C API compatibility. This patch
includes the stub for it.

Patch

diff --git a/mercurial/policy.py b/mercurial/policy.py
--- a/mercurial/policy.py
+++ b/mercurial/policy.py
@@ -24,6 +24,14 @@  import sys
 policy = b'allow'
 policynoc = (b'cffi', b'cffi-allow', b'py')
 policynocffi = (b'c', b'py')
+_packageprefs = {
+    # policy: (versioned package, pure package)
+    b'c': (r'cext', None),
+    b'allow': (r'cext', r'pure'),
+    b'cffi': (None, r'pure'),  # TODO: (r'cffi', None)
+    b'cffi-allow': (None, r'pure'),  # TODO: (r'cffi', r'pure')
+    b'py': (None, r'pure'),
+}
 
 try:
     from . import __modulepolicy__
@@ -49,3 +57,40 @@  if sys.version_info[0] >= 3:
         policy = os.environ[r'HGMODULEPOLICY'].encode(r'utf-8')
 else:
     policy = os.environ.get(r'HGMODULEPOLICY', policy)
+
+def _importfrom(pkgname, modname):
+    # from .<pkgname> import <modname> (where . is looked through this module)
+    fakelocals = {}
+    pkg = __import__(pkgname, globals(), fakelocals, [modname], level=1)
+    try:
+        fakelocals[modname] = mod = getattr(pkg, modname)
+    except AttributeError:
+        raise ImportError(r'cannot import name %s' % modname)
+    # force import; fakelocals[modname] may be replaced with the real module
+    getattr(mod, r'__doc__', None)
+    return fakelocals[modname]
+
+def _checkmod(pkgname, modname, mod):
+    expected = 1  # TODO: maybe defined in table?
+    actual = getattr(mod, r'version', None)
+    if actual != expected:
+        raise ImportError(r'cannot import module %s.%s '
+                          r'(expected version: %d, actual: %r)'
+                          % (pkgname, modname, expected, actual))
+
+def importmod(modname):
+    """Import module according to policy and check API version"""
+    try:
+        verpkg, purepkg = _packageprefs[policy]
+    except KeyError:
+        raise ImportError(r'invalid HGMODULEPOLICY %r' % policy)
+    assert verpkg or purepkg
+    if verpkg:
+        try:
+            mod = _importfrom(verpkg, modname)
+            _checkmod(verpkg, modname, mod)
+            return mod
+        except ImportError:
+            if not purepkg:
+                raise
+    return _importfrom(purepkg, modname)