ldp.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import logging
  2. from collections import defaultdict
  3. from flask import Blueprint, request
  4. from lakesuperior.exceptions import InvalidResourceError, \
  5. ResourceExistsError, ResourceNotExistsError, \
  6. InvalidResourceError, ServerManagedTermError
  7. from lakesuperior.model.ldp_rs import Ldpc, LdpRs
  8. from lakesuperior.model.ldp_nr import LdpNr
  9. from lakesuperior.store_layouts.rdf.base_rdf_layout import BaseRdfLayout
  10. from lakesuperior.util.translator import Translator
  11. logger = logging.getLogger(__name__)
  12. # Blueprint for LDP REST API. This is what is usually found under `/rest/` in
  13. # standard fcrepo4. Here, it is under `/ldp` but initially `/rest` can be kept
  14. # for backward compatibility.
  15. ldp = Blueprint('ldp', __name__)
  16. accept_patch = (
  17. 'application/sparql-update',
  18. )
  19. accept_post_rdf = (
  20. 'application/ld+json',
  21. 'application/n-triples',
  22. 'application/rdf+xml',
  23. #'application/x-turtle',
  24. #'application/xhtml+xml',
  25. #'application/xml',
  26. #'text/html',
  27. 'text/n3',
  28. #'text/plain',
  29. 'text/rdf+n3',
  30. 'text/turtle',
  31. )
  32. #allow = (
  33. # 'COPY',
  34. # 'DELETE',
  35. # 'GET',
  36. # 'HEAD',
  37. # 'MOVE',
  38. # 'OPTIONS',
  39. # 'PATCH',
  40. # 'POST',
  41. # 'PUT',
  42. #)
  43. std_headers = {
  44. 'Accept-Patch' : ','.join(accept_patch),
  45. 'Accept-Post' : ','.join(accept_post_rdf),
  46. #'Allow' : ','.join(allow),
  47. }
  48. ## REST SERVICES ##
  49. @ldp.route('/<path:uuid>', methods=['GET'])
  50. @ldp.route('/', defaults={'uuid': None}, methods=['GET'],
  51. strict_slashes=False)
  52. def get_resource(uuid):
  53. '''
  54. Retrieve RDF or binary content.
  55. '''
  56. out_headers = std_headers
  57. pref_return = defaultdict(dict)
  58. if 'prefer' in request.headers:
  59. prefer = Translator.parse_rfc7240(request.headers['prefer'])
  60. logger.debug('Parsed Prefer header: {}'.format(prefer))
  61. if 'return' in prefer:
  62. pref_return = prefer['return']
  63. # @TODO Add conditions for LDP-NR
  64. rsrc = Ldpc(uuid)
  65. try:
  66. out = rsrc.get(pref_return=pref_return)
  67. except ResourceNotExistsError:
  68. return 'Resource #{} not found.'.format(rsrc.uuid), 404
  69. else:
  70. out_headers = rsrc.head()
  71. return (out.graph.serialize(format='turtle'), out_headers)
  72. @ldp.route('/<path:parent>', methods=['POST'])
  73. @ldp.route('/', defaults={'parent': None}, methods=['POST'],
  74. strict_slashes=False)
  75. def post_resource(parent):
  76. '''
  77. Add a new resource in a new URI.
  78. '''
  79. out_headers = std_headers
  80. try:
  81. slug = request.headers['Slug']
  82. except KeyError:
  83. slug = None
  84. if 'Content-Type' in request.headers:
  85. logger.debug('Content type: {}'.format(request.headers['Content-Type']))
  86. if request.headers['Content-Type'] in accept_post_rdf:
  87. cls = Ldpc
  88. else:
  89. cls = LdpNr
  90. else:
  91. # @TODO guess content type from magic number
  92. cls = Ldpc
  93. logger.info('POSTing resource of type: {}'.format(cls.__name__))
  94. try:
  95. rsrc = cls.inst_for_post(parent, slug)
  96. except ResourceNotExistsError as e:
  97. return str(e), 404
  98. except InvalidResourceError as e:
  99. return str(e), 409
  100. try:
  101. rsrc.post(request.get_data().decode('utf-8'))
  102. except ServerManagedTermError as e:
  103. return str(e), 412
  104. out_headers.update({
  105. 'Location' : rsrc.uri,
  106. })
  107. return rsrc.uri, out_headers, 201
  108. @ldp.route('/<path:uuid>', methods=['PUT'])
  109. def put_resource(uuid):
  110. '''
  111. Add a new resource at a specified URI.
  112. '''
  113. logger.info('Request headers: {}'.format(request.headers))
  114. rsp_headers = std_headers
  115. rsrc = Ldpc(uuid)
  116. # Parse headers.
  117. pref_handling = None
  118. if 'prefer' in request.headers:
  119. prefer = Translator.parse_rfc7240(request.headers['prefer'])
  120. logger.debug('Parsed Prefer header: {}'.format(prefer))
  121. if 'handling' in prefer:
  122. pref_handling = prefer['handling']['value']
  123. try:
  124. ret = rsrc.put(
  125. request.get_data().decode('utf-8'),
  126. handling=pref_handling
  127. )
  128. except InvalidResourceError as e:
  129. return str(e), 409
  130. except ResourceExistsError as e:
  131. return str(e), 409
  132. except ServerManagedTermError as e:
  133. return str(e), 412
  134. else:
  135. res_code = 201 if ret == BaseRdfLayout.RES_CREATED else 204
  136. return '', res_code, rsp_headers
  137. @ldp.route('/<path:uuid>', methods=['PATCH'])
  138. def patch_resource(uuid):
  139. '''
  140. Update an existing resource with a SPARQL-UPDATE payload.
  141. '''
  142. headers = std_headers
  143. rsrc = Ldpc(uuid)
  144. try:
  145. rsrc.patch(request.get_data().decode('utf-8'))
  146. except ResourceNotExistsError:
  147. return 'Resource #{} not found.'.format(rsrc.uuid), 404
  148. except ServerManagedTermError as e:
  149. return str(e), 412
  150. return '', 204, headers
  151. @ldp.route('/<path:uuid>', methods=['DELETE'])
  152. def delete_resource(uuid):
  153. '''
  154. Delete a resource.
  155. '''
  156. headers = std_headers
  157. rsrc = Ldpc(uuid)
  158. try:
  159. rsrc.delete()
  160. except ResourceNotExistsError:
  161. return 'Resource #{} not found.'.format(rsrc.uuid), 404
  162. return '', 204, headers