Skip to content

Commit

Permalink
Config loading and parsing restructuring:
Browse files Browse the repository at this point in the history
* Move AppGlobals to lakesuperior module and remove app_globals module
* Change env setup method
* Add deprecation notice for lakesuperior.env_setup
* Do not load WSGI config if not running the WSGI server
* Do not set LMDB readers based on WSGI parameters
* Move some constants to more specific modules
* Move core namespaces to new `core_config` folder
  • Loading branch information
scossu committed Feb 20, 2020
1 parent 4427a7b commit 5dea35d
Show file tree
Hide file tree
Showing 28 changed files with 319 additions and 280 deletions.
3 changes: 1 addition & 2 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from lakesuperior import env
from lakesuperior.config_parser import parse_config
from lakesuperior.globals import AppGlobals
from lakesuperior.util.generators import random_image


Expand All @@ -20,7 +19,7 @@
config['application']['store']['ldp_rs']['location'] = (
path.join(data_dir, 'ldprs_store'))

env.app_globals = AppGlobals(config)
env.setup(config=config)
from lakesuperior.app import create_app


Expand Down
27 changes: 21 additions & 6 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,33 @@ Python API
Set up the environment
~~~~~~~~~~~~~~~~~~~~~~

Before using the API, either do::
Before importing the API modules or other Lakesuperior modules, the environment
must be set up. This can be done in several ways. The simplest one is to rely
on a default configuration directory set up by the ``FCREPO_CONFIG_DIR``
environment variable::

>>> import lakesuperior.env_setup
>>> from lakesuperior import env
>>> env.setup()
Reading configuration at /my/default/config_dir

If ``FCREPO_CONFIG_DIR`` is not set up, the ``etc.defaults`` location under
the library root is used.

Alternatively, a custom configuration directory can be specified::

>>> from lakesuperior import env
>>> env.setup('/my/config/dir')
Reading configuration at /my/custom/config_dir

Or, to specify an alternative configuration::
A configuration can also be loaded and modified before setting up the
environment::

>>> from lakesuperior import env
>>> from lakesuperior.config_parser import parse_config
>>> from lakesuperior.globals import AppGlobals
>>> config = parse_config('/my/custom/config_dir')
>>> config = parse_config('/my/config/dir')
Reading configuration at /my/custom/config_dir
>>> env.app_globals = AppGlobals(config)
>>> config['application']['data_dir'] = '/data/ext/mystore'
>>> env.setup(config=config)

Create and replace resources
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
204 changes: 197 additions & 7 deletions lakesuperior/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import logging
import threading

from collections import deque
from importlib import import_module
from os import path


logger = logging.getLogger(__name__)

version = '1.0 alpha'
release = '1.0.0a22'

Expand All @@ -23,27 +28,64 @@ class Env:
Instances of this class contain the environment necessary to run a
self-standing instance of Lakesuperior in a Python environment.
"""
pass

def setup(self, config_dir=None, config=None):
"""
Set the environment up.
This must be done before using the application.
This method will warn and not do anything if it has already been
called in the same runtime environment,
:param str config_dir: Path to a directory containing the
configuration ``.yml`` files. If this and ``config`` are omitted,
the configuration files are read from the default directory defined
in :py:meth:`~lakesuperior.config_parser.parse_config()`.
:param dict config: Fully-formed configuration as a dictionary. If
this is provided, ``config_dir`` is ignored. This is useful to
call ``parse_config()`` separately and modify the configuration
manually before passing it to the setup.
"""
if hasattr(self, 'app_globals'):
logger.warn('The environment is already set up.')
return

if not config:
from .config_parser import parse_config
config = parse_config(config_dir)

self.app_globals = _AppGlobals(config)


env = Env()
"""
A pox on "globals are evil".
All-purpose bucket for storing global variables. Different environments
Object for storing global variables. Different environments
(e.g. webapp, test suite) put the appropriate value in it.
The most important values to be stored are app_conf (either from
lakesuperior.config_parser.config or lakesuperior.config_parser.test_config)
and app_globals (obtained by an instance of lakesuperior.globals.AppGlobals).
e.g.::
>>> from lakesuperior.config_parser import config
>>> from lakesuperior.globals import AppGlobals
>>> from lakesuperior import env
>>> env.app_globals = AppGlobals(config)
>>> env.setup()
Or, with a custom configuration directory::
>>> from lakesuperior import env
>>> env.setup('/my/config/dir')
This is automated in non-test environments by importing
`lakesuperior.env_setup`.
Or, to load a configuration and modify it before setting up the environment::
>>> from lakesuperior import env
>>> from lakesuperior.config_parser import parse_config
>>> config = parse_config(config_dir)
>>> config['application']['data_dir'] = '/data/ext/mystore'
>>> env.setup(config=config)
:rtype: Object
"""
Expand All @@ -57,3 +99,151 @@ class Env:
:rtype: threading.local
"""


## Private members. Nothing interesting here.

class _AppGlobals:
"""
Application Globals.
This class is instantiated and used as a carrier for all connections and
various global variables outside of the Flask app context.
The variables are set on initialization by passing a configuration dict.
Usually this is done when starting an application. The instance with the
loaded variables is then assigned to the :data:`lakesuperior.env`
global variable.
:see_also: lakesuperior.env.setup()
"""
def __init__(self, config):
"""
Generate global variables from configuration.
"""
## Initialize metadata store.
#from lakesuperior.store.metadata_store import MetadataStore

# Exposed globals.
self._config = config
#self._md_store = MetadataStore(path.join(
# self.config['application']['data_dir'], 'metadata'),
# create=True)
self._changelog = deque()


@property
def config(self):
"""
Global configuration.
This is a collection of all configuration options **except** for the
WSGI configuration which is initialized at a different time and is
stored under :data:`lakesuperior.env.wsgi_options`.
*TODO:* Update class reference when interface will be separated from
implementation.
"""
return self._config

@property
def rdfly(self):
"""
Current RDF layout.
Lazy loaded because it needs the config to be set up.
This is an instance of
:class:`~lakesuperior.store.ldp_rs.rsrc_centric_layout.RsrcCentricLayout`.
*TODO:* Update class reference when interface will be separated from
implementation.
"""
if not hasattr(self, '_rdfly'):
# Initialize RDF layout.
rdfly_mod_name = (
self.config['application']['store']['ldp_rs']['layout'])
rdfly_mod = import_module('lakesuperior.store.ldp_rs.{}'.format(
rdfly_mod_name))
rdfly_cls = getattr(rdfly_mod, self.camelcase(rdfly_mod_name))
#logger.info('RDF layout: {}'.format(rdfly_mod_name))
self._rdfly = rdfly_cls(
self.config['application']['store']['ldp_rs'])

return self._rdfly

@property
def rdf_store(self):
"""
Current RDF low-level store.
Lazy loaded because it needs the config to be set up.
This is an instance of
:class:`~lakesuperior.store.ldp_rs.lmdb_store.LmdbStore`.
"""
return self.rdfly.store

@property
def nonrdfly(self):
"""
Current non-RDF (binary contents) layout.
Lazy loaded because it needs the config to be set up.
This is an instance of
:class:`~lakesuperior.store.ldp_nr.base_non_rdf_layout.BaseNonRdfLayout`.
"""
if not hasattr(self, '_nonrdfly'):
# Initialize file layout.
nonrdfly_mod_name = (
self.config['application']['store']['ldp_nr']['layout'])
nonrdfly_mod = import_module('lakesuperior.store.ldp_nr.{}'.format(
nonrdfly_mod_name))
nonrdfly_cls = getattr(nonrdfly_mod, self.camelcase(nonrdfly_mod_name))
#logger.info('Non-RDF layout: {}'.format(nonrdfly_mod_name))
self._nonrdfly = nonrdfly_cls(
self.config['application']['store']['ldp_nr'])
return self._nonrdfly

#@property
#def md_store(self):
# """
# Metadata store (LMDB).

# This is an instance of
# :class:`~lakesuperior.store.metadata_store.MetadataStore`.
# """
# return self._md_store

@property
def messenger(self):
"""
Current message handler.
Lazy loaded because it needs the config to be set up.
This is an instance of
:class:`~lakesuperior.messaging.messenger.Messenger`.
"""
if not hasattr(self, '_messenger'):
from lakesuperior.messaging.messenger import Messenger
self._messenger = Messenger(
self.config['application']['messaging'])

return self._messenger

@property
def changelog(self):
return self._changelog


@staticmethod
def camelcase(word):
"""
Convert a string with underscores to a camel-cased one.
Ripped from https://stackoverflow.com/a/6425628
"""
return ''.join(x.capitalize() or '_' for x in word.split('_'))
8 changes: 4 additions & 4 deletions lakesuperior/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
import logging

from lakesuperior import env
from lakesuperior.config_parser import parse_config
from lakesuperior.dictionaries.namespaces import ns_collection as nsc
from lakesuperior.exceptions import (
ChecksumValidationError, IncompatibleLdpTypeError)
from lakesuperior.migrator import Migrator
from lakesuperior.store.ldp_nr.default_layout import DefaultLayout as FileLayout

__doc__ = """
Admin API.
Expand All @@ -25,7 +22,7 @@ def stats():
:rtype: dict
:return: Store statistics, resource statistics.
"""
import lakesuperior.env_setup
env.setup()
with env.app_globals.rdf_store.txn_ctx():
repo_stats = {
'rsrc_stats': env.app_globals.rdfly.count_rsrc(),
Expand Down Expand Up @@ -62,6 +59,7 @@ def integrity_check():
At the moment this is limited to referential integrity. Other checks can
be added and triggered by different argument flags.
"""
env.setup()
with env.app_globals.rdfly.store.txn_ctx():
return set(env.app_globals.rdfly.find_refint_violations())

Expand All @@ -82,7 +80,9 @@ def fixity_check(uid):
:raises: lakesuperior.exceptions.IncompatibleLdpTypeError: if the
resource is not an LDP-NR.
"""
env.setup()
from lakesuperior.api import resource as rsrc_api
from lakesuperior.dictionaries.namespaces import ns_collection as nsc
from lakesuperior.model.ldp.ldp_factory import LDP_NR_TYPE

rsrc = rsrc_api.get(uid)
Expand Down
7 changes: 3 additions & 4 deletions lakesuperior/api/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@
from rdflib import Literal
from rdflib.namespace import XSD

from lakesuperior.config_parser import config
from lakesuperior.dictionaries.namespaces import ns_collection as nsc
from lakesuperior.exceptions import (
InvalidResourceError, ResourceNotExistsError, TombstoneError)
from lakesuperior import env, thread_env
from lakesuperior.globals import RES_DELETED, RES_UPDATED
from lakesuperior.model.ldp.ldp_factory import LDP_NR_TYPE, LdpFactory
from lakesuperior.model.ldp.ldpr import Ldpr
from lakesuperior.model.ldp.ldpr import RES_UPDATED, Ldpr
from lakesuperior.util.toolbox import rel_uri_to_urn


Expand All @@ -29,7 +27,8 @@
Quickstart::
>>> # First import default configuration and globals—only done once.
>>> import lakesuperior.default_env
>>> from lakesuperior import env
>>> env.setup()
>>> from lakesuperior.api import resource
>>> # Get root resource.
>>> rsrc = resource.get('/')
Expand Down
Loading

0 comments on commit 5dea35d

Please sign in to comment.