changeset 552:39e5d07d8dbc

Provide a C implementation of configmix.config._split_filters. This is needed as precondition for implementation interpolate_variables in C also. Currently the speedup is not measurable but it does not hurt also. Also provide some unit-tests for _split_filters().
author Franz Glasner <fzglas.hg@dom66.de>
date Sun, 02 Jan 2022 20:40:09 +0100
parents 4c968c5cfce6
children 9d2bd411f5c5
files configmix/_speedups.c configmix/config.py tests/test.py
diffstat 3 files changed, 159 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/configmix/_speedups.c	Sun Jan 02 16:05:35 2022 +0100
+++ b/configmix/_speedups.c	Sun Jan 02 20:40:09 2022 +0100
@@ -22,6 +22,7 @@
     PyObject *DOT;
     PyObject *QUOTE;
     PyObject *NS_SEPARATOR;
+    PyObject *FILTER_SEPARATOR;
     PyObject *EMPTY_STR;
     PyObject *QUOTE_MAP;
 };
@@ -251,7 +252,7 @@
 
 static
 PyObject *
-_fast_unquote(PyObject *self, PyObject *s, Py_ssize_t s_len, struct speedups_state *sstate)
+_fast_unquote(PyObject *s, Py_ssize_t s_len, PyObject *self, struct speedups_state *sstate)
 {
     Py_ssize_t find;
     Py_ssize_t parts_len;
@@ -397,7 +398,7 @@
 PyObject *
 fast_unquote(PyObject *self, PyObject *s)
 {
-    return _fast_unquote(self, s, -1, NULL);
+    return _fast_unquote(s, -1, self, NULL);
 }
 
 
@@ -499,7 +500,7 @@
     }
     for (i=0; i < parts_len; i++) {
         o = PyList_GetItem(parts, i);   /* borrowed */
-        u = _fast_unquote(self, o, -1, sstate);
+        u = _fast_unquote(o, -1, NULL, sstate);
         if (u == NULL) {
             goto error;
         }
@@ -518,6 +519,122 @@
 
 static
 PyObject *
+_fast_split_filters(PyObject *varname, PyObject *self, struct speedups_state *sstate)
+{
+    Py_ssize_t varname_len;
+    Py_ssize_t sep;
+    PyObject *res = NULL;
+    PyObject *filters = NULL;
+    PyObject *name = NULL;
+    PyObject *tmp;
+
+    varname_len = PyUnicode_GetLength(varname);
+    if (varname_len < 0) {
+        return NULL;
+    }
+    if (varname_len == 0) {
+        sep = -1;
+    }
+    else {
+        sep = PyUnicode_FindChar(varname, '|', 0, varname_len, 1);
+        if (sep == -2) {
+            return NULL;
+        }
+    }
+    if (sep == -1) {
+        res = PyTuple_New(2);
+        if (res == NULL) {
+            goto error;
+        }
+        Py_INCREF(varname);                /* because PyTuple_SetItem steals */
+        PyTuple_SetItem(res, 0, varname);  /* steals */
+        filters = PyList_New(0);
+        if (filters == NULL) {
+            goto error;
+        }
+        PyTuple_SetItem(res, 1, filters);  /* steals */
+        return res;
+    }
+    name = PyUnicode_Substring(varname, 0, sep);
+    if (name == NULL) {
+        goto error;
+    }
+    tmp = PyObject_CallMethod(name, "rstrip", NULL);
+    if (tmp == NULL) {
+        goto error;
+    }
+    Py_DECREF(name);
+    name = tmp;
+
+    filters = PyUnicode_Substring(varname, sep+1, varname_len);
+    if (filters == NULL) {
+        goto error;
+    }
+    tmp = PyObject_CallMethod(filters, "strip", NULL);
+    if (tmp == NULL) {
+        goto error;
+    }
+    Py_DECREF(filters);
+    filters = tmp;
+
+    if (PyObject_Not(filters)) {
+        Py_DECREF(filters); filters = NULL;
+
+        res = PyTuple_New(2);
+        if (res == NULL) {
+            goto error;
+        }
+        PyTuple_SetItem(res, 0, name);  /* steals */
+        name = NULL;                    /* no ownership any more */
+        filters = PyList_New(0);
+        if (filters == NULL) {
+            goto error;
+        }
+        PyTuple_SetItem(res, 1, filters);  /* steals */
+        return res;
+    }
+
+    if (sstate == NULL) {
+        sstate = PyModule_GetState(self);
+        if (sstate == NULL) {
+            PyErr_SetString(PyExc_RuntimeError, "no module state available");
+            goto error;
+        }
+    }
+
+    tmp = PyUnicode_Split(filters, sstate->FILTER_SEPARATOR, -1);
+    if (tmp == NULL) {
+        goto error;
+    }
+    Py_DECREF(filters);
+    filters = tmp;
+
+    res = PyTuple_New(2);
+    if (res == NULL) {
+        goto error;
+    }
+    PyTuple_SetItem(res, 0, name);     /* steals -- ownership changed */
+    PyTuple_SetItem(res, 1, filters);  /* steals -- ownership changed */
+    return res;
+
+error:
+    Py_XDECREF(res);
+    Py_XDECREF(filters);
+    Py_XDECREF(name);
+    return NULL;
+}
+
+
+static
+PyObject *
+fast_split_filters(PyObject *self, PyObject *varname)
+{
+    return _fast_split_filters(varname, self, NULL);
+}
+
+
+static
+PyObject *
 fast_split_ns(PyObject *self, PyObject *varname)
 {
     PyObject *res = NULL;
@@ -555,7 +672,7 @@
         Py_DECREF(res);
         return NULL;
     }
-    o2 = _fast_unquote(self, o1, ns_idx, NULL);
+    o2 = _fast_unquote(o1, ns_idx, self, NULL);
     if (o2 == NULL) {
         Py_DECREF(o1);
         Py_DECREF(res);
@@ -577,6 +694,7 @@
     {"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")},
     {"fast_pathstr2path", fast_pathstr2path, METH_O, PyDoc_STR("C-implementation of configmix.pathstr2path")},
+    {"_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")},
     {NULL, NULL, 0, NULL}
 };
@@ -621,6 +739,12 @@
     }
     PyUnicode_InternInPlace(&(sstate->NS_SEPARATOR));
 
+    sstate->FILTER_SEPARATOR = PyUnicode_FromStringAndSize("|", 1);
+    if (sstate->FILTER_SEPARATOR == NULL) {
+        return -1;
+    }
+    PyUnicode_InternInPlace(&(sstate->FILTER_SEPARATOR));
+
     sstate->EMPTY_STR = PyUnicode_FromStringAndSize("", 0);
     if (sstate->EMPTY_STR == NULL) {
         return -1;
@@ -658,6 +782,7 @@
         Py_VISIT(sstate->DOT);
         Py_VISIT(sstate->QUOTE);
         Py_VISIT(sstate->NS_SEPARATOR);
+        Py_VISIT(sstate->FILTER_SEPARATOR);
         Py_VISIT(sstate->EMPTY_STR);
         Py_VISIT(sstate->QUOTE_MAP);
     }
@@ -675,6 +800,7 @@
         Py_CLEAR(sstate->DOT);
         Py_CLEAR(sstate->QUOTE);
         Py_CLEAR(sstate->NS_SEPARATOR);
+        Py_CLEAR(sstate->FILTER_SEPARATOR);
         Py_CLEAR(sstate->EMPTY_STR);
         Py_CLEAR(sstate->QUOTE_MAP);
     }
--- a/configmix/config.py	Sun Jan 02 16:05:35 2022 +0100
+++ b/configmix/config.py	Sun Jan 02 20:40:09 2022 +0100
@@ -32,12 +32,13 @@
 from .constants import REF_NAMESPACE, NONE_FILTER, EMPTY_FILTER
 try:
     from ._speedups import (fast_unquote, fast_quote, fast_pathstr2path,
-                            _fast_split_ns)
+                            _fast_split_ns, _fast_split_filters)
 except ImportError:
     fast_unquote = None
     fast_quote = None
     fast_pathstr2path = None
     _fast_split_ns = None
+    _fast_split_filters = None
 
 
 _MARKER = object()
@@ -397,7 +398,7 @@
     _split_ns = _py_split_ns
 
 
-def _split_filters(varname):
+def _py_split_filters(varname):
     """Split off the filter part from the `varname` string
 
     :type varname: str
@@ -418,6 +419,12 @@
         return (name, [])
 
 
+if _fast_split_filters:
+    _split_filters = _fast_split_filters
+else:
+    _split_filters = _py_split_filters
+
+
 class Configuration(CoercingMethodsMixin, _AttributeDict):
 
     """The configuration dictionary with attribute support or
--- a/tests/test.py	Sun Jan 02 16:05:35 2022 +0100
+++ b/tests/test.py	Sun Jan 02 20:40:09 2022 +0100
@@ -1903,6 +1903,24 @@
     def test_split_ns_quoting(self):
         self.assertEqual((u":", u"%x3a"), self.split_ns(u"%x3a:%x3a"))
 
+    def test_split_filters_empty(self):
+        self.assertEqual((u"", []), self.split_filters(u""))
+
+    def test_split_filters_varname_only(self):
+        self.assertEqual((u"varname ", []), self.split_filters(u"varname "))
+
+    def test_split_filters_single_stripping(self):
+        self.assertEqual((u" the-varname", []),
+                         self.split_filters(u" the-varname |  "))
+
+    def test_split_filters_one(self):
+        self.assertEqual((u"the-varname", [u"None"]),
+                         self.split_filters(u"the-varname |None"))
+
+    def test_split_filters_many(self):
+        self.assertEqual((u"the-varname", [u"Empty", u"None"]),
+                         self.split_filters(u"the-varname |Empty|None"))
+        
 
 class T09Parser(_TParserMixin, unittest.TestCase):
 
@@ -1911,6 +1929,7 @@
         self.quote = configmix.config.py_quote
         self.pathstr2path = configmix.config.py_pathstr2path
         self.split_ns = configmix.config._py_split_ns
+        self.split_filters = configmix.config._py_split_filters
 
     def test_split_ns_wrong_type(self):
         self.assertRaises(
@@ -1939,6 +1958,7 @@
             self.quote = configmix.config.fast_quote
             self.pathstr2path = configmix.config.fast_pathstr2path
             self.split_ns = configmix.config._fast_split_ns
+            self.split_filters = configmix.config._fast_split_filters
 
         def test_split_ns_wrong_type(self):
             self.assertRaises(