__init__.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import logging
  2. import threading
  3. from collections import deque
  4. from importlib import import_module
  5. from os import path
  6. logger = logging.getLogger(__name__)
  7. version = '1.0 alpha'
  8. release = '1.0.0a22'
  9. basedir = path.dirname(path.realpath(__file__))
  10. """
  11. Base directory for the module.
  12. This can be used by modules looking for configuration and data files to be
  13. referenced or copied with a known path relative to the package root.
  14. :rtype: str
  15. """
  16. class Env:
  17. """
  18. Lakesuperior environment.
  19. Instances of this class contain the environment necessary to run a
  20. self-standing instance of Lakesuperior in a Python environment.
  21. """
  22. def setup(self, config_dir=None, config=None):
  23. """
  24. Set the environment up.
  25. This must be done before using the application.
  26. This method will warn and not do anything if it has already been
  27. called in the same runtime environment,
  28. :param str config_dir: Path to a directory containing the
  29. configuration ``.yml`` files. If this and ``config`` are omitted,
  30. the configuration files are read from the default directory defined
  31. in :py:meth:`~lakesuperior.config_parser.parse_config()`.
  32. :param dict config: Fully-formed configuration as a dictionary. If
  33. this is provided, ``config_dir`` is ignored. This is useful to
  34. call ``parse_config()`` separately and modify the configuration
  35. manually before passing it to the setup.
  36. """
  37. if hasattr(self, 'app_globals'):
  38. logger.warning('The environment is already set up.')
  39. return
  40. if not config:
  41. from .config_parser import parse_config
  42. config = parse_config(config_dir)
  43. self.app_globals = _AppGlobals(config)
  44. env = Env()
  45. """
  46. A pox on "globals are evil".
  47. Object for storing global variables. Different environments
  48. (e.g. webapp, test suite) put the appropriate value in it.
  49. The most important values to be stored are app_conf (either from
  50. lakesuperior.config_parser.config or lakesuperior.config_parser.test_config)
  51. and app_globals (obtained by an instance of lakesuperior.globals.AppGlobals).
  52. e.g.::
  53. >>> from lakesuperior import env
  54. >>> env.setup()
  55. Or, with a custom configuration directory::
  56. >>> from lakesuperior import env
  57. >>> env.setup('/my/config/dir')
  58. Or, to load a configuration and modify it before setting up the environment::
  59. >>> from lakesuperior import env
  60. >>> from lakesuperior.config_parser import parse_config
  61. >>> config = parse_config(config_dir)
  62. >>> config['application']['data_dir'] = '/data/ext/mystore'
  63. >>> env.setup(config=config)
  64. :rtype: Object
  65. """
  66. thread_env = threading.local()
  67. """
  68. Thread-local environment.
  69. This is used to store thread-specific variables such as start/end request
  70. timestamps.
  71. :rtype: threading.local
  72. """
  73. ## Private members. Nothing interesting here.
  74. class _AppGlobals:
  75. """
  76. Application Globals.
  77. This class is instantiated and used as a carrier for all connections and
  78. various global variables outside of the Flask app context.
  79. The variables are set on initialization by passing a configuration dict.
  80. Usually this is done when starting an application. The instance with the
  81. loaded variables is then assigned to the :data:`lakesuperior.env`
  82. global variable.
  83. :see_also: lakesuperior.env.setup()
  84. """
  85. def __init__(self, config):
  86. """
  87. Generate global variables from configuration.
  88. """
  89. ## Initialize metadata store.
  90. #from lakesuperior.store.metadata_store import MetadataStore
  91. # Exposed globals.
  92. self._config = config
  93. #self._md_store = MetadataStore(path.join(
  94. # self.config['application']['data_dir'], 'metadata'),
  95. # create=True)
  96. self._changelog = deque()
  97. @property
  98. def config(self):
  99. """
  100. Global configuration.
  101. This is a collection of all configuration options **except** for the
  102. WSGI configuration which is initialized at a different time and is
  103. stored under :data:`lakesuperior.env.wsgi_options`.
  104. *TODO:* Update class reference when interface will be separated from
  105. implementation.
  106. """
  107. return self._config
  108. @property
  109. def rdfly(self):
  110. """
  111. Current RDF layout.
  112. Lazy loaded because it needs the config to be set up.
  113. This is an instance of
  114. :class:`~lakesuperior.store.ldp_rs.rsrc_centric_layout.RsrcCentricLayout`.
  115. *TODO:* Update class reference when interface will be separated from
  116. implementation.
  117. """
  118. if not hasattr(self, '_rdfly'):
  119. # Initialize RDF layout.
  120. rdfly_mod_name = (
  121. self.config['application']['store']['ldp_rs']['layout'])
  122. rdfly_mod = import_module('lakesuperior.store.ldp_rs.{}'.format(
  123. rdfly_mod_name))
  124. rdfly_cls = getattr(rdfly_mod, self.camelcase(rdfly_mod_name))
  125. #logger.info('RDF layout: {}'.format(rdfly_mod_name))
  126. self._rdfly = rdfly_cls(
  127. self.config['application']['store']['ldp_rs'])
  128. return self._rdfly
  129. @property
  130. def rdf_store(self):
  131. """
  132. Current RDF low-level store.
  133. Lazy loaded because it needs the config to be set up.
  134. This is an instance of
  135. :class:`~lakesuperior.store.ldp_rs.lmdb_store.LmdbStore`.
  136. """
  137. return self.rdfly.store
  138. @property
  139. def nonrdfly(self):
  140. """
  141. Current non-RDF (binary contents) layout.
  142. Lazy loaded because it needs the config to be set up.
  143. This is an instance of
  144. :class:`~lakesuperior.store.ldp_nr.base_non_rdf_layout.BaseNonRdfLayout`.
  145. """
  146. if not hasattr(self, '_nonrdfly'):
  147. # Initialize file layout.
  148. nonrdfly_mod_name = (
  149. self.config['application']['store']['ldp_nr']['layout'])
  150. nonrdfly_mod = import_module('lakesuperior.store.ldp_nr.{}'.format(
  151. nonrdfly_mod_name))
  152. nonrdfly_cls = getattr(nonrdfly_mod, self.camelcase(nonrdfly_mod_name))
  153. #logger.info('Non-RDF layout: {}'.format(nonrdfly_mod_name))
  154. self._nonrdfly = nonrdfly_cls(
  155. self.config['application']['store']['ldp_nr'])
  156. return self._nonrdfly
  157. #@property
  158. #def md_store(self):
  159. # """
  160. # Metadata store (LMDB).
  161. # This is an instance of
  162. # :class:`~lakesuperior.store.metadata_store.MetadataStore`.
  163. # """
  164. # return self._md_store
  165. @property
  166. def messenger(self):
  167. """
  168. Current message handler.
  169. Lazy loaded because it needs the config to be set up.
  170. This is an instance of
  171. :class:`~lakesuperior.messaging.messenger.Messenger`.
  172. """
  173. if not hasattr(self, '_messenger'):
  174. from lakesuperior.messaging.messenger import Messenger
  175. self._messenger = Messenger(
  176. self.config['application']['messaging'])
  177. return self._messenger
  178. @property
  179. def changelog(self):
  180. return self._changelog
  181. @staticmethod
  182. def camelcase(word):
  183. """
  184. Convert a string with underscores to a camel-cased one.
  185. Ripped from https://stackoverflow.com/a/6425628
  186. """
  187. return ''.join(x.capitalize() or '_' for x in word.split('_'))