''' interface with whatever connectors the app has ''' import importlib import re from urllib.parse import urlparse from requests import HTTPError from bookwyrm import models from bookwyrm.tasks import app class ConnectorException(HTTPError): ''' when the connector can't do what was asked ''' def search(query, min_confidence=0.1): ''' find books based on arbitary keywords ''' results = [] # Have we got a ISBN ? isbn = re.sub('[\W_]', '', query) maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13 dedup_slug = lambda r: '%s/%s/%s' % (r.title, r.author, r.year) result_index = set() for connector in get_connectors(): result_set = None if maybe_isbn: # Search on ISBN if not connector.isbn_search_url or connector.isbn_search_url == '': result_set = [] else: try: result_set = connector.isbn_search(isbn) except (HTTPError, ConnectorException): pass # if no isbn search or results, we fallback to generic search if result_set == None or result_set == []: try: result_set = connector.search(query, min_confidence=min_confidence) except (HTTPError, ConnectorException): continue result_set = [r for r in result_set \ if dedup_slug(r) not in result_index] # `|=` concats two sets. WE ARE GETTING FANCY HERE result_index |= set(dedup_slug(r) for r in result_set) results.append({ 'connector': connector, 'results': result_set, }) return results def local_search(query, min_confidence=0.1, raw=False): ''' only look at local search results ''' connector = load_connector(models.Connector.objects.get(local=True)) return connector.search(query, min_confidence=min_confidence, raw=raw) def isbn_local_search(query, raw=False): ''' only look at local search results ''' connector = load_connector(models.Connector.objects.get(local=True)) return connector.isbn_search(query, raw=raw) def first_search_result(query, min_confidence=0.1): ''' search until you find a result that fits ''' for connector in get_connectors(): result = connector.search(query, min_confidence=min_confidence) if result: return result[0] return None def get_connectors(): ''' load all connectors ''' for info in models.Connector.objects.order_by('priority').all(): yield load_connector(info) def get_or_create_connector(remote_id): ''' get the connector related to the author's server ''' url = urlparse(remote_id) identifier = url.netloc if not identifier: raise ValueError('Invalid remote id') try: connector_info = models.Connector.objects.get(identifier=identifier) except models.Connector.DoesNotExist: connector_info = models.Connector.objects.create( identifier=identifier, connector_file='bookwyrm_connector', base_url='https://%s' % identifier, books_url='https://%s/book' % identifier, covers_url='https://%s/images/covers' % identifier, search_url='https://%s/search?q=' % identifier, priority=2 ) return load_connector(connector_info) @app.task def load_more_data(connector_id, book_id): ''' background the work of getting all 10,000 editions of LoTR ''' connector_info = models.Connector.objects.get(id=connector_id) connector = load_connector(connector_info) book = models.Book.objects.select_subclasses().get(id=book_id) connector.expand_book_data(book) def load_connector(connector_info): ''' instantiate the connector class ''' connector = importlib.import_module( 'bookwyrm.connectors.%s' % connector_info.connector_file ) return connector.Connector(connector_info.identifier)