base_rdf_strategy.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import logging
  2. from abc import ABCMeta, abstractmethod
  3. from flask import request
  4. from rdflib import Graph
  5. from rdflib.resource import Resource
  6. from rdflib.term import URIRef
  7. from lakesuperior.connectors.graph_store_connector import GraphStoreConnector
  8. from lakesuperior.core.namespaces import ns_collection as nsc
  9. from lakesuperior.core.namespaces import ns_mgr as nsm
  10. def needs_rsrc(fn):
  11. '''
  12. Decorator for methods that cannot be called without `self.rsrc` set.
  13. '''
  14. def wrapper(self, *args, **kwargs):
  15. if not isset(self, '_rsrc') or self._rsrc is None:
  16. raise TypeError(
  17. 'This method must be called by an instance with `rsrc` set.')
  18. return fn(self, *args, **kwargs)
  19. return wrapper
  20. class BaseRdfStrategy(metaclass=ABCMeta):
  21. '''
  22. This class exposes an interface to build graph store strategies.
  23. Some store strategies are provided. New ones aimed at specific uses
  24. and optimizations of the repository may be developed by extending this
  25. class and implementing all its abstract methods.
  26. A strategy is implemented via application configuration. However, once
  27. contents are ingested in a repository, changing a strategy will most likely
  28. require a migration.
  29. The custom strategy must be in the lakesuperior.store_strategies.rdf
  30. package and the class implementing the strategy must be called
  31. `StoreStrategy`. The module name is the one defined in the app
  32. configuration.
  33. E.g. if the configuration indicates `simple_strategy` the application will
  34. look for
  35. `lakesuperior.store_strategies.rdf.simple_strategy.SimpleStrategy`.
  36. Some method naming conventions:
  37. - Methods starting with `get_` return a resource.
  38. - Methods starting with `list_` return an iterable or generator of URIs.
  39. - Methods starting with `select_` return an iterable or generator with
  40. table-like data such as from a SELECT statement.
  41. - Methods starting with `ask_` return a boolean value.
  42. '''
  43. # N.B. This is Fuseki-specific.
  44. UNION_GRAPH_URI = URIRef('urn:x-arq:UnionGraph')
  45. _logger = logging.getLogger(__module__)
  46. ## MAGIC METHODS ##
  47. def __init__(self, urn=None):
  48. '''
  49. The strategy can be initialized with a URN to make resource-centric
  50. operations simpler. However, for generic queries, urn can be None and
  51. no `self.rsrc` is assigned. In this case, some methods will not be
  52. available.
  53. '''
  54. self.conn = GraphStoreConnector()
  55. self.ds = self.conn.ds
  56. self._base_urn = urn
  57. @property
  58. def base_urn(self):
  59. '''
  60. The base URN for the current resource being handled.
  61. This value is only here for convenience. It does not preclude one from
  62. using an instance of this class with more than one subject.
  63. '''
  64. return self._base_urn
  65. @property
  66. def rsrc(self):
  67. '''
  68. Reference to a live data set that can be updated. This exposes the
  69. whole underlying triplestore structure and is used to update a
  70. resource.
  71. '''
  72. if self.base_urn is None:
  73. return None
  74. return self.ds.resource(self.base_urn)
  75. @property
  76. @abstractmethod
  77. @needs_rsrc
  78. def out_graph(self):
  79. '''
  80. Graph obtained by querying the triplestore and adding any abstraction
  81. and filtering to make up a graph that can be used for read-only,
  82. API-facing results. Different strategies can implement this in very
  83. different ways, so it is an abstract method.
  84. '''
  85. pass
  86. @property
  87. @abstractmethod
  88. @needs_rsrc
  89. def headers(self):
  90. '''
  91. Return minimal information for generating HTTP headers.
  92. '''
  93. pass
  94. ## PUBLIC METHODS ##
  95. @abstractmethod
  96. def ask_rsrc_exists(self):
  97. '''
  98. Ask if a resource exists (is stored) in the graph store.
  99. @param rsrc (rdflib.resource.Resource) If this is provided, this method
  100. will look for the specified resource. Otherwise, it will look for the
  101. default resource. If this latter is not specified, the result is False.
  102. @return boolean
  103. '''
  104. pass
  105. @abstractmethod
  106. @needs_rsrc
  107. def create_or_replace_rsrc(self, urn, data, commit=True):
  108. '''Create a resource graph in the main graph if it does not exist.
  109. If it exists, replace the existing one retaining the creation date.
  110. '''
  111. pass
  112. @abstractmethod
  113. @needs_rsrc
  114. def create_rsrc(self, urn, data, commit=True):
  115. '''Create a resource graph in the main graph.
  116. If the resource exists, raise an exception.
  117. '''
  118. pass
  119. @abstractmethod
  120. @needs_rsrc
  121. def patch_rsrc(self, urn, data, commit=False):
  122. '''
  123. Perform a SPARQL UPDATE on a resource.
  124. '''
  125. pass
  126. @abstractmethod
  127. @needs_rsrc
  128. def delete_rsrc(self, urn, commit=True):
  129. pass