This repository has been archived by the owner on Dec 7, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added search support for a Solr backend
re #142
- Loading branch information
Showing
10 changed files
with
273 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,6 @@ endpoint: | |
|
||
[gsa] | ||
url: | ||
|
||
[solr] | ||
url: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import httplib | ||
import itertools | ||
import json | ||
import logging | ||
import urllib | ||
|
||
from .. import exceptions | ||
from .base import HTTPBackend, SearchResult | ||
|
||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class Solr(HTTPBackend): | ||
def __init__(self, url_template): | ||
""" | ||
:param url_template: PEP3101 string that is a URL that will accept a | ||
single argument to its .format() method, which | ||
is the url-encoded search string. | ||
:type url_template: str | ||
""" | ||
self.url_template = url_template | ||
|
||
def search(self, query): | ||
""" | ||
Searches a Solr search backend based on a given query parameter. | ||
:param query: a string representing the search input from a user that | ||
should be passed through to the solr backend | ||
:type query: basestring | ||
:return: a collection of search results as a generator of | ||
SearchResult instances. These results have been filtered | ||
to exclude any repositories that are not being served by | ||
this deployment of this app. | ||
:rtype: generator | ||
""" | ||
quoted_query = urllib.quote(query) | ||
url = self.url_template.format(quoted_query) | ||
_logger.debug('searching with URL: %s' % url) | ||
|
||
body = self._get_data(url) | ||
|
||
results = self._parse(body) | ||
filtered_results = itertools.ifilter(self._filter_result, results) | ||
return itertools.imap(self._format_result, filtered_results) | ||
|
||
def _parse(self, body): | ||
""" | ||
Processes the raw response body into search results | ||
:param body: body from the web response object | ||
:type body: str | ||
:return: generator of SearchResult instances | ||
:rtype: generator | ||
""" | ||
try: | ||
data = json.loads(body) | ||
for item in data['response']['docs']: | ||
yield SearchResult(item['allTitle'], item['ir_description']) | ||
except Exception, e: | ||
_logger.error('could not parse response body: %s' % e) | ||
_logger.exception('could not parse response') | ||
raise exceptions.HTTPError(httplib.BAD_GATEWAY, | ||
'error communicating with backend search service') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[gsa] | ||
url: http://foo/bar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[solr] | ||
url: http://foo/bar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import httplib | ||
import json | ||
|
||
import mock | ||
import unittest2 | ||
|
||
from crane import exceptions | ||
from crane.search import Solr | ||
from crane.search.base import SearchResult | ||
|
||
|
||
class BaseSolrTest(unittest2.TestCase): | ||
def setUp(self): | ||
super(BaseSolrTest, self).setUp() | ||
self.url = 'http://pulpproject.org/search?q={0}' | ||
self.solr = Solr(self.url) | ||
|
||
|
||
class TestInit(BaseSolrTest): | ||
def test_stores_data(self): | ||
self.assertEqual(self.solr.url_template, self.url) | ||
|
||
|
||
class TestSearch(BaseSolrTest): | ||
@mock.patch('crane.search.solr.Solr._get_data') | ||
def test_quotes_query(self, mock_parse): | ||
self.solr.search('hi mom') | ||
|
||
mock_parse.assert_called_once_with(self.url.format('hi%20mom')) | ||
|
||
@mock.patch('crane.search.solr.Solr._filter_result', spec_set=True, return_value=True) | ||
@mock.patch('crane.search.solr.Solr._get_data', spec_set=True) | ||
@mock.patch('crane.search.solr.Solr._parse') | ||
def test_workflow_filter_true(self, mock_parse, mock_get_data, mock_filter): | ||
mock_parse.return_value = [SearchResult('rhel', 'Red Hat Enterprise Linux')] | ||
|
||
ret = self.solr.search('foo') | ||
|
||
mock_get_data.assert_called_once_with('http://pulpproject.org/search?q=foo') | ||
self.assertDictEqual(list(ret)[0], { | ||
'name': 'rhel', | ||
'description': 'Red Hat Enterprise Linux', | ||
'star_count': 5, | ||
'is_trusted': True, | ||
'is_official': True, | ||
}) | ||
|
||
@mock.patch('crane.search.solr.Solr._filter_result', spec_set=True, return_value=False) | ||
@mock.patch('crane.search.solr.Solr._get_data', spec_set=True) | ||
@mock.patch('crane.search.solr.Solr._parse') | ||
def test_workflow_filter_true(self, mock_parse, mock_get_data, mock_filter): | ||
mock_parse.return_value = [SearchResult('rhel', 'Red Hat Enterprise Linux')] | ||
|
||
ret = self.solr.search('foo') | ||
|
||
mock_get_data.assert_called_once_with('http://pulpproject.org/search?q=foo') | ||
self.assertEqual(len(list(ret)), 0) | ||
|
||
|
||
class TestParse(BaseSolrTest): | ||
def test_normal(self): | ||
result = list(self.solr._parse(json.dumps(fake_body))) | ||
|
||
self.assertEqual(len(result), 1) | ||
|
||
self.assertTrue(isinstance(result[0], SearchResult)) | ||
self.assertEqual(result[0].name, 'foo/bar') | ||
self.assertEqual(result[0].description, 'marketing speak yada yada') | ||
|
||
def test_json_exception(self): | ||
""" | ||
when an exception occurs, it should raise an HTTPError | ||
""" | ||
with self.assertRaises(exceptions.HTTPError) as assertion: | ||
list(self.solr._parse('this is not valid json')) | ||
|
||
self.assertEqual(assertion.exception.status_code, httplib.BAD_GATEWAY) | ||
|
||
def test_attribute_exception(self): | ||
""" | ||
when an exception occurs, it should raise an HTTPError | ||
""" | ||
with self.assertRaises(exceptions.HTTPError) as assertion: | ||
list(self.solr._parse(json.dumps({}))) | ||
|
||
self.assertEqual(assertion.exception.status_code, httplib.BAD_GATEWAY) | ||
|
||
|
||
fake_body = { | ||
'response': { | ||
'docs': [ | ||
{ | ||
'allTitle': 'foo/bar', | ||
'ir_description': 'marketing speak yada yada', | ||
} | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters