Source code for PyOpenWorm

# -*- coding: utf-8 -*-

"""
.. _pow_module:

PyOpenWorm
==========

OpenWorm Unified Data Abstract Layer.

An introduction to PyOpenWorm can be found in the README on our
`Github page <https://github.com/openworm/PyOpenWorm>`_.

Most statements correspond to some action on the database.
Some of these actions may be complex, but intuitively ``a.B()``, the Query form,
will query against the database for the value or values that are related to ``a`` through ``B``;
on the other hand, ``a.B(c)``, the Update form, will add a statement to the database that ``a``
relates to ``c`` through ``B``. For the Update form, a Statement object describing the
relationship stated is returned as a side-effect of the update.

The Update form can also be accessed through the set() method of a Property and the Query form through the get()
method like::

    a.B.set(c)

and::

    a.B.get()

The get() method also allows for parameterizing the query in ways specific to the Property.

"""

from __future__ import print_function
__version__ = '0.11.2'
__author__ = 'Stephen Larson'

import sys
import os
import logging

LOGGER = logging.getLogger(__name__)

BASE_SCHEMA_URL = 'http://openworm.org/schema'

# The c extensions are incompatible with our code...
os.environ['WRAPT_DISABLE_EXTENSIONS'] = '1'
from .import_override import Overrider
from .module_recorder import ModuleRecorder as MR
from .mapper import Mapper

ImportOverrider = None
ModuleRecorder = None


BASE_MAPPER = Mapper(base_class_names=('PyOpenWorm.dataObject.DataObject',
    'PyOpenWorm.simpleProperty.RealSimpleProperty'))
'''
Handles some of the PyOpenWorm DataObjects regardless of whether there's been any connection. Used by Contexts outside
of a connection.
'''


def install_module_import_wrapper():
    global ImportOverrider
    global ModuleRecorder

    if ImportOverrider is None:
        ModuleRecorder = MR()
        ImportOverrider = Overrider(mapper=ModuleRecorder)
        ImportOverrider.wrap_import()
        ImportOverrider.install_excepthook()
    else:
        LOGGER.info("Import overrider already installed")
    return ImportOverrider


install_module_import_wrapper()
ModuleRecorder.add_listener(BASE_MAPPER)
from .configure import Configureable
from .context import Context
import yarom

__all__ = [
    "get_data",
    "loadConfig",
    "loadData",
    "disconnect",
    "connect",
    "config",
    ]

DEF_CTX = Context()

RDF_CONTEXT = Context(ident='http://www.w3.org/1999/02/22-rdf-syntax-ns',
                      base_namespace='http://www.w3.org/1999/02/22-rdf-syntax-ns#')

RDFS_CONTEXT = Context(ident='http://www.w3.org/2000/01/rdf-schema',
                       imported=(RDF_CONTEXT,),
                       base_namespace='http://www.w3.org/2000/01/rdf-schema#')

BASE_CONTEXT = Context(imported=(RDFS_CONTEXT,),
                       ident=BASE_SCHEMA_URL,
                       base_namespace=BASE_SCHEMA_URL + '#')

SCI_CTX = Context(imported=(BASE_CONTEXT,),
                  ident=BASE_SCHEMA_URL + '/sci',
                  base_namespace=BASE_SCHEMA_URL + '/sci#')

SCI_BIO_CTX = Context(imported=(SCI_CTX,),
                      ident=BASE_SCHEMA_URL + '/sci/bio',
                      base_namespace=BASE_SCHEMA_URL + '/sci/bio#')

CONTEXT = Context(imported=(SCI_BIO_CTX,),
                  ident=BASE_SCHEMA_URL + '/bio',
                  base_namespace=BASE_SCHEMA_URL + '/bio#')


def get_data(path):
    # get a resource from the installed package location

    from sysconfig import get_path
    from pkgutil import get_loader
    from glob import glob
    package_paths = glob(os.path.join(get_path('platlib'), '*'))
    sys.path = package_paths + sys.path
    installed_package_root = os.path.dirname(get_loader('PyOpenWorm').get_filename())
    sys.path = sys.path[len(package_paths):]
    filename = os.path.join(installed_package_root, path)
    return filename


[docs]def config(key=None): """ Gets the main configuration for the whole PyOpenWorm library. :return: the instance of the Configure class currently operating. """ if key is None: return Configureable.default else: return Configureable.default[key]
class Connection(object): def __init__(self, conf): self.conf = conf def disconnect(self): self.conf.closeDatabase() ModuleRecorder.remove_listener(self.conf['mapper']) def __enter__(self): return self def __exit__(self, *args): self.disconnect()
[docs]def loadConfig(f): """ Load configuration for the module. """ from .data import Data return Data.open(f)
[docs]def disconnect(c=False): """ Close the database. """ if c: c.disconnect()
[docs]def loadData( conf, data='OpenWormData/WormData.n3', dataFormat='n3', skipIfNewer=False): """ Load data into the underlying database of this library. XXX: This is only guaranteed to work with the ZODB database. :param data: (Optional) Specify the file to load into the library :param dataFormat: (Optional) Specify the file format to load into the library. Currently n3 is supported :param skipIfNewer: (Optional) Skips loading of data if the database file is newer than the data to be loaded in. This is determined by the modified time on the main database file compared to the modified time on the data file. """ import logging if not os.path.isfile(data): raise Exception("No such data file: " + data) if skipIfNewer: try: db_file_name = conf['rdf.store.conf'] if os.path.isfile(db_file_name): data_file_time = os.path.getmtime(data) db_file_time = os.path.getmtime(conf['rdf.store_conf']) if data_file_time < db_file_time: return except Exception as e: logging.exception("Failed to determine if the serialized data file is older than the binary database." " The data file will be reloaded. Reason: {}".format(e.message)) sys.stderr.write("[PyOpenWorm] Loading data into the graph; this may take several minutes!!\n") conf['rdf.graph'].parse(data, format=dataFormat)
class ConnectionFailError(Exception): def __init__(self, cause, *args): if args: super(ConnectionFailError, self).__init__('PyOpenWorm connection failed: {}. {}'.format(cause, *args)) else: super(ConnectionFailError, self).__init__('PyOpenWorm connection failed: {}'.format(cause))
[docs]def connect(configFile=False, conf=None, do_logging=False, data=False, dataFormat='n3'): """ Load desired configuration and open the database :param configFile: (Optional) The configuration file for PyOpenWorm :param conf: (Optional) a configuration object for the connection. Takes precedence over `configFile` :param do_logging: (Optional) If true, turn on debug level logging :param data: (Optional) specify the file to load into the library :param dataFormat: (Optional) file format of `data`. Currently n3 is supported """ import logging from .data import Data, ZODBSourceOpenFailError, DatabaseConflict if do_logging: logging.basicConfig(level=logging.DEBUG) if conf: if not isinstance(conf, Data): conf = Data(conf) elif configFile: conf = Data.open(configFile) else: conf = Data({ "rdf.source": "ZODB", "rdf.store": "ZODB", "rdf.store_conf": get_data('worm.db'), "rdf.upload_block_statement_count": 50 }) try: conf.init_database() except ZODBSourceOpenFailError as e: # Special handling for a common user error with pow which, nonetheless, # may be encontered when *not* using pow if e.openstr.endswith('.pow/worm.db'): raise ConnectionFailError(e, 'Perhaps you need to do a `pow clone`?') raise ConnectionFailError(e) except DatabaseConflict as e: raise ConnectionFailError(e, "It looks like a connection is already opened by a living process") except Exception as e: raise ConnectionFailError(e) logging.getLogger('PyOpenWorm').info("Connected to database") if data: loadData(conf, data, dataFormat) # Base class names is empty because we won't be adding any objects to the # context automatically mapper = Mapper(base_class_names=('PyOpenWorm.dataObject.DataObject', 'PyOpenWorm.simpleProperty.RealSimpleProperty')) conf['mapper'] = mapper # An "empty" context, that serves as the default when no context is defined yarom.MAPPER = mapper ModuleRecorder.add_listener(mapper) return Connection(conf)