Source code for collecting_society_web.views.api.v1.creations

# For copyright and license terms, see COPYRIGHT.rst (top level of repository)
# Repository: https://github.com/C3S/collecting_society_web

import operator
import logging
import colander

from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.authorization import (
    Allow,
    Everyone
)
from pyramid.httpexceptions import HTTPNotFound
from cornice.resource import resource, view
from cornice.validators import (
    colander_path_validator,
    colander_querystring_validator
)

from portal_web.resources import ResourceBase
from ....services import _
from ....models import (
    Creation as CreationModel,
    CreationIdentifier,
    CreationIdentifierSpace
)
from . import apiversion

log = logging.getLogger(__name__)


# --- schemas -----------------------------------------------------------------

[docs] class BodySchema(colander.MappingSchema): """Create a body schema for our requests""" value = colander.SchemaNode(colander.String(), description='Some JSON returned')
[docs] class ResponseSchema(colander.MappingSchema): """Create a response schema for our 200 responses""" body = BodySchema()
# Aggregate the response schemas for get requests response_schemas = { '200': ResponseSchema( description="Return 'http ok' response " "code because creation was found" ), '404': ResponseSchema( description="Return 'http not found' response code a creation with " "matching id couldn't be found in the database" ) } _VALUES = {} # --- resources ---------------------------------------------------------------
[docs] class UserResource(object): def __init__(self, request): self.request = request self.readonly = False
# --- API --------------------------------------------------------------------- # ... Fields (Querystring Parameters) ...
[docs] class CodeField(colander.SchemaNode): oid = "native_id" name = "native_id" schema_type = colander.String validator = colander.Regex(r'^C\d{10}\Z')
[docs] class ArtistField(colander.SchemaNode): oid = "artist" name = "artist" schema_type = colander.String validator = colander.Length(min=1) missing = ""
[docs] class TitleField(colander.SchemaNode): oid = "title" name = "title" schema_type = colander.String validator = colander.Length(min=1) missing = ""
[docs] class CreationGetSchema(colander.Schema): code = CodeField(title=_("Native Code"))
[docs] def deferred_idspace_schemas_node(request): schema = colander.SchemaNode( colander.Mapping(), description=_("artist/title combinations, native code, or " "foreign identifier spaces like isrc or iswc") ) schema.add(CodeField(name='native_id', title=_("Native Code"), missing="")) schema.add(ArtistField(name='artist', title=_("Artist"))) schema.add(TitleField(name='title', title=_("Title"))) for id_space in CreationIdentifierSpace.search_all(): schema.add( colander.SchemaNode( colander.String(), oid=id_space.name, name=id_space.name, missing="" ) ) return schema
[docs] def deferred_querystring_validator(request, schema=None, deserializer=None, **kwargs): """ Workaround to trick cornice into digesting a deferred schemas_node as root node. """ if callable(schema): schema = schema(request) return colander_querystring_validator(request, schema, deserializer, **kwargs)
# --- service for creation api ------------------------------------------------
[docs] @resource(collection_path=apiversion + '/creations', path=apiversion + '/creations/{native_id}', permission=NO_PERMISSION_REQUIRED) class Creation(ResourceBase): def __acl__(self): return [(Allow, Everyone, 'view')]
[docs] @view(tags=['creations'], schema=deferred_idspace_schemas_node, validators=(deferred_querystring_validator,), response_schemas=response_schemas) def collection_get(self, permission='view'): """ Returns the properties of a specific creation that is identified using multiple identifiers, which are provided as querystring parameters. Example URL: http://api.collecting_society.test/v1/creations? native_id=C0000000001&artist=Registered Name 001 &title=Title of Song 001&ISRC=DE-A00-20-00001&ISWC=T-000.000.001-C """ multicode = self.request.validated scores = {} # count hits for creation codes identified by parameters number_of_identifiers_given = 0 # 1st special case: artistname / tracktitle as composite identifier if ('artist' in multicode and 'title' in multicode and multicode['artist'] and multicode['title']): number_of_identifiers_given += 1 cs = CreationModel.search_by_artistname_and_title( multicode['artist'], multicode['title']) if cs: c = cs[0] if c.code in scores: scores[c.code] = scores[c.code] + 1 else: scores[c.code] = 1 if 'artist' in multicode: del multicode['artist'] if 'title' in multicode: del multicode['title'] # 2nd special case: the native code of the creation: if 'native_id' in multicode and multicode['native_id']: number_of_identifiers_given += 1 c = CreationModel.search_by_code(multicode['native_id']) if c: if c.code in scores: scores[c.code] = scores[c.code] + 1 else: scores[c.code] = 1 del multicode['native_id'] # TODO: 3rd special case: audio fingerprint # ... # ... # last (general) case: search in foreign id spaces defined in # CreationIdentifierSpace, e.g. isrc, iswc ... foreign_idspaces = [space.name for space in CreationIdentifierSpace.search_all()] for queried_space_id in multicode: if (queried_space_id in foreign_idspaces and multicode[queried_space_id]): number_of_identifiers_given += 1 cid = CreationIdentifier.search_by_spacecode( queried_space_id, multicode[queried_space_id]) if cid: # found creation via foreign id? c = cid.creation if c.code in scores: scores[c.code] += 1 else: scores[c.code] = 1 # nothing found? number_of_hits = sum(count for count in scores.values()) if number_of_hits == 0: raise HTTPNotFound # sort: highest score first for code in scores.keys(): scores[code] = int(100 * scores[code] / number_of_identifiers_given) sorted_scores = sorted(scores.items(), key=operator.itemgetter(1), reverse=True) return self.creation_data(sorted_scores)
[docs] @view(tags=['creations'], schema=CreationGetSchema, validators=(colander_path_validator,), response_schemas=response_schemas) def get(self, permission='view'): """ Returns the properties of a specific creation that is identified using the common REST scheme with the native code following the creation object. Example URL: http://api.collecting_society.test/v1/creations/C0000012345 """ return self.creation_data([(self.request.matchdict['native_id'], 100)])
[docs] def creation_data(self, sorted_scores): """ Returns the properties of the found creations. Args: sorted_scores [(String, Int)]: native (C3S) code, i.e "C0000000123" together with a score certainty in %, derived from the query parameter(s) (to be returned in the result dict) Return: creation record (Dictionary) including the score Exceptions: HTTPNotFound, if creation can't be found from the code """ creations = [] for code, score in sorted_scores: creation = CreationModel.search_by_code(code) if not creation: raise HTTPNotFound # assemble creations foreign identifier list cfids = {} for cfid in creation.identifiers: cfids[cfid.space.name] = cfid.id_code # assemble rightsholders rights = [] for crh in creation.rightsholders: rights.append({ 'rightsholder': crh.rightsholder.code, 'rightsobject': crh.rightsobject.code, 'contribution': crh.contribution, # 'successor': crh.successor, # 'instrument': ? 'type_of_right': crh.type_of_right, 'valid_from': str(crh.valid_from), 'valid_to': str(crh.valid_to), 'country': crh.country.name, 'collecting_society': crh.collecting_society.name or None }) creations.append({ 'native_id': creation.code, 'artist': creation.artist.name, 'title': creation.title, 'lyrics': creation.lyrics, 'license': { 'name': creation.license.name, 'code': creation.license.code, 'version': creation.license.version, 'country': creation.license.country, 'link': creation.license.link }, 'derivatives': [d.derivative_creation.code for d in creation.derivative_relations], 'originals': [o.original_creation.code for o in creation.original_relations], 'releases': [r.release.code for r in creation.releases], 'genres': [g.name for g in creation.genres], 'styles': [s.name for s in creation.styles], 'tariff_categories': [ { 'name': t.category.name, 'code': t.category.code, 'description': t.category.description } for t in creation.tariff_categories], 'foreign_ids': cfids, 'rights': rights, 'score': str(score) }) return creations