Permalink
Browse files

Initial code commit.

  • Loading branch information...
1 parent 7fb135a commit 937f67b65a3178d6a184bbbaf4b48db2366e134a @davedash davedash committed May 17, 2011
Showing with 212 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +6 −0 MANIFEST.in
  3. +128 −0 elasticutils/__init__.py
  4. +2 −0 requirements.txt
  5. +2 −0 settings.py
  6. +27 −0 setup.py
  7. 0 tests/__init__.py
  8. +44 −0 tests/tests.py
View
@@ -0,0 +1,3 @@
+build/
+dist/
+*.egg-info
View
@@ -0,0 +1,6 @@
+include README
+include docs/*.rst
+include docs/Makefile
+include docs/make.bat
+include docs/conf.py
+include settings.py
View
@@ -0,0 +1,128 @@
+import logging
+from functools import wraps
+from threading import local
+
+from pyes import ES
+
+try:
+ from django.conf import settings
+except:
+ import settings
+
+
+_local = local()
+log = logging.getLogger('elasticsearch')
+
+
+def get_es():
+ """Return one es object."""
+ if not hasattr(_local, 'es'):
+ _local.es = ES(settings.ES_HOSTS, default_indexes=[settings.ES_INDEX])
+ return _local.es
+
+
+def es_required(f):
+ @wraps(f)
+ def wrapper(*args, **kw):
+ if settings.ES_DISABLED:
+ log.warning('Search not available for %s.' % f)
+ return
+
+ return f(*args, es=get_es(), **kw)
+ return wrapper
+
+
+def _process_filters(filters):
+ qfilters = []
+ for field, value in filters.iteritems():
+ qfilters.append(dict(term={field: value}))
+
+ if len(qfilters) > 1:
+ return {'and': qfilters}
+ else:
+ return qfilters[0]
+
+
+class Q(object):
+ def __init__(self, query=None, type=None, **filters):
+ if query:
+ self.query = dict(query_string=dict(query=query))
+ else:
+ self.query = dict(match_all={})
+ if filters:
+ filter = _process_filters(filters)
+ self.query = dict(filtered=dict(query=self.query, filter=filter))
+ self.filter_ = None
+ self.results = None
+ self.facets = {}
+ self.objects = []
+ self.type = type
+
+ def filter(self, **filters):
+ self.filter_ = _process_filters(filters)
+ return self
+
+ def facet(self, field, global_=False):
+ facet = dict(terms=dict(field=field))
+ if global_:
+ facet['global'] = True
+ self.facets[field] = facet
+ return self
+
+ def execute(self, page=1, perpage=None):
+ es = get_es()
+ query = dict(query=self.query)
+ if self.filter_:
+ query['filter'] = self.filter_
+ if self.facets:
+ query['facets'] = self.facets
+ if page and perpage:
+ query['size'] = perpage
+ query['from'] = (page - 1) * perpage
+ self.offset = query.get('from', 0)
+ self.results = es.search(query, settings.ES_INDEX, self.type)
+ return self
+
+ def get_results(self, **kw):
+ if not self.results:
+ self.execute(**kw)
+ return self.results['hits']['hits']
+
+ def get_facet(self, key):
+ if not self.results:
+ self.execute()
+
+ if 'facets' not in self.results:
+ return None
+
+ return dict((t['term'], t['count']) for t
+ in self.results['facets'][key]['terms'])
+
+ def results_as(self, meth):
+ self.objects = meth(self.get_results())
+ return self.objects
+
+ def __len__(self):
+ if not self.results:
+ self.execute()
+
+ return self.results['hits']['total']
+
+ def __iter__(self):
+ return iter(self.objects)
+
+ def __getitem__(self, k):
+ """
+ ``__getitem__`` gets the elements specified by doing ``rs[k]`` where
+ ``k`` is a slice (e.g. ``1:2``) or an integer.
+
+ ``objects`` doesn't contain all ``total`` items, just the items for
+ the current page, so we need to adjust ``k``
+ """
+ if isinstance(k, slice) and k.start >= self.offset:
+ k = slice(k.start - self.offset, k.stop - self.offset if k.stop
+ else None)
+ elif isinstance(k, int):
+ k -= self.offset
+
+ return self.objects.__getitem__(k)
View
@@ -0,0 +1,2 @@
+nose
+pyes
View
@@ -0,0 +1,2 @@
+ES_HOSTS = ['127.0.0.1:9201']
+ES_INDEX = 'test'
View
@@ -0,0 +1,27 @@
+import sys
+
+from setuptools import setup, find_packages
+
+setup(
+ name='elasticutils',
+ version='0.1',
+ description='Chainable interface to querying ElasticSearch',
+ long_description=open('README').read(),
+ author='Dave Dash',
+ author_email='dd+pypi@davedash.com',
+ license='BSD',
+ packages=find_packages(),
+ install_requires=['Nose', 'pyes'],
+ include_package_data=True,
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Natural Language :: English',
+ ]
+)
+
View
No changes.
View
@@ -0,0 +1,44 @@
+"""
+Run me using nose!
+
+Also run elastic search on the default ports locally.
+"""
+from unittest import TestCase
+
+from elasticutils import Q, get_es
+from nose.tools import eq_
+
+
+class QueryTest(TestCase):
+
+ @classmethod
+ def setup_class(cls):
+ es = get_es()
+ data1 = dict(id=1, foo='bar', tag='awesome')
+ data2 = dict(id=2, foo='barf', tag='boring')
+ data3 = dict(id=3, foo='car', tag='awesome')
+ data4 = dict(id=4, foo='duck', tag='boat')
+ data5 = dict(id=5, foo='train car', tag='awesome')
+
+ for data in (data1, data2, data3, data4, data5):
+ es.index(data, 'test', 'boondongles', bulk=True)
+ es.refresh()
+
+ def test_q(self):
+ eq_(len(Q('bar', type='boondongles')), 1)
+ eq_(len(Q('car', type='boondongles')), 2)
+
+ def test_q_all(self):
+ eq_(len(Q()), 5)
+
+ def test_filter(self):
+ eq_(len(Q(tag='awesome')), 3)
+
+ def test_facet(self):
+ eq_(Q().facet('tag').get_facet('tag'),
+ dict(awesome=3, boring=1, boat=1))
+
+ @classmethod
+ def teardown_class(cls):
+ es = get_es()
+ es.delete_index('test')

0 comments on commit 937f67b

Please sign in to comment.