changeset 603:e55a42144ba9

C-implementations for Configuration.getvarl() and Configuration.getvar_s()
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 11 Jan 2022 02:50:17 +0100
parents a2fff0d93d83
children be25b1ae693d
files configmix/_speedups.c configmix/config.py
diffstat 2 files changed, 272 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/configmix/_speedups.c	Tue Jan 11 00:52:56 2022 +0100
+++ b/configmix/_speedups.c	Tue Jan 11 02:50:17 2022 +0100
@@ -30,11 +30,16 @@
     PyObject *EMPTY_STR;
     PyObject *QUOTE_MAP;
     PyObject *MISSING;
+    PyObject *MARKER;
     PyObject *STARTTOK;
     PyObject *ENDTOK;
+    PyObject *REF_NAMESPACE;
 };
 
 
+static PyObject * _fast_getvar_s(PyObject *, PyObject *, PyObject *, PyObject *, struct speedups_state *, int*);
+
+
 static
 int
 _hex2ucs4(PyObject *s, Py_ssize_t end, Py_UCS4 *result)
@@ -835,9 +840,8 @@
         filters = Py_NewRef(PyTuple_GetItem(tmp, 1));
         py_clear_ref(&tmp);
 
-        tmp = PyObject_CallMethod(
-            config, "_getvar_s_with_cache_info", "O", varname);
-        if (tmp == NULL) {
+        varvalue = _fast_getvar_s(config, varname, NULL, self, sstate, &cacheable);
+        if (varvalue == NULL) {
             if (PyErr_ExceptionMatches(PyExc_KeyError)) {
                 cacheable = 1;
                 if (PySequence_Contains(filters, sstate->NONE_FILTER) == 1) {
@@ -863,18 +867,6 @@
                 goto error;
             }
         }
-        else {
-            if (PyTuple_Size(tmp) != 2) {
-                py_clear_ref(&tmp);
-                PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
-                goto error;
-            }
-            /* unpack the result */
-            /* borrowed -- cannot fail -- but want ownership */
-            varvalue = Py_NewRef(PyTuple_GetItem(tmp, 0));
-            cacheable = PyObject_IsTrue(PyTuple_GetItem(tmp, 1));
-            py_clear_ref(&tmp);
-        }
 
         if (!cacheable) {
             use_cache = 0;
@@ -1065,9 +1057,9 @@
         filters = Py_NewRef(PyTuple_GetItem(tmp, 1));
         py_clear_ref(&tmp);
 
-        tmp = PyObject_CallMethod(
-            config, "_getvar_s_with_cache_info", "O", varname);
-        if (tmp == NULL) {
+        varvalue = _fast_getvar_s(config, varname, NULL, self, sstate, &cacheable);
+
+        if (varvalue == NULL) {
             if (PyErr_ExceptionMatches(PyExc_KeyError)) {
                 cacheable = 1;
                 if (PySequence_Contains(filters, sstate->NONE_FILTER) == 1) {
@@ -1095,18 +1087,6 @@
                 goto error;
             }
         }
-        else {
-            if (PyTuple_Size(tmp) != 2) {
-                py_clear_ref(&tmp);
-                PyErr_SetString(PyExc_TypeError, "tuple of size 2 expected");
-                goto error;
-            }
-            /* unpack the result */
-            /* borrowed -- but want own */
-            varvalue = Py_NewRef(PyTuple_GetItem(tmp, 0));
-            cacheable = PyObject_IsTrue(PyTuple_GetItem(tmp, 1));
-            py_clear_ref(&tmp);
-        }
 
         if (!cacheable) {
             use_cache = 0;
@@ -1191,6 +1171,205 @@
 
 static
 PyObject *
+_fast_getvarl(PyObject *config, PyObject *path, PyObject *namespace, PyObject *default_, struct speedups_state *sstate)
+{
+    PyObject *varvalue;
+    PyObject *lookupfn = NULL;
+
+    if ((namespace == NULL) || PyObject_Not(namespace)) {
+        lookupfn = PyObject_GetAttrString(config, "_lookupvar");
+        if (lookupfn == NULL) {
+            goto error;
+        }
+    }
+    else {
+        int ns_equals_ref = PyUnicode_Compare(namespace, sstate->REF_NAMESPACE);
+        if ((ns_equals_ref < 0) && PyErr_Occurred()) {
+            return NULL;
+        }
+        if (ns_equals_ref == 0) {
+            lookupfn = PyObject_GetAttrString(config, "_lookupref");
+            if (lookupfn == NULL) {
+                goto error;
+            }
+        }
+        else {
+            /* lookup_varns */
+            /* this is borrowed */
+            PyObject *cfg_vars_mod = PyImport_AddModule("configmix.variables");
+            if (cfg_vars_mod == NULL) {
+                goto error;
+            }
+            lookupfn = PyObject_CallMethod(cfg_vars_mod, "lookup_varns", "O", namespace);
+            if (lookupfn == NULL) {
+                goto handle_possible_keyerror;
+            }
+        }
+    }
+    varvalue = PyObject_CallObject(lookupfn, path);
+    if (varvalue == NULL) {
+        goto handle_possible_keyerror;
+    }
+    Py_DECREF(lookupfn);
+    return varvalue;
+
+handle_possible_keyerror:
+    if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+        if ((default_ == NULL) || py_object_is(default_, sstate->MARKER)) {
+            PyErr_Format(PyExc_KeyError, "Variable %R not found", path);
+            /* fall through */
+        }
+        else {
+            PyErr_Clear();
+            Py_XDECREF(lookupfn);
+            return Py_NewRef(default_);
+        }
+    }
+    /* fall through */
+error:
+    Py_XDECREF(lookupfn);
+    return NULL;
+}
+
+
+static
+PyObject *
+fast_getvarl(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {"config", "path", "namespace", "default", NULL};
+    PyObject *path;
+    PyObject *config;
+    PyObject *namespace = NULL;
+    PyObject *default_ = NULL;
+
+    struct speedups_state *sstate;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO!|$OO", kwlist, &config, &PyTuple_Type, &path, &namespace, &default_)) {
+
+        return NULL;
+    }
+    sstate = PyModule_GetState(self);
+    if (sstate == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "no module state available");
+        return NULL;
+    }
+    return _fast_getvarl(config, path, namespace, default_, sstate);
+}
+
+
+/**
+ * Mixture of py_getvar_s and _py_getvar_s_with_cache_info
+ */
+static
+PyObject *
+_fast_getvar_s(PyObject *config, PyObject *varname, PyObject *default_, PyObject *self, struct speedups_state *sstate, int *cacheable)
+{
+    PyObject *varname_b;            /* always borrowed */
+    PyObject *namespace_b;          /* always borrowed */
+    PyObject *splitted = NULL;
+    PyObject *res = NULL;
+    PyObject *tmp1;
+    PyObject *tmp2;
+
+    splitted = fast_split_ns(self, varname);
+    if (splitted == NULL) {
+        goto error;
+    }
+    namespace_b = PyTuple_GetItem(splitted, 0);   /* borrowed */
+    varname_b = PyTuple_GetItem(splitted, 1);     /* borrowed */
+
+    if (PyObject_Not(namespace_b)) {
+        tmp1 = _fast_pathstr2path(varname_b, NULL, sstate);
+        if (tmp1 == NULL) {
+            goto error;
+        }
+        tmp2 = _fast_getvarl(config, tmp1, NULL, default_, sstate);
+        if (tmp2 == NULL) {
+            py_clear_ref(&tmp1);
+            goto handle_possible_keyerror;
+        }
+        py_clear_ref(&tmp1);
+        res = PyObject_CallMethod(config, "substitute_variables_in_obj", "O", tmp2);
+        if (res == NULL) {
+            py_clear_ref(&tmp2);
+            goto handle_possible_keyerror;
+        }
+        py_clear_ref(&tmp2);
+        /* no namespace -> cacheable */
+        *cacheable = 1;
+    }
+    else {
+        tmp1 = PyTuple_New(1);
+        if (tmp1 == NULL) {
+            goto error;
+        }
+        PyTuple_SetItem(tmp1, 0, Py_NewRef(varname_b));
+        tmp2 = _fast_getvarl(config, tmp1, namespace_b, NULL, sstate);
+        if (tmp2 == NULL) {
+            py_clear_ref(&tmp1);
+            goto handle_possible_keyerror;
+        }
+        py_clear_ref(&tmp1);
+        res = PyObject_CallMethod(config, "substitute_variables_in_obj", "O", tmp2);
+        if (res == NULL) {
+            py_clear_ref(&tmp2);
+            goto handle_possible_keyerror;
+        }
+        py_clear_ref(&tmp2);
+        /* results from namespaced lookups are currently not cacheable */
+        *cacheable = 0;
+    }
+
+    /* free splitted last because of using borrowed refs from it */
+    Py_DECREF(splitted);
+    return res;
+
+handle_possible_keyerror:
+    if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+        if ((default_ == NULL) || py_object_is(default_, sstate->MARKER)) {
+            /* fall through */
+        }
+        else {
+            PyErr_Clear();
+            Py_XDECREF(res);
+            Py_XDECREF(splitted);
+            return Py_NewRef(default_);
+        }
+    }
+    /* fall through */    
+    
+error:
+    Py_XDECREF(res);
+    Py_XDECREF(splitted);
+    return NULL;
+}
+
+
+static
+PyObject *
+fast_getvar_s(PyObject *self, PyObject *args)
+{
+    PyObject *config;
+    PyObject *varname;
+    PyObject *default_;
+    int cacheable;
+    struct speedups_state *sstate;
+
+    if (!PyArg_UnpackTuple(args, "config", 3, 3, &config, &varname, &default_)) {
+        return NULL;
+    }
+
+    sstate = PyModule_GetState(self);
+    if (sstate == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "no module state available");
+        return NULL;
+    }
+    return _fast_getvar_s(config, varname, default_, self, sstate, &cacheable);
+}
+
+
+static
+PyObject *
 sync_MISSING(PyObject *self, PyObject *missing)
 {
     struct speedups_state *sstate;
@@ -1209,6 +1388,26 @@
 }
 
 
+static
+PyObject *
+sync_MARKER(PyObject *self, PyObject *marker)
+{
+    struct speedups_state *sstate;
+
+    sstate = PyModule_GetState(self);
+    if (sstate == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "no module state available");
+        return NULL;
+    }
+    if (sstate->MARKER != NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "_MARKER already set");
+        return NULL;
+    }
+    sstate->MARKER = Py_NewRef(marker);
+    Py_RETURN_NONE;
+}
+
+
 static struct PyMethodDef speedups_methods[] = {
     {"fast_unquote", fast_unquote, METH_O, PyDoc_STR("C-implementation of configmix.unquote")},
     {"fast_quote", fast_quote, METH_O, PyDoc_STR("C-implementation of configmix.quote")},
@@ -1216,8 +1415,10 @@
     {"_fast_split_filters", fast_split_filters, METH_O, PyDoc_STR("C-implementation of configmix.config._split_filters")},
     {"_fast_split_ns", fast_split_ns, METH_O, PyDoc_STR("C-implementation of configmix.config._split_ns")},
     {"_fast_interpolate_variables", fast_interpolate_variables, METH_VARARGS, PyDoc_STR("C-implementation of configmix.config.Configuration.interpolate_variables")},
+    {"_fast_getvarl", (PyCFunction)fast_getvarl, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("C-Implementation of configmix.config.Configuration.getvarl")},
+    {"_fast_getvar_s", fast_getvar_s, METH_VARARGS, PyDoc_STR("C-Implementation of configmix.config.Configuration.getvar_s")},    
     {"_sync_MISSING", sync_MISSING, METH_O, PyDoc_STR("Internal function to easily sync the _MISSING object with configmix.config")},
-
+    {"_sync_MARKER", sync_MARKER, METH_O, PyDoc_STR("Internal function to easily sync the _MARKER object with configmix.config")},
     {NULL, NULL, 0, NULL}
 };
 
@@ -1314,6 +1515,12 @@
     }
     PyUnicode_InternInPlace(&(sstate->ENDTOK));
 
+    sstate->REF_NAMESPACE = PyUnicode_FromStringAndSize("ref", 3);
+    if (sstate->REF_NAMESPACE == NULL) {
+        return -1;
+    }
+    PyUnicode_InternInPlace(&(sstate->REF_NAMESPACE));
+
     return 0;
 }
 
@@ -1334,8 +1541,10 @@
         Py_VISIT(sstate->EMPTY_STR);
         Py_VISIT(sstate->QUOTE_MAP);
         Py_VISIT(sstate->MISSING);
+        Py_VISIT(sstate->MARKER);
         Py_VISIT(sstate->STARTTOK);
         Py_VISIT(sstate->ENDTOK);
+        Py_VISIT(sstate->REF_NAMESPACE);
     }
     return 0;
 }
@@ -1357,8 +1566,10 @@
         Py_CLEAR(sstate->EMPTY_STR);
         Py_CLEAR(sstate->QUOTE_MAP);
         Py_CLEAR(sstate->MISSING);
+        Py_CLEAR(sstate->MARKER);
         Py_CLEAR(sstate->STARTTOK);
         Py_CLEAR(sstate->ENDTOK);
+        Py_CLEAR(sstate->REF_NAMESPACE);
     }
     return 0;
 }
--- a/configmix/config.py	Tue Jan 11 00:52:56 2022 +0100
+++ b/configmix/config.py	Tue Jan 11 02:50:17 2022 +0100
@@ -33,14 +33,17 @@
 try:
     from ._speedups import (fast_unquote, fast_quote, fast_pathstr2path,
                             _fast_split_ns, _fast_split_filters,
+                            _fast_getvarl, _fast_getvar_s,
                             _fast_interpolate_variables,
-                            _sync_MISSING)
+                            _sync_MISSING, _sync_MARKER)
 except ImportError:
     fast_unquote = None
     fast_quote = None
     fast_pathstr2path = None
     _fast_split_ns = None
     _fast_split_filters = None
+    _fast_getvarl = None
+    _fast_getvar_s = None
     _fast_interpolate_variables = None
     _sync_MISSING = None
 
@@ -503,7 +506,7 @@
         for k in self:
             yield self.getitem_ns(k)
 
-    def getvarl(self, *path, **kwds):
+    def py_getvarl(self, *path, **kwds):
         """Get a variable where the hierarchy is given in `path` as sequence
         and the namespace is given in the `namespace` keyword argument.
 
@@ -531,6 +534,16 @@
         else:
             return varvalue
 
+    if _fast_getvarl:
+
+        def fast_getvarl(self, *args, **kwds):
+            return _fast_getvarl(self, args, **kwds)
+
+        getvarl = fast_getvarl
+
+    else:
+        getvarl = py_getvarl
+
     def getkeysl(self, *path, **kwds):
         """Yield the keys of a variable value.
 
@@ -709,7 +722,7 @@
         else:
             return default
 
-    def getvar_s(self, varname, default=_MARKER):
+    def py_getvar_s(self, varname, default=_MARKER):
         """Get a variable - including variables from other namespaces.
 
         `varname` is interpreted as in :meth:`.getvar`. But variables
@@ -733,7 +746,18 @@
             else:
                 return default
 
-    def _getvar_s_with_cache_info(self, varname):
+    if _fast_getvar_s:
+
+        def fast_getvar_s(self, varname, default=_MARKER):
+            return _fast_getvar_s(self, varname, default)
+
+        getvar_s = fast_getvar_s
+
+    else:
+
+        getvar_s = py_getvar_s
+
+    def _py_getvar_s_with_cache_info(self, varname):
         """Internal variant of :meth:`~.getvar_s` that returns some information
         whether caching of interpolated values is allowed
 
@@ -909,7 +933,7 @@
             varname, filters = _split_filters(
                 s[start+2:end])     # noqa: E226
             try:
-                varvalue, cacheable = self._getvar_s_with_cache_info(varname)
+                varvalue, cacheable = self._py_getvar_s_with_cache_info(varname)
             except KeyError:
                 cacheable = True
                 if NONE_FILTER in filters:
@@ -1241,3 +1265,4 @@
 
 if _sync_MISSING:
     _sync_MISSING(_MISSING)
+    _sync_MARKER(_MARKER)