2 from sunburnt import sunburnt
6 from sunburnt import search
8 from httplib2 import socket
10 class TermVectorOptions(search.Options):
11 def __init__(self, schema, original=None):
15 self.positions = False
17 self.fields = copy.copy(original.fields)
18 self.positions = copy.copy(original.positions)
20 def update(self, positions=False, fields=None):
23 if isinstance(fields, basestring):
25 self.schema.check_fields(fields, {"stored": True})
26 self.fields.update(fields)
27 self.positions = positions
31 if self.positions or self.fields:
34 opts['tv.positions'] = 'true'
36 opts['tv.fl'] = ','.join(sorted(self.fields))
40 class CustomSolrConnection(sunburnt.SolrConnection):
41 def __init__(self, *args, **kw):
42 super(CustomSolrConnection, self).__init__(*args, **kw)
43 self.analysis_url = self.url + "analysis/field/"
45 def analyze(self, params):
46 qs = urllib.urlencode(params)
47 url = "%s?%s" % (self.analysis_url, qs)
48 if len(url) > self.max_length_get_url:
49 warnings.warn("Long query URL encountered - POSTing instead of "
50 "GETting. This query will not be cached at the HTTP layer")
51 url = self.analysis_url
55 headers={"Content-Type": "application/x-www-form-urlencoded"},
58 kwargs = dict(method="GET")
59 r, c = self.request(url, **kwargs)
61 raise sunburnt.SolrError(r, c)
65 # monkey patching sunburnt SolrSearch
66 search.SolrSearch.option_modules += ('term_vectorer',)
69 def __term_vector(self, positions=False, fields=None):
70 newself = self.clone()
71 newself.term_vectorer.update(positions, fields)
73 setattr(search.SolrSearch, 'term_vector', __term_vector)
76 def __patched__init_common_modules(self):
77 __original__init_common_modules(self)
78 self.term_vectorer = TermVectorOptions(self.schema)
79 __original__init_common_modules = search.SolrSearch._init_common_modules
80 setattr(search.SolrSearch, '_init_common_modules', __patched__init_common_modules)
83 class CustomSolrInterface(sunburnt.SolrInterface):
84 # just copied from parent and SolrConnection -> CustomSolrConnection
85 def __init__(self, url, schemadoc=None, http_connection=None, mode='', retry_timeout=-1, max_length_get_url=sunburnt.MAX_LENGTH_GET_URL):
86 self.conn = CustomSolrConnection(url, http_connection, retry_timeout, max_length_get_url)
87 self.schemadoc = schemadoc
89 self.writeable = False
94 except socket.error, e:
95 raise socket.error, "Cannot connect to Solr server, and search indexing is enabled (%s)" % str(e)
98 def _analyze(self, **kwargs):
100 raise TypeError("This Solr instance is only for writing")
102 'analysis_showmatch': True
104 if 'field' in kwargs: args['analysis_fieldname'] = kwargs['field']
105 if 'text' in kwargs: args['analysis_fieldvalue'] = kwargs['text']
106 if 'q' in kwargs: args['q'] = kwargs['q']
107 if 'query' in kwargs: args['q'] = kwargs['q']
109 params = map(lambda (k, v): (k.replace('_', '.'), v), sunburnt.params_from_dict(**args))
111 content = self.conn.analyze(params)
112 doc = etree.fromstring(content)
115 def highlight(self, **kwargs):
116 doc = self._analyze(**kwargs)
117 analyzed = doc.xpath("//lst[@name='index']/arr[last()]/lst[bool/@name='match']")
120 start = int(wrd.xpath("int[@name='start']")[0].text)
121 end = int(wrd.xpath("int[@name='end']")[0].text)
122 matches.add((start, end))
125 return self.substring(kwargs['text'], matches,
126 margins=kwargs.get('margins', 30),
127 mark=kwargs.get('mark', ("<b>", "</b>")))
131 def analyze(self, **kwargs):
132 doc = self._analyze(**kwargs)
133 terms = doc.xpath("//lst[@name='index']/arr[last()]/lst/str[1]")
134 terms = map(lambda n: unicode(n.text), terms)
137 def substring(self, text, matches, margins=30, mark=("<b>", "</b>")):
141 matches_margins = map(lambda (s, e):
143 (max(0, s - margins), min(totlen, e + margins))),
145 (start, end) = matches_margins[0][1]
147 for (m, (s, e)) in matches_margins[1:]:
148 if end < s or start > e:
150 start = min(start, s)
154 snip = text[start:end]
155 matches.sort(lambda a, b: cmp(b[0], a[0]))
157 for (s, e) in matches:
159 snip = snip[:e + off] + mark[1] + snip[e + off:]
160 snip = snip[:s + off] + mark[0] + snip[s + off:]
161 # maybe break on word boundaries