#!/usr/bin/env python
# -*- coding: utf-8
# Copyright (c) 2009 Piotr HusiatyĆski.
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
__version__ = '0.1'
__author__ = 'Piotr Husiatynski <phusiatynski@gmail.com>'
# http://code.google.com/p/pysolr/source/browse/trunk/pysolr.py
import pysolr
SOLR_URL = 'http://127.0.0.1:8983/solr/'
def _mapper_int_to_sint(field, value):
data = u'(%s:"%d")' % (field, value)
return Token(data)
def _mapper_bool_to_sbool(field, value):
data = u'(%s:"true")' % field
token = Token(data)
if value is False:
token.negate()
return token
def _mapper_str_to_str(field, value):
data = u'(%s:"%s")' % (field, value)
return Token(data)
def _mapper_date_to_sdate(field, value):
date = value.strftime("SOME_DATE:Z000")
data = u'(%s:"%s")' % (field, date)
return Token(data)
def _mapper_date_to_sdate_lt(field, value):
date = value.strftime("LESS_THAN(to_sorl_format)")
data = u'(%s:"%s")' % (field, date)
return Token(data)
def _mapper_str_list_to_str(field, value):
value = ', '.join(u'"%s"' % v for v in value)
data = u'(%s:%s)' % (field, value)
return Token(data)
FIELD_MAPPER = {
'title': ('title', _mapper_str_to_str),
'description': ('description', _mapper_str_to_str),
'tags': ('tags', _mapper_str_list_to_str),
'video': ('contains_video', _mapper_bool_to_sbool),
'photo': ('contains_photo', _mapper_bool_to_sbool),
'date': ('date', _mapper_date_to_sdate),
'date__lt': ('date', _mapper_date_to_sdate_lt),
}
def to_solr(obj):
if not obj:
return None
if hasattr(obj, 'to_solr'):
return unicode(obj.to_solr())
return unicode(obj)
class Atom(object):
def __init__(self):
self._negate = False
def negate(self):
self._negate = not self._negate
return self
def to_solr(self):
raise NotImplementedError
def __repr__(self):
return u'<%s: %s>' % (type(self).__name__, self.to_solr())
class MultiAtom(Atom):
def __init__(self, *args, **kwds):
super(MultiAtom, self).__init__()
self._data = [Token.build(k, v) for k, v in kwds.iteritems()]
self._data.extend(args)
self._joiner = ''
def join(self):
raise NotImplementedError
def get_tokens(self):
return self._data
def append(self, token):
self._data.append(token)
def extend(self, multi_atom):
self._data.extend(multi_atom)
def to_solr(self):
if not len(self._data):
return u''
if len(self._data) == 1:
if self._negate:
self._data[0].negate()
return self._data[0]
data = self.join(t.to_solr() for t in self._data)
return u'(%s)' % data
class NOT(Atom):
def __init__(self, atom):
super(NOT, self).__init__()
atom.negate()
self._data = atom
self._negate = atom._negate
def to_solr(self):
if isinstance(self._data, AND):
return OR(*[t.negate() for t in self._data.get_tokens()])
elif isinstance(self._data, OR):
return AND(*[t.negate() for t in self._data.get_tokens()])
return self._data.to_solr()
class AND(MultiAtom):
def __init__(self, *args, **kwds):
super(AND, self).__init__(*args, **kwds)
def join(self, tokens):
solr_tokens = map(to_solr, tokens)
return u" AND ".join(t for t in solr_tokens if t)
class OR(MultiAtom):
def __init__(self, *args, **kwds):
super(OR, self).__init__(*args, **kwds)
def join(self, tokens):
solr_tokens = map(to_solr, tokens)
return u" OR ".join(t for t in solr_tokens if t)
class Token(Atom):
@classmethod
def build(klass, field, value):
mapper = FIELD_MAPPER[field]
return mapper[1](mapper[0], value)
def __init__(self, data):
super(Token, self).__init__()
self._data = data
def to_solr(self):
if self._negate:
self._data = u'NOT %s' % self._data
return self._data
class Query(object):
"""
"""
def __init__(self):
self._query = AND()
self._sort = None
self._offset = 0
self._rows = 10
self._limit = None
self._facet = {}
def __repr__(self):
return u"<%s: '%s'>" % (type(self).__name__, self.query)
def filter(self, *args, **kwds):
atoms = []
self._query.extend(args)
for key, value in kwds.iteritems():
atoms.append(Token.build(key, value))
self._query.append(AND(*atoms))
return self
def exclude(self, *args, **kwds):
atoms = []
self._query.extend([NOT(a) for a in args])
for key, value in kwds.iteritems():
atoms.append(Token.build(key, value))
atoms = NOT(AND(*atoms))
self._query.append(atoms)
return self
def sort(self, sort_method):
"Set sort method"
self._sort = sort_method
return self
def facet(self, **kwds):
self.facet.update(kwds)
return self
def __getslice__(self, offset, limit):
self._offset = offset
self._limit = limit
self._rows = self._limit - self._offset
return self
@property
def query(self):
return self._query.to_solr()[1:-1]
def pprint(self):
lines = []
deep = 0
for word in self.query.split():
if word.startswith('('):
deep += 1
if word.endswith(')'):
deep -= 1
if word == 'AND':
lines.append('\n' + '\t' * deep)
lines.append(word)
return ' '.join(lines)
def fetch(self):
if not hasattr(self, '__fetched_data'):
solr = pysolr.Solr(SOLR_URL)
args = {
'fl': 'id',
'start': self._offset,
'rows': self._rows,
'facet': self._facet,
'request_handler': 'user_frontend',
}
self.__fetched_data = solr.search(self.query, **args)
return self.__fetched_data
raise NotImplementedError
def count(self):
# will this work?
return len(self.fetch())
def hits(self):
return self.fetch().hits
def __iter__(self):
"yields ids (as int) from fetched results"
for result in self.fetch():
yield int(result['id'])
def _test():
import datetime
dt = datetime.datetime.now() - datetime.timedelta(days=1)
q = Query()
q.filter(OR(title='Hello world!', description='Hello world!'), date__lt=dt)
q.exclude(photo=True, video=False)
q[:2]
print q.pprint()
print "hits:", q.hits()
print "results:", list(q)
def main():
_test()
if __name__ == '__main__':
main()