diff --git a/mysolr/mysolrha.py b/mysolr/mysolrha.py new file mode 100644 index 0000000..619d12c --- /dev/null +++ b/mysolr/mysolrha.py @@ -0,0 +1,448 @@ +# -*- coding: utf-8 -*- +""" +mysolr.mysolr +~~~~~~~~~~~~~ + +This module impliments the mysolr Solr class with software High Availability methods, providing an easy access to +operate with a group of Solr servers to read or write. + +>>> from mysolrha import Solr +>>> solr = Solr({'http://myserver:8080/solr':{'write':True,read':True},'http://backupserver:80/solr':{'write':True,'read':True}}) +>>> query = {'q':'*:*', 'rows': 0, 'start': 0, 'facet': 'true', + 'facet.field': 'province'} +>>> query_response = solr.search(**query) + +""" +from .response import SolrResponse +from .compat import urljoin, get_wt, compat_args, get_basestring +from xml.sax.saxutils import escape + +import json +import requests + +class Solr(object): + + """Acts as an easy-to-use interface to Solr.""" + def HA(need_write=False): + def wrap(fn): + def exception_wrapper(*args, **kwargs): + #print("Entrando en HADecorator. Lanzando:%s"%str(fn.__name__)) + #print("HADecorator args:%s "%str(dir(*args))) + #print("HADecorator kwargs:%s "%str(kwargs)) + #print("HADecorator need_write Argument: %s"%(need_write)) + try: + out = fn(*args, **kwargs) + return out + except requests.exceptions.ConnectionError as exception: + args[0]._get_working_url(Write=need_write) + #print("Inside the HADecorator new base_url selected %s"%args[0].base_url) + out = fn(*args, **kwargs) + return out + except: + pass + return exception_wrapper + return wrap + + def __init__(self, base_url={'http://localhost:8080/solr/':{'write':True}}, auth=None, + version=None): + """ Initializes a Solr object. Solr URL is a needed parameter. + + :param base_url: Url to solr index dictionary(Write and Read must be true in this version) + :param readers: Dictionary with info about the + :param auth: Described in requests documentation: + http://docs.python-requests.org/en/latest/user/quickstart/#basic-authentication + :param version: first number of the solr version. i.e. 4 if solr + version is 4.0.0 If you set to none this parameter + a request to admin/system will be done at init time + in order to guess the version. + """ + self.solrs = base_url + self.is_writer = False + self.auth = auth + self.base_url = None + self._get_working_url(Write=True) + #print("Base_URL %s"%self.base_url) + self.version = version + if not version: + self.version = self.get_version() + assert(self.version in (1, 3, 4)) + + @HA(need_write=False) + def search(self, resource='select', **kwargs): + """Queries Solr with the given kwargs and returns a SolrResponse + object. + + :param resource: Request dispatcher. 'select' by default. + :param **kwargs: Dictionary containing any of the available Solr query + parameters described in + http://wiki.apache.org/solr/CommonQueryParameters. + 'q' is a mandatory parameter. + + """ + query = build_request(kwargs) + + #try: + http_response = requests.get(urljoin(self.base_url, resource), + params=query, auth=self.auth) + #except: + # _get_working_url(Write=self.is_writer) + # return(search(resource=resource,kwargs)) + + solr_response = SolrResponse(http_response) + return solr_response + + def search_cursor(self, resource='select', **kwargs): + """ """ + query = build_request(kwargs) + cursor = Cursor(urljoin(self.base_url, resource), query, self.auth) + + return cursor + + def async_search(self, queries, size=10, resource='select'): + """ Asynchronous search using async module from requests. + + :param queries: List of queries. Each query is a dictionary containing + any of the available Solr query parameters described in + http://wiki.apache.org/solr/CommonQueryParameters. + 'q' is a mandatory parameter. + :param size: Size of threadpool + :param resource: Request dispatcher. 'select' by default. + """ + try: + import grequests + except: + raise RuntimeError('grequests is required for Solr.async_search.') + + url = urljoin(self.base_url, resource) + queries = map(build_request, queries) + rs = (grequests.get(url, params=query) for query in queries) + responses = grequests.map(rs, size=size) + return [SolrResponse(http_response) for http_response in responses] + + @HA(need_write=True) + def update(self, documents, input_type='json', commit=True): + """Sends an update/add message to add the array of hashes(documents) to + Solr. + + :param documents: A list of solr-compatible documents to index. You + should use unicode strings for text/string fields. + :param input_type: The format which documents are sent. Remember that + json is not supported until version 3. + :param commit: If True, sends a commit message after the operation is + executed. + + """ + assert input_type in ['xml', 'json'] + + if not self.is_writer: + self._get_working_url(Write=True) + + if input_type == 'xml': + http_response = self._post_xml(_get_add_xml(documents)) + else: + http_response = self._post_json(json.dumps(documents)) + if commit: + self.commit() + + return SolrResponse(http_response) + + def delete_by_key(self, identifier, commit=True): + """Sends an ID delete message to Solr. + + :param commit: If True, sends a commit message after the operation is + executed. + + """ + xml = '%s' % (identifier) + http_response = self._post_xml(xml) + if commit: + self.commit() + return SolrResponse(http_response) + + def delete_by_query(self, query, commit=True): + """Sends a query delete message to Solr. + + :param commit: If True, sends a commit message after the operation is + executed. + + """ + xml = '%s' % (query) + http_response = self._post_xml(xml) + if commit: + self.commit() + return SolrResponse(http_response) + + def commit(self, wait_flush=True, + wait_searcher=True, expunge_deletes=False): + """Sends a commit message to Solr. + + :param wait_flush: Block until index changes are flushed to disk + (default is True). + :param wait_searcher: Block until a new searcher is opened and + registered as the main query searcher, making the + changes visible (default is True). + :param expunge_deletes: Merge segments with deletes away (default is + False) + + """ + xml = '