api.proquest package

Submodules

api.proquest.client module

class api.proquest.client.ProQuestAPIClient(configuration_storage, configuration_factory)[source]

Bases: object

ProQuest API client.

MAX_PAGE_INDEX = 32766
MAX_PAGE_SIZE = 32766
RESPONSE_OPDS_FEED_FIELD = 'opdsFeed'
RESPONSE_STATUS_CODE_FIELD = 'statusCode'
SUCCESS_STATUS_CODE = 200
TOKEN_FIELD = 'token'
create_token(db, affiliation_id)[source]

Create a new JWT bearer token.

Parameters:
  • db (sqlalchemy.orm.session.Session) – Database session

  • affiliation_id (str) – SAML affiliation ID used as a patron’s unique identifier by ProQuest

Returns:

New JWT bearer token

Return type:

str

download_all_feed_pages(db)[source]

Download all available feed pages.

Parameters:

db (sqlalchemy.orm.session.Session) – Database session

Returns:

Iterable list of feed pages in a form of Python dictionaries

Return type:

Iterable[dict]

download_feed_page(db, page, hits_per_page)[source]

Download a single page of a paginated OPDS 2.0 feed.

Parameters:
  • db (sqlalchemy.orm.session.Session) – Database session

  • page (int) – Page index (max = 32,766)

  • hits_per_page (int) – Number of publications on a single page (max = 32,766)

Returns:

Python dictionary object containing the feed’s page

Return type:

dict

get_book(db, token, document_id)[source]

Get a book by it’s ProQuest Doc ID.

NOTE: There are two different cases to consider: - Open-access books: in this case ProQuest API returns the book content. - Adobe DRM copy protected books: in this case ProQuest API returns an ACSM file containing information about downloading a digital publication.

Parameters:
  • db (sqlalchemy.orm.session.Session) – Database session

  • token (str) – JWT bearer token created using ProQuestAPIClient.create_token method

  • document_id (str) – ProQuest Doc ID

Returns:

Book instance containing either an ACS link to the book or the book content

Return type:

ProQuestBook

class api.proquest.client.ProQuestAPIClientConfiguration(configuration_storage, db)[source]

Bases: ConfigurationGrouping

Contains configuration settings of ProQuest API client.

DEFAULT_PAGE_SIZE = 5000
books_catalog_service_url

Contains configuration metadata

Contains configuration metadata

http_proxy_url

Contains configuration metadata

https_proxy_url

Contains configuration metadata

page_size

Contains configuration metadata

partner_auth_token_service_url

Contains configuration metadata

class api.proquest.client.ProQuestAPIClientFactory[source]

Bases: object

Factory used for creating ProQuestAPIClient instances.

create(integration_association)[source]

Create a new instance of ProQuestAPIClientFactory.

Parameters:

integration_association (core.model.configuration.HasExternalIntegration) – Association with an external integration

Returns:

New instance of ProQuestAPIClient

Return type:

ProQuestAPIClient

exception api.proquest.client.ProQuestAPIInvalidJSONResponseError(response)[source]

Bases: BaseError

Raised when the client receives from ProQuest API a response with incorrect JSON document.

property response

Return the response associated with this error.

Returns:

Response associated with this error

Return type:

requests.models.Response

exception api.proquest.client.ProQuestAPIMissingJSONPropertyError(response, missing_property)[source]

Bases: ProQuestAPIInvalidJSONResponseError

Raised when the client receives from ProQuest API a response with incorrect JSON document.

property missing_property

Return the name of the missing property.

Returns:

Name of the missing property

Return type:

str

class api.proquest.client.ProQuestBook(link=None, content=None, content_type=None)[source]

Bases: object

POCO class containing information about a ProQuest book.

property content

Return the book’s content.

Returns:

Book’s content

Return type:

Optional[Union[str, bytes]]

property content_type

Return the content type.

Returns:

Content type

Return type:

Optional[str]

Return the book’s link.

Returns:

Book’s link

Return type:

Optional[str]

api.proquest.credential module

class api.proquest.credential.ProQuestCredentialManager[source]

Bases: object

Manages ProQuest credentials.

lookup_patron_affiliation_id(db, patron, affiliation_attributes=('eduPersonPrincipalName', 'eduPersonScopedAffiliation'))[source]

Look up for patron’s SAML affiliation ID.

Parameters:
  • db (sqlalchemy.orm.session.Session) – Database session

  • patron (core.model.patron.Patron) – Patron object

  • affiliation_attributes (Tuple) – SAML attributes containing an affiliation ID

Returns:

Patron’s SAML affiliation ID (if any)

Return type:

Optional[str]

lookup_proquest_token(db, patron)[source]

Look up for a JWT bearer token used required to use ProQuest API.

Parameters:
Returns:

Credential object containing the existing ProQuest JWT bearer token (if any)

Return type:

Optional[core.model.credential.Credential]

save_proquest_token(db, patron, duration, token)[source]

Save a ProQuest JWT bearer token for later use.

Parameters:
  • db (sqlalchemy.orm.session.Session) – Database session

  • patron (core.model.patron.Patron) – Patron object

  • duration (datetime.timedelta) – How long this token can be valid

  • token (str) – ProQuest JWT bearer token

Returns:

Credential object containing a new ProQuest JWT bearer token

Return type:

Optional[core.model.credential.Credential]

class api.proquest.credential.ProQuestCredentialType(value)[source]

Bases: Enum

Contains an enumeration of different ProQuest credential types

PROQUEST_JWT_TOKEN = 'ProQuest JWT Token'

api.proquest.identifier module

class api.proquest.identifier.ProQuestIdentifierParser[source]

Bases: IdentifierParser

Parser for ProQuest Doc IDs.

PROQUEST_ID_REGEX = re.compile('urn:proquest.com/document-id/(\\d+)')
parse(identifier_string)[source]

Parse a string containing an identifier, extract it and determine its type.

Parameters:

identifier_string (str) – String containing an identifier

Returns:

2-tuple containing the identifier’s type and identifier itself or None if the string contains an incorrect identifier

Return type:

Optional[Tuple[str, str]]

api.proquest.importer module

exception api.proquest.importer.CannotCreateProQuestTokenError(inner_exception)[source]

Bases: BaseError

class api.proquest.importer.ProQuestOPDS2ImportMonitor(client_factory, db, collection, import_class, force_reimport=False, process_removals=False, **import_class_kwargs)[source]

Bases: OPDS2ImportMonitor, HasExternalIntegration

PROTOCOL = 'ProQuest'
run_once(progress_ignore)[source]

Do the actual work of the Monitor.

Parameters:

progress – A TimestampData representing the work done by the Monitor up to this point.

Returns:

A TimestampData representing how you want the Monitor’s entry in the timestamps table to look like from this point on. NOTE: Modifying the incoming progress and returning it is generally a bad idea, because the incoming progress is full of old data. Instead, return a new TimestampData containing data for only the fields you want to set.

class api.proquest.importer.ProQuestOPDS2Importer(db, collection, data_source_name=None, identifier_mapping=None, http_get=None, metadata_client=None, content_modifier=None, map_from_collection=None, mirrors=None)[source]

Bases: OPDS2Importer, BaseCirculationAPI, HasExternalIntegration

Allows to import ProQuest OPDS 2.0 feeds into Circulation Manager.

DESCRIPTION = l'Import books from a ProQuest OPDS 2.0 feed.'
LIBRARY_SETTINGS = [{'key': 'ebook_loan_duration', 'label': l'Default Loan Period (in Days)', 'default': 21, 'type': 'number', 'description': l'Until it hears otherwise from the distributor, this server will assume that any given loan for this library from this collection will last this number of days. This number is usually a negotiated value between the library and the distributor. This only affects estimates—it cannot affect the actual length of loans.'}]
NAME = 'ProQuest'
SETTINGS = [{'key': 'data_source', 'label': l'Data source name', 'description': l'Name of the data source associated with this collection.', 'type': None, 'required': True, 'default': 'ProQuest', 'options': None, 'category': None}, {'key': 'token_expiration_timeout', 'label': l'ProQuest JWT token's expiration timeout', 'description': l'Determines how long in seconds can a ProQuest JWT token be valid.', 'type': 'number', 'required': False, 'default': 3600, 'options': None, 'category': None}, {'key': 'affiliation_attributes', 'label': l'List of SAML attributes containing an affiliation ID', 'description': l'ProQuest integration assumes that the SAML provider is used for authentication. ProQuest JWT bearer tokens required by the most ProQuest API services are created based on the affiliation ID - SAML attribute uniquely identifying the patron.This setting determines what attributes the ProQuest integration will use to look for affiliation IDs. The ProQuest integration will investigate the specified attributes sequentially and will take the first non-empty value.', 'type': 'menu', 'required': False, 'default': ['eduPersonPrincipalName', 'eduPersonScopedAffiliation'], 'options': [{'key': 'uid', 'label': 'uid'}, {'key': 'givenName', 'label': 'givenName'}, {'key': 'surname', 'label': 'surname'}, {'key': 'mail', 'label': 'mail'}, {'key': 'displayName', 'label': 'displayName'}, {'key': 'eduPerson', 'label': 'eduPerson'}, {'key': 'eduPersonAffiliation', 'label': 'eduPersonAffiliation'}, {'key': 'eduPersonNickname', 'label': 'eduPersonNickname'}, {'key': 'eduPersonOrgDN', 'label': 'eduPersonOrgDN'}, {'key': 'eduPersonOrgUnitDN', 'label': 'eduPersonOrgUnitDN'}, {'key': 'eduPersonPrimaryAffiliation', 'label': 'eduPersonPrimaryAffiliation'}, {'key': 'eduPersonPrincipalName', 'label': 'eduPersonPrincipalName'}, {'key': 'eduPersonEntitlement', 'label': 'eduPersonEntitlement'}, {'key': 'eduPersonPrimaryOrgUnitDN', 'label': 'eduPersonPrimaryOrgUnitDN'}, {'key': 'eduPersonScopedAffiliation', 'label': 'eduPersonScopedAffiliation'}, {'key': 'eduPersonTargetedID', 'label': 'eduPersonTargetedID'}, {'key': 'eduPersonAssurance', 'label': 'eduPersonAssurance'}, {'key': 'eduPersonOrcid', 'label': 'eduPersonOrcid'}, {'key': 'eduPersonUniqueId', 'label': 'eduPersonUniqueId'}, {'key': 'eduPersonPrincipalNamePrior', 'label': 'eduPersonPrincipalNamePrior'}, {'key': 'eduOrg', 'label': 'eduOrg'}, {'key': 'eduOrgHomePageURI', 'label': 'eduOrgHomePageURI'}, {'key': 'eduOrgIdentityAuthNPolicyURI', 'label': 'eduOrgIdentityAuthNPolicyURI'}, {'key': 'eduOrgLegalName', 'label': 'eduOrgLegalName'}, {'key': 'eduOrgSuperiorURI', 'label': 'eduOrgSuperiorURI'}, {'key': 'eduOrgWhitePagesURI', 'label': 'eduOrgWhitePagesURI'}], 'category': None}, {'key': 'test_affiliation_id', 'label': l'Test SAML affiliation ID', 'description': l'Test SAML affiliation ID used for testing ProQuest API. Please contact ProQuest before using it.', 'type': None, 'required': False, 'default': None, 'options': None, 'category': None}, {'key': 'default_audience', 'label': l'Default audience', 'description': l'If ProQuest does not specify the target audience for their books, assume the books have this target audience.', 'type': 'select', 'required': False, 'default': '', 'options': [{'key': '', 'label': l'No default audience'}, {'key': 'Adult', 'label': 'Adult'}, {'key': 'Adults Only', 'label': 'Adults Only'}, {'key': 'All Ages', 'label': 'All Ages'}, {'key': 'Children', 'label': 'Children'}, {'key': 'Research', 'label': 'Research'}, {'key': 'Young Adult', 'label': 'Young Adult'}], 'category': None}, {'key': 'books_catalog_service_url', 'label': l'BooksCatalog service's URL', 'description': l'URL of the BooksCatalog service endpoint', 'type': None, 'required': True, 'default': None, 'options': None, 'category': None}, {'key': 'page_size', 'label': l'Feed page's size', 'description': l'This value determines how many publications will be on a single page fetched from the BooksCatalog service.', 'type': 'number', 'required': False, 'default': 5000, 'options': None, 'category': None}, {'key': 'partner_auth_token_service_url', 'label': l'PartnerAuthToken service's URL', 'description': l'URL of the PartnerAuthToken service endpoint.', 'type': None, 'required': True, 'default': None, 'options': None, 'category': None}, {'key': 'download_link_service_url', 'label': l'DownloadLink service's URL', 'description': l'URL of the DownloadLink service endpoint.', 'type': None, 'required': True, 'default': None, 'options': None, 'category': None}, {'key': 'http_proxy_url', 'label': l'HTTP proxy's URL', 'description': l'URL of the proxy handling HTTP traffic.', 'type': None, 'required': False, 'default': None, 'options': None, 'category': None}, {'key': 'https_proxy_url', 'label': l'HTTPS proxy's URL', 'description': l'URL of the proxy handling HTTPS traffic.', 'type': None, 'required': False, 'default': None, 'options': None, 'category': None}]
checkout(patron, pin, licensepool, internal_format)[source]

Checkout the book.

NOTE: This method requires the patron to have either: - an active ProQuest JWT bearer token - or a SAML affiliation ID which will be used to create a new ProQuest JWT bearer token.

external_integration(db)[source]

Return an external integration associated with this object.

Parameters:

db (sqlalchemy.orm.session.Session) – Database session

Returns:

External integration associated with this object

Return type:

core.model.configuration.ExternalIntegration

Extract “next” links from the feed.

Parameters:

feed (Union[str, opds2_ast.OPDS2Feed]) – OPDS 2.0 feed

Returns:

List of “next” links

Return type:

List[str]

fulfill(patron, pin, licensepool, internal_format=None, part=None, fulfill_part_url=None)[source]

Fulfill the loan.

NOTE: This method requires the patron to have either: - an active ProQuest JWT bearer token - or a SAML affiliation ID which will be used to create a new ProQuest JWT bearer token.

internal_format(delivery_mechanism)[source]

Look up the internal format for this delivery mechanism or raise an exception.

Parameters:

delivery_mechanism (LicensePoolDeliveryMechanism) – A LicensePoolDeliveryMechanism

patron_activity(patron, pin)[source]

Return patron’s loans.

TODO This and code from ODLAPI should be refactored into a generic set of rules for any situation where the CM, not the remote API, is responsible for managing loans and holds.

Parameters:
  • patron (Patron) – A Patron object for the patron who wants to check out the book

  • pin (string) – The patron’s alleged password

Returns:

List of patron’s loans

Return type:

List[LoanInfo]

place_hold(patron, pin, licensepool, notification_email_address)[source]

Place a book on hold.

Returns:

A HoldInfo object

release_hold(patron, pin, licensepool)[source]

Release a patron’s hold on a book.

Raises:

CannotReleaseHold – If there is an error communicating with the provider, or the provider refuses to release the hold for any reason.

class api.proquest.importer.ProQuestOPDS2ImporterConfiguration(configuration_storage, db)[source]

Bases: ConfigurationGrouping

Contains configuration settings of ProQuestOPDS2Importer.

DEFAULT_AFFILIATION_ATTRIBUTES = ('eduPersonPrincipalName', 'eduPersonScopedAffiliation')
DEFAULT_TOKEN_EXPIRATION_TIMEOUT_SECONDS = 3600
TEST_AFFILIATION_ID = 1
affiliation_attributes

Contains configuration metadata

data_source_name

Contains configuration metadata

default_audience

Contains configuration metadata

test_affiliation_id

Contains configuration metadata

token_expiration_timeout

Contains configuration metadata

api.proquest.importer.parse_identifier(db, identifier)[source]

Parse the identifier and return an Identifier object representing it.

Parameters:
  • db (sqlalchemy.orm.session.Session) – Database session

  • identifier (str) – String containing the identifier

Returns:

Identifier object

Return type:

core.model.identifier.Identifier

api.proquest.scripts module

class api.proquest.scripts.ProQuestOPDS2ImportScript(*args, **kwargs)[source]

Bases: OPDSImportScript

Runs a ProQuestOPDS2ImportMonitor.

classmethod arg_parser()[source]
run_monitor(collection, force=None)[source]

Run the monitor for the specified collection.

Parameters:
  • collection (core.model.collection.Collection) – Collection object

  • force (bool) – Boolean value indicating whether the import process should be run from scratch

Module contents