toolbox.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import logging
  2. import os
  3. import re
  4. from collections import defaultdict
  5. from hashlib import sha1
  6. from rdflib import Graph
  7. from rdflib.term import URIRef, Variable
  8. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  9. from lakesuperior.globals import ROOT_RSRC_URI
  10. logger = logging.getLogger(__name__)
  11. __doc__ = ''' Utility to translate and generate strings and other objects. '''
  12. def fsize_fmt(num, suffix='b'):
  13. """
  14. Format an integer into 1024-block file size format.
  15. Adapted from Python 2 code on
  16. https://stackoverflow.com/a/1094933/3758232
  17. :param int num: Size value in bytes.
  18. :param str suffix: Suffix label (defaults to ``b``).
  19. :rtype: str
  20. :return: Formatted size to largest fitting unit.
  21. """
  22. for unit in ['','K','M','G','T','P','E','Z']:
  23. if abs(num) < 1024.0:
  24. return f'{num:3.1f} {unit}{suffix}'
  25. num /= 1024.0
  26. return f'{num:.1f} Y{suffix}'
  27. def get_tree_size(path, follow_symlinks=True):
  28. """
  29. Return total size of files in given path and subdirs.
  30. Ripped from https://www.python.org/dev/peps/pep-0471/
  31. """
  32. total = 0
  33. for entry in os.scandir(path):
  34. if entry.is_dir(follow_symlinks=follow_symlinks):
  35. total += get_tree_size(entry.path)
  36. else:
  37. total += entry.stat(
  38. follow_symlinks=follow_symlinks
  39. ).st_size
  40. return total
  41. def replace_term_domain(term, search, replace):
  42. '''
  43. Replace the domain of a term.
  44. :param rdflib.URIRef term: The term (URI) to change.
  45. :param str search: Domain string to replace.
  46. :param str replace: Domain string to use for replacement.
  47. :rtype: rdflib.URIRef
  48. '''
  49. s = str(term)
  50. if s.startswith(search):
  51. s = s.replace(search, replace)
  52. return URIRef(s)
  53. def parse_rfc7240(h_str):
  54. '''
  55. Parse ``Prefer`` header as per https://tools.ietf.org/html/rfc7240
  56. The ``cgi.parse_header`` standard method does not work with all
  57. possible use cases for this header.
  58. :param str h_str: The header(s) as a comma-separated list of Prefer
  59. statements, excluding the ``Prefer:`` token.
  60. '''
  61. parsed_hdr = defaultdict(dict)
  62. # Split up headers by comma
  63. hdr_list = [ x.strip() for x in h_str.split(',') ]
  64. for hdr in hdr_list:
  65. parsed_pref = defaultdict(dict)
  66. # Split up tokens by semicolon
  67. token_list = [ token.strip() for token in hdr.split(';') ]
  68. prefer_token = token_list.pop(0).split('=')
  69. prefer_name = prefer_token[0]
  70. # If preference has a '=', it has a value, else none.
  71. if len(prefer_token)>1:
  72. parsed_pref['value'] = prefer_token[1].strip('"')
  73. for param_token in token_list:
  74. # If the token list had a ';' the preference has a parameter.
  75. param_parts = [ prm.strip().strip('"') \
  76. for prm in param_token.split('=') ]
  77. param_value = param_parts[1] if len(param_parts) > 1 else None
  78. parsed_pref['parameters'][param_parts[0]] = param_value
  79. parsed_hdr[prefer_name] = parsed_pref
  80. return parsed_hdr
  81. def split_uuid(uuid):
  82. '''
  83. Split a UID into pairtree segments. This mimics FCREPO4 behavior.
  84. :param str uuid: UUID to split.
  85. :rtype: str
  86. '''
  87. path = '{}/{}/{}/{}/{}'.format(uuid[:2], uuid[2:4],
  88. uuid[4:6], uuid[6:8], uuid)
  89. return path
  90. class RequestUtils:
  91. """
  92. Utilities that require access to an HTTP request context.
  93. Initialize this within a Flask request context.
  94. """
  95. def __init__(self):
  96. from flask import g
  97. self.webroot = g.webroot
  98. def uid_to_uri(self, uid):
  99. '''Convert a UID to a URI.
  100. :rtype: rdflib.URIRef
  101. '''
  102. return URIRef(self.webroot + uid)
  103. def uri_to_uid(self, uri):
  104. '''Convert an absolute URI (internal or external) to a UID.
  105. :rtype: str
  106. '''
  107. if uri.startswith(nsc['fcres']):
  108. return str(uri).replace(nsc['fcres'], '')
  109. else:
  110. return '/' + str(uri).replace(self.webroot, '').strip('/')
  111. def localize_uri_string(self, s):
  112. '''Convert URIs into URNs in a string using the application base URI.
  113. :param str: s Input string.
  114. :rtype: str
  115. '''
  116. if s.strip('/') == self.webroot:
  117. return str(ROOT_RSRC_URI)
  118. else:
  119. return s.rstrip('/').replace(
  120. self.webroot, str(nsc['fcres']))
  121. def localize_term(self, uri):
  122. '''
  123. Localize an individual term.
  124. :param rdflib.URIRef: urn Input URI.
  125. :rtype: rdflib.URIRef
  126. '''
  127. return URIRef(self.localize_uri_string(str(uri)))
  128. def localize_triple(self, trp):
  129. '''
  130. Localize terms in a triple.
  131. :param tuple(rdflib.URIRef) trp: The triple to be converted
  132. :rtype: tuple(rdflib.URIRef)
  133. '''
  134. s, p, o = trp
  135. if s.startswith(self.webroot):
  136. s = self.localize_term(s)
  137. if o.startswith(self.webroot):
  138. o = self.localize_term(o)
  139. return s, p, o
  140. def localize_graph(self, gr):
  141. '''
  142. Localize a graph.
  143. '''
  144. l_id = self.localize_term(gr.identifier)
  145. l_gr = Graph(identifier=l_id)
  146. for trp in gr:
  147. l_gr.add(self.localize_triple(trp))
  148. return l_gr
  149. def localize_payload(self, data):
  150. '''
  151. Localize an RDF stream with domain-specific URIs.
  152. :param bytes data: Binary RDF data.
  153. :rtype: bytes
  154. '''
  155. return data.replace(
  156. (self.webroot + '/').encode('utf-8'),
  157. (nsc['fcres'] + '/').encode('utf-8')
  158. ).replace(
  159. self.webroot.encode('utf-8'),
  160. (nsc['fcres'] + '/').encode('utf-8')
  161. )
  162. def localize_ext_str(self, s, urn):
  163. '''
  164. Convert global URIs to local in a SPARQL or RDF string.
  165. Also replace empty URIs (`<>`) with a fixed local URN and take care
  166. of fragments and relative URIs.
  167. This is a 3-pass replacement. First, global URIs whose webroot matches
  168. the application ones are replaced with internal URIs. Then, relative
  169. URIs are converted to absolute using the internal URI as the base;
  170. finally, the root node is appropriately addressed.
  171. '''
  172. esc_webroot = self.webroot.replace('/', '\\/')
  173. #loc_ptn = r'<({}\/?)?(.*?)?(\?.*?)?(#.*?)?>'.format(esc_webroot)
  174. loc_ptn1 = r'<{}\/?(.*?)>'.format(esc_webroot)
  175. loc_sub1 = '<{}/\\1>'.format(nsc['fcres'])
  176. s1 = re.sub(loc_ptn1, loc_sub1, s)
  177. loc_ptn2 = r'<([#?].*?)?>'
  178. loc_sub2 = '<{}\\1>'.format(urn)
  179. s2 = re.sub(loc_ptn2, loc_sub2, s1)
  180. loc_ptn3 = r'<{}([#?].*?)?>'.format(nsc['fcres'])
  181. loc_sub3 = '<{}\\1>'.format(ROOT_RSRC_URI)
  182. s3 = re.sub(loc_ptn3, loc_sub3, s2)
  183. return s3
  184. def globalize_string(self, s):
  185. '''Convert URNs into URIs in a string using the application base URI.
  186. :param string s: Input string.
  187. :rtype: string
  188. '''
  189. return s.replace(str(nsc['fcres']), self.webroot)
  190. def globalize_term(self, urn):
  191. '''
  192. Convert an URN into an URI using the application base URI.
  193. :param rdflib.URIRef urn: Input URN.
  194. :rtype: rdflib.URIRef
  195. '''
  196. return URIRef(self.globalize_string(str(urn)))
  197. def globalize_triple(self, trp):
  198. '''
  199. Globalize terms in a triple.
  200. :param tuple(rdflib.URIRef) trp: The triple to be converted
  201. :rtype: tuple(rdflib.URIRef)
  202. '''
  203. s, p, o = trp
  204. if s.startswith(nsc['fcres']):
  205. s = self.globalize_term(s)
  206. if o.startswith(nsc['fcres']):
  207. o = self.globalize_term(o)
  208. return s, p, o
  209. def globalize_imr(self, imr):
  210. '''
  211. Globalize an Imr.
  212. :rtype: rdflib.Graph
  213. '''
  214. g_gr = Graph(identifier=self.globalize_term(imr.uri))
  215. for trp in imr:
  216. g_gr.add(self.globalize_triple(trp))
  217. return g_gr
  218. def globalize_graph(self, gr):
  219. '''
  220. Globalize a graph.
  221. '''
  222. g_id = self.globalize_term(gr.identifier)
  223. g_gr = Graph(identifier=g_id)
  224. for trp in gr:
  225. g_gr.add(self.globalize_triple(trp))
  226. return g_gr
  227. def globalize_rsrc(self, rsrc):
  228. '''
  229. Globalize a resource.
  230. '''
  231. gr = rsrc.graph
  232. urn = rsrc.identifier
  233. global_gr = self.globalize_graph(gr)
  234. global_uri = self.globalize_term(urn)
  235. return global_gr.resource(global_uri)