Skip to content

Commit

Permalink
Merge pull request #15 from zopefoundation/issue14
Browse files Browse the repository at this point in the history
Make the C implementation of okascore importable under Python 3.
  • Loading branch information
jamadden committed Apr 21, 2017
2 parents 2cc8446 + eef4e44 commit 8b62393
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Expand Up @@ -4,6 +4,9 @@ matrix:
include:
- os: linux
python: 2.7
- os: linux
python: 2.7
env: PURE_PYTHON=1
- os: linux
python: 3.3
- os: linux
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.rst
Expand Up @@ -11,6 +11,10 @@ Changes
because it was expected that these were usually ``None``.
- Add support for Python 3.6. See `issue 8
<https://github.com/zopefoundation/zope.index/issues/8>`_.
- Make the C implementation of the text index's score function
(``zope.text.index.okascore``) importable under Python 3. Previously
we would fall back to a pure-Python implementation. See `issue 14
<https://github.com/zopefoundation/zope.index/issues/14>`_.

4.2.0 (2016-06-10)
------------------
Expand Down
18 changes: 15 additions & 3 deletions src/zope/index/text/okapiindex.py
Expand Up @@ -189,6 +189,9 @@
regardless of k3's value. So, in a trivial sense, we are incorporating
this measure (and optimizing it by not bothering to multiply by 1 <wink>).
"""
import os
import platform

from zope.index.text.baseindex import BaseIndex
from zope.index.text.baseindex import inverse_doc_frequency
try:
Expand All @@ -197,6 +200,15 @@
score = None
from BTrees.Length import Length


_py_impl = getattr(platform, 'python_implementation', lambda: None)
_is_pypy = _py_impl() == 'PyPy'
PURE_PYTHON = os.environ.get('PURE_PYTHON') or _is_pypy
if PURE_PYTHON:
score = None

PY2 = str is bytes

class OkapiIndex(BaseIndex):

# BM25 free parameters.
Expand Down Expand Up @@ -253,7 +265,7 @@ def _change_doc_len(self, delta):
# D to TF(D,t)*IDF(t) directly, where the product is computed as a float.
# NOTE: This may be overridden below, by a function that computes the
# same thing but with the inner scoring loop in C.
if score is None: #pragma NO COVERAGE
if score is None:
def _search_wids(self, wids):
if not wids:
return []
Expand Down Expand Up @@ -330,7 +342,8 @@ def _search_wids(self, wids):
d2f = self._wordinfo[t] # map {docid -> f(docid, t)}
idf = inverse_doc_frequency(len(d2f), N) # an unscaled float
result = self.family.IF.Bucket()
score(result, d2f.items(), docid2len, idf, meandoclen)
items = d2f.items() if PY2 else list(d2f.items())
score(result, items, docid2len, idf, meandoclen)
L.append((result, 1))
return L

Expand Down Expand Up @@ -358,4 +371,3 @@ def _get_frequencies(self, wids):
for wid in wids:
d[wid] = dget(wid, 0) + 1
return d, len(wids)

35 changes: 30 additions & 5 deletions src/zope/index/text/okascore.c
Expand Up @@ -35,6 +35,10 @@

#include "Python.h"

#if PY_MAJOR_VERSION >= 3
#define PY3K
#endif

#define K1 1.2
#define B 0.75

Expand Down Expand Up @@ -128,11 +132,32 @@ static PyMethodDef okascore_functions[] = {
{NULL}
};

void
initokascore(void)
#ifdef PY3K
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"okascore", /* m_name */
"inner scoring loop for Okapi rank", /* m_doc */
-1, /* m_size */
okascore_functions, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};

PyMODINIT_FUNC
PyInit_okascore(void)
{
PyObject *m;

m = Py_InitModule3("okascore", okascore_functions,
"inner scoring loop for Okapi rank");
m = PyModule_Create(&moduledef);
return m;
}
#else
PyMODINIT_FUNC
initokascore(void)
{
/* XXX: Error checking */
Py_InitModule3("okascore", okascore_functions,
"inner scoring loop for Okapi rank");
}
#endif
14 changes: 10 additions & 4 deletions src/zope/index/text/tests/test_okapiindex.py
Expand Up @@ -150,8 +150,14 @@ def _getBTreesFamily(self):
import BTrees
return BTrees.family64

class TestScore(unittest.TestCase):

def test_score_extension(self):
from zope.index.text.okapiindex import PURE_PYTHON, score
if PURE_PYTHON:
self.assertIsNone(score)
else:
self.assertIsNotNone(score)

def test_suite():
return unittest.TestSuite((
unittest.makeSuite(OkapiIndexTest32),
unittest.makeSuite(OkapiIndexTest64),
))
return unittest.defaultTestLoader.loadTestsFromName(__name__)

0 comments on commit 8b62393

Please sign in to comment.