ldp_factory.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import logging
  2. from pprint import pformat
  3. from uuid import uuid4
  4. from rdflib.resource import Resource
  5. from rdflib.namespace import RDF
  6. from lakesuperior import env
  7. from lakesuperior import exceptions as exc
  8. from lakesuperior.model.ldp.ldpr import Ldpr
  9. from lakesuperior.model.ldp.ldp_nr import LdpNr
  10. from lakesuperior.model.ldp.ldp_rs import LdpRs, Ldpc, LdpDc, LdpIc
  11. from lakesuperior.config_parser import config
  12. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  13. from lakesuperior.model.rdf.graph import Graph, from_rdf
  14. from lakesuperior.util.toolbox import rel_uri_to_urn
  15. LDP_NR_TYPE = nsc['ldp'].NonRDFSource
  16. LDP_RS_TYPE = nsc['ldp'].RDFSource
  17. rdfly = env.app_globals.rdfly
  18. logger = logging.getLogger(__name__)
  19. class LdpFactory:
  20. """
  21. Generate LDP instances.
  22. The instance classes are based on provided client data or on stored data.
  23. """
  24. @staticmethod
  25. def new_container(uid):
  26. if not uid.startswith('/') or uid == '/':
  27. raise exc.InvalidResourceError(uid)
  28. if rdfly.ask_rsrc_exists(uid):
  29. raise exc.ResourceExistsError(uid)
  30. rsrc = Ldpc(uid, provided_imr=Graph(uri=nsc['fcres'][uid]))
  31. return rsrc
  32. @staticmethod
  33. def from_stored(uid, ver_label=None, repr_opts={}, strict=True, **kwargs):
  34. """
  35. Create an instance for retrieval purposes.
  36. This factory method creates and returns an instance of an LDPR subclass
  37. based on information that needs to be queried from the underlying
  38. g"http://api.edu/location/id/1234/thumbnail"raph store.
  39. N.B. The resource must exist.
  40. :param str uid: UID of the instance.
  41. """
  42. # This will blow up if strict is True and the resource is a tombstone.
  43. rsrc_meta = rdfly.get_metadata(uid, strict=strict)
  44. rdf_types = rsrc_meta[nsc['fcres'][uid] : RDF.type]
  45. if LDP_NR_TYPE in rdf_types:
  46. logger.info('Resource is a LDP-NR.')
  47. cls = LdpNr
  48. elif LDP_RS_TYPE in rdf_types:
  49. logger.info('Resource is a LDP-RS.')
  50. cls = LdpRs
  51. else:
  52. raise exc.ResourceNotExistsError(uid)
  53. rsrc = cls(uid, repr_opts, **kwargs)
  54. # Sneak in the already extracted metadata to save a query.
  55. rsrc._metadata = rsrc_meta
  56. return rsrc
  57. @staticmethod
  58. def from_provided(
  59. uid, mimetype=None, stream=None, graph=None, rdf_data=None,
  60. rdf_fmt=None, **kwargs):
  61. r"""
  62. Create and LDPR instance from provided data.
  63. the LDP class (LDP-RS, LDP_NR, etc.) is determined by the contents
  64. passed.
  65. :param str uid: UID of the resource to be created or updated.
  66. :param str mimetype: The provided content MIME type. If this is
  67. specified the resource is considered a LDP-NR and a ``stream``
  68. *must* be provided.
  69. :param IOStream stream: The provided data stream.
  70. :param rdflib.Graph graph: Initial graph to populate the
  71. resource with. This can be used for LDP-RS and LDP-NR types alike.
  72. :param bytes rdf_data: Serialized RDF to build the initial graph.
  73. :param str rdf_fmt: Serialization format of RDF data.
  74. :param \*\*kwargs: Arguments passed to the LDP class constructor.
  75. :raise ValueError: if ``mimetype`` is specified but no data stream is
  76. provided.
  77. """
  78. uri = nsc['fcres'][uid]
  79. if rdf_data:
  80. try:
  81. provided_imr = from_rdf(
  82. uri=uri, data=rdf_data,
  83. format=rdf_fmt, publicID=nsc['fcres'][uid])
  84. except Exception as e:
  85. raise exc.RdfParsingError(rdf_fmt, str(e))
  86. elif graph:
  87. provided_imr = Graph(
  88. uri=uri, data={
  89. (rel_uri_to_urn(s, uid), p, rel_uri_to_urn(o, uid))
  90. for s, p, o in graph
  91. }
  92. )
  93. else:
  94. provided_imr = Graph(uri=uri)
  95. #logger.debug('Provided graph: {}'.format(
  96. # pformat(set(provided_imr))))
  97. if stream is None:
  98. # Resource is a LDP-RS.
  99. if mimetype:
  100. raise ValueError(
  101. 'Binary stream must be provided if mimetype is specified.')
  102. # Determine whether it is a basic, direct or indirect container.
  103. if provided_imr[nsc['rdf'].type] == nsc['ldp'].IndirectContainer:
  104. cls = LdpIc
  105. elif provided_imr[nsc['rdf'].type] == nsc['ldp'].DirectContainer:
  106. cls = LdpDc
  107. else:
  108. cls = Ldpc
  109. inst = cls(uid, provided_imr=provided_imr, **kwargs)
  110. # Make sure we are not updating an LDP-NR with an LDP-RS.
  111. if inst.is_stored and LDP_NR_TYPE in inst.ldp_types:
  112. raise exc.IncompatibleLdpTypeError(uid, mimetype)
  113. if kwargs.get('handling', 'strict') != 'none':
  114. inst.check_mgd_terms(inst.provided_imr)
  115. else:
  116. # Resource is a LDP-NR.
  117. if not mimetype:
  118. mimetype = 'application/octet-stream'
  119. inst = LdpNr(uid, stream=stream, mimetype=mimetype,
  120. provided_imr=provided_imr, **kwargs)
  121. # Make sure we are not updating an LDP-RS with an LDP-NR.
  122. if inst.is_stored and LDP_RS_TYPE in inst.ldp_types:
  123. raise exc.IncompatibleLdpTypeError(uid, mimetype)
  124. logger.debug('Creating resource of type: {}'.format(
  125. inst.__class__.__name__))
  126. return inst
  127. @staticmethod
  128. def mint_uid(parent_uid, path=None):
  129. """
  130. Mint a new resource UID based on client directives.
  131. This method takes a parent ID and a tentative path and returns an LDP
  132. resource UID.
  133. This may raise an exception resulting in a 404 if the parent is not
  134. found or a 409 if the parent is not a valid container.
  135. :param str parent_uid: UID of the parent resource. It must be an
  136. existing LDPC.
  137. :param str path: path to the resource, relative to the parent.
  138. :rtype: str
  139. :return: The confirmed resource UID. This may be different from
  140. what has been indicated.
  141. """
  142. if path and path.startswith('/'):
  143. raise ValueError('Slug cannot start with a slash.')
  144. # Shortcut!
  145. if not path and parent_uid == '/':
  146. return f'/{uuid4()}'
  147. if not parent_uid.startswith('/'):
  148. raise ValueError('Invalid parent UID: {}'.format(parent_uid))
  149. parent = LdpFactory.from_stored(parent_uid)
  150. if nsc['ldp'].Container not in parent.types:
  151. raise exc.InvalidResourceError(parent_uid,
  152. 'Parent {} is not a container.')
  153. pfx = parent_uid.rstrip('/') + '/'
  154. if path:
  155. cnd_uid = pfx + path
  156. if not rdfly.ask_rsrc_exists(cnd_uid):
  157. return cnd_uid
  158. return f'{pfx}{uuid4()}'