core.model package

Submodules

core.model.admin module

class core.model.admin.Admin(**kwargs)[source]

Bases: Base, HasFullTableCache

add_role(role, library=None)[source]
classmethod authenticate(_db, email, password)[source]

Finds an authenticated Admin by email and password :return: Admin or None

cache_key()[source]
can_see_collection(collection)[source]
credential
email
has_password(password)[source]
id
is_librarian(library)[source]
is_library_manager(library)[source]
is_sitewide_librarian()[source]
is_sitewide_library_manager()[source]
is_system_admin()[source]
password
password_hashed
remove_role(role, library=None)[source]
roles
update_credentials(_db, credential=None)[source]
validate_email(key, address)[source]
classmethod with_password(_db)[source]

Get Admins that have a password.

class core.model.admin.AdminRole(**kwargs)[source]

Bases: Base, HasFullTableCache

LIBRARIAN = 'librarian'
LIBRARY_MANAGER = 'manager'
ROLES = ['system', 'manager-all', 'manager', 'librarian-all', 'librarian']
SITEWIDE_LIBRARIAN = 'librarian-all'
SITEWIDE_LIBRARY_MANAGER = 'manager-all'
SYSTEM_ADMIN = 'system'
admin
admin_id
cache_key()[source]
id
library
library_id
role
to_dict()[source]

core.model.cachedfeed module

class core.model.cachedfeed.CachedFeed(**kwargs)[source]

Bases: Base

CACHE_FOREVER = <object object>
CONTRIBUTOR_TYPE = 'contributor'
CRAWLABLE_TYPE = 'crawlable'
class CachedFeedKeys(feed_type, library, work, lane_id, unique_key, facets_key, pagination_key)

Bases: tuple

facets_key

Alias for field number 5

feed_type

Alias for field number 0

lane_id

Alias for field number 3

library

Alias for field number 1

pagination_key

Alias for field number 6

unique_key

Alias for field number 4

work

Alias for field number 2

GROUPS_TYPE = 'groups'
IGNORE_CACHE = <object object>
NAVIGATION_TYPE = 'navigation'
PAGE_TYPE = 'page'
RECOMMENDATIONS_TYPE = 'recommendations'
RELATED_TYPE = 'related'
SERIES_TYPE = 'series'
content
facets
classmethod feed_type(worklist, facets)[source]

Determine the ‘type’ of the feed.

This may be defined either by worklist or by facets, with facets taking priority.

Returns:

A string that can go into cachedfeeds.type.

classmethod fetch(_db, worklist, facets, pagination, refresher_method, max_age=None, raw=False, **response_kwargs)[source]

Retrieve a cached feed from the database if possible.

Generate it from scratch and store it in the database if necessary.

Return it in the most useful form to the caller.

Parameters:
  • _db – A database connection.

  • worklist – The WorkList associated with this feed.

  • facets – A Facets object that distinguishes this feed from others (for instance, by its sort order).

  • pagination – A Pagination object that explains which page of a larger feed is being cached.

  • refresher_method – A function to call if it turns out the contents of the feed need to be regenerated. This function must take no arguments and return an object that implements __unicode__. (A Unicode string or an OPDSFeed is fine.)

  • max_age – If a cached feed is older than this, it will be considered stale and regenerated. This may be either a number of seconds or a timedelta. If no value is specified, a default value will be calculated based on WorkList and Facets configuration. Setting this value to zero will force a refresh.

  • raw – If this is False (the default), a Response ready to be converted into a Flask Response object will be returned. If this is True, the CachedFeed object itself will be returned. In most non-test situations the default is better.

Returns:

A Response or CachedFeed containing up-to-date content.

id
lane
lane_id
library
library_id
log = <Logger CachedFeed (WARNING)>
classmethod max_cache_age(worklist, type, facets, override=None)[source]

Determine the number of seconds that a cached feed of a given type can remain fresh.

Order of precedence: override, facets, worklist.

Parameters:
  • worklist – A WorkList which may have an opinion on this topic.

  • type – The type of feed being generated.

  • facets – A faceting object that may have an opinion on this topic.

  • override – A specific value passed in by the caller. This may either be a number of seconds or a timedelta.

Returns:

A number of seconds, or CACHE_FOREVER or IGNORE_CACHE

pagination
timestamp
type
unique_key
update(_db, content)[source]
work
work_id
class core.model.cachedfeed.CachedMARCFile(**kwargs)[source]

Bases: Base

A record that a MARC file has been created and cached for a particular lane.

end_time
id
lane
lane_id
library
library_id
representation
representation_id
start_time
exception core.model.cachedfeed.WillNotGenerateExpensiveFeed[source]

Bases: Exception

This exception is raised when a feed is not cached, but it’s too expensive to generate.

core.model.circulationevent module

class core.model.circulationevent.CirculationEvent(**kwargs)[source]

Bases: Base

Changes to a license pool’s circulation status. We log these so we can measure things like the velocity of individual books.

CLIENT_EVENTS = ['open_book']
CM_CHECKIN = 'circulation_manager_check_in'
CM_CHECKOUT = 'circulation_manager_check_out'
CM_FULFILL = 'circulation_manager_fulfill'
CM_HOLD_PLACE = 'circulation_manager_hold_place'
CM_HOLD_RELEASE = 'circulation_manager_hold_release'
DISTRIBUTOR_AVAILABILITY_NOTIFY = 'distributor_availability_notify'
DISTRIBUTOR_CHECKIN = 'distributor_check_in'
DISTRIBUTOR_CHECKOUT = 'distributor_check_out'
DISTRIBUTOR_HOLD_PLACE = 'distributor_hold_place'
DISTRIBUTOR_HOLD_RELEASE = 'distributor_hold_release'
DISTRIBUTOR_LICENSE_ADD = 'distributor_license_add'
DISTRIBUTOR_LICENSE_REMOVE = 'distributor_license_remove'
DISTRIBUTOR_TITLE_ADD = 'distributor_title_add'
DISTRIBUTOR_TITLE_REMOVE = 'distributor_title_remove'
NEW_PATRON = 'circulation_manager_new_patron'
NO_DATE = <object object>
OPEN_BOOK = 'open_book'
SOURCE = 'source'
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S+00:00'
TYPE = 'event'
delta
end
id
library
library_id
license_pool
license_pool_id
location
classmethod log(_db, license_pool, event_name, old_value, new_value, start=None, end=None, library=None, location=None)[source]

Log a CirculationEvent to the database, assuming it hasn’t already been recorded.

new_value
old_value
start
type

core.model.classification module

class core.model.classification.Classification(**kwargs)[source]

Bases: Base

The assignment of a Identifier to a Subject.

TRUSTED_DISTRIBUTOR_WEIGHT = 100.0
property comes_from_license_source

Does this Classification come from a data source that also provided a license for this book?

data_source
data_source_id
property generic_juvenile_audience

Is this a classification that mentions (e.g.) a Children’s audience but is actually a generic ‘Juvenile’ classification?

id
identifier
identifier_id
property quality_as_indicator_of_target_age
property scaled_weight
subject
subject_id
weight
property weight_as_indicator_of_target_age
class core.model.classification.Genre(**kwargs)[source]

Bases: Base, HasFullTableCache

A subject-matter classification for a book. Much, much more general than Classification.

cache_key()[source]
property default_fiction
property genredata
id
lane_genres
classmethod lookup(_db, name, autocreate=False, use_cache=True)[source]
name
property self_and_subgenres
property subgenres
subjects
work_genres
works = ObjectAssociationProxyInstance(AssociationProxy('work_genres', 'work'))
class core.model.classification.Subject(**kwargs)[source]

Bases: Base

A subject under which books might be classified.

AGE_RANGE = 'schema:typicalAgeRange'
ATOS_SCORE = 'ATOS'
AXIS_360_AUDIENCE = 'Axis 360 Audience'
BIC = 'BIC'
BISAC = 'BISAC'
DDC = 'DDC'
FAST = 'FAST'
FREEFORM_AUDIENCE = 'schema:audience'
GRADE_LEVEL = 'Grade level'
GUTENBERG_BOOKSHELF = 'gutenberg:bookshelf'
INTEREST_LEVEL = 'Interest Level'
LCC = 'LCC'
LCSH = 'LCSH'
LEXILE_SCORE = 'Lexile'
NYPL_APPEAL = 'NYPL Appeal'
ORGANIZATION = 'schema:Organization'
OVERDRIVE = 'Overdrive'
PERSON = 'schema:Person'
PLACE = 'schema:Place'
RBDIGITAL = 'RBdigital'
RBDIGITAL_AUDIENCE = 'RBdigital Audience'
SIMPLIFIED_FICTION_STATUS = 'http://librarysimplified.org/terms/fiction/'
SIMPLIFIED_GENRE = 'http://librarysimplified.org/terms/genres/Simplified/'
TAG = 'tag'
TOPIC = 'schema:Topic'
assign_to_genre()[source]

Assign this subject to a genre.

classmethod assign_to_genres(_db, type_restriction=None, force=False, batch_size=1000)[source]

Find subjects that have not been checked yet, assign each a genre/audience/fiction status if possible, and mark each as checked.

Parameters:
  • type_restriction – Only consider subjects of the given type.

  • force – Assign a genre to all subjects not just the ones that have been checked.

  • batch_size – Perform a database commit every time this many subjects have been checked.

audience
by_uri = {'http://id.worldcat.org/fast/': 'FAST', 'http://librarysimplified.org/terms/fiction/': 'http://librarysimplified.org/terms/fiction/', 'http://librarysimplified.org/terms/genres/3M/': 'BISAC', 'http://librarysimplified.org/terms/genres/Overdrive/': 'Overdrive', 'http://librarysimplified.org/terms/genres/Simplified/': 'http://librarysimplified.org/terms/genres/Simplified/', 'http://purl.org/dc/terms/DDC': 'DDC', 'http://purl.org/dc/terms/LCC': 'LCC', 'http://purl.org/dc/terms/LCSH': 'LCSH', 'http://schema.org/audience': 'schema:audience', 'http://schema.org/typicalAgeRange': 'schema:typicalAgeRange', 'http://www.bisg.org/standards/bisac_subject/': 'BISAC', 'http://www.feedbooks.com/categories': 'BISAC'}
checked
classifications
classmethod common_but_not_assigned_to_genre(_db, min_occurances=1000, type_restriction=None)[source]
property describes_format

Does this Subject describe a format of book rather than subject matter, audience, etc? If so, there are limitations on when we believe this Subject actually applies to a given book–it may describe a very different adaptation of the same underlying work. TODO: See note in assign_genres about the hacky way this is used.

fiction
genre
genre_id
id
identifier
k = 'http://www.feedbooks.com/categories'
locked
classmethod lookup(_db, type, identifier, name, autocreate=True)[source]

Turn a subject type and identifier into a Subject.

name
target_age
property target_age_string
type
uri_lookup = {'BISAC': 'http://www.feedbooks.com/categories', 'DDC': 'http://purl.org/dc/terms/DDC', 'FAST': 'http://id.worldcat.org/fast/', 'LCC': 'http://purl.org/dc/terms/LCC', 'LCSH': 'http://purl.org/dc/terms/LCSH', 'Overdrive': 'http://librarysimplified.org/terms/genres/Overdrive/', 'http://librarysimplified.org/terms/fiction/': 'http://librarysimplified.org/terms/fiction/', 'http://librarysimplified.org/terms/genres/Simplified/': 'http://librarysimplified.org/terms/genres/Simplified/', 'schema:audience': 'http://schema.org/audience', 'schema:typicalAgeRange': 'http://schema.org/typicalAgeRange'}
v = 'BISAC'

core.model.collection module

class core.model.collection.Collection(**kwargs)[source]

Bases: Base, HasFullTableCache

A Collection is a set of LicensePools obtained through some mechanism.

AUDIOBOOK_LOAN_DURATION_KEY = 'audio_loan_duration'
DATA_SOURCE_NAME_SETTING = 'data_source'
DEFAULT_AUDIENCE_KEY = 'default_audience'
DEFAULT_RESERVATION_PERIOD_KEY = 'default_reservation_period'
EBOOK_LOAN_DURATION_KEY = 'ebook_loan_duration'
EXTERNAL_ACCOUNT_ID_KEY = 'external_account_id'
GLOBAL_COLLECTION_DATA_SOURCES = ['Enki']
STANDARD_DEFAULT_LOAN_PERIOD = 21
STANDARD_DEFAULT_RESERVATION_PERIOD = 3
classmethod by_datasource(_db, data_source)[source]

Query collections that are associated with the given DataSource.

Collections marked for deletion are not included.

classmethod by_name_and_protocol(_db, name, protocol)[source]

Find or create a Collection with the given name and the given protocol.

This method uses the full-table cache if possible.

Returns:

A 2-tuple (collection, is_new)

classmethod by_protocol(_db, protocol)[source]

Query collections that get their licenses through the given protocol.

Collections marked for deletion are not included.

Parameters:

protocol – Protocol to use. If this is None, all Collections will be returned except those marked for deletion.

cache_key()[source]
catalog
catalog_identifier(identifier)[source]

Inserts an identifier into a catalog

catalog_identifiers(identifiers)[source]

Inserts identifiers into the catalog

children
coverage_records
create_external_integration(protocol)[source]

Create an ExternalIntegration for this Collection.

To be used immediately after creating a new Collection, e.g. in by_name_and_protocol, from_metadata_identifier, and various test methods that create mock Collections.

If an external integration already exists, return it instead of creating another one.

Parameters:

protocol – The protocol known to be in use when getting licenses for this collection.

credentials
customlists
data_source

Find the data source associated with this Collection.

Bibliographic metadata obtained through the collection protocol is recorded as coming from this data source. A LicensePool inserted into this collection will be associated with this data source, unless its bibliographic metadata indicates some other data source.

For most Collections, the integration protocol sets the data source. For collections that use the OPDS import protocol, the data source is a Collection-specific setting.

default_audience

Return the default audience set up for this collection.

Returns:

Default audience

Return type:

Optional[str]

default_loan_period(library, medium='Book')[source]

Until we hear otherwise from the license provider, we assume that someone who borrows a non-open-access item from this collection has it for this number of days.

default_loan_period_setting(library, medium='Book')[source]

Until we hear otherwise from the license provider, we assume that someone who borrows a non-open-access item from this collection has it for this number of days.

default_reservation_period

Until we hear otherwise from the license provider, we assume that someone who puts an item on hold has this many days to check it out before it goes to the next person in line.

delete(search_index=None)[source]

Delete a collection.

Collections can have hundreds of thousands of LicensePools. This deletes a collection gradually in a way that can be confined to the background and survive interruption.

disassociate_library(library)[source]

Disassociate a Library from this Collection and delete any relevant ConfigurationSettings.

explain(include_secrets=False)[source]

Create a series of human-readable strings to explain a collection’s settings.

Parameters:

include_secrets – For security reasons, sensitive settings such as passwords are not displayed by default.

Returns:

A list of explanatory strings.

external_account_id
property external_integration

Find the external integration for this Collection, assuming it already exists.

This is generally a safe assumption since by_name_and_protocol and from_metadata_identifier both create ExternalIntegrations for the Collections they create.

external_integration_id
classmethod from_metadata_identifier(_db, metadata_identifier, data_source=None)[source]

Finds or creates a Collection on the metadata wrangler, based on its unique metadata_identifier.

id
isbns_updated_since(_db, timestamp)[source]

Finds all ISBNs in a collection’s catalog that have been updated since the timestamp but don’t have a Work to show for it. Used in the metadata wrangler.

Returns:

a Query

libraries
licensepools
licensepools_with_works_updated_since(_db, timestamp)[source]

Finds all LicensePools in a collection’s catalog whose Works’ OPDS entries have been updated since the timestamp. Used by the metadata wrangler.

Parameters:
  • _db – A database connection,

  • timestamp – A datetime.timestamp object

Returns:

a Query that yields LicensePools. The Work and Identifier associated with each LicensePool have been pre-loaded, giving the caller all the information necessary to create full OPDS entries for the works.

marked_for_deletion
property metadata_identifier

Identifier based on collection details that uniquely represents this Collection on the metadata wrangler. This identifier is composed of the Collection protocol and account identifier.

A circulation manager provides a Collection’s metadata identifier as part of collection registration. The metadata wrangler creates a corresponding Collection on its side, named after the metadata identifier – regardless of the name of that collection on the circulation manager side.

name
parent
parent_id
property parents
property pools_with_no_delivery_mechanisms

Find all LicensePools in this Collection that have no delivery mechanisms whatsoever.

Returns:

A query object.

primary_identifier_source

Identify if should try to use another identifier than <id>

protocol

What protocol do we need to use to get licenses for this collection?

classmethod restrict_to_ready_deliverable_works(query, collection_ids=None, show_suppressed=False, allow_holds=True)[source]

Restrict a query to show only presentation-ready works present in an appropriate collection which the default client can fulfill.

Note that this assumes the query has an active join against LicensePool and Edition.

Parameters:
  • query – The query to restrict.

  • show_suppressed – Include titles that have nothing but suppressed LicensePools.

  • collection_ids – Only include titles in the given collections.

  • allow_holds – If false, pools with no available copies will be hidden.

timestamps
property unique_account_id

Identifier that uniquely represents this Collection of works

unresolved_catalog(_db, data_source_name, operation)[source]

Returns a query with all identifiers in a Collection’s catalog that have unsuccessfully attempted resolution. This method is used on the metadata wrangler.

Returns:

a sqlalchemy.Query

class core.model.collection.CollectionConfigurationStorage(external_integration_association, collection)[source]

Bases: BaseConfigurationStorage

Serializes and deserializes values as library’s configuration settings

load(db, setting_name)[source]

Loads and returns the library’s configuration setting

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

  • setting_name (string) – Name of the library’s configuration setting

Returns:

Any

save(db, setting_name, value)[source]

Save the value as as a new configuration setting

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

  • setting_name (string) – Name of the library’s configuration setting

  • value (Any) – Value to be saved

class core.model.collection.CollectionIdentifier[source]

Bases: object

collection_id
identifier_id
exception core.model.collection.CollectionMissing[source]

Bases: Exception

An operation was attempted that can only happen within the context of a Collection, but there was no Collection available.

class core.model.collection.HasExternalIntegrationPerCollection[source]

Bases: object

Interface allowing to get access to an external integration

abstract collection_external_integration(collection)[source]

Returns an external integration associated with the collection

Parameters:

collection (core.model.Collection) – Collection

Returns:

External integration associated with the collection

Return type:

core.model.configuration.ExternalIntegration

core.model.complaint module

class core.model.complaint.Complaint(**kwargs)[source]

Bases: Base

A complaint about a LicensePool (or, potentially, something else).

LICENSE_POOL_TYPES = ['cannot-fulfill-loan', 'cannot-issue-loan', 'cannot-render', 'cannot-return']
VALID_TYPES = {'http://librarysimplified.org/terms/problem/bad-cover-image', 'http://librarysimplified.org/terms/problem/bad-description', 'http://librarysimplified.org/terms/problem/cannot-fulfill-loan', 'http://librarysimplified.org/terms/problem/cannot-issue-loan', 'http://librarysimplified.org/terms/problem/cannot-render', 'http://librarysimplified.org/terms/problem/cannot-return', 'http://librarysimplified.org/terms/problem/wrong-age-range', 'http://librarysimplified.org/terms/problem/wrong-audience', 'http://librarysimplified.org/terms/problem/wrong-author', 'http://librarysimplified.org/terms/problem/wrong-genre', 'http://librarysimplified.org/terms/problem/wrong-medium', 'http://librarysimplified.org/terms/problem/wrong-title'}
detail
property for_license_pool
id
license_pool
license_pool_id
classmethod register(license_pool, type, source, detail, resolved=None)[source]

Register a problem detail document as a Complaint against the given LicensePool.

resolve()[source]
resolved
source
timestamp
type

core.model.configuration module

class core.model.configuration.BaseConfigurationStorage[source]

Bases: object

Serializes and deserializes values as configuration settings

abstract load(db, setting_name)[source]

Loads and returns the library’s configuration setting

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

  • setting_name (string) – Name of the configuration setting

Returns:

Any

abstract save(db, setting_name, value)[source]

Save the value as as a new configuration setting

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

  • setting_name (string) – Name of the configuration setting

  • value (Any) – Value to be saved

class core.model.configuration.ConfigurationAttribute(value)[source]

Bases: Enum

Enumeration of configuration setting attributes

CATEGORY = 'category'
DEFAULT = 'default'
DESCRIPTION = 'description'
FORMAT = 'format'
KEY = 'key'
LABEL = 'label'
OPTIONS = 'options'
REQUIRED = 'required'
TYPE = 'type'
class core.model.configuration.ConfigurationAttributeType(value)[source]

Bases: Enum

Enumeration of configuration setting types

LIST = 'list'
MENU = 'menu'
NUMBER = 'number'
SELECT = 'select'
TEXT = 'text'
TEXTAREA = 'textarea'
to_control_type()[source]

Converts the value to a attribute type understandable by circulation-web

Returns:

String representation of attribute’s type

Return type:

string

class core.model.configuration.ConfigurationFactory[source]

Bases: object

Factory creating new instances of ConfigurationGrouping class descendants.

create(configuration_storage, db, configuration_grouping_class)[source]

Create a new instance of ConfigurationGrouping.

Parameters:
  • configuration_storage (ConfigurationStorage) – ConfigurationStorage object

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

  • configuration_grouping_class (Type[ConfigurationGrouping]) – Configuration bucket’s class

Returns:

ConfigurationGrouping instance

Return type:

ConfigurationGrouping

class core.model.configuration.ConfigurationGrouping(configuration_storage, db)[source]

Bases: HasConfigurationSettings

Base class for all classes containing configuration settings

NOTE: Be aware that it’s valid only while a database session is valid and must not be stored between requests

get_setting_value(setting_name)[source]

Returns a settings’value

Parameters:

setting_name (string) – Name of the setting

Returns:

Setting’s value

Return type:

Any

set_setting_value(setting_name, setting_value)[source]

Sets setting’s value

Parameters:
  • setting_name (string) – Name of the setting

  • setting_value (Any) – New value of the setting

classmethod to_settings()[source]

Return a list of settings in a format understandable by circulation-web.

Returns:

list of settings in a format understandable by circulation-web.

Return type:

List[Dict]

classmethod to_settings_generator()[source]

Return a generator object returning settings in a format understandable by circulation-web.

Returns:

list of settings in a format understandable by circulation-web.

Return type:

List[Dict]

class core.model.configuration.ConfigurationMetadata(key, label, description, type, required=False, default=None, options=None, category=None, format=None, index=None)[source]

Bases: object

Contains configuration metadata

property category

Returns the setting’s category

Returns:

Setting’s category

Return type:

string

property default

Returns the setting’s default value

Returns:

Setting’s default value

Return type:

string

property description

Returns the setting’s description

Returns:

Setting’s description

Return type:

string

property format

Returns the setting’s format

Returns:

Setting’s format

Return type:

string

static get_configuration_metadata(cls)[source]

Returns a list of 2-tuples containing information ConfigurationMetadata properties in the specified class

Parameters:

cls (type) – Class

Returns:

List of 2-tuples containing information ConfigurationMetadata properties in the specified class

Return type:

List[Tuple[string, ConfigurationMetadata]]

property index
property key

Returns the setting’s key

Returns:

Setting’s key

Return type:

string

property label

Returns the setting’s label

Returns:

Setting’s label

Return type:

string

property options

Returns the setting’s options (used in the case of select)

Returns:

Setting’s options (used in the case of select)

Return type:

string

property required

Returns the boolean value indicating whether the setting is required or not

Returns:

Boolean value indicating whether the setting is required or not

Return type:

string

static to_bool(metadata)[source]
Return a boolean scalar indicating whether the configuration setting

contains a value that can be treated as True (see ConfigurationSetting.MEANS_YES).

Parameters:

metadata (ConfigurationMetadata) – ConfigurationMetadata object

Returns:

Boolean scalar indicating whether this configuration setting contains a value that can be treated as True

Return type:

bool

to_settings()[source]
property type

Returns the setting’s type

Returns:

Setting’s type

Return type:

string

class core.model.configuration.ConfigurationOption(key, label)[source]

Bases: object

Key-value pair containing information about configuration attribute option

static from_enum(cls)[source]

Convers Enum to a list of options in the SETTINGS format

Parameters:

cls (type) – Enum type

Returns:

List of options in the SETTINGS format

Return type:

List[Dict]

property key

Returns option’s key

Returns:

Option’s key

Return type:

string

property label

Returns option’s label

Returns:

Option’s label

Return type:

string

to_settings()[source]

Returns a dictionary containing option metadata in the SETTINGS format

Returns:

Dictionary containing option metadata in the SETTINGS format

Return type:

Dict

class core.model.configuration.ConfigurationSetting(**kwargs)[source]

Bases: Base, HasFullTableCache

An extra piece of site configuration. A ConfigurationSetting may be associated with an ExternalIntegration, a Library, both, or neither. * The secret used by the circulation manager to sign OAuth bearer tokens is not associated with an ExternalIntegration or with a Library. * The link to a library’s privacy policy is associated with the Library, but not with any particular ExternalIntegration. * The “website ID” for an Overdrive collection is associated with an ExternalIntegration (the Overdrive integration), but not with any particular Library (since multiple libraries might share an Overdrive collection). * The “identifier prefix” used to determine which library a patron is a patron of, is associated with both a Library and an ExternalIntegration.

EXCLUDED_AUDIO_DATA_SOURCES_DEFAULT = []
MEANS_YES = {'t', 'true', 'y', 'yes'}
property bool_value

Turn the value into a boolean if possible. :return: A boolean, or None if there is no value.

cache_key()[source]
classmethod excluded_audio_data_sources(_db)[source]

List the data sources whose audiobooks should not be published in feeds, either because this server can’t fulfill them or the expected client can’t play them. Most methods like this go into Configuration, but this one needs to reference data model objects for its default value.

classmethod explain(_db, include_secrets=False)[source]

Explain all site-wide ConfigurationSettings.

external_integration
external_integration_id
property float_value

Turn the value into an float if possible. :return: A float, or None if there is no value. :raise ValueError: If the value cannot be converted to a float.

classmethod for_externalintegration(key, externalintegration)[source]

Find or create a ConfigurationSetting for the given ExternalIntegration.

classmethod for_library(key, library)[source]

Find or create a ConfigurationSetting for the given Library.

classmethod for_library_and_externalintegration(_db, key, library, external_integration)[source]

Find or create a ConfigurationSetting associated with a Library and an ExternalIntegration.

id
property int_value

Turn the value into an int if possible. :return: An integer, or None if there is no value. :raise ValueError: If the value cannot be converted to an int.

property is_secret

Should the value of this key be treated as secret?

property json_value

Interpret the value as JSON if possible. :return: An object, or None if there is no value. :raise ValueError: If the value cannot be parsed as JSON.

key
library
library_id
classmethod sitewide(_db, key)[source]

Find or create a sitewide ConfigurationSetting.

classmethod sitewide_secret(_db, key)[source]

Find or create a sitewide shared secret. The value of this setting doesn’t matter, only that it’s unique across the site and that it’s always available.

value

What’s the current value of this configuration setting? If not present, the value may be inherited from some other ConfigurationSetting.

value_or_default(default)[source]

Return the value of this setting. If the value is None, set it to default and return that instead.

class core.model.configuration.ConfigurationStorage(integration_association)[source]

Bases: BaseConfigurationStorage

Serializes and deserializes values as configuration settings

load(db, setting_name)[source]

Loads and returns the library’s configuration setting

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

  • setting_name (string) – Name of the library’s configuration setting

Returns:

Any

save(db, setting_name, value)[source]

Save the value as as a new configuration setting

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

  • setting_name (string) – Name of the configuration setting

  • value (Any) – Value to be saved

class core.model.configuration.ExternalIntegration(**kwargs)[source]

Bases: Base, HasFullTableCache

An external integration contains configuration for connecting to a third-party API.

ADMIN_AUTH_GOAL = 'admin_auth'
ADMIN_AUTH_PROTOCOLS = ['Google OAuth']
ADOBE_VENDOR_ID = 'Adobe Vendor ID'
ANALYTICS_GOAL = 'analytics'
AXIS_360 = 'Axis 360'
BIBBLIO = 'Bibblio'
BIBLIOTHECA = 'Bibliotheca'
CATALOG_GOAL = 'ils_catalog'
CDN = 'CDN'
CDN_GOAL = 'CDN'
CLOUDWATCH = 'AWS Cloudwatch Logs'
CONTENT_CAFE = 'Content Cafe'
CONTENT_SERVER = 'Content Server'
CUSTOM_ACCEPT_HEADER = 'custom_accept_header'
DATA_SOURCE_FOR_LICENSE_PROTOCOL = {'Axis 360': 'Axis 360', 'Bibliotheca': 'Bibliotheca', 'Enki': 'Enki', 'FeedBooks': 'FeedBooks', 'Odilo': 'Odilo', 'Overdrive': 'Overdrive', 'RBdigital': 'RBdigital'}
DCTERMS_IDENTIFIER = 'first_dcterms_identifier'
DIRECTORY_IMPORT = 'Directory Import'
DISCOVERY_GOAL = 'discovery'
DRM_GOAL = 'drm'
ELASTICSEARCH = 'Elasticsearch'
ENKI = 'Enki'
FEEDBOOKS = 'FeedBooks'
GOOGLE_ANALYTICS = 'Google Analytics'
GOOGLE_OAUTH = 'Google OAuth'
GUTENBERG = 'Gutenberg'
INTERNAL_LOGGING = 'Internal logging'
LCP = 'LCP'
LICENSE_GOAL = 'licenses'
LICENSE_PROTOCOLS = ['OPDS Import', 'Overdrive', 'Odilo', 'Bibliotheca', 'Axis 360', 'RBdigital', 'Gutenberg', 'Enki', 'Manual intervention']
LOGGING_GOAL = 'logging'
LOGGLY = 'Loggly'
MANUAL = 'Manual intervention'
MARC_EXPORT = 'MARC Export'
METADATA_GOAL = 'metadata'
METADATA_WRANGLER = 'Metadata Wrangler'
MINIO = 'MinIO'
NOVELIST = 'NoveList Select'
NYPL_SHADOWCAT = 'Shadowcat'
NYT = 'New York Times'
ODILO = 'Odilo'
ONE_CLICK = 'RBdigital'
OPDS2_IMPORT = 'OPDS 2.0 Import'
OPDS_FOR_DISTRIBUTORS = 'OPDS for Distributors'
OPDS_IMPORT = 'OPDS Import'
OPDS_REGISTRATION = 'OPDS Registration'
OVERDRIVE = 'Overdrive'
PASSWORD = 'password'
PATRON_AUTH_GOAL = 'patron_auth'
PRIMARY_IDENTIFIER_SOURCE = 'primary_identifier_source'
PROQUEST = 'ProQuest'
RB_DIGITAL = 'RBdigital'
S3 = 'Amazon S3'
SEARCH_GOAL = 'search'
STORAGE_GOAL = 'storage'
URL = 'url'
USERNAME = 'username'
classmethod admin_authentication(_db)[source]
cache_key()[source]
collections
custom_accept_header
explain(library=None, include_secrets=False)[source]

Create a series of human-readable strings to explain an ExternalIntegration’s settings.

Parameters:
  • library – Include additional settings imposed upon this ExternalIntegration by the given Library.

  • include_secrets – For security reasons, sensitive settings such as passwords are not displayed by default.

Returns:

A list of explanatory strings.

classmethod for_collection_and_purpose(_db, collection, purpose)[source]

Find the ExternalIntegration for the collection.

Parameters:
  • collection – Use the mirror configuration for this Collection.

  • purpose – Use the purpose of the mirror configuration.

classmethod for_goal(_db, goal)[source]

Return all external integrations by goal type.

classmethod for_library_and_goal(_db, library, goal)[source]

Find all ExternalIntegrations associated with the given Library and the given goal. :return: A Query.

goal
id
libraries
classmethod lookup(_db, protocol, goal, library=None)[source]
name
classmethod one_for_library_and_goal(_db, library, goal)[source]

Find the ExternalIntegration associated with the given Library and the given goal. :return: An ExternalIntegration, or None. :raise: CannotLoadConfiguration

password
primary_identifier_source
protocol
set_setting(key, value)[source]

Create or update a key-value setting for this ExternalIntegration.

setting(key)[source]

Find or create a ConfigurationSetting on this ExternalIntegration. :param key: Name of the setting. :return: A ConfigurationSetting

settings
url
username
classmethod with_setting_value(_db, protocol, goal, key, value)[source]

Find ExternalIntegrations with the given protocol, goal, and with a particular ConfigurationSetting key/value pair. This is useful in a scenario where an ExternalIntegration is made unique by a ConfigurationSetting, such as ExternalIntegration.URL, rather than by anything in the ExternalIntecation itself.

Parameters:
  • protocol – ExternalIntegrations must have this protocol.

  • goal – ExternalIntegrations must have this goal.

  • key – Look only at ExternalIntegrations with a ConfigurationSetting for this key.

  • value – Find ExternalIntegrations whose ConfigurationSetting has this value.

Returns:

A Query object.

Bases: Base, HasFullTableCache

COLLECTION_MIRROR_SETTINGS = [{'key': 'covers_mirror_integration_id', 'label': l'Covers Mirror', 'description': l'Any cover images encountered while importing content from this collection can be mirrored to a server you control.', 'type': 'select', 'options': [{'key': 'NO_MIRROR', 'label': l'None - Do not mirror cover images'}]}, {'key': 'books_mirror_integration_id', 'label': l'Open Access Books Mirror', 'description': l'Any free books encountered while importing content from this collection can be mirrored to a server you control.', 'type': 'select', 'options': [{'key': 'NO_MIRROR', 'label': l'None - Do not mirror free books'}]}, {'key': 'protected_access_books_mirror_integration_id', 'label': l'Protected Access Books Mirror', 'description': l'Any self-hosted, commercially licensed books encountered while importing content from this collection can be mirrored to a server you control.', 'type': 'select', 'options': [{'key': 'NO_MIRROR', 'label': l'None - Do not mirror self-hosted, commercially licensed books'}]}]
COVERS = 'covers_mirror'
COVERS_KEY = 'covers_mirror_integration_id'
MARC = 'MARC_mirror'
NO_MIRROR_INTEGRATION = 'NO_MIRROR'
OPEN_ACCESS_BOOKS = 'books_mirror'
OPEN_ACCESS_BOOKS_KEY = 'books_mirror_integration_id'
PROTECTED_ACCESS_BOOKS = 'protected_access_books_mirror'
PROTECTED_ACCESS_BOOKS_KEY = 'protected_access_books_mirror_integration_id'
external_integration_id
id
library_id
mirror_description_type = 'self-hosted, commercially licensed books'
mirror_label = 'Protected Access Books Mirror'
mirror_setting = {'description_type': 'self-hosted, commercially licensed books', 'key': 'protected_access_books_mirror_integration_id', 'label': 'Protected Access Books Mirror', 'type': 'protected_access_books_mirror'}
mirror_settings = [{'key': 'covers_mirror_integration_id', 'type': 'covers_mirror', 'description_type': 'cover images', 'label': 'Covers Mirror'}, {'key': 'books_mirror_integration_id', 'type': 'books_mirror', 'description_type': 'free books', 'label': 'Open Access Books Mirror'}, {'key': 'protected_access_books_mirror_integration_id', 'type': 'protected_access_books_mirror', 'description_type': 'self-hosted, commercially licensed books', 'label': 'Protected Access Books Mirror'}]
mirror_type = 'protected_access_books_mirror'
other_integration
other_integration_id
purpose
settings = [{'key': 'covers_mirror_integration_id', 'label': l'Covers Mirror', 'description': l'Any cover images encountered while importing content from this collection can be mirrored to a server you control.', 'type': 'select', 'options': [{'key': 'NO_MIRROR', 'label': l'None - Do not mirror cover images'}]}, {'key': 'books_mirror_integration_id', 'label': l'Open Access Books Mirror', 'description': l'Any free books encountered while importing content from this collection can be mirrored to a server you control.', 'type': 'select', 'options': [{'key': 'NO_MIRROR', 'label': l'None - Do not mirror free books'}]}, {'key': 'protected_access_books_mirror_integration_id', 'label': l'Protected Access Books Mirror', 'description': l'Any self-hosted, commercially licensed books encountered while importing content from this collection can be mirrored to a server you control.', 'type': 'select', 'options': [{'key': 'NO_MIRROR', 'label': l'None - Do not mirror self-hosted, commercially licensed books'}]}]
class core.model.configuration.HasConfigurationSettings[source]

Bases: object

Interface representing class containing ConfigurationMetadata properties

abstract get_setting_value(setting_name)[source]

Returns a settings’value

Parameters:

setting_name (string) – Name of the setting

Returns:

Setting’s value

Return type:

Any

abstract set_setting_value(setting_name, setting_value)[source]

Sets setting’s value

Parameters:
  • setting_name (string) – Name of the setting

  • setting_value (Any) – New value of the setting

class core.model.configuration.HasExternalIntegration[source]

Bases: object

Interface allowing to get access to an external integration

abstract external_integration(db)[source]

Returns 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

core.model.constants module

class core.model.constants.DataSourceConstants[source]

Bases: object

ADOBE = 'Adobe DRM'
AMAZON = 'Amazon'
AXIS_360 = 'Axis 360'
BIBBLIO = 'Bibblio'
BIBLIOTHECA = 'Bibliotheca'
CONTENT_CAFE = 'Content Cafe'
COVER_IMAGE_PRIORITY = ['Library Simplified metadata wrangler', 'Library staff', 'Manual intervention']
DEPRECATED_NAMES = {'3M': 'Bibliotheca', 'OneClick': 'RBdigital'}
ELIB = 'eLiburutegia'
ENKI = 'Enki'
FEEDBOOKS = 'FeedBooks'
GUTENBERG = 'Gutenberg'
GUTENBERG_COVER_GENERATOR = 'Gutenberg Illustrated'
GUTENBERG_EPUB_GENERATOR = 'Project Gutenberg EPUB Generator'
INTERNAL_PROCESSING = 'Library Simplified Internal Process'
LCP = 'LCP'
LIBRARY_STAFF = 'Library staff'
MANUAL = 'Manual intervention'
METADATA_WRANGLER = 'Library Simplified metadata wrangler'
NOVELIST = 'NoveList Select'
NYPL_SHADOWCAT = 'NYPL Shadowcat'
NYT = 'New York Times'
OA_CONTENT_SERVER = 'Library Simplified Open Access Content Server'
OCLC = 'OCLC Classify'
OCLC_LINKED_DATA = 'OCLC Linked Data'
ODILO = 'Odilo'
ONECLICK = 'RBdigital'
OPEN_ACCESS_SOURCE_PRIORITY = ['unglue.it', 'Gutenberg', 'Project Gutenberg EPUB Generator', 'Project GITenberg', 'eLiburutegia', 'FeedBooks', 'Plympton', 'Standard Ebooks']
OPEN_LIBRARY = 'Open Library'
OVERDRIVE = 'Overdrive'
PLYMPTON = 'Plympton'
PRESENTATION_EDITION = 'Presentation edition generator'
PRESENTATION_EDITION_PRIORITY = ['Library staff', 'Manual intervention']
PROJECT_GITENBERG = 'Project GITenberg'
PROQUEST = 'ProQuest'
RB_DIGITAL = 'RBdigital'
STANDARD_EBOOKS = 'Standard Ebooks'
THREEM = 'Bibliotheca'
UNGLUE_IT = 'unglue.it'
VIAF = 'VIAF'
WEB = 'Web'
XID = 'WorldCat xID'
class core.model.constants.EditionConstants[source]

Bases: object

ALL_MEDIUM = <object object>
AUDIO_MEDIUM = 'Audio'
BOOK_MEDIUM = 'Book'
CODEX_FORMAT = 'Codex'
COURSEWARE_MEDIUM = 'Courseware'
ELECTRONIC_FORMAT = 'Electronic'
FULFILLABLE_MEDIA = ['Book', 'Audio']
IMAGE_MEDIUM = 'Image'
KNOWN_MEDIA = ('Book', 'Periodical', 'Audio', 'Music', 'Video', 'Image', 'Courseware')
MUSIC_MEDIUM = 'Music'
PERIODICAL_MEDIUM = 'Periodical'
VIDEO_MEDIUM = 'Video'
additional_type_to_medium = {'http://bib.schema.org/Audiobook': 'Audio', 'http://schema.org/Book': 'Book', 'http://schema.org/Course': 'Courseware', 'http://schema.org/EBook': 'Book', 'http://schema.org/ImageObject': 'Image', 'http://schema.org/MusicRecording': 'Music', 'http://schema.org/PublicationIssue': 'Periodical', 'http://schema.org/VideoObject': 'Video'}
k = 'Courseware'
medium_for_permanent_work_id = {'Audio': 'book', 'Book': 'book', 'Courseware': 'courseware', 'Image': 'image', 'Music': 'music', 'Periodical': 'book', 'Video': 'movie'}
medium_to_additional_type = {'Audio': 'http://bib.schema.org/Audiobook', 'Book': 'http://schema.org/EBook', 'Courseware': 'http://schema.org/Course', 'Image': 'http://schema.org/ImageObject', 'Music': 'http://schema.org/MusicRecording', 'Periodical': 'http://schema.org/PublicationIssue', 'Video': 'http://schema.org/VideoObject'}
v = 'http://schema.org/Course'
class core.model.constants.IdentifierConstants[source]

Bases: object

ASIN = 'ASIN'
AXIS_360_ID = 'Axis 360 ID'
BIBBLIO_CONTENT_ITEM_ID = 'Bibblio Content Item ID'
BIBLIOCOMMONS_ID = 'Bibliocommons ID'
BIBLIOTHECA_ID = 'Bibliotheca ID'
DEPRECATED_NAMES = {'3M ID': 'Bibliotheca ID', 'OneClick ID': 'RBdigital ID'}
DOI = 'DOI'
ELIB_ID = 'eLiburutegia ID'
ENKI_ID = 'Enki ID'
GUTENBERG_ID = 'Gutenberg ID'
GUTENBERG_URN_SCHEME_PREFIX = 'http://www.gutenberg.org/ebooks/'
GUTENBERG_URN_SCHEME_RE = re.compile('http://www.gutenberg.org/ebooks/([0-9]+)')
IDEAL_COVER_ASPECT_RATIO = 0.6666666666666666
IDEAL_IMAGE_HEIGHT = 240
IDEAL_IMAGE_WIDTH = 160
ISBN = 'ISBN'
ISBN_URN_SCHEME_PREFIX = 'urn:isbn:'
LICENSE_PROVIDING_IDENTIFIER_TYPES = ['Bibliotheca ID', 'Overdrive ID', 'Odilo ID', 'Axis 360 ID', 'Gutenberg ID', 'eLiburutegia ID', 'SuDoc Call Number']
NOVELIST_ID = 'NoveList ID'
OCLC_NUMBER = 'OCLC Number'
OCLC_WORK = 'OCLC Work ID'
ODILO_ID = 'Odilo ID'
ONECLICK_ID = 'RBdigital ID'
OPEN_LIBRARY_ID = 'OLID'
OTHER_URN_SCHEME_PREFIX = 'urn:'
OVERDRIVE_ID = 'Overdrive ID'
PROQUEST_ID = 'ProQuest Doc ID'
RB_DIGITAL_ID = 'RBdigital ID'
SUDOC_CALL_NUMBER = 'SuDoc Call Number'
THREEM_ID = 'Bibliotheca ID'
UPC = 'UPC'
URI = 'URI'
URN_SCHEME_PREFIX = 'urn:librarysimplified.org/terms/id/'
class core.model.constants.LinkRelations[source]

Bases: object

ALTERNATE = 'alternate'
AUTHOR = 'http://schema.org/author'
BORROW = 'http://opds-spec.org/acquisition/borrow'
CANONICAL = 'canonical'
CIRCULATION_ALLOWED = ['http://opds-spec.org/acquisition/open-access', 'http://opds-spec.org/acquisition/', 'http://opds-spec.org/acquisition/borrow', 'http://opds-spec.org/acquisition']
DESCRIPTION = 'http://schema.org/description'
DRM_ENCRYPTED_DOWNLOAD = 'http://opds-spec.org/acquisition/'
GENERIC_OPDS_ACQUISITION = 'http://opds-spec.org/acquisition'
ILLUSTRATION = 'http://librarysimplified.org/terms/rel/illustration'
IMAGE = 'http://opds-spec.org/image'
METADATA_ALLOWED = ['canonical', 'http://opds-spec.org/image', 'http://opds-spec.org/image/thumbnail', 'http://librarysimplified.org/terms/rel/illustration', 'http://schema.org/Review', 'http://schema.org/description', 'http://librarysimplified.org/terms/rel/short-description', 'http://schema.org/author', 'alternate', 'http://opds-spec.org/acquisition/sample']
MIRRORED = ['http://opds-spec.org/acquisition/open-access', 'http://opds-spec.org/acquisition', 'http://opds-spec.org/image', 'http://opds-spec.org/image/thumbnail']
OPEN_ACCESS_DOWNLOAD = 'http://opds-spec.org/acquisition/open-access'
REVIEW = 'http://schema.org/Review'
SAMPLE = 'http://opds-spec.org/acquisition/sample'
SELF_HOSTED_BOOKS = ['http://opds-spec.org/acquisition', 'http://opds-spec.org/acquisition/open-access']
SHORT_DESCRIPTION = 'http://librarysimplified.org/terms/rel/short-description'
THUMBNAIL_IMAGE = 'http://opds-spec.org/image/thumbnail'
class core.model.constants.MediaTypes[source]

Bases: object

AMAZON_KF8_MEDIA_TYPE = 'application/x-mobi8-ebook'
APPLICATION_XML_MEDIA_TYPE = 'application/xml'
AUDIOBOOK_MANIFEST_MEDIA_TYPE = 'application/audiobook+json'
AUDIOBOOK_MEDIA_TYPES = ['application/vnd.overdrive.circulation.api+json;profile=audiobook', 'application/audiobook+json', 'application/audiobook+zip']
AUDIOBOOK_PACKAGE_MEDIA_TYPE = 'application/audiobook+zip'
BOOK_MEDIA_TYPES = ['application/epub+zip', 'application/pdf', 'application/x-mobipocket-ebook', 'audio/mpeg', 'application/x-mobi8-ebook']
COMMON_EBOOK_EXTENSIONS = ['.epub', '.pdf', '.audiobook']
COMMON_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif']
EPUB_MEDIA_TYPE = 'application/epub+zip'
FILE_EXTENSIONS = {'application/audiobook+json': 'audiobook-manifest', 'application/audiobook+zip': 'audiobook', 'application/epub+zip': 'epub', 'application/pdf': 'pdf', 'application/vnd.librarysimplified.scorm+zip': 'zip', 'application/x-mobipocket-ebook': 'mobi', 'application/xml': 'xml', 'application/zip': 'zip', 'audio/mpeg': 'mp3', 'image/gif': 'gif', 'image/jpeg': 'jpg', 'image/png': 'png', 'image/svg+xml': 'svg', 'text/html': 'html', 'text/plain': 'txt', 'video/mp4': 'mp4', 'video/x-ms-wmv': 'wmv'}
GENERIC_MEDIA_TYPES = ['application/octet-stream']
GIF_MEDIA_TYPE = 'image/gif'
IMAGE_MEDIA_TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/svg+xml']
JPEG_MEDIA_TYPE = 'image/jpeg'
MARC_MEDIA_TYPE = 'application/marc'
MEDIA_TYPE_FOR_EXTENSION = {'.audiobook': 'application/audiobook+zip', '.audiobook-manifest': 'application/audiobook+json', '.epub': 'application/epub+zip', '.gif': 'image/gif', '.htm': 'text/html', '.html': 'text/html', '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', '.mobi': 'application/x-mobipocket-ebook', '.mp3': 'audio/mpeg', '.mp4': 'video/mp4', '.pdf': 'application/pdf', '.png': 'image/png', '.svg': 'image/svg+xml', '.txt': 'text/plain', '.wmv': 'video/x-ms-wmv', '.xml': 'application/xml', '.zip': 'application/zip'}
MOBI_MEDIA_TYPE = 'application/x-mobipocket-ebook'
MP3_MEDIA_TYPE = 'audio/mpeg'
MP4_MEDIA_TYPE = 'video/mp4'
OCTET_STREAM_MEDIA_TYPE = 'application/octet-stream'
OVERDRIVE_AUDIOBOOK_MANIFEST_MEDIA_TYPE = 'application/vnd.overdrive.circulation.api+json;profile=audiobook'
OVERDRIVE_EBOOK_MANIFEST_MEDIA_TYPE = 'application/vnd.overdrive.circulation.api+json;profile=ebook'
OVERDRIVE_MANIFEST_MEDIA_TYPE = 'application/vnd.overdrive.circulation.api+json'
PDF_MEDIA_TYPE = 'application/pdf'
PNG_MEDIA_TYPE = 'image/png'
SCORM_MEDIA_TYPE = 'application/vnd.librarysimplified.scorm+zip'
SUPPORTED_BOOK_MEDIA_TYPES = ['application/epub+zip', 'application/pdf', 'application/audiobook+json']
SVG_MEDIA_TYPE = 'image/svg+xml'
TEXT_HTML_MEDIA_TYPE = 'text/html'
TEXT_PLAIN = 'text/plain'
TEXT_XML_MEDIA_TYPE = 'text/xml'
WMV_MEDIA_TYPE = 'video/x-ms-wmv'
ZIP_MEDIA_TYPE = 'application/zip'
extension = '.zip'
media_type = 'application/vnd.librarysimplified.scorm+zip'

core.model.contributor module

class core.model.contributor.Contribution(**kwargs)[source]

Bases: Base

A contribution made by a Contributor to a Edition.

contributor
contributor_id
edition
edition_id
id
role
class core.model.contributor.Contributor(**kwargs)[source]

Bases: Base

Someone (usually human) who contributes to books.

ACTOR_ROLE = 'Actor'
ADAPTER_ROLE = 'Adapter'
AFTERWORD_ROLE = 'Afterword Author'
ALPHABETIC = re.compile('[a-zA-z]')
ARTIST_ROLE = 'Artist'
ASSOCIATED_ROLE = 'Associated name'
AUTHOR_ROLE = 'Author'
AUTHOR_ROLES = {'Author', 'Primary Author'}
AUTHOR_SUBSTITUTE_ROLES = ['Editor', 'Compiler', 'Composer', 'Director', 'Contributor', 'Translator', 'Adapter', 'Photographer', 'Artist', 'Lyricist', 'Copyright holder']
BIRTH_DATE = 'birthDate'
COLLABORATOR_ROLE = 'Collaborator'
COLOPHON_ROLE = 'Colophon Author'
COLORIST_ROLE = 'Colorist'
COMPILER_ROLE = 'Compiler'
COMPOSER_ROLE = 'Composer'
CONTRIBUTOR_ROLE = 'Contributor'
COPYRIGHT_HOLDER_ROLE = 'Copyright holder'
DATE_RES = [re.compile('\\(?[0-9?]+-\\)?'), re.compile('\\(?[0-9]+st cent\\)?'), re.compile('\\(?[0-9]+nd cent\\)?'), re.compile('\\(?[0-9]+th cent\\)?'), re.compile('\\(?\x08circa\\)?')]
DEATH_DATE = 'deathDate'
DESIGNER_ROLE = 'Designer'
DIRECTOR_ROLE = 'Director'
EDITOR_ROLE = 'Editor'
ENGINEER_ROLE = 'Engineer'
EXECUTIVE_PRODUCER_ROLE = 'Executive Producer'
FOREWORD_ROLE = 'Foreword Author'
ILLUSTRATOR_ROLE = 'Illustrator'
INKER_ROLE = 'Inker'
INTRODUCTION_ROLE = 'Introduction Author'
LETTERER_ROLE = 'Letterer'
LYRICIST_ROLE = 'Lyricist'
MARC_ROLE_CODES = {'Actor': 'act', 'Adapter': 'adp', 'Afterword Author': 'aft', 'Artist': 'art', 'Associated name': 'asn', 'Author': 'aut', 'Collaborator': 'ctb', 'Colophon Author': 'aft', 'Colorist': 'clr', 'Compiler': 'com', 'Composer': 'cmp', 'Contributor': 'ctb', 'Copyright holder': 'cph', 'Designer': 'dsr', 'Director': 'drt', 'Editor': 'edt', 'Engineer': 'eng', 'Executive Producer': 'pro', 'Foreword Author': 'wpr', 'Illustrator': 'ill', 'Inker': 'ctb', 'Introduction Author': 'win', 'Letterer': 'ctb', 'Lyricist': 'lyr', 'Musician': 'mus', 'Narrator': 'nrt', 'Penciler': 'ctb', 'Performer': 'prf', 'Photographer': 'pht', 'Primary Author': 'aut', 'Producer': 'pro', 'Transcriber': 'trc', 'Translator': 'trl', 'Unknown': 'asn'}
MUSICIAN_ROLE = 'Musician'
NARRATOR_ROLE = 'Narrator'
NUMBERS = re.compile('[0-9]')
PARENTHETICAL = re.compile('\\([^)]*\\)')
PENCILER_ROLE = 'Penciler'
PERFORMER_ROLE = 'Performer'
PERFORMER_ROLES = ['Actor', 'Performer', 'Narrator', 'Musician']
PHOTOGRAPHER_ROLE = 'Photographer'
PRIMARY_AUTHOR_ROLE = 'Primary Author'
PRODUCER_ROLE = 'Producer'
TRANSCRIBER_ROLE = 'Transcriber'
TRANSLATOR_ROLE = 'Translator'
UNKNOWN_ROLE = 'Unknown'
aliases
classmethod author_contributor_tiers()[source]
biography
contributions
default_names(default_display_name=None)[source]

Attempt to derive a family name (“Twain”) and a display name (“Mark Twain”) from a catalog name (“Twain, Mark”).

This is full of pitfalls, which is why we prefer to use data from VIAF. But when there is no data from VIAF, the output of this algorithm is better than the input in pretty much every case.

display_name
extra
family_name
id
lc
classmethod lookup(_db, sort_name=None, viaf=None, lc=None, aliases=None, extra=None, create_new=True, name=None)[source]

Find or create a record (or list of records) for the given Contributor. :return: A tuple of found Contributor (or None), and a boolean flag indicating if new Contributor database object has beed created.

merge_into(destination)[source]

Two Contributor records should be the same.

Merge this one into the other one.

For now, this should only be used when the exact same record comes in through two sources. It should not be used when two Contributors turn out to represent different names for the same human being, e.g. married names or (especially) pen names. Just because we haven’t thought that situation through well enough.

sort_name
viaf
wikipedia_name

core.model.coverage module

class core.model.coverage.BaseCoverageRecord[source]

Bases: object

Contains useful constants used by both CoverageRecord and WorkCoverageRecord.

ALL_STATUSES = ['registered', 'success', 'transient failure', 'persistent failure']
DEFAULT_COUNT_AS_COVERED = ['success', 'persistent failure']
PERSISTENT_FAILURE = 'persistent failure'
PREVIOUSLY_ATTEMPTED = ['success', 'transient failure', 'persistent failure']
REGISTERED = 'registered'
SUCCESS = 'success'
TRANSIENT_FAILURE = 'transient failure'
classmethod not_covered(count_as_covered=None, count_as_not_covered_if_covered_before=None)[source]

Filter a query to find only items without coverage records.

Parameters:
  • count_as_covered – A list of constants that indicate types of coverage records that should count as ‘coverage’ for purposes of this query.

  • count_as_not_covered_if_covered_before – If a coverage record exists, but is older than the given date, do not count it as covered.

Returns:

A clause that can be passed in to Query.filter().

status_enum = Enum('success', 'transient failure', 'persistent failure', 'registered', name='coverage_status')
class core.model.coverage.CoverageRecord(**kwargs)[source]

Bases: Base, BaseCoverageRecord

A record of a Identifier being used as input into some process.

CHOOSE_COVER_OPERATION = 'choose-cover'
IMPORT_OPERATION = 'import'
METADATA_UPLOAD_OPERATION = 'metadata-upload'
REAP_OPERATION = 'reap'
REPAIR_SORT_NAME_OPERATION = 'repair-sort-name'
RESOLVE_IDENTIFIER_OPERATION = 'resolve-identifier'
SET_EDITION_METADATA_OPERATION = 'set-edition-metadata'
classmethod add_for(edition, data_source, operation=None, timestamp=None, status='success', collection=None)[source]
classmethod bulk_add(identifiers, data_source, operation=None, timestamp=None, status='success', exception=None, collection=None, force=False)[source]

Create and update CoverageRecords so that every Identifier in identifiers has an identical record.

collection
collection_id
data_source
data_source_id
exception
human_readable(template)[source]

Interpolate data into a human-readable template.

id
identifier
identifier_id
classmethod lookup(edition_or_identifier, data_source, operation=None, collection=None)[source]
operation
status
timestamp
class core.model.coverage.Timestamp(**kwargs)[source]

Bases: Base

Tracks the activities of Monitors, CoverageProviders, and general scripts.

CLEAR_VALUE = <object object>
COVERAGE_PROVIDER_TYPE = 'coverage_provider'
MONITOR_TYPE = 'monitor'
SCRIPT_TYPE = 'script'
achievements
collection
collection_id
counter
exception
finish
id
classmethod lookup(_db, service, service_type, collection)[source]
service
service_type
service_type_enum = Enum('monitor', 'coverage_provider', 'script', name='service_type')
classmethod stamp(_db, service, service_type, collection=None, start=None, finish=None, achievements=None, counter=None, exception=None)[source]

Set a Timestamp, creating it if necessary.

This should be called once a service has stopped running, whether or not it was able to complete its task.

Parameters:
  • _db – A database connection.

  • service – The name of the service associated with the Timestamp.

  • service_type – The type of the service associated with the Timestamp. This must be one of the values in Timestmap.service_type_enum.

  • collection – The Collection, if any, on which this service just ran.

  • start – The time at which this service started running. Defaults to now.

  • finish – The time at which this service stopped running. Defaults to now.

  • achievements – A human-readable description of what the service did during its run.

  • counter – An integer item of state that the service may use to track its progress between runs.

  • exception – A stack trace for the exception, if any, which stopped the service from running.

start
to_data()[source]

Convert this Timestamp to an unfinalized TimestampData.

update(start=None, finish=None, achievements=None, counter=None, exception=None)[source]

Use a single method to update all the fields that aren’t used to identify a Timestamp.

classmethod value(_db, service, service_type, collection)[source]

Return the current value of the given Timestamp, if it exists.

class core.model.coverage.WorkCoverageRecord(**kwargs)[source]

Bases: Base, BaseCoverageRecord

A record of some operation that was performed on a Work. This is similar to CoverageRecord, which operates on Identifiers, but since Work identifiers have no meaning outside of the database, we presume that all the operations involve internal work only, and as such there is no data_source_id.

CHOOSE_EDITION_OPERATION = 'choose-edition'
CLASSIFY_OPERATION = 'classify'
GENERATE_MARC_OPERATION = 'generate-marc'
GENERATE_OPDS_OPERATION = 'generate-opds'
QUALITY_OPERATION = 'quality'
SUMMARY_OPERATION = 'summary'
UPDATE_SEARCH_INDEX_OPERATION = 'update-search-index'
classmethod add_for(work, operation, timestamp=None, status='success')[source]
classmethod bulk_add(works, operation, timestamp=None, status='success', exception=None)[source]

Create and update WorkCoverageRecords so that every Work in works has an identical record.

exception
id
classmethod lookup(work, operation)[source]
operation
status
timestamp
work
work_id

core.model.credential module

class core.model.credential.Credential(**kwargs)[source]

Bases: Base

A place to store credentials for external services.

IDENTIFIER_FROM_REMOTE_SERVICE = 'Identifier Received From Remote Service'
IDENTIFIER_TO_REMOTE_SERVICE = 'Identifier Sent To Remote Service'
collection
collection_id
credential
data_source
data_source_id
deregister_drm_device_identifier(device_identifier)[source]
drm_device_identifiers
expires
id
classmethod lookup(_db, data_source, token_type, patron, refresher_method, allow_persistent_token=False, allow_empty_token=False, collection=None, force_refresh=False)[source]
classmethod lookup_and_expire_temporary_token(_db, data_source, type, token)[source]

Look up a temporary token and expire it immediately.

classmethod lookup_by_patron(_db, data_source_name, token_type, patron, allow_persistent_token=False, auto_create_datasource=True)[source]

Look up a unique token. Lookup will fail on expired tokens. Unless persistent tokens are specifically allowed, lookup will fail on persistent tokens.

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

  • data_source_name (str) – Name of the data source

  • token_type (str) – Token type

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

  • allow_persistent_token (bool) – Boolean value indicating whether persistent tokens are allowed or not

  • auto_create_datasource (bool) – Boolean value indicating whether a data source should be created in the case it doesn’t

classmethod lookup_by_token(_db, data_source, token_type, token, allow_persistent_token=False)[source]

Look up a unique token. Lookup will fail on expired tokens. Unless persistent tokens are specifically allowed, lookup will fail on persistent tokens.

patron
patron_id
classmethod persistent_token_create(_db, data_source, type, patron, token_string=None)[source]

Create or retrieve a persistent token for the given data_source/type/patron.

register_drm_device_identifier(device_identifier)[source]
classmethod temporary_token_create(_db, data_source, token_type, patron, duration, value=None)[source]

Create a temporary token for the given data_source/type/patron. The token will be good for the specified duration.

type
class core.model.credential.DRMDeviceIdentifier(**kwargs)[source]

Bases: Base

A device identifier for a particular DRM scheme. Associated with a Credential, most commonly a patron’s “Identifier for Adobe account ID purposes” Credential.

credential
credential_id
device_identifier
id
class core.model.credential.DelegatedPatronIdentifier(**kwargs)[source]

Bases: Base

This library is in charge of coming up with, and storing, identifiers associated with the patrons of some other library. e.g. NYPL provides Adobe IDs for patrons of all libraries that use the SimplyE app. Those identifiers are stored here.

ADOBE_ACCOUNT_ID = 'Adobe Account ID'
delegated_identifier
classmethod get_one_or_create(_db, library_uri, patron_identifier, identifier_type, create_function)[source]

Look up the delegated identifier for the given patron. If there is none, create one.

Parameters:
  • library_uri – A URI identifying the patron’s library.

  • patron_identifier – An identifier used by that library to distinguish between this patron and others. This should be an identifier created solely for the purpose of identifying the patron with _this_ library, and not (e.g.) the patron’s barcode.

  • identifier_type – The type of the delegated identifier to look up. (probably ADOBE_ACCOUNT_ID)

  • create_function – If this patron does not have a DelegatedPatronIdentifier, one will be created, and this function will be called to determine the value of DelegatedPatronIdentifier.delegated_identifier.

Returns:

A 2-tuple (DelegatedPatronIdentifier, is_new)

id
library_uri
patron_identifier
type

core.model.customlist module

class core.model.customlist.CustomList(**kwargs)[source]

Bases: Base

A custom grouping of Editions.

STAFF_PICKS_NAME = 'Staff Picks'
add_entry(work_or_edition, annotation=None, first_appearance=None, featured=None, update_external_index=True)[source]

Add a Work or Edition to a CustomList.

Parameters:
  • work_or_edition – A Work or an Edition. If this is a Work, that specific Work will be added to the CustomList. If this is an Edition, that Edition will be added to the CustomList, assuming there’s no equivalent Edition already in the list.

  • update_external_index – When a Work is added to a list, its external index needs to be updated. The only reason not to do this is when the current database session already contains a new WorkCoverageRecord for this purpose (e.g. because the Work was just created) and creating another one would violate the workcoveragerecords table’s unique constraint. TODO: This is probably no longer be necessary since we no longer update the external index in real time.

classmethod all_from_data_sources(_db, data_sources)[source]

All custom lists from the given data sources.

collections
created
data_source
data_source_id
description
entries
entries_for_work(work_or_edition)[source]

Find all of the entries in the list representing a particular Edition or Work.

property featured_works
classmethod find(_db, foreign_identifier_or_name, data_source=None, library=None)[source]

Finds a foreign list in the database by its foreign_identifier or its name.

foreign_identifier
id
lane
library
library_id
name
primary_language
remove_entry(work_or_edition)[source]

Remove the entry for a particular Work or Edition and/or any of its equivalent Editions.

responsible_party
size
update_size()[source]
updated
class core.model.customlist.CustomListEntry(**kwargs)[source]

Bases: Base

annotation
customlist
edition
edition_id
featured
first_appearance
id
list_id
most_recent_appearance
set_work(metadata=None, metadata_client=None, policy=None)[source]

If possible, identify a locally known Work that is the same title as the title identified by this CustomListEntry.

Parameters:

policy – A PresentationCalculationPolicy, used to determine how far to go when looking for equivalent Identifiers.

update(_db, equivalent_entries=None)[source]

Combines any number of equivalent entries into a single entry and updates the edition being used to represent the Work.

work
work_id

core.model.datasource module

class core.model.datasource.DataSource(**kwargs)[source]

Bases: Base, HasFullTableCache, DataSourceConstants

A source for information about books, and possibly the books themselves.

URI_PREFIX = 'http://librarysimplified.org/terms/sources/'
cache_key()[source]
classifications
coverage_records
credentials
custom_lists
delivery_mechanisms
editions
extra
classmethod from_uri(_db, uri)[source]
id
id_equivalencies
integration_client
integration_client_id
license_lanes
license_pools
classmethod license_source_for(_db, identifier)[source]

Find the one DataSource that provides licenses for books identified by the given identifier. If there is no such DataSource, or there is more than one, raises an exception.

classmethod license_sources_for(_db, identifier)[source]

A query that locates all DataSources that provide licenses for books identified by the given identifier.

list_lanes
classmethod lookup(_db, name, autocreate=False, offers_licenses=False, primary_identifier_type=None)[source]
measurements
classmethod metadata_sources_for(_db, identifier)[source]

Finds the DataSources that provide metadata for books identified by the given identifier.

name
classmethod name_from_uri(uri)[source]

Turn a data source URI into a name suitable for passing into lookup().

offers_licenses
primary_identifier_type
resources
property uri
classmethod well_known_sources(_db)[source]

Make sure all the well-known sources exist in the database.

core.model.edition module

class core.model.edition.Edition(**kwargs)[source]

Bases: Base, EditionConstants

A lightly schematized collection of metadata for a work, or an edition of a work, or a book, or whatever. If someone thinks of it as a “book” with a “title” it can go in here.

MAX_FALLBACK_THUMBNAIL_HEIGHT = 500
MAX_THUMBNAIL_HEIGHT = 300
MAX_THUMBNAIL_WIDTH = 200
MEDIUM_ENUM = Enum('Book', 'Periodical', 'Audio', 'Music', 'Video', 'Image', 'Courseware', name='medium')
UNKNOWN_AUTHOR = '[Unknown]'
add_contributor(name, roles, aliases=None, lc=None, viaf=None, **kwargs)[source]

Assign a contributor to this Edition.

apply_similarity_threshold(candidates, threshold=0.5)[source]

Yield the Editions from the given list that are similar enough to this one.

author
property author_contributors

All distinct ‘author’-type contributors, with the primary author first, other authors sorted by sort name. Basically, we’re trying to figure out what would go on the book cover. The primary author should go first, and be followed by non-primary authors in alphabetical order. People whose role does not rise to the level of “authorship” (e.g. author of afterword) do not show up. The list as a whole should contain no duplicates. This might happen because someone is erroneously listed twice in the same role, someone is listed as both primary author and regular author, someone is listed as both author and translator, etc. However it happens, your name only shows up once on the front of the book.

property author_for_permanent_work_id
best_cover_within_distance(distance, rel=None, policy=None)[source]
calculate_author()[source]

Turn the list of Contributors into string values for .author and .sort_author.

calculate_permanent_work_id(debug=False)[source]
classmethod calculate_permanent_work_id_for_title_and_author(title, author, medium)[source]
calculate_presentation(policy=None)[source]

Make sure the presentation of this Edition is up-to-date.

choose_cover(policy=None)[source]

Try to find a cover that can be used for this Edition.

contributions
property contributors
cover
cover_full_url
cover_id
cover_thumbnail_url
custom_list_entries
data_source
data_source_id
equivalent_editions(policy=None)[source]

All Editions whose primary ID is equivalent to this Edition’s primary ID, according to the given PresentationCalculationPolicy.

equivalent_identifiers(type=None, policy=None)[source]

All Identifiers equivalent to this Edition’s primary identifier, according to the given PresentationCalculationPolicy

extra
classmethod for_foreign_id(_db, data_source, foreign_id_type, foreign_id, create_if_not_exists=True)[source]

Find the Edition representing the given data source’s view of the work that it primarily identifies by foreign ID. e.g. for_foreign_id(_db, DataSource.OVERDRIVE, Identifier.OVERDRIVE_ID, uuid) finds the Edition for Overdrive’s view of a book identified by Overdrive UUID. This: for_foreign_id(_db, DataSource.OVERDRIVE, Identifier.ISBN, isbn) will probably return nothing, because although Overdrive knows that books have ISBNs, it doesn’t use ISBN as a primary identifier.

id
imprint
is_presentation_for
issued
language
property language_code
property license_pools

The LicensePools that provide access to the book described by this Edition.

medium
classmethod medium_from_media_type(media_type)[source]

Derive a value for Edition.medium from a media type.

TODO: It’s not necessary right now, but we could theoretically derive this information from some other types such as our internal types for Overdrive manifests.

Parameters:

media_type – A media type with optional parameters

Returns:

A value for Edition.medium.

classmethod missing_coverage_from(_db, edition_data_sources, coverage_data_source, operation=None)[source]

Find Editions from edition_data_source whose primary identifiers have no CoverageRecord from coverage_data_source. e.g.:: gutenberg = DataSource.lookup(_db, DataSource.GUTENBERG) oclc_classify = DataSource.lookup(_db, DataSource.OCLC) missing_coverage_from(_db, gutenberg, oclc_classify)

will find Editions that came from Project Gutenberg and have never been used as input to the OCLC Classify web service.

permanent_work_id
primary_identifier
primary_identifier_id
published
publisher
series
series_position
set_cover(resource)[source]
similarity_to(other_record)[source]

How likely is it that this record describes the same book as the given record? 1 indicates very strong similarity, 0 indicates no similarity at all. For now we just compare the sets of words used in the titles and the authors’ names. This should be good enough for most cases given that there is usually some preexisting reason to suppose that the two records are related (e.g. OCLC said they were). Most of the Editions are from OCLC Classify, and we expect to get some of them wrong (e.g. when a single OCLC work is a compilation of several novels by the same author). That’s okay because those Editions aren’t backed by LicensePools. They’re purely informative. We will have some bad information in our database, but the clear-cut cases should outnumber the fuzzy cases, so we we should still group the Editions that really matter–the ones backed by LicensePools–together correctly. TODO: apply much more lenient terms if the two Editions are identified by the same ISBN or other unique identifier.

simple_opds_entry
sort_author
classmethod sort_by_priority(editions, license_source=None)[source]

Return all Editions that describe the Identifier associated with this LicensePool, in the order they should be used to create a presentation Edition for the LicensePool.

sort_title
subtitle
title
property title_for_permanent_work_id
work

core.model.hasfulltablecache module

class core.model.hasfulltablecache.HasFullTableCache[source]

Bases: object

A mixin class for ORM classes that maintain an in-memory cache of (hopefully) every item in the database table for performance reasons.

RESET = <object object>
classmethod by_cache_key(_db, cache_key, lookup_hook)[source]
classmethod by_id(_db, id)[source]

Look up an item by its unique database ID.

cache_key()[source]
classmethod populate_cache(_db)[source]

Populate the in-memory caches from scratch with every single object from the database table.

classmethod reset_cache()[source]

core.model.identifier module

class core.model.identifier.Equivalency(**kwargs)[source]

Bases: Base

An assertion that two Identifiers identify the same work. This assertion comes with a ‘strength’ which represents how confident the data source is in the assertion.

data_source
data_source_id
enabled
classmethod for_identifiers(_db, identifiers, exclude_ids=None)[source]

Find all Equivalencies for the given Identifiers.

id
input
input_id
input_identifiers
output
output_id
output_identifiers
strength
votes
class core.model.identifier.Identifier(**kwargs)[source]

Bases: Base, IdentifierConstants

A way of uniquely referring to a particular edition.

exception UnresolvableIdentifierException[source]

Bases: Exception

Create a link between this Identifier and a (potentially new) Resource. TODO: There’s some code in metadata_layer for automatically fetching, mirroring and scaling Representations as links are created. It might be good to move that code into here.

add_measurement(data_source, quantity_measured, value, weight=1, taken_at=None)[source]

Associate a new Measurement with this Identifier.

annotations
classmethod best_cover_for(_db, identifier_ids, rel=None)[source]
classifications
classmethod classifications_for_identifier_ids(_db, identifier_ids)[source]
classify(data_source, subject_type, subject_identifier, subject_name=None, weight=1)[source]

Classify this Identifier under a Subject.

Parameters:
  • type – Classification scheme; one of the constants from Subject.

  • subject_identifier – Internal ID of the subject according to that classification scheme.

  • value – Human-readable description of the subject, if different from the ID.

  • weight – How confident the data source is in classifying a book under this subject. The meaning of this number depends entirely on the source of the information.

collections
coverage_records
delivery_mechanisms
equivalencies
equivalent_identifier_ids(policy=None)[source]
equivalent_to(data_source, identifier, strength)[source]

Make one Identifier equivalent to another. data_source is the DataSource that believes the two identifiers are equivalent.

classmethod evaluate_summary_quality(_db, identifier_ids, privileged_data_sources=None)[source]

Evaluate the summaries for the given group of Identifier IDs. This is an automatic evaluation based solely on the content of the summaries. It will be combined with human-entered ratings to form an overall quality score. We need to evaluate summaries from a set of Identifiers (typically those associated with a single work) because we need to see which noun phrases are most frequently used to describe the underlying work. :param privileged_data_sources: If present, a summary from one of these data source will be instantly chosen, short-circuiting the decision process. Data sources are in order of priority. :return: The single highest-rated summary Resource.

classmethod for_foreign_id(_db, foreign_identifier_type, foreign_id, autocreate=True)[source]

Turn a foreign ID into an Identifier.

classmethod from_asin(_db, asin, autocreate=True)[source]

Turn an ASIN-like string into an Identifier. If the string is an ISBN10 or ISBN13, the Identifier will be of type ISBN and the value will be the equivalent ISBN13. Otherwise the Identifier will be of type ASIN and the value will be the value of asin.

id
identifier
inbound_equivalencies
licensed_through
licensed_through_collection(collection)[source]

Find the LicensePool, if any, for this Identifier in the given Collection. :return: At most one LicensePool.

measurements
classmethod missing_coverage_from(_db, identifier_types, coverage_data_source, operation=None, count_as_covered=None, count_as_missing_before=None, identifiers=None, collection=None)[source]

Find identifiers of the given types which have no CoverageRecord from coverage_data_source. :param count_as_covered: Identifiers will be counted as covered if their CoverageRecords have a status in this list. :param identifiers: Restrict search to a specific set of identifier objects.

opds_entry()[source]

Create an OPDS entry using only resources directly associated with this Identifier. This makes it possible to create an OPDS entry even when there is no Edition. Currently the only things in this OPDS entry will be description, cover image, and popularity. NOTE: The timestamp doesn’t take into consideration when the description was added. Rather than fixing this it’s probably better to get rid of this hack and create real Works where we would be using this method.

classmethod parse(_db, identifier_string, parser, must_support_license_pools=False)[source]

Parse identifier string.

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

  • identifier_string (str) – String containing an identifier

  • parser (IdentifierParser) – Identifier parser

  • must_support_license_pools (bool) – Boolean value indicating whether there should be a DataSource that provides licenses for books identified by the given identifier

Returns:

2-tuple containing Identifier object and a boolean value indicating whether it’s new

Return type:

Tuple[core.model.identifier.Identifier, bool]

classmethod parse_urn(_db, identifier_string, must_support_license_pools=False)[source]

Parse identifier string.

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

  • identifier_string (str) – String containing an identifier

  • must_support_license_pools (bool) – Boolean value indicating whether there should be a DataSource that provides licenses for books identified by the given identifier

Returns:

2-tuple containing Identifier object and a boolean value indicating whether it’s new

Return type:

Tuple[core.model.identifier.Identifier, bool]

classmethod parse_urns(_db, identifier_strings, autocreate=True, allowed_types=None)[source]

Converts a batch of URNs into Identifier objects.

Parameters:
  • _db – A database connection

  • identifier_strings – A list of strings, each a URN identifying some identifier.

  • autocreate – Create an Identifier for a URN if none presently exists.

  • allowed_types – If this is a list of Identifier types, only identifiers of those types may be looked up. All other identifier types will be treated as though they did not exist.

Returns:

A 2-tuple (identifiers, failures). identifiers is a list of Identifiers. failures is a list of URNs that did not become Identifiers.

classmethod prepare_foreign_type_and_identifier(foreign_type, foreign_identifier)[source]
primarily_identifies
classmethod recursively_equivalent_identifier_ids(_db, identifier_ids, policy=None)[source]

All Identifier IDs equivalent to the given set of Identifier IDs at the given confidence threshold. This uses the function defined in files/recursive_equivalents.sql. Four levels is enough to go from a Gutenberg text to an ISBN. Gutenberg ID -> OCLC Work IS -> OCLC Number -> ISBN Returns a dictionary mapping each ID in the original to a list of equivalent IDs.

Parameters:

policy – A PresentationCalculationPolicy that explains how you’ve chosen to make the tradeoff between performance, data quality, and sheer number of equivalent identifiers.

classmethod recursively_equivalent_identifier_ids_query(identifier_id_column, policy=None)[source]

Get a SQL statement that will return all Identifier IDs equivalent to a given ID at the given confidence threshold. identifier_id_column can be a single Identifier ID, or a column like Edition.primary_identifier_id if the query will be used as a subquery. This uses the function defined in files/recursive_equivalents.sql.

classmethod resources_for_identifier_ids(_db, identifier_ids, rel=None, data_source=None)[source]
type
classmethod type_and_identifier_for_urn(identifier_string)[source]
property urn
classmethod valid_as_foreign_identifier(type, id)[source]

Return True if the given id can be an Identifier of the given type. This is not a complete implementation; we will add to it as necessary. In general we err on the side of allowing IDs that look invalid (e.g. all Overdrive IDs look like UUIDs, but we currently don’t enforce that). We only reject an ID out of hand if it will cause problems with a third-party API.

property work

Find the Work, if any, associated with this Identifier. Although one Identifier may be associated with multiple LicensePools, all of them must share a Work.

class core.model.identifier.IdentifierParser[source]

Bases: object

Interface for identifier parsers.

abstract 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]]

core.model.integrationclient module

class core.model.integrationclient.IntegrationClient(**kwargs)[source]

Bases: Base

A client that has authenticated access to this application.

Currently used to represent circulation managers that have access to the metadata wrangler.

classmethod authenticate(_db, shared_secret)[source]
created
data_source
enabled
classmethod for_url(_db, url)[source]

Finds the IntegrationClient for the given server URL.

Returns:

an IntegrationClient. If it didn’t already exist, it will be created. If it didn’t already have a secret, no secret will be set.

holds
id
last_accessed
loans
classmethod normalize_url(url)[source]
randomize_secret()[source]
classmethod register(_db, url, submitted_secret=None)[source]

Creates a new server with client details.

shared_secret
url

core.model.library module

class core.model.library.Library(**kwargs)[source]

Bases: Base, HasFullTableCache

A library that uses this circulation manager to authenticate its patrons and manage access to its content. A circulation manager may serve many libraries.

ALLOW_HOLDS = 'allow_holds'
DEFAULT_FACET_KEY_PREFIX = 'facets_default_'
ENABLED_FACETS_KEY_PREFIX = 'facets_enabled_'
EXTERNAL_TYPE_REGULAR_EXPRESSION = 'external_type_regular_expression'
FEATURED_LANE_SIZE = 'featured_lane_size'
adminroles
property all_collections
property allow_holds

Does this library allow patrons to put items on hold?

cache_key()[source]
cachedfeeds
cachedmarcfiles
circulation_events
collections
custom_lists
classmethod default(_db)[source]

Find the default Library.

default_facet(group_name)[source]

Look up the default facet for a given facet group.

default_facet_setting(group_name)[source]
enabled_facets(group_name)[source]

Look up the enabled facets for a given facet group.

enabled_facets_setting(group_name)[source]
property entrypoints

The EntryPoints enabled for this library.

estimated_holdings_by_language(include_open_access=True)[source]

Estimate how many titles this library has in various languages. The estimate is pretty good but should not be relied upon as exact. :return: A Counter mapping languages to the estimated number of titles in that language.

explain(include_secrets=False)[source]

Create a series of human-readable strings to explain a library’s settings.

Parameters:

include_secrets – For security reasons, secrets are not displayed by default.

Returns:

A list of explanatory strings.

property featured_lane_size

The minimum quality a book must have to be ‘featured’.

property has_root_lanes

Does this library have any lanes that act as the root lane for a certain patron type?

Returns:

A boolean

id
integrations
property is_default
lanes
library_registry_shared_secret
library_registry_short_name

Gets library_registry_short_name from database

classmethod lookup(_db, short_name)[source]

Look up a library by short name.

The minimum quality a book must have to be ‘featured’.

name
patrons
restrict_to_ready_deliverable_works(query, collection_ids=None, show_suppressed=False)[source]

Restrict a query to show only presentation-ready works present in an appropriate collection which the default client can fulfill. Note that this assumes the query has an active join against LicensePool. :param query: The query to restrict. :param collection_ids: Only include titles in the given collections. :param show_suppressed: Include titles that have nothing but suppressed LicensePools.

setting(key)[source]

Find or create a ConfigurationSetting on this Library. :param key: Name of the setting. :return: A ConfigurationSetting

settings
short_name
uuid

core.model.licensing module

class core.model.licensing.DeliveryMechanism(**kwargs)[source]

Bases: Base, HasFullTableCache

A technique for delivering a book to a patron. There are two parts to this: a DRM scheme and a content type. Either may be identified with a MIME media type (e.g. “application/vnd.adobe.adept+xml” or “application/epub+zip”) or an informal name (“Kindle via Amazon”).

ADOBE_DRM = 'application/vnd.adobe.adept+xml'
AXISNOW_DRM = 'application/vnd.librarysimplified.axisnow+json'
BEARER_TOKEN = 'application/vnd.librarysimplified.bearer-token+json'
FEEDBOOKS_AUDIOBOOK_DRM = 'http://www.feedbooks.com/audiobooks/access-restriction'
FEEDBOOKS_AUDIOBOOK_PROFILE = ';profile="http://www.feedbooks.com/audiobooks/access-restriction"'
FINDAWAY_DRM = 'application/vnd.librarysimplified.findaway.license+json'
KINDLE_CONTENT_TYPE = 'Kindle via Amazon'
KINDLE_DRM = 'Kindle DRM'
KNOWN_DRM_TYPES = {'Kindle DRM', 'Libby DRM', 'Nook DRM', 'Overdrive DRM', 'Streaming', 'application/vnd.adobe.adept+xml', 'application/vnd.librarysimplified.axisnow+json', 'application/vnd.librarysimplified.findaway.license+json', 'application/vnd.readium.lcp.license.v1.0+json'}
LCP_DRM = 'application/vnd.readium.lcp.license.v1.0+json'
LIBBY_DRM = 'Libby DRM'
MEDIA_TYPES_FOR_STREAMING = {'Streaming Audio': 'text/html', 'Streaming Text': 'text/html'}
NOOK_CONTENT_TYPE = 'Nook via B&N'
NOOK_DRM = 'Nook DRM'
NO_DRM = None
OVERDRIVE_DRM = 'Overdrive DRM'
STREAMING_AUDIO_CONTENT_TYPE = 'Streaming Audio'
STREAMING_DRM = 'Streaming'
STREAMING_PROFILE = ';profile="http://librarysimplified.org/terms/profiles/streaming-media"'
STREAMING_TEXT_CONTENT_TYPE = 'Streaming Text'
STREAMING_VIDEO_CONTENT_TYPE = 'Streaming Video'
cache_key()[source]
compatible_with(other, open_access_rules=False)[source]

Can a single loan be fulfilled with both this delivery mechanism and the given one?

Parameters:
  • other – A DeliveryMechanism

  • open_access – If this is True, the rules for open-access fulfillment will be applied. If not, the stricted rules for commercial fulfillment will be applied.

content_type
property content_type_media_type

Return the media type for this delivery mechanism’s content type, assuming it’s represented as a media type.

default_client_can_fulfill
default_client_can_fulfill_lookup = {('application/pdf', None), ('application/vnd.overdrive.circulation.api+json;profile=audiobook', 'Libby DRM'), (None, 'application/vnd.librarysimplified.findaway.license+json'), ('application/epub+zip', 'application/vnd.librarysimplified.bearer-token+json'), ('application/pdf', 'application/vnd.librarysimplified.bearer-token+json'), ('application/epub+zip', None), ('application/epub+zip', 'application/vnd.adobe.adept+xml'), ('application/audiobook+json', 'application/vnd.librarysimplified.bearer-token+json'), ('application/audiobook+json', None)}
drm_scheme
property drm_scheme_media_type

Return the media type for this delivery mechanism’s DRM scheme, assuming it’s represented that way.

id
property implicit_medium

What would be a good setting for EditionConstants.MEDIUM for an edition available through this DeliveryMechanism?

classmethod is_media_type(x)[source]

Does this string look like a media type?

property is_streaming
license_pool_delivery_mechanisms
classmethod lookup(_db, content_type, drm_scheme)[source]
property name
class core.model.licensing.License(**kwargs)[source]

Bases: Base

A single license for a work from a given source.

TODO: This currently assumes all licenses for a pool have the same delivery mechanisms, which may not always be true.

checkout_url
concurrent_checkouts
expires
id
identifier
property is_expired
property is_loan_limited
property is_perpetual
property is_time_limited
license_pool
license_pool_id
loan_to(patron_or_client, **kwargs)[source]
loans
remaining_checkouts
status_url
class core.model.licensing.LicensePool(**kwargs)[source]

Bases: Base

A pool of undifferentiated licenses for a work from a given source.

UNLIMITED_ACCESS = -1

Add a link between this LicensePool and a Resource.

Parameters:
  • rel – The relationship between this LicensePool and the resource on the other end of the link.

  • href – The URI of the resource on the other end of the link.

  • media_type – Media type of the representation associated with the resource.

  • content – Content of the representation associated with the resource.

  • content_path – Path (relative to DATA_DIRECTORY) of the representation associated with the resource.

  • rights_status_uri – The URI of the RightsStatus for this resource.

  • rights_explanation – A free text explanation of why the RightsStatus applies.

  • original_resource – Another resource that this resource was derived from.

  • transformation_settings – The settings used to transform the original resource into this resource.

availability_time
best_available_license()[source]

Determine the next license that should be lent out for this pool.

Time-limited licenses and perpetual licenses are the best. It doesn’t matter which is used first, unless a time-limited license would expire within the loan period, in which case it’s better to loan the time-limited license so the perpetual one is still available. We can handle this by always loaning the time-limited one first, followed by perpetual. If there is more than one time-limited license, it’s better to use the one expiring soonest.

If no time-limited or perpetual licenses are available, the next best is a loan-limited license. We should choose the license with the most remaining loans, so that we’ll maximize the number of concurrent checkouts available in the future.

The worst option would be pay-per-use, but we don’t yet support any distributors that offer that model.

Find the best available licensing link for the work associated with this LicensePool. # TODO: This needs work and may not be necessary anymore.

Find the best open-access link for this LicensePool. Cache it so that the next access will be faster.

property best_open_access_resource

Determine the best open-access Resource currently provided by this LicensePool.

better_open_access_pool_than(champion)[source]

Is this open-access pool generally known for better-quality download files than the passed-in pool?

calculate_work(known_edition=None, exclude_search=False, even_if_no_title=False)[source]

Find or create a Work for this LicensePool. A pool that is not open-access will always have its own Work. Open-access LicensePools will be grouped together with other open-access LicensePools based on the permanent work ID of the LicensePool’s presentation edition. :param even_if_no_title: Ordinarily this method will refuse to create a Work for a LicensePool whose Edition has no title. However, in components that don’t present information directly to readers, it’s sometimes useful to create a Work even if the title is unknown. In that case, pass in even_if_no_title=True and the Work will be created. TODO: I think known_edition is mostly useless. We should either remove it or replace it with a boolean that stops us from calling set_presentation_edition() and assumes we’ve already done that work.

circulation_changelog(old_licenses_owned, old_licenses_available, old_licenses_reserved, old_patrons_in_hold_queue)[source]

Generate a log message describing a change to the circulation. :return: a 2-tuple (message, args) suitable for passing into logging.info or a similar method

circulation_events
collect_analytics_event(analytics, event_name, as_of, old_value, new_value)[source]
collection
collection_id
complaints
classmethod consolidate_works(_db, batch_size=10)[source]

Assign a (possibly new) Work to every unassigned LicensePool.

data_source
data_source_id
property deliverable

This LicensePool can actually be delivered to patrons.

delivery_mechanisms
classmethod for_foreign_id(_db, data_source, foreign_id_type, foreign_id, rights_status=None, collection=None, autocreate=True)[source]

Find or create a LicensePool for the given foreign ID.

holds
id
identifier
identifier_id
last_checked
license_exception
licenses
licenses_available
licenses_owned
licenses_reserved
loan_to(patron_or_client, start=None, end=None, fulfillment=None, external_identifier=None)[source]
loans
needs_update()[source]

Is it time to update the circulation info for this license pool?

on_hold_to(patron_or_client, start=None, end=None, position=None, external_identifier=None)[source]
open_access
property open_access_download_url

Alias for best_open_access_link. If _open_access_download_url is currently None, this will set to a good value if possible.

Yield all open-access Resources for this LicensePool.

property open_access_source_priority

What priority does this LicensePool’s DataSource have in our list of open-access content sources? e.g. GITenberg books are prefered over Gutenberg books, because there’s a defined process for fixing errors and they are more likely to have good cover art.

patrons_in_hold_queue
presentation_edition
presentation_edition_id
self_hosted
set_delivery_mechanism(*args, **kwargs)[source]

Ensure that this LicensePool (and any other LicensePools for the same book) have a LicensePoolDeliveryMechanism for this media type, DRM scheme, rights status, and resource.

set_open_access_status()[source]

Set .open_access based on whether there is currently an open-access LicensePoolDeliveryMechanism for this LicensePool.

set_presentation_edition(equivalent_editions=None)[source]

Create or update the presentation Edition for this LicensePool. The presentation Edition is made of metadata from all Editions associated with the LicensePool’s identifier. :param equivalent_editions: An optional list of Edition objects that don’t share this LicensePool’s identifier but are associated with its equivalent identifiers in some way. This option is used to create Works on the Metadata Wrangler. :return: A boolean explaining whether any of the presentation information associated with this LicensePool actually changed.

superceded
suppressed
unlimited_access

Returns a Boolean value indicating whether this LicensePool allows unlimited access. For example, in the case of LCP books without explicit licensing information

Returns:

Boolean value indicating whether this LicensePool allows unlimited access

Return type:

bool

update_availability(new_licenses_owned, new_licenses_available, new_licenses_reserved, new_patrons_in_hold_queue, analytics=None, as_of=None)[source]

Update the LicensePool with new availability information. Log the implied changes with the analytics provider.

update_availability_from_delta(event_type, event_date, delta, analytics=None)[source]

Call update_availability based on a single change seen in the distributor data, rather than a complete snapshot of distributor information as of a certain time. This information is unlikely to be completely accurate, but it should suffice until more accurate information can be obtained. No CirculationEvent is created until update_availability is called. Events must be processed in chronological order. Any event that happened than LicensePool.last_checked is ignored, and calling this method will update LicensePool.last_checked to the time of the event. :param event_type: A CirculationEvent constant representing the type of change that was seen. :param event_date: A datetime corresponding to when the change was seen. :param delta: The magnitude of the change that was seen.

classmethod with_complaint(library, resolved=False)[source]

Return query for LicensePools that have at least one Complaint.

classmethod with_no_delivery_mechanisms(_db)[source]

Find LicensePools that have no delivery mechanisms.

Returns:

A query object.

classmethod with_no_work(_db)[source]

Find LicensePools that have no corresponding Work.

work
work_id
class core.model.licensing.LicensePoolDeliveryMechanism(**kwargs)[source]

Bases: Base

A mechanism for delivering a specific book from a specific distributor. It’s presumed that all LicensePools for a given DataSource and Identifier have the same set of LicensePoolDeliveryMechanisms. This is mostly an association class between DataSource, Identifier and DeliveryMechanism, but it also may incorporate a specific Resource (i.e. a static link to a downloadable file) which explains exactly where to go for delivery.

compatible_with(other)[source]

Can a single loan be fulfilled with both this LicensePoolDeliveryMechanism and the given one?

Parameters:

other – A LicensePoolDeliveryMechanism.

data_source
data_source_id
delete()[source]

Delete a LicensePoolDeliveryMechanism.

delivery_mechanism
delivery_mechanism_id
fulfills
id
identifier
identifier_id
property is_open_access

Is this an open-access delivery mechanism?

property license_pools

Find all LicensePools for this LicensePoolDeliveryMechanism.

resource
resource_id
rights_status
rightsstatus_id
classmethod set(data_source, identifier, content_type, drm_scheme, rights_uri, resource=None, autocommit=True)[source]

Register the fact that a distributor makes a title available in a certain format.

Parameters:
  • data_source – A DataSource identifying the distributor.

  • identifier – An Identifier identifying the title.

  • content_type – The title is available in this media type.

  • drm_scheme – Access to the title is confounded by this DRM scheme.

  • rights_uri – A URI representing the public’s rights to the title.

  • resource – A Resource representing the book itself in a freely redistributable form.

  • autocommit – Commit the database session immediately if anything changes in the database. If you’re already inside a nested transaction, pass in False here to avoid committing prematurely, but understand that if a LicensePool’s open-access status changes as a result of calling this method, the change may not be properly reflected in LicensePool.open_access.

set_rights_status(uri)[source]
exception core.model.licensing.PolicyException[source]

Bases: Exception

class core.model.licensing.RightsStatus(**kwargs)[source]

Bases: Base

The terms under which a book has been made available to the general public. This will normally be ‘in copyright’, or ‘public domain’, or a Creative Commons license.

ALLOWS_DERIVATIVES = ['http://librarysimplified.org/terms/rights-status/public-domain-usa', 'https://creativecommons.org/publicdomain/zero/1.0/', 'http://creativecommons.org/licenses/by/4.0/', 'https://creativecommons.org/licenses/by-sa/4.0', 'https://creativecommons.org/licenses/by-nc/4.0', 'https://creativecommons.org/licenses/by-nc-sa/4.0']
CC0 = 'https://creativecommons.org/publicdomain/zero/1.0/'
CC_BY = 'http://creativecommons.org/licenses/by/4.0/'
CC_BY_NC = 'https://creativecommons.org/licenses/by-nc/4.0'
CC_BY_NC_ND = 'https://creativecommons.org/licenses/by-nc-nd/4.0'
CC_BY_NC_SA = 'https://creativecommons.org/licenses/by-nc-sa/4.0'
CC_BY_ND = 'https://creativecommons.org/licenses/by-nd/4.0'
CC_BY_SA = 'https://creativecommons.org/licenses/by-sa/4.0'
DATA_SOURCE_DEFAULT_RIGHTS_STATUS = {'Axis 360': 'http://librarysimplified.org/terms/rights-status/in-copyright', 'Bibliotheca': 'http://librarysimplified.org/terms/rights-status/in-copyright', 'Gutenberg': 'http://librarysimplified.org/terms/rights-status/public-domain-usa', 'Library Simplified Open Access Content Server': 'http://librarysimplified.org/terms/rights-status/generic-open-access', 'Overdrive': 'http://librarysimplified.org/terms/rights-status/in-copyright', 'Plympton': 'https://creativecommons.org/licenses/by-nc/4.0'}
GENERIC_OPEN_ACCESS = 'http://librarysimplified.org/terms/rights-status/generic-open-access'
NAMES = {'http://creativecommons.org/licenses/by/4.0/': 'Creative Commons Attribution (CC BY)', 'http://librarysimplified.org/terms/rights-status/generic-open-access': 'Open access with no specific license', 'http://librarysimplified.org/terms/rights-status/in-copyright': 'In Copyright', 'http://librarysimplified.org/terms/rights-status/public-domain-usa': 'Public domain in the USA', 'http://librarysimplified.org/terms/rights-status/unknown': 'Unknown', 'https://creativecommons.org/licenses/by-nc-nd/4.0': 'Creative Commons Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)', 'https://creativecommons.org/licenses/by-nc-sa/4.0': 'Creative Commons Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)', 'https://creativecommons.org/licenses/by-nc/4.0': 'Creative Commons Attribution-NonCommercial (CC BY-NC)', 'https://creativecommons.org/licenses/by-nd/4.0': 'Creative Commons Attribution-NoDerivs (CC BY-ND)', 'https://creativecommons.org/licenses/by-sa/4.0': 'Creative Commons Attribution-ShareAlike (CC BY-SA)', 'https://creativecommons.org/publicdomain/zero/1.0/': 'Creative Commons Public Domain Dedication (CC0)'}
OPEN_ACCESS = ['http://librarysimplified.org/terms/rights-status/public-domain-usa', 'https://creativecommons.org/publicdomain/zero/1.0/', 'http://creativecommons.org/licenses/by/4.0/', 'https://creativecommons.org/licenses/by-sa/4.0', 'https://creativecommons.org/licenses/by-nd/4.0', 'https://creativecommons.org/licenses/by-nc/4.0', 'https://creativecommons.org/licenses/by-nc-sa/4.0', 'https://creativecommons.org/licenses/by-nc-nd/4.0', 'http://librarysimplified.org/terms/rights-status/generic-open-access']
PUBLIC_DOMAIN_UNKNOWN = 'http://librarysimplified.org/terms/rights-status/public-domain-unknown'
PUBLIC_DOMAIN_USA = 'http://librarysimplified.org/terms/rights-status/public-domain-usa'
UNKNOWN = 'http://librarysimplified.org/terms/rights-status/unknown'
id
licensepooldeliverymechanisms
classmethod lookup(_db, uri)[source]
name
resources
classmethod rights_uri_from_string(rights)[source]
uri

core.model.listeners module

core.model.listeners.add_work_to_customlists_for_collection(pool_or_work, value, oldvalue, initiator)[source]
core.model.listeners.configuration_relevant_collection_change(target, value, initiator)[source]
core.model.listeners.configuration_relevant_lifecycle_event(mapper, connection, target)[source]
core.model.listeners.configuration_relevant_update(mapper, connection, target)[source]
core.model.listeners.directly_modified(obj)[source]

Return True only if obj has itself been modified, as opposed to having an object added or removed to one of its associated collections.

core.model.listeners.last_update_time_change(target, value, oldvalue, initator)[source]

A Work needs to have its search document re-indexed whenever its last_update_time changes.

Among other things, this happens whenever the LicensePool’s availability information changes.

core.model.listeners.licensepool_collection_change(target, value, oldvalue, initiator)[source]

A LicensePool should never change collections, but if it is, we need to keep the search index up to date.

core.model.listeners.licensepool_deleted(mapper, connection, target)[source]

A LicensePool is deleted only when its collection is deleted. If this happens, we need to keep the Work’s index up to date.

core.model.listeners.licensepool_removed_from_work(target, value, initiator)[source]

When a Work gains or loses a LicensePool, it needs to be reindexed.

core.model.listeners.licensepool_storage_status_change(target, value, oldvalue, initiator)[source]

A Work may need to have its search document re-indexed if one of its LicensePools changes its open-access status.

This shouldn’t ever happen.

core.model.listeners.refresh_admin_cache(mapper, connection, target)[source]
core.model.listeners.refresh_admin_role_cache(mapper, connection, target)[source]
core.model.listeners.refresh_collection_cache(mapper, connection, target)[source]
core.model.listeners.refresh_configuration_settings(mapper, connection, target)[source]
core.model.listeners.refresh_datasource_cache(mapper, connection, target)[source]
core.model.listeners.refresh_genre_cache(mapper, connection, target)[source]
core.model.listeners.refresh_library_cache(mapper, connection, target)[source]
core.model.listeners.site_configuration_has_changed(_db, cooldown=1)[source]

Call this whenever you want to indicate that the site configuration has changed and needs to be reloaded.

This is automatically triggered on relevant changes to the data model, but you also should call it whenever you change an aspect of what you consider “site configuration”, just to be safe.

Parameters:
  • _db – Either a Session or (to save time in a common case) an ORM object that can turned into a Session.

  • cooldown – Nothing will happen if it’s been fewer than this number of seconds since the last site configuration change was recorded.

core.model.measurement module

class core.model.measurement.Measurement(**kwargs)[source]

Bases: Base

A measurement of some numeric quantity associated with a Identifier.

AWARDS = 'http://librarysimplified.org/terms/rel/awards'
DOWNLOADS = 'https://schema.org/UserDownloads'
GUTENBERG_FAVORITE = 'http://librarysimplified.org/terms/rel/lists/gutenberg-favorite'
HOLDINGS = 'http://librarysimplified.org/terms/rel/holdings'
PAGE_COUNT = 'https://schema.org/numberOfPages'
PERCENTILE_SCALES = {'http://librarysimplified.org/terms/rel/editions': {'OCLC Classify': [1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25, 26, 28, 30, 32, 34, 36, 39, 42, 46, 50, 56, 64, 73, 87, 112, 156, 281, 2812]}, 'http://librarysimplified.org/terms/rel/holdings': {'OCLC Classify': [1, 8, 12, 16, 20, 24, 28, 33, 37, 43, 49, 55, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 143, 151, 160, 170, 178, 187, 196, 205, 214, 225, 233, 243, 253, 263, 275, 286, 298, 310, 321, 333, 345, 358, 370, 385, 398, 413, 427, 443, 458, 475, 492, 511, 530, 549, 567, 586, 606, 627, 647, 669, 693, 718, 741, 766, 794, 824, 852, 882, 914, 947, 980, 1018, 1056, 1098, 1142, 1188, 1235, 1288, 1347, 1410, 1477, 1545, 1625, 1714, 1812, 1923, 2039, 2164, 2304, 2479, 2671, 2925, 3220, 3565, 3949, 4476, 5230, 7125, 34811]}, 'http://librarysimplified.org/terms/rel/popularity': {'Amazon': [14937330, 1974074, 1702163, 1553600, 1432635, 1327323, 1251089, 1184878, 1131998, 1075720, 1024272, 978514, 937726, 898606, 868506, 837523, 799879, 770211, 743194, 718052, 693932, 668030, 647121, 627642, 609399, 591843, 575970, 559942, 540713, 524397, 511183, 497576, 483884, 470850, 458438, 444475, 432528, 420088, 408785, 398420, 387895, 377244, 366837, 355406, 344288, 333747, 324280, 315002, 305918, 296420, 288522, 279185, 270824, 262801, 253865, 246224, 238239, 230537, 222611, 215989, 208641, 202597, 195817, 188939, 181095, 173967, 166058, 160032, 153526, 146706, 139981, 133348, 126689, 119201, 112447, 106795, 101250, 96534, 91052, 85837, 80619, 75292, 69957, 65075, 59901, 55616, 51624, 47598, 43645, 39403, 35645, 31795, 27990, 24496, 20780, 17740, 14102, 10498, 7090, 3861], 'Content Cafe': [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, 9, 10, 11, 14, 18, 25, 41, 125, 387], 'Overdrive': [1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, 11, 12, 13, 14, 15, 15, 16, 18, 19, 20, 21, 22, 24, 25, 26, 28, 30, 31, 33, 35, 37, 39, 41, 43, 46, 48, 51, 53, 56, 59, 63, 66, 70, 74, 78, 82, 87, 92, 97, 102, 108, 115, 121, 128, 135, 142, 150, 159, 168, 179, 190, 202, 216, 230, 245, 260, 277, 297, 319, 346, 372, 402, 436, 478, 521, 575, 632, 702, 777, 861, 965, 1100, 1248, 1428, 1665, 2020, 2560, 3535, 5805]}, 'https://schema.org/UserDownloads': {'Gutenberg': [0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 14, 15, 15, 16, 16, 17, 18, 18, 19, 19, 20, 21, 21, 22, 23, 23, 24, 25, 26, 27, 28, 28, 29, 30, 32, 33, 34, 35, 36, 37, 38, 40, 41, 43, 45, 46, 48, 50, 52, 55, 57, 60, 62, 65, 69, 72, 76, 79, 83, 87, 93, 99, 106, 114, 122, 130, 140, 152, 163, 179, 197, 220, 251, 281, 317, 367, 432, 501, 597, 658, 718, 801, 939, 1065, 1286, 1668, 2291, 4139]}}
POPULARITY = 'http://librarysimplified.org/terms/rel/popularity'
PUBLISHED_EDITIONS = 'http://librarysimplified.org/terms/rel/editions'
QUALITY = 'http://librarysimplified.org/terms/rel/quality'
RATING = 'http://schema.org/ratingValue'
RATING_SCALES = {'Amazon': [1, 5], 'Library staff': [1, 5], 'NoveList Select': [0, 5], 'Overdrive': [1, 5], 'unglue.it': [1, 5]}
data_source
data_source_id
id
identifier
identifier_id
is_most_recent
property normalized_value

Normalize a measured value, possibly using the rating scales in RATING_SCALES or the empirically determined percentile scales in PERCENTILE_SCALES.

classmethod overall_quality(measurements, popularity_weight=0.3, rating_weight=0.7, default_value=0)[source]

Turn a bunch of measurements into an overall measure of quality.

quantity_measured
taken_at
value
weight

core.model.patron module

class core.model.patron.Annotation(**kwargs)[source]

Bases: Base

BOOKMARKING = 'http://www.w3.org/ns/oa#bookmarking'
IDLING = 'http://librarysimplified.org/terms/annotation/idling'
LS_NAMESPACE = 'http://librarysimplified.org/terms/annotation/'
MOTIVATIONS = ['http://librarysimplified.org/terms/annotation/idling', 'http://www.w3.org/ns/oa#bookmarking']
OA_NAMESPACE = 'http://www.w3.org/ns/oa#'
active
content
classmethod get_one_or_create(_db, patron, *args, **kwargs)[source]

Find or create an Annotation, but only if the patron has annotation sync turned on.

id
identifier
identifier_id
motivation
patron
patron_id
set_inactive()[source]
target
timestamp
class core.model.patron.Hold(**kwargs)[source]

Bases: Base, LoanAndHoldMixin

A patron is in line to check out a book.

end
external_identifier
id
integration_client
integration_client_id
license_pool
license_pool_id
patron
patron_id
position
start
until(default_loan_period, default_reservation_period)[source]

Give or estimate the time at which the book will be available to this patron. This is a very rough estimate that should be treated more or less as a worst case. (Though it could be even worse than this–the library’s license might expire and then you’ll _never_ get the book.)

update(start, end, position)[source]

When the book becomes available, position will be 0 and end will be set to the time at which point the patron will lose their place in line. Otherwise, end is irrelevant and is set to None.

class core.model.patron.Loan(**kwargs)[source]

Bases: Base, LoanAndHoldMixin

cached_content_type
cached_manifest
end
external_identifier
fulfillment
fulfillment_id
id
integration_client
integration_client_id
license
license_id
license_pool
license_pool_id
patron
patron_id
start
until(default_loan_period)[source]

Give or estimate the time at which the loan will end.

class core.model.patron.LoanAndHoldMixin[source]

Bases: object

property library

Try to find the corresponding library for this Loan/Hold.

property work

Try to find the corresponding work for this Loan/Hold.

class core.model.patron.Patron(**kwargs)[source]

Bases: Base

MAX_SYNC_TIME = datetime.timedelta(seconds=43200)
classmethod age_appropriate_match(work_audience, work_target_age, reader_audience, reader_age)[source]

Match the audience and target age of a work with that of a reader, and see whether they are an age-appropriate match.

NOTE: What “age-appropriate” means depends on some policy questions that have not been answered and may be library-specific. For now, non-children’s books are age-inappropriate for young children, and children’s books are age-inappropriate for children too young to be in the book’s target age range.

Parameters:
  • reader_audience – One of the audience constants from Classifier, representing the general reading audience to which the reader belongs.

  • reader_age – A number or 2-tuple representing the age or age range of the reader.

annotations
authorization_expires
authorization_identifier
block_reason
cached_neighborhood
credentials
external_identifier
external_type
fines
holds
id
identifier_to_remote_service(remote_data_source, generator=None)[source]

Find or randomly create an identifier to use when identifying this patron to a remote service. :param remote_data_source: A DataSource object (or name of a DataSource) corresponding to the remote service.

last_external_sync
last_loan_activity_sync

When was the last time we asked the vendors about this patron’s loan activity?

Returns:

A datetime, or None if we know our loan data is stale.

library
library_id
property loan_activity_max_age

In the absence of any other information, how long should loan activity be considered ‘fresh’ for this patron?

We reset Patron.last_loan_activity_sync immediately if we hear about a change to a patron’s loans or holds. This handles cases where patron activity happens where we can’t see it, e.g. on a vendor website or mobile app.

TODO: This is currently a constant, but in the future it could become a per-library setting.

loans
property root_lane

Find the Lane, if any, to be used as the Patron’s root lane.

A patron with a root Lane can only access that Lane and the Lanes beneath it. In addition, a patron with a root lane cannot conduct a transaction on a book intended for an older audience than the one defined by their root lane.

synchronize_annotations
username
work_is_age_appropriate(work_audience, work_target_age)[source]

Is the given audience and target age an age-appropriate match for this Patron?

NOTE: What “age-appropriate” means depends on some policy questions that have not been answered and may be library-specific. For now, it is determined by comparing audience and target age to that of the Patron’s root lane.

This is designed for use when reasoning about works in general. If you have a specific Work in mind, use Work.age_appropriate_for_patron.

Parameters:
  • work_audience – One of the audience constants from Classifier, representing the general reading audience to which a putative work belongs.

  • work_target_age – A number or 2-tuple representing the target age or age range of a putative work.

Returns:

A boolean

works_on_loan()[source]
works_on_loan_or_on_hold()[source]
class core.model.patron.PatronProfileStorage(patron, url_for=None)[source]

Bases: ProfileStorage

Interface between a Patron object and the User Profile Management Protocol.

property profile_document

Create a Profile document representing the patron’s current status.

update(settable, full)[source]

Bring the Patron’s status up-to-date with the given document. Right now this means making sure Patron.synchronize_annotations is up to date.

property writable_setting_names

Return the subset of settings that are considered writable.

core.model.resource module

Bases: Base, LinkRelations

A link between an Identifier and a Resource.

data_source
data_source_id
property default_filename
classmethod generic_uri(data_source, identifier, rel, content=None)[source]

Create a generic URI for the other end of this hyperlink. This is useful for resources that are obtained through means other than fetching a single URL via HTTP. It lets us get a URI that’s most likely unique, so we can create a Resource object without violating the uniqueness constraint. If the output of this method isn’t unique in your situation (because the data source provides more than one link with a given link relation for a given identifier), you’ll need some other way of coming up with generic URIs.

id
identifier
identifier_id
rel
resource
resource_id
classmethod unmirrored(collection)[source]

Find all Hyperlinks associated with an item in the given Collection that could be mirrored but aren’t. TODO: We don’t cover the case where an image was mirrored but no thumbnail was created of it. (We do cover the case where the thumbnail was created but not mirrored.)

class core.model.resource.Representation(**kwargs)[source]

Bases: Base, MediaTypes

A cached document obtained from (and possibly mirrored to) the Web at large. Sometimes this is a DataSource’s representation of a specific book. Sometimes it’s associated with a database Resource (which has a well-defined relationship to one specific book). Sometimes it’s just a web page that we need a cached local copy of.

AVOID_WHEN_CAUTIOUS_DOMAINS = ['gutenberg.org', 'books.google.com']
BROWSER_USER_AGENT = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:37.0) Gecko/20100101 Firefox/37.0'
EXERCISE_CAUTION_DOMAINS = ['unglue.it']
property age
as_image()[source]

Load this Representation’s contents as a PIL image.

property best_thumbnail

Find the best thumbnail among all the thumbnails associated with this Representation. Basically, we prefer a thumbnail that has been mirrored.

classmethod browser_http_get(url, headers, **kwargs)[source]

GET the representation that would be displayed to a web browser.

classmethod cautious_http_get(url, headers, **kwargs)[source]

Examine the URL we’re about to GET, possibly going so far as to perform a HEAD request, to avoid making a request (or following a redirect) to a site known to cause problems. The motivating case is that unglue.it contains gutenberg.org links that appear to be direct links to EPUBs, but 1) they’re not direct links to EPUBs, and 2) automated requests to gutenberg.org quickly result in IP bans. So we don’t make those requests.

property clean_media_type

The most basic version of this representation’s media type. No profiles or anything.

content
content_fh()[source]

Return an open filehandle to the representation’s contents. This works whether the representation is kept in the database or in a file on disk.

default_filename(link=None, destination_type=None)[source]

Try to come up with a good filename for this representation.

etag
extension(destination_type=None)[source]

Try to come up with a good file extension for this representation.

external_content()[source]

Return a filehandle to the representation’s contents, as they should be mirrored externally, and the media type to be used when mirroring.

property external_media_type
fetch_exception
fetched_at
file_size
classmethod get(_db, url, do_get=None, extra_request_headers=None, accept=None, max_age=None, pause_before=0, allow_redirects=True, presumed_media_type=None, debug=True, response_reviewer=None, exception_handler=None, url_normalizer=None)[source]

Retrieve a representation from the cache if possible. If not possible, retrieve it from the web and store it in the cache.

Parameters:
  • _db – A database connection.

  • url – The URL to use as the target of any HTTP request.

  • do_get – A function that takes arguments (url, headers) and retrieves a representation over the network.

  • accept – A value for the Accept HTTP header.

  • extra_request_headers – Any additional HTTP headers to include with the request.

  • max_age – A timedelta object representing the maximum time to consider a cached representation fresh. (We ignore the caching directives from web servers because they’re usually far too conservative for our purposes.)

  • pause_before – A number of seconds to pause before sending the HTTP request. This is for use in situations where HTTP requests are subject to throttling.

  • allow_redirects – Not currently used. (TODO: this seems like a problem!)

  • presumed_media_type – If the response does not contain a Content-Type header, or if the specified Content-Type is too generic to use, the representation will be presumed to be of this media type.

  • debug – If True, progress reports on the HTTP request will be logged.

  • response_reviewer – A function that takes a 3-tuple (status_code, headers, content) and raises an exception if the response should not be treated as cacheable.

  • exception_handler – A function that takes a 3-tuple (Representation, Exception, traceback) and handles an exceptional condition that occured during the HTTP request.

  • url_normalizer – A function that takes the URL to be used in the HTTP request, and returns the URL to use when storing the corresponding Representation in the database. This can be used to strip irrelevant or sensitive information from URLs to increase the chances of a cache hit.

Returns:

A 2-tuple (representation, obtained_from_cache)

classmethod get_would_be_useful(url, headers, do_not_access=None, check_for_redirect=None, head_client=None)[source]

Determine whether making a GET request to a given URL is likely to have a useful result.

Parameters:
  • URL – URL under consideration.

  • headers – Headers that would be sent with the GET request.

  • do_not_access – Domains to which GET requests are not useful.

  • check_for_redirect – Domains to which we should make a HEAD request, in case they redirect to a do_not_access domain.

  • head_client – Function for making the HEAD request, if one becomes necessary. Should return requests.Response or a mock.

classmethod guess_media_type(filename)[source]

Guess a likely media type from a filename.

classmethod guess_url_media_type_from_path(url)[source]

Guess a likely media type from the URL’s path component.

property has_content
headers
classmethod headers_to_string(d)[source]
classmethod http_get_no_redirect(url, headers, **kwargs)[source]

HTTP-based GET with no redirects.

classmethod http_get_no_timeout(url, headers, **kwargs)[source]
id
image_height
image_width
is_fresher_than(max_age)[source]
property is_image
classmethod is_media_type(s)[source]

Return true if the given string looks like a media type.

property is_usable

Returns True if the Representation has some data or received a status code that’s not in the 5xx series.

last_modified
local_content_path
property local_path

Return the full local path to the representation on disk.

location
marc_file
media_type
mirror_exception
mirror_url
property mirrorable_media_type

Does this Representation look like the kind of thing we create mirrors of? Basically, images and books.

mirrored_at
classmethod normalize_content_path(content_path, base=None)[source]
pil_format_for_media_type = {'image/gif': 'gif', 'image/jpeg': 'jpeg', 'image/png': 'png'}
classmethod post(_db, url, data, max_age=None, response_reviewer=None, **kwargs)[source]

Finds or creates POST request as a Representation

property public_url

Find the best URL to publish when referencing this Representation in a public space. :return: a bytestring

classmethod record_exception(representation, exception, traceback)[source]

Deal with a fetch exception by recording it and moving on.

classmethod reraise_exception(representation, exception, traceback)[source]

Deal with a fetch exception by re-raising it.

resource
scale(max_height, max_width, destination_url, destination_media_type, force=False)[source]

Return a Representation that’s a scaled-down version of this Representation, creating it if necessary. :param destination_url: The URL the scaled-down resource will (eventually) be uploaded to. :return: A 2-tuple (Representation, is_new)

scale_exception
scaled_at
set_as_mirrored(mirror_url)[source]

Record the fact that the representation has been mirrored to the given URL. This should only be called upon successful completion of the mirror operation.

set_fetched_content(content, content_path=None)[source]

Simulate a successful HTTP request for this representation. This is used when the content of the representation is obtained through some other means.

classmethod simple_http_get(url, headers, **kwargs)[source]

The most simple HTTP-based GET.

classmethod simple_http_post(url, headers, **kwargs)[source]

The most simple HTTP-based POST.

status_code
thumbnail_of
thumbnail_of_id
property thumbnail_size_quality_penalty
thumbnails
property unicode_content

Attempt to convert the content into Unicode. If all attempts fail, we will return None rather than raise an exception.

update_image_size()[source]

Make sure .image_height and .image_width are up to date. Clears .image_height and .image_width if the representation is not an image.

url
property url_extension

The file extension in this representation’s original url.

class core.model.resource.Resource(**kwargs)[source]

Bases: Base

An external resource that may be mirrored locally. E.g: a cover image, an epub, a description.

ESTIMATED_QUALITY_WEIGHT = 5
MINIMUM_IMAGE_QUALITY = 0.25
class Work(**kwargs)

Bases: Base

ALL = 'all'
APPEALS_URI = 'http://librarysimplified.org/terms/appeals/'
CHARACTER_APPEAL = 'Character'
CURRENTLY_AVAILABLE = 'currently_available'
ELASTICSEARCH_TIME_FORMAT = 'YYYY-MM-DD"T"HH24:MI:SS"."MS'
LANGUAGE_APPEAL = 'Language'
LARGE_FIELDS = ['simple_opds_entry', 'verbose_opds_entry', 'marc_record', 'summary_text']
NOT_APPLICABLE_APPEAL = 'Not Applicable'
NO_APPEAL = 'None'
SETTING_APPEAL = 'Setting'
STORY_APPEAL = 'Story'
UNKNOWN_APPEAL = 'Unknown'
active_license_pool()
age_appropriate_for_patron(patron)

Is this Work age-appropriate for the given Patron?

Parameters:

patron – A Patron.

Returns:

A boolean

all_editions(policy=None)

All Editions identified by an Identifier equivalent to the identifiers of this Work’s license pools.

Parameters:

policy – A PresentationCalculationPolicy, used to determine how far to go when looking for equivalent Identifiers.

all_identifier_ids(policy=None)

Return all Identifier IDs associated with this Work.

Parameters:

policy – A PresentationCalculationPolicy.

Returns:

A set containing all Identifier IDs associated with this Work (as per the rules set down in policy).

appeal_character
appeal_language
appeal_setting
appeal_story
appeal_type = Enum('Character', 'Language', 'Setting', 'Story', 'Not Applicable', 'None', 'Unknown', name='appeal')
assign_appeals(character, language, setting, story, cutoff=0.2)

Assign the given appeals to the corresponding database fields, as well as calculating the primary and secondary appeal.

assign_genres(identifier_ids, default_fiction=False, default_audience='Adult')

Set classification information for this work based on the subquery to get equivalent identifiers. :return: A boolean explaining whether or not any data actually changed.

assign_genres_from_weights(genre_weights)
audience
property author
cached_feeds
calculate_marc_record()
calculate_opds_entries(verbose=True)
calculate_presentation(policy=None, search_index_client=None, exclude_search=False, default_fiction=None, default_audience=None)

Make a Work ready to show to patrons. Call calculate_presentation_edition() to find the best-quality presentation edition that could represent this work. Then determine the following information, global to the work: * Subject-matter classifications for the work. * Whether or not the work is fiction. * The intended audience for the work. * The best available summary for the work. * The overall popularity of the work.

calculate_presentation_edition(policy=None)

Which of this Work’s Editions should be used as the default? First, every LicensePool associated with this work must have its presentation edition set. Then, we go through the pools, see which has the best presentation edition, and make it our presentation edition.

calculate_quality(identifier_ids, default_quality=0)
classifications_with_genre()
property complaints
property cover_full_url
property cover_thumbnail_url
coverage_records
custom_list_entries
default_quality_by_data_source = {'Axis 360': 0.65, 'Bibliotheca': 0.65, 'Gutenberg': 0, 'Overdrive': 0.4, 'Plympton': 0.5, 'RBdigital': 0.4, 'Standard Ebooks': 0.8, 'unglue.it': 0.4}
delete(search_index=None)

Delete the work from both the DB and search index.

property detailed_representation

A description of this work more detailed than repr()

external_index_needs_updating()

Mark this work as needing to have its search document reindexed. This is a more efficient alternative to reindexing immediately, since these WorkCoverageRecords are handled in large batches.

fiction
classmethod for_unchecked_subjects(_db)
classmethod from_identifiers(_db, identifiers, base_query=None, policy=None)

Returns all of the works that have one or more license_pools associated with either an identifier in the given list or an identifier considered equivalent to one of those listed.

Parameters:

policy – A PresentationCalculationPolicy, used to determine how far to go when looking for equivalent Identifiers. By default, this method will be very strict about equivalencies.

genres = ObjectAssociationProxyInstance(AssociationProxy('work_genres', 'genre'))
property has_open_access_license
id
property imprint
property language
property language_code

A single 2-letter language code for display purposes.

last_update_time
license_pools
make_exclusive_open_access_for_permanent_work_id(pwid, medium, language)

Ensure that every open-access LicensePool associated with this Work has the given PWID and medium. Any non-open-access LicensePool, and any LicensePool with a different PWID or a different medium, is kicked out and assigned to a different Work. LicensePools with no presentation edition or no PWID are kicked out. In most cases this Work will be the _only_ work for this PWID, but inside open_access_for_permanent_work_id this is called as a preparatory step for merging two Works, and after the call (but before the merge) there may be two Works for a given PWID.

marc_record
mark_licensepools_as_superceded()

Make sure that all but the single best open-access LicensePool for this Work are superceded. A non-open-access LicensePool should never be superceded, and this method will mark them as un-superceded.

merge_into(other_work)

Merge this Work into another Work and delete it.

classmethod missing_coverage_from(_db, operation=None, count_as_covered=None, count_as_missing_before=None)

Find Works which have no WorkCoverageRecord for the given operation.

needs_full_presentation_recalculation()

Mark this work as needing to have its presentation completely recalculated.

This shifts the time spent recalculating presentation to a script dedicated to this purpose, rather than a script that interacts with APIs. It’s also more efficient, since a work might be flagged multiple times before we actually get around to recalculating the presentation.

needs_new_presentation_edition()

Mark this work as needing to have its presentation edition regenerated. This is significantly less work than calling needs_full_presentation_recalculation, but it will not update a Work’s quality score, summary, or genre classification.

classmethod open_access_for_permanent_work_id(_db, pwid, medium, language)

Find or create the Work encompassing all open-access LicensePools whose presentation Editions have the given permanent work ID, the given medium, and the given language. This may result in the consolidation or splitting of Works, if a book’s permanent work ID has changed without calculate_work() being called, or if the data is in an inconsistent state for any other reason.

popularity
presentation_edition
presentation_edition_id
presentation_ready
presentation_ready_attempt
presentation_ready_exception
primary_appeal
property publisher
property pwids

Return the set of permanent work IDs associated with this Work. There should only be one permanent work ID associated with a given work, but if there is more than one, this will find all of them.

quality
rating
reject_cover(search_index_client=None)

Suppresses the current cover of the Work

classmethod reject_covers(_db, works_or_identifiers, search_index_client=None)

Suppresses the currently visible covers of a number of Works

classmethod restrict_to_custom_lists(_db, base_query, custom_lists, on_list_as_of=None)

Annotate a query that joins Work against Edition to match only Works that are on one of the given custom lists.

classmethod restrict_to_custom_lists_from_data_source(_db, base_query, data_source, on_list_as_of=None)

Annotate a query that joins Work against Edition to match only Works that are on a custom list from the given data source.

secondary_appeal
property series
property series_position
set_presentation_edition(new_presentation_edition)

Sets presentation edition and lets owned pools and editions know. Raises exception if edition to set to is None.

set_presentation_ready(as_of=None, search_index_client=None, exclude_search=False)

Set this work as presentation-ready, no matter what.

This assumes that we know the work has the minimal information necessary to be found with typical queries and that patrons will be able to understand what work we’re talking about.

In most cases you should call set_presentation_ready_based_on_content instead, which runs those checks.

set_presentation_ready_based_on_content(search_index_client=None)

Set this work as presentation ready, if it appears to be ready based on its data.

Presentation ready means the book is ready to be shown to patrons and (pending availability) checked out. It doesn’t necessarily mean the presentation is complete.

The absolute minimum data necessary is a title, a language, and a medium. We don’t need a cover or an author – we can fill in that info later if it exists.

TODO: search_index_client is redundant here.

set_summary(resource)
simple_opds_entry
property sort_author
property sort_title
property subtitle
summary
summary_id
summary_text
target_age
classmethod target_age_query(foreign_work_id_field)
property target_age_string
property title
to_search_document()

Generate a search document for this Work.

classmethod to_search_documents(works, policy=None)

Generate search documents for these Works. This is done by constructing an extremely complicated SQL query. The code is ugly, but it’s about 100 times faster than using python to create documents for each work individually. When working on the search index, it’s very important for this to be fast.

Parameters:

policy – A PresentationCalculationPolicy to use when deciding how deep to go to find Identifiers equivalent to these works.

top_genre()
update_external_index(client, add_coverage_record=True)

Create a WorkCoverageRecord so that this work’s entry in the search index can be modified or deleted. This method is deprecated – call external_index_needs_updating() instead.

verbose_opds_entry
classmethod with_genre(_db, genre)

Find all Works classified under the given genre.

classmethod with_no_genres(q)

Modify a query so it finds only Works that are not classified under any genre.

work_genres
add_derivative(derivative_resource, settings=None)[source]
add_quality_votes(quality, weight=1)[source]

Record someone’s vote as to the quality of this resource.

approve()[source]

Approve a rejected Resource by making its human-generated voted_quality positive while taking its rejection into account.

as_delivery_mechanism_for(licensepool)[source]

If this Resource is used in a LicensePoolDeliveryMechanism for the given LicensePool, return that LicensePoolDeliveryMechanism.

classmethod best_covers_among(resources)[source]

Choose the best covers from a list of Resources.

cover_editions
data_source
data_source_id
derived_through
estimated_quality
property final_url

URL to the final, mirrored version of this resource, suitable for serving to the client. :return: A URL, or None if the resource has no mirrored representation.

id
classmethod image_type_priority(media_type)[source]

Where does the given image media type rank on our list of preferences? :return: A lower number is better. None means it’s not an image type or we don’t care about it at all.

licensepooldeliverymechanisms
quality
property quality_as_thumbnail_image

Determine this image’s suitability for use as a thumbnail image.

reject()[source]

Reject a Resource by making its voted_quality negative. If the Resource is a cover, this rejection will render it unusable to all Editions and Identifiers. Even if the cover is later approved a rejection impacts the overall weight of the vote_quality.

representation
representation_id
rights_explanation
rights_status
rights_status_id
set_estimated_quality(estimated_quality)[source]

Update the estimated quality.

set_fetched_content(media_type, content, content_path)[source]

Simulate a successful HTTP request for a representation of this resource. This is used when the content of the representation is obtained through some other means.

summary_works
transformations
update_quality()[source]

Combine computer-generated estimated_quality with human-generated voted_quality to form overall quality.

url
voted_quality
votes_for_quality
class core.model.resource.ResourceTransformation(**kwargs)[source]

Bases: Base

A record that a resource is a derivative of another resource, and the settings that were used to transform the original into it.

derivative
derivative_id
original
original_id
settings

core.model.work module

class core.model.work.Work(**kwargs)[source]

Bases: Base

ALL = 'all'
APPEALS_URI = 'http://librarysimplified.org/terms/appeals/'
CHARACTER_APPEAL = 'Character'
CURRENTLY_AVAILABLE = 'currently_available'
ELASTICSEARCH_TIME_FORMAT = 'YYYY-MM-DD"T"HH24:MI:SS"."MS'
LANGUAGE_APPEAL = 'Language'
LARGE_FIELDS = ['simple_opds_entry', 'verbose_opds_entry', 'marc_record', 'summary_text']
NOT_APPLICABLE_APPEAL = 'Not Applicable'
NO_APPEAL = 'None'
SETTING_APPEAL = 'Setting'
STORY_APPEAL = 'Story'
UNKNOWN_APPEAL = 'Unknown'
active_license_pool()[source]
age_appropriate_for_patron(patron)[source]

Is this Work age-appropriate for the given Patron?

Parameters:

patron – A Patron.

Returns:

A boolean

all_editions(policy=None)[source]

All Editions identified by an Identifier equivalent to the identifiers of this Work’s license pools.

Parameters:

policy – A PresentationCalculationPolicy, used to determine how far to go when looking for equivalent Identifiers.

all_identifier_ids(policy=None)[source]

Return all Identifier IDs associated with this Work.

Parameters:

policy – A PresentationCalculationPolicy.

Returns:

A set containing all Identifier IDs associated with this Work (as per the rules set down in policy).

appeal_character
appeal_language
appeal_setting
appeal_story
appeal_type = Enum('Character', 'Language', 'Setting', 'Story', 'Not Applicable', 'None', 'Unknown', name='appeal')
assign_appeals(character, language, setting, story, cutoff=0.2)[source]

Assign the given appeals to the corresponding database fields, as well as calculating the primary and secondary appeal.

assign_genres(identifier_ids, default_fiction=False, default_audience='Adult')[source]

Set classification information for this work based on the subquery to get equivalent identifiers. :return: A boolean explaining whether or not any data actually changed.

assign_genres_from_weights(genre_weights)[source]
audience
property author
cached_feeds
calculate_marc_record()[source]
calculate_opds_entries(verbose=True)[source]
calculate_presentation(policy=None, search_index_client=None, exclude_search=False, default_fiction=None, default_audience=None)[source]

Make a Work ready to show to patrons. Call calculate_presentation_edition() to find the best-quality presentation edition that could represent this work. Then determine the following information, global to the work: * Subject-matter classifications for the work. * Whether or not the work is fiction. * The intended audience for the work. * The best available summary for the work. * The overall popularity of the work.

calculate_presentation_edition(policy=None)[source]

Which of this Work’s Editions should be used as the default? First, every LicensePool associated with this work must have its presentation edition set. Then, we go through the pools, see which has the best presentation edition, and make it our presentation edition.

calculate_quality(identifier_ids, default_quality=0)[source]
classifications_with_genre()[source]
property complaints
property cover_full_url
property cover_thumbnail_url
coverage_records
custom_list_entries
default_quality_by_data_source = {'Axis 360': 0.65, 'Bibliotheca': 0.65, 'Gutenberg': 0, 'Overdrive': 0.4, 'Plympton': 0.5, 'RBdigital': 0.4, 'Standard Ebooks': 0.8, 'unglue.it': 0.4}
delete(search_index=None)[source]

Delete the work from both the DB and search index.

property detailed_representation

A description of this work more detailed than repr()

external_index_needs_updating()[source]

Mark this work as needing to have its search document reindexed. This is a more efficient alternative to reindexing immediately, since these WorkCoverageRecords are handled in large batches.

fiction
classmethod for_unchecked_subjects(_db)[source]
classmethod from_identifiers(_db, identifiers, base_query=None, policy=None)[source]

Returns all of the works that have one or more license_pools associated with either an identifier in the given list or an identifier considered equivalent to one of those listed.

Parameters:

policy – A PresentationCalculationPolicy, used to determine how far to go when looking for equivalent Identifiers. By default, this method will be very strict about equivalencies.

genres = ObjectAssociationProxyInstance(AssociationProxy('work_genres', 'genre'))
property has_open_access_license
id
property imprint
property language
property language_code

A single 2-letter language code for display purposes.

last_update_time
license_pools
make_exclusive_open_access_for_permanent_work_id(pwid, medium, language)[source]

Ensure that every open-access LicensePool associated with this Work has the given PWID and medium. Any non-open-access LicensePool, and any LicensePool with a different PWID or a different medium, is kicked out and assigned to a different Work. LicensePools with no presentation edition or no PWID are kicked out. In most cases this Work will be the _only_ work for this PWID, but inside open_access_for_permanent_work_id this is called as a preparatory step for merging two Works, and after the call (but before the merge) there may be two Works for a given PWID.

marc_record
mark_licensepools_as_superceded()[source]

Make sure that all but the single best open-access LicensePool for this Work are superceded. A non-open-access LicensePool should never be superceded, and this method will mark them as un-superceded.

merge_into(other_work)[source]

Merge this Work into another Work and delete it.

classmethod missing_coverage_from(_db, operation=None, count_as_covered=None, count_as_missing_before=None)[source]

Find Works which have no WorkCoverageRecord for the given operation.

needs_full_presentation_recalculation()[source]

Mark this work as needing to have its presentation completely recalculated.

This shifts the time spent recalculating presentation to a script dedicated to this purpose, rather than a script that interacts with APIs. It’s also more efficient, since a work might be flagged multiple times before we actually get around to recalculating the presentation.

needs_new_presentation_edition()[source]

Mark this work as needing to have its presentation edition regenerated. This is significantly less work than calling needs_full_presentation_recalculation, but it will not update a Work’s quality score, summary, or genre classification.

classmethod open_access_for_permanent_work_id(_db, pwid, medium, language)[source]

Find or create the Work encompassing all open-access LicensePools whose presentation Editions have the given permanent work ID, the given medium, and the given language. This may result in the consolidation or splitting of Works, if a book’s permanent work ID has changed without calculate_work() being called, or if the data is in an inconsistent state for any other reason.

popularity
presentation_edition
presentation_edition_id
presentation_ready
presentation_ready_attempt
presentation_ready_exception
primary_appeal
property publisher
property pwids

Return the set of permanent work IDs associated with this Work. There should only be one permanent work ID associated with a given work, but if there is more than one, this will find all of them.

quality
rating
reject_cover(search_index_client=None)[source]

Suppresses the current cover of the Work

classmethod reject_covers(_db, works_or_identifiers, search_index_client=None)[source]

Suppresses the currently visible covers of a number of Works

classmethod restrict_to_custom_lists(_db, base_query, custom_lists, on_list_as_of=None)[source]

Annotate a query that joins Work against Edition to match only Works that are on one of the given custom lists.

classmethod restrict_to_custom_lists_from_data_source(_db, base_query, data_source, on_list_as_of=None)[source]

Annotate a query that joins Work against Edition to match only Works that are on a custom list from the given data source.

secondary_appeal
property series
property series_position
set_presentation_edition(new_presentation_edition)[source]

Sets presentation edition and lets owned pools and editions know. Raises exception if edition to set to is None.

set_presentation_ready(as_of=None, search_index_client=None, exclude_search=False)[source]

Set this work as presentation-ready, no matter what.

This assumes that we know the work has the minimal information necessary to be found with typical queries and that patrons will be able to understand what work we’re talking about.

In most cases you should call set_presentation_ready_based_on_content instead, which runs those checks.

set_presentation_ready_based_on_content(search_index_client=None)[source]

Set this work as presentation ready, if it appears to be ready based on its data.

Presentation ready means the book is ready to be shown to patrons and (pending availability) checked out. It doesn’t necessarily mean the presentation is complete.

The absolute minimum data necessary is a title, a language, and a medium. We don’t need a cover or an author – we can fill in that info later if it exists.

TODO: search_index_client is redundant here.

set_summary(resource)[source]
simple_opds_entry
property sort_author
property sort_title
property subtitle
summary
summary_id
summary_text
target_age
classmethod target_age_query(foreign_work_id_field)[source]
property target_age_string
property title
to_search_document()[source]

Generate a search document for this Work.

classmethod to_search_documents(works, policy=None)[source]

Generate search documents for these Works. This is done by constructing an extremely complicated SQL query. The code is ugly, but it’s about 100 times faster than using python to create documents for each work individually. When working on the search index, it’s very important for this to be fast.

Parameters:

policy – A PresentationCalculationPolicy to use when deciding how deep to go to find Identifiers equivalent to these works.

top_genre()[source]
update_external_index(client, add_coverage_record=True)[source]

Create a WorkCoverageRecord so that this work’s entry in the search index can be modified or deleted. This method is deprecated – call external_index_needs_updating() instead.

verbose_opds_entry
classmethod with_genre(_db, genre)[source]

Find all Works classified under the given genre.

classmethod with_no_genres(q)[source]

Modify a query so it finds only Works that are not classified under any genre.

work_genres
class core.model.work.WorkGenre(**kwargs)[source]

Bases: Base

An assignment of a genre to a work.

affinity
classmethod from_genre(genre)[source]
genre
genre_id
id
work
work_id

Module contents

class core.model.PresentationCalculationPolicy(choose_edition=True, set_edition_metadata=True, classify=True, choose_summary=True, calculate_quality=True, choose_cover=True, regenerate_opds_entries=False, regenerate_marc_record=False, update_search_index=False, verbose=True, equivalent_identifier_levels=3, equivalent_identifier_threshold=0.5, equivalent_identifier_cutoff=1000)[source]

Bases: object

Which parts of the Work or Edition’s presentation are we actually looking to update?

DEFAULT_CUTOFF = 1000
DEFAULT_LEVELS = 3
DEFAULT_THRESHOLD = 0.5
classmethod recalculate_everything()[source]

A PresentationCalculationPolicy that always recalculates everything, even when it doesn’t seem necessary.

classmethod reset_cover()[source]

A PresentationCalculationPolicy that only resets covers (including updating cached entries, if necessary) without impacting any other metadata.

class core.model.SessionManager[source]

Bases: object

RECURSIVE_EQUIVALENTS_FUNCTION = 'recursive_equivalents.sql'
classmethod engine(url=None)[source]
engine_for_url = {}
classmethod initialize(url, initialize_data=True, initialize_schema=True)[source]

Initialize the database.

This includes the schema, the custom functions, and the initial content.

classmethod initialize_data(session, set_site_configuration=True)[source]
classmethod initialize_schema(engine)[source]

Initialize the database schema.

classmethod resource_directory()[source]

The directory containing SQL files used in database setup.

classmethod session(url, initialize_data=True, initialize_schema=True)[source]
classmethod sessionmaker(url=None, session=None)[source]
core.model.create(db, model, create_method='', create_method_kwargs=None, **kwargs)[source]
core.model.dump_query(query)[source]
core.model.flush(db)[source]

Flush the database connection unless it’s known to already be flushing.

core.model.get_one(db, model, on_multiple='error', constraint=None, **kwargs)[source]

Gets an object from the database based on its attributes.

Parameters:

constraint – A single clause that can be passed into sqlalchemy.Query.filter to limit the object that is returned.

Returns:

object or None

core.model.get_one_or_create(db, model, create_method='', create_method_kwargs=None, **kwargs)[source]
core.model.numericrange_to_string(r)[source]

Helper method to convert a NumericRange to a human-readable string.

core.model.numericrange_to_tuple(r)[source]

Helper method to normalize NumericRange into a tuple.

core.model.production_session(initialize_data=True)[source]
core.model.tuple_to_numericrange(t)[source]

Helper method to convert a tuple to an inclusive NumericRange.