Skip to content

Commit

Permalink
Split openlibrary/core/vendors/
Browse files Browse the repository at this point in the history
  • Loading branch information
cclauss committed Jul 14, 2022
1 parent be421fe commit 4451ad0
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 137 deletions.
2 changes: 1 addition & 1 deletion openlibrary/core/sponsorships.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from collections import OrderedDict
from infogami.utils.view import public
from openlibrary.core import lending
from openlibrary.core.vendors import get_betterworldbooks_metadata, get_amazon_metadata
from openlibrary.core.vendors import get_amazon_metadata, get_betterworldbooks_metadata
from openlibrary import accounts
from openlibrary.accounts.model import get_internet_archive_id, sendmail
from openlibrary.core.civicrm import (
Expand Down
6 changes: 6 additions & 0 deletions openlibrary/core/vendors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .amazon import ( # noqa: F401
create_edition_from_amazon_metadata,
get_amazon_metadata,
setup,
)
from .betterworldbooks import get_betterworldbooks_metadata # noqa: F401
102 changes: 5 additions & 97 deletions openlibrary/core/vendors.py → openlibrary/core/vendors/amazon.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,13 @@
normalize_isbn,
)

logger = logging.getLogger("openlibrary.vendors")
logger = logging.getLogger("openlibrary.vendors.amazon")

BETTERWORLDBOOKS_BASE_URL = 'https://betterworldbooks.com'
BETTERWORLDBOOKS_API_URL = (
'https://products.betterworldbooks.com/service.aspx?IncludeAmazon=True&ItemId='
)
affiliate_server_url = None
BWB_AFFILIATE_LINK = 'http://www.anrdoezrs.net/links/{}/type/dlg/http://www.betterworldbooks.com/-id-%s'.format(
h.affiliate_id('betterworldbooks')
)
AMAZON_FULL_DATE_RE = re.compile(r'\d{4}-\d\d-\d\d')
ISBD_UNIT_PUNCT = ' : ' # ISBD cataloging title-unit separator punctuation

affiliate_server_url = None


def setup(config):
global affiliate_server_url
Expand Down Expand Up @@ -226,7 +220,7 @@ def serialize(product) -> dict:
'url': "https://www.amazon.com/dp/{}/?tag={}".format(
product.asin, h.affiliate_id('amazon')
),
'source_records': ['amazon:%s' % product.asin],
'source_records': [f'amazon:{product.asin}'],
'isbn_10': [product.asin],
'isbn_13': [isbn_10_to_isbn_13(product.asin)],
'price': price and price.display_amount,
Expand Down Expand Up @@ -400,7 +394,7 @@ def clean_amazon_metadata_for_load(metadata: dict) -> dict:
conforming_metadata['subtitle'] = subtitle
# Record original title if some content has been removed (i.e. parentheses)
if metadata['title'] != conforming_metadata.get('full_title', title):
conforming_metadata['notes'] = "Source title: %s" % metadata['title']
conforming_metadata['notes'] = f"Source title: {metadata['title']}"

return conforming_metadata

Expand Down Expand Up @@ -451,89 +445,3 @@ def cached_get_amazon_metadata(*args, **kwargs):
result = memoized_get_amazon_metadata(*args, **kwargs)
# if no result, then recache / update this controller's cached value
return result or memoized_get_amazon_metadata.update(*args, **kwargs)[0]


@public
def get_betterworldbooks_metadata(isbn: str) -> Optional[dict]:
"""
:param str isbn: Unnormalisied ISBN10 or ISBN13
:return: Metadata for a single BWB book, currently listed on their catalog, or
an error dict.
:rtype: dict or None
"""

isbn = normalize_isbn(isbn)
try:
return _get_betterworldbooks_metadata(isbn)
except Exception:
logger.exception(f"_get_betterworldbooks_metadata({isbn})")
return betterworldbooks_fmt(isbn)


def _get_betterworldbooks_metadata(isbn: str) -> Optional[dict]:
"""Returns price and other metadata (currently minimal)
for a book currently available on betterworldbooks.com
:param str isbn: Normalised ISBN10 or ISBN13
:return: Metadata for a single BWB book currently listed on their catalog,
or an error dict.
:rtype: dict or None
"""

url = BETTERWORLDBOOKS_API_URL + isbn
response = requests.get(url)
if response.status_code != requests.codes.ok:
return {'error': response.text, 'code': response.status_code}
text = response.text
new_qty = re.findall("<TotalNew>([0-9]+)</TotalNew>", text)
new_price = re.findall(r"<LowestNewPrice>\$([0-9.]+)</LowestNewPrice>", text)
used_price = re.findall(r"<LowestUsedPrice>\$([0-9.]+)</LowestUsedPrice>", text)
used_qty = re.findall("<TotalUsed>([0-9]+)</TotalUsed>", text)
market_price = re.findall(
r"<LowestMarketPrice>\$([0-9.]+)</LowestMarketPrice>", text
)
price = qlt = None

if used_qty and used_qty[0] and used_qty[0] != '0':
price = used_price[0] if used_price else ''
qlt = 'used'

if new_qty and new_qty[0] and new_qty[0] != '0':
_price = new_price[0] if new_price else None
if _price and (not price or float(_price) < float(price)):
price = _price
qlt = 'new'

market_price = ('$' + market_price[0]) if market_price else None
return betterworldbooks_fmt(isbn, qlt, price, market_price)


def betterworldbooks_fmt(
isbn: str,
qlt: Optional[str] = None,
price: Optional[str] = None,
market_price: Optional[list[str]] = None,
) -> Optional[dict]:
"""Defines a standard interface for returning bwb price info
:param str isbn:
:param str qlt: Quality of the book, e.g. "new", "used"
:param str price: Price of the book as a decimal str, e.g. "4.28"
:rtype: dict or None
"""
price_fmt = f"${price} ({qlt})" if price and qlt else None
return {
'url': BWB_AFFILIATE_LINK % isbn,
'isbn': isbn,
'market_price': market_price,
'price': price_fmt,
'price_amt': price,
'qlt': qlt,
}


cached_get_betterworldbooks_metadata = cache.memcache_memoize(
_get_betterworldbooks_metadata,
"upstream.code._get_betterworldbooks_metadata",
timeout=dateutil.HALF_DAY_SECS,
)
107 changes: 107 additions & 0 deletions openlibrary/core/vendors/betterworldbooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import logging
import re

import requests
from infogami.utils.view import public

from openlibrary.core import cache
from openlibrary.core import helpers as h
from openlibrary.utils import dateutil
from openlibrary.utils.isbn import normalize_isbn

logger = logging.getLogger("openlibrary.vendors.betterworldbooks")

BETTERWORLDBOOKS_API_URL = (
'https://products.betterworldbooks.com/service.aspx?IncludeAmazon=True&ItemId='
)
BETTERWORLDBOOKS_BASE_URL = 'https://betterworldbooks.com'
BWB_AFFILIATE_LINK = (
f'http://www.anrdoezrs.net/links/{h.affiliate_id("betterworldbooks")}/type/dlg/'
'http://www.betterworldbooks.com/-id-%s'
)


@public
def get_betterworldbooks_metadata(isbn: str) -> dict:
"""
:param str isbn: Unnormalisied ISBN10 or ISBN13
:return: Metadata for a single BWB book, currently listed on their catalog, or
an error dict.
:rtype: dict
"""

isbn = normalize_isbn(isbn)
try:
return _get_betterworldbooks_metadata(isbn)
except Exception:
logger.exception(f"_get_betterworldbooks_metadata({isbn})")
return betterworldbooks_fmt(isbn)


def _get_betterworldbooks_metadata(isbn: str) -> dict:
"""Returns price and other metadata (currently minimal)
for a book currently available on betterworldbooks.com
:param str isbn: Normalised ISBN10 or ISBN13
:return: Metadata for a single BWB book currently listed on their catalog,
or an error dict.
:rtype: dict
"""

url = BETTERWORLDBOOKS_API_URL + isbn
response = requests.get(url)
if response.status_code != requests.codes.ok:
return {'error': response.text, 'code': response.status_code}
text = response.text
new_qty = re.findall("<TotalNew>([0-9]+)</TotalNew>", text)
new_price = re.findall(r"<LowestNewPrice>\$([0-9.]+)</LowestNewPrice>", text)
used_price = re.findall(r"<LowestUsedPrice>\$([0-9.]+)</LowestUsedPrice>", text)
used_qty = re.findall("<TotalUsed>([0-9]+)</TotalUsed>", text)
market_price = re.findall(
r"<LowestMarketPrice>\$([0-9.]+)</LowestMarketPrice>", text
)
price = qlt = None

if used_qty and used_qty[0] and used_qty[0] != '0':
price = used_price[0] if used_price else ''
qlt = 'used'

if new_qty and new_qty[0] and new_qty[0] != '0':
_price = new_price[0] if new_price else None
if _price and (not price or float(_price) < float(price)):
price = _price
qlt = 'new'

market_price = ('$' + market_price[0]) if market_price else None
return betterworldbooks_fmt(isbn, qlt, price, market_price)


def betterworldbooks_fmt(
isbn: str,
qlt: str = None,
price: str = None,
market_price: list[str] = None,
) -> dict:
"""Defines a standard interface for returning bwb price info
:param str isbn:
:param str qlt: Quality of the book, e.g. "new", "used"
:param str price: Price of the book as a decimal str, e.g. "4.28"
:rtype: dict
"""
price_fmt = f"${price} ({qlt})" if price and qlt else None
return {
'url': BWB_AFFILIATE_LINK % isbn,
'isbn': isbn,
'market_price': market_price,
'price': price_fmt,
'price_amt': price,
'qlt': qlt,
}


cached_get_betterworldbooks_metadata = cache.memcache_memoize(
_get_betterworldbooks_metadata,
"upstream.code._get_betterworldbooks_metadata",
timeout=dateutil.HALF_DAY_SECS,
)
6 changes: 3 additions & 3 deletions openlibrary/tests/core/test_vendors.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import pytest
from openlibrary.core.vendors import (
split_amazon_title,
from openlibrary.core.vendors.amazon import (
clean_amazon_metadata_for_load,
betterworldbooks_fmt,
split_amazon_title,
)
from openlibrary.core.vendors.betterworldbooks import betterworldbooks_fmt


def test_clean_amazon_metadata_for_load_non_ISBN():
Expand Down
Loading

0 comments on commit 4451ad0

Please sign in to comment.