helpers.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import logging
  2. from flask import Response
  3. from memoization import cached
  4. from lakesuperior import env as lsenv
  5. from lakesuperior.api import query as qry_api
  6. from lakesuperior.api import resource as rsrc_api
  7. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  8. from rdflib import Literal
  9. from grayspread import store
  10. from grayspread.config import config
  11. from grayspread.store import txn_ctx
  12. logger = logging.getLogger(__name__)
  13. def uri_to_uid(uri):
  14. """ Convert a lsup URI to an internal UID. """
  15. return uri.replace(nsc['fcres'], '')
  16. # # # Blueprint helpers # # #
  17. def fmt_dimensions(rsrc):
  18. """
  19. Format a resource dimensions.
  20. Return:
  21. list: A list of tuples containig:
  22. - Literal rendition of the dimensions
  23. - Dictionary of numeric values to plug into drawing functions.
  24. The list is sorted alphabetically by the literal description.
  25. """
  26. dim_nodes = rsrc.imr[rsrc.uri: nsc['lii']['measurement']]
  27. dims = []
  28. for dnode in dim_nodes:
  29. dim_parts = []
  30. logger.info(f'Dim node: {dnode}')
  31. unit = rsrc.imr[dnode: nsc['lii']['unit']].pop()
  32. labels = rsrc.imr[dnode: nsc['skos']['prefLabel']]
  33. try:
  34. di = rsrc.imr[dnode: nsc['lii']['diameter']].pop()
  35. except KeyError:
  36. di = 0
  37. try:
  38. w = rsrc.imr[dnode: nsc['lii']['width']].pop()
  39. except KeyError:
  40. w = 0
  41. try:
  42. h = rsrc.imr[dnode: nsc['lii']['height']].pop()
  43. except KeyError:
  44. h = 0
  45. try:
  46. d = rsrc.imr[dnode: nsc['lii']['depth']].pop()
  47. except KeyError:
  48. d = 0
  49. dim_values = {
  50. 'w': float(w), 'h': float(h), 'd': float(d), 'di': float(di)}
  51. if di:
  52. dim_parts.append(f'Ø: {di} {unit}')
  53. if w:
  54. dim_parts.append(f'width: {w} {unit}')
  55. if h:
  56. dim_parts.append(f'height: {h} {unit}')
  57. if d:
  58. dim_parts.append(f'depth: {d} {unit}')
  59. for label in labels: # There should be one, but you never know...
  60. dims.append((
  61. (f'{label}: ' + '; '.join(dim_parts).capitalize()),
  62. dim_values
  63. ))
  64. return sorted(dims, key=lambda x: x[0])
  65. def inv_rel(rsrc, p):
  66. """
  67. Set of resources pointing to this resource based on predicate.
  68. """
  69. return rsrc.imr[:p:rsrc.uri]
  70. def get_values(rsrc, pred):
  71. """ Return a set of values for a resource predicate. """
  72. return {str(o) for o in rsrc.imr[rsrc.uri: pred]}
  73. def get_value(rsrc, pred):
  74. """ Return a single value for a resource predicate. """
  75. ret = rsrc.imr.value(pred)
  76. return str(ret) if ret else ''
  77. @cached(ttl=60)
  78. def link_meta(uri):
  79. """
  80. Get basic metadata for a resource to be used to build a hyperlink.
  81. Return:
  82. tuple: uid and preferred label.
  83. """
  84. rsrc = rsrc_api.get(uri_to_uid(uri))
  85. if get_lii_type(rsrc) == nsc['lii']['Document']:
  86. return (
  87. rsrc.uid,
  88. get_value(rsrc, nsc['skos'].prefLabel),
  89. rsrc.uid,
  90. )
  91. else:
  92. return (
  93. rsrc.uid,
  94. get_value(rsrc, nsc['skos'].prefLabel),
  95. get_value(rsrc, nsc['lii'].hasPreferredRepresentation).replace(
  96. nsc['fcres'], ''),
  97. )
  98. @cached(ttl=60)
  99. def type_meta(type_id):
  100. """
  101. Get metadata about a lii type (class) from a resource stored in LS.
  102. Args:
  103. type_id (str): Type ID i.e. class name, e.g. ``Artifact`` or ``Place``.
  104. """
  105. uris = qry_api.triple_match(p=nsc['owl'].sameAs, o=nsc['lii'][type_id])
  106. return rsrc_api.get(uri_to_uid(uris.pop()))
  107. def get_lii_type(rsrc):
  108. """
  109. Get main LII type for a resource.
  110. There should be only one.
  111. :rtype: URIRef
  112. """
  113. main_types = {nsc['lii'][t] for t in store.highlight_types}
  114. lii_types = store.get_lii_types(rsrc) & main_types
  115. if len(lii_types):
  116. # There should be only one!
  117. return lii_types.pop()
  118. @cached(ttl=60)
  119. def get_related_media(rsrc):
  120. """
  121. Get related media as set of ordered aggregation resources.
  122. """
  123. media = []
  124. for uri in get_values(rsrc, nsc['lii'].hasRelatedMedium):
  125. uid = uri_to_uid(uri)
  126. media.append((rsrc_api.get(uid), tuple(aggregation_list(uid))))
  127. from pprint import pformat
  128. logger.info(f'media: {pformat(media)}')
  129. return media
  130. @cached(ttl=60)
  131. def uid_from_local_uid(local_uid):
  132. """
  133. Get a resource by its local UID,
  134. """
  135. uri = qry_api.triple_match(
  136. p=nsc['lii'].localUid, o=Literal(local_uid)).pop()
  137. return uri.replace(nsc['fcres'], '')
  138. def aggregation_list(uid):
  139. """
  140. Build an ordered list of resources from an aggregation UID.
  141. """
  142. agg = rsrc_api.get(uid)
  143. ars = []
  144. px_uri = agg.imr.value(nsc['nav']['first'])
  145. while px_uri:
  146. ar_match = qry_api.triple_match(
  147. s=px_uri, p=nsc['ore'].proxyFor, return_full=True)
  148. ars.append(link_meta(ar_match.pop()[2]))
  149. px_match = qry_api.triple_match(
  150. s=px_uri, p=nsc['nav']['next'], return_full=True)
  151. px_uri = px_match.pop()[2] if px_match else None
  152. return ars
  153. def local_path(uid):
  154. """ Get the local file path of a LDP-NR relative to the root path. """
  155. rsrc = rsrc_api.get(uid)
  156. with txn_ctx():
  157. path = rsrc.local_path
  158. return path.replace(lsenv.app_globals.nonrdfly.root, '')
  159. def image_iiif_url(uid, params):
  160. """ Get full IIIF URL from a public image UID. """
  161. local_uid = local_path(uid)
  162. url = f'{config["iiif_image_root_url"]}{local_uid}/{params}'
  163. logger.info(f"IIIF URL: {url}")
  164. return url
  165. def stream(data, mimetype='application/octet-stream', headers={}):
  166. """ Stream out data from an incoming streaming request. """
  167. def _stream_contents(rsp):
  168. for chunk in rsp.iter_content(config["chunk_size"]):
  169. yield chunk
  170. return Response(_stream_contents(data), mimetype=mimetype, headers=headers)