Example #1
0
    def from_isbn(cls, isbn):
        """Attempts to fetch an edition by isbn, or if no edition is found,
        attempts to import from amazon
        :param str isbn:
        :rtype: edition|None
        :return: an open library work for this isbn
        """
        isbn = canonical(isbn)

        if len(isbn) not in [10, 13]:
            return None  # consider raising ValueError

        isbn13 = to_isbn_13(isbn)
        isbn10 = isbn_13_to_isbn_10(isbn13)

        # Attempt to fetch book from OL
        for isbn in [isbn13, isbn10]:
            if isbn:
                matches = web.ctx.site.things({
                    "type": "/type/edition", 'isbn_%s' % len(isbn): isbn
                })
                if matches:
                    return web.ctx.site.get(matches[0])

        # Attempt to create from amazon, then fetch from OL
        key = (isbn10 or isbn13) and create_edition_from_amazon_metadata(isbn10 or isbn13)
        if key:
            return web.ctx.site.get(key)
Example #2
0
def _get_amazon_metadata(id_, id_type='isbn', resources=None):
    """Uses the Amazon Product Advertising API ItemLookup operation to locatate a
    specific book by identifier; either 'isbn' or 'asin'.
    https://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemLookup.html

    :param str id_: The item id: isbn (10/13), or Amazon ASIN.
    :param str id_type: 'isbn' or 'asin'.
    :return: A single book item's metadata, or None.
    :rtype: dict or None
    """
    if not affiliate_server_url:
        return None

    if id_type == 'isbn':
        id_ = normalize_isbn(id_)
        if len(id_) == 13 and id_.startswith('978'):
            id_ = isbn_13_to_isbn_10(id_)

    try:
        r = requests.get('http://%s/isbn/%s' % (affiliate_server_url, id_))
        r.raise_for_status()
        return r.json().get('hit') or None
    except requests.exceptions.ConnectionError:
        logger.exception("Affiliate Server unreachable")
    except requests.exceptions.HTTPError:
        logger.exception("Affiliate Server: id {} not found".format(id_))
    return None
Example #3
0
 def get_isbn10(self):
     """Fetches either isbn_10 or isbn_13 from record and returns canonical
     isbn_10
     """
     isbn_10 = self.isbn_10 and canonical(self.isbn_10[0])
     if not isbn_10:
         isbn_13 = self.get_isbn13()
         return isbn_13 and isbn_13_to_isbn_10(isbn_13)
     return isbn_10
Example #4
0
def _get_amazon_metadata(isbn):
    # XXX some esbns may be < 10!
    isbn_10 = isbn if len(isbn) == 10 else isbn_13_to_isbn_10(isbn)
    isbn_13 = isbn if len(isbn) == 13 else isbn_10_to_isbn_13(isbn)
    try:
        if not lending.amazon_api:
            raise Exception
        product = lending.amazon_api.lookup(ItemId=isbn_10)
    except Exception as e:
        return None

    price_fmt, price, qlt = (None, None, None)
    used = product._safe_get_element_text(
        'OfferSummary.LowestUsedPrice.Amount')
    new = product._safe_get_element_text('OfferSummary.LowestNewPrice.Amount')

    # prioritize lower prices and newer, all things being equal
    if used and new:
        price, qlt = (used, 'used') if int(used) < int(new) else (new, 'new')
    # accept whichever is available
    elif used or new:
        price, qlt = (used, 'used') if used else (new, 'new')

    if price:
        price = '{:00,.2f}'.format(int(price) / 100.)
        if qlt:
            price_fmt = "$%s (%s)" % (price, qlt)

    return {
        'url':
        "https://www.amazon.com/dp/%s/?tag=%s" %
        (isbn, h.affiliate_id('amazon')),
        'price':
        price_fmt,
        'price_amt':
        price,
        'qlt':
        qlt,
        'title':
        product.title,
        'authors': [{
            'name': name
        } for name in product.authors],
        'publish_date':
        product.publication_date.strftime('%b %d, %Y'),
        'source_records': ['amazon:%s' % product.asin],
        'number_of_pages':
        product.pages,
        'languages':
        list(product.languages),  # needs to be normalized
        'publishers': [product.publisher],
        'cover':
        product.large_image_url,
        'isbn_10': [isbn_10],
        'isbn_13': [isbn_13]
    }
Example #5
0
def _get_amazon_metadata(isbn=None):
    # XXX @hornc, you should be extending this to work with
    # isbn=, asin=, title=, authors=, etc
    isbn = normalize_isbn(isbn)
    try:
        if not lending.amazon_api:
            raise Exception
        product = lending.amazon_api.lookup(
            ItemId=isbn, IdType="ISBN", SearchIndex="Books")
    except Exception as e:
        return None

    price_fmt, price, qlt = (None, None, None)
    used = product._safe_get_element_text('OfferSummary.LowestUsedPrice.Amount')
    new = product._safe_get_element_text('OfferSummary.LowestNewPrice.Amount')

    # prioritize lower prices and newer, all things being equal
    if used and new:
        price, qlt = (used, 'used') if int(used) < int(new) else (new, 'new')
    # accept whichever is available
    elif used or new:
        price, qlt = (used, 'used') if used else (new, 'new')

    if price:
        price = '{:00,.2f}'.format(int(price)/100.)
        if qlt:
            price_fmt = "$%s (%s)" % (price, qlt)

    data = {
        'url': "https://www.amazon.com/dp/%s/?tag=%s" % (
            isbn, h.affiliate_id('amazon')),
        'price': price_fmt,
        'price_amt': price,
        'qlt': qlt,
        'title': product.title,
        'authors': [{'name': name} for name in product.authors],
        'publish_date': product.publication_date.strftime('%b %d, %Y'),
        'source_records': ['amazon:%s' % product.asin],
        'number_of_pages': product.pages,
        'languages': list(product.languages),  # needs to be normalized
        'cover': product.large_image_url,
    }
    if product.publisher:
        data['publishers'] = [product.publisher]
    if len(isbn) == 10:
        data['isbn_10'] = [isbn]
        data['isbn_13'] = [isbn_10_to_isbn_13(isbn)]
    if len(isbn) == 13:
        data['isbn_13'] = [isbn]
        if isbn.startswith('978'):
            data['isbn_10'] = [isbn_13_to_isbn_10(isbn)]
    return data
Example #6
0
def _get_amazon_metadata(id_, id_type='isbn', resources=None):
    """Uses the Amazon Product Advertising API ItemLookup operation to locatate a
    specific book by identifier; either 'isbn' or 'asin'.
    https://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemLookup.html

    :param str id_: The item id: isbn (10/13), or Amazon ASIN.
    :param str id_type: 'isbn' or 'asin'.
    :return: A single book item's metadata, or None.
    :rtype: dict or None
    """
    if id_type == 'isbn':
        id_ = normalize_isbn(id_)
        if len(id_) == 13 and id_.startswith('978'):
            id_ = isbn_13_to_isbn_10(id_)

    if amazon_api:
        try:
            return amazon_api.get_product(id_, serialize=True, resources=resources)
        except Exception:
            return None
Example #7
0
def _get_amazon_metadata(
    id_: str,
    id_type: str = 'isbn',
    resources=None,
    retries: int = 3,
    sleep_sec: float = 0.1,
) -> Optional[dict]:
    """Uses the Amazon Product Advertising API ItemLookup operation to locatate a
    specific book by identifier; either 'isbn' or 'asin'.
    https://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemLookup.html

    :param str id_: The item id: isbn (10/13), or Amazon ASIN.
    :param str id_type: 'isbn' or 'asin'.
    :param resources: Used for AWSE Commerce Service lookup -- See Amazon docs
    :param int retries: Number of times to query affiliate server before returning None
    :param float sleep_sec: Delay time.sleep(sleep_sec) seconds before each retry
    :return: A single book item's metadata, or None.
    :rtype: dict or None
    """
    if not affiliate_server_url:
        return None

    if id_type == 'isbn':
        id_ = normalize_isbn(id_)
        if len(id_) == 13 and id_.startswith('978'):
            id_ = isbn_13_to_isbn_10(id_)

    try:
        r = requests.get(f'http://{affiliate_server_url}/isbn/{id_}')
        r.raise_for_status()
        if hit := r.json().get('hit'):
            return hit
        if retries <= 1:
            return None
        time.sleep(sleep_sec)  # sleep before recursive call
        return _get_amazon_metadata(id_, id_type, resources, retries - 1,
                                    sleep_sec)
Example #8
0
def _serialize_amazon_product(product):
    """Takes a full Amazon product Advertising API returned AmazonProduct
    with multiple ResponseGroups, and extracts the data we are interested in.

    :param amazon.api.AmazonProduct product:
    :return: Amazon metadata for one product
    :rtype: dict
    """

    price_fmt = price = qlt = None
    used = product._safe_get_element_text(
        'OfferSummary.LowestUsedPrice.Amount')
    new = product._safe_get_element_text('OfferSummary.LowestNewPrice.Amount')

    # prioritize lower prices and newer, all things being equal
    if used and new:
        price, qlt = (used, 'used') if int(used) < int(new) else (new, 'new')
    # accept whichever is available
    elif used or new:
        price, qlt = (used, 'used') if used else (new, 'new')

    if price:
        price = '{:00,.2f}'.format(int(price) / 100.)
        if qlt:
            price_fmt = "$%s (%s)" % (price, qlt)

    data = {
        'url':
        "https://www.amazon.com/dp/%s/?tag=%s" %
        (product.asin, h.affiliate_id('amazon')),
        'price':
        price_fmt,
        'price_amt':
        price,
        'qlt':
        qlt,
        'title':
        product.title,
        'authors': [{
            'name': name
        } for name in product.authors],
        'source_records': ['amazon:%s' % product.asin],
        'number_of_pages':
        product.pages,
        'languages':
        list(product.languages),
        'cover':
        product.large_image_url,
        'product_group':
        product.product_group,
    }
    if product._safe_get_element('OfferSummary') is not None:
        data['offer_summary'] = {
            'total_new':
            int(product._safe_get_element_text('OfferSummary.TotalNew')),
            'total_used':
            int(product._safe_get_element_text('OfferSummary.TotalUsed')),
            'total_collectible':
            int(product._safe_get_element_text(
                'OfferSummary.TotalCollectible')),
        }
        collectible = product._safe_get_element_text(
            'OfferSummary.LowestCollectiblePrice.Amount')
        if new:
            data['offer_summary']['lowest_new'] = int(new)
        if used:
            data['offer_summary']['lowest_used'] = int(used)
        if collectible:
            data['offer_summary']['lowest_collectible'] = int(collectible)
        amazon_offers = product._safe_get_element_text('Offers.TotalOffers')
        if amazon_offers:
            data['offer_summary']['amazon_offers'] = int(amazon_offers)

    if product.publication_date:
        data['publish_date'] = product._safe_get_element_text(
            'ItemAttributes.PublicationDate')
        if re.match(AMAZON_FULL_DATE_RE, data['publish_date']):
            data['publish_date'] = product.publication_date.strftime(
                '%b %d, %Y')

    if product.binding:
        data['physical_format'] = product.binding.lower()
    if product.edition:
        data['edition'] = product.edition
    if product.publisher:
        data['publishers'] = [product.publisher]
    if product.isbn:
        isbn = product.isbn
        if len(isbn) == 10:
            data['isbn_10'] = [isbn]
            data['isbn_13'] = [isbn_10_to_isbn_13(isbn)]
        elif len(isbn) == 13:
            data['isbn_13'] = [isbn]
            if isbn.startswith('978'):
                data['isbn_10'] = [isbn_13_to_isbn_10(isbn)]
    return data
Example #9
0
def _get_amazon_metadata(id_=None, id_type='isbn'):
    # TODO: extend this to work with
    # isbn=, asin=, title=, authors=, etc
    kwargs = {}
    if id_type == 'isbn':
        id_ = normalize_isbn(id_)
        kwargs = {'SearchIndex': 'Books', 'IdType': 'ISBN'}
    kwargs['ItemId'] = id_
    try:
        if not lending.amazon_api:
            raise Exception
        product = lending.amazon_api.lookup(**kwargs)
        # sometimes more than one product can be returned, choose first
        if isinstance(product, list):
            product = product[0]
    except Exception as e:
        return None

    price_fmt, price, qlt = (None, None, None)
    used = product._safe_get_element_text(
        'OfferSummary.LowestUsedPrice.Amount')
    new = product._safe_get_element_text('OfferSummary.LowestNewPrice.Amount')

    # prioritize lower prices and newer, all things being equal
    if used and new:
        price, qlt = (used, 'used') if int(used) < int(new) else (new, 'new')
    # accept whichever is available
    elif used or new:
        price, qlt = (used, 'used') if used else (new, 'new')

    if price:
        price = '{:00,.2f}'.format(int(price) / 100.)
        if qlt:
            price_fmt = "$%s (%s)" % (price, qlt)

    data = {
        'url':
        "https://www.amazon.com/dp/%s/?tag=%s" %
        (id_, h.affiliate_id('amazon')),
        'price':
        price_fmt,
        'price_amt':
        price,
        'qlt':
        qlt,
        'title':
        product.title,
        'authors': [{
            'name': name
        } for name in product.authors],
        'source_records': ['amazon:%s' % product.asin],
        'number_of_pages':
        product.pages,
        'languages':
        list(product.languages),  # needs to be normalized
        'cover':
        product.large_image_url,
        'product_group':
        product.product_group,
    }
    if product.publication_date:
        # TODO: Don't populate false month and day for older products
        data['publish_date'] = product.publication_date.strftime('%b %d, %Y')
    if product.binding:
        data['physical_format'] = product.binding.lower()
    if product.edition:
        data['edition'] = product.edition
    if product.publisher:
        data['publishers'] = [product.publisher]
    if product.isbn:
        isbn = product.isbn
        if len(isbn) == 10:
            data['isbn_10'] = [isbn]
            data['isbn_13'] = [isbn_10_to_isbn_13(isbn)]
        elif len(isbn) == 13:
            data['isbn_13'] = [isbn]
            if isbn.startswith('978'):
                data['isbn_10'] = [isbn_13_to_isbn_10(isbn)]
    return data
Example #10
0
def test_isbn_13_to_isbn_10():
    assert isbn_13_to_isbn_10('978-0-940787-08-7') == '0940787083'
    assert isbn_13_to_isbn_10('9780940787087') == '0940787083'
    assert isbn_13_to_isbn_10('BAD-ISBN') is None