Source code for summer.context

# -*- coding: utf-8 -*-
# Time-stamp: < context.py (2016-02-14 02:25) >

# Copyright (C) 2009-2016 Martin Slouf <martin.slouf@sourceforge.net>
#
# This file is a part of Summer.
#
# Summer is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Module ``context`` defines an application context (:py:class:`Context`
class)-- a container for (business) objects, that should be available
throughout the application, application layer or module.

"""

from configparser import SafeConfigParser
import logging
import os

from . import l10n
from . import lsf
from . import sf
from . import utils
from .ex import (
    SummerConfigurationException,
    NoObjectFoundException,
)

logger = logging.getLogger(__name__)

# summer config sections
SQLALCHEMY = "SQLALCHEMY"
L10N = "L10N"
LDAP = "LDAP"


[docs]class Context(object): """Context is inteligent container for your business objects, it is a core of *summer framework*. It is responsible for: #. summer framework initialization (reading config files) #. instantiating your business classes with their interdependencies #. proxy creation It uses convention over configuration approach, so it supposes specific config file names etc, though much of it may be overwritten. Emulates mapping type, so you can access business objects by their name if required. Usually in medium sized applications, you define single global context somewhere in main entry point of your program. """
[docs] def __init__(self, path_to_module: str, customcfg: str=None, summercfg: str='summer.cfg'): """Context initialization is separated into several steps: #. call :py:meth:`core_init` (not indended to be overriden) which parses config files and creates core objects managing key resources (sql database, ldap server, localization) #. call :py:meth:`orm_init` (intended to be overriden) which initializes ORM #. call :py:meth:`context_init` (intended to be overriden) -- a place to define your business beans Args: path_to_module (str): path to module, usually pass ``__file__`` built-in. It will look for config files up to several directories levels up. customcfg (str): name of custom config file summercfg (str): name of summer config file """ logger.info("context initialization started") self.path_to_module = path_to_module self.customcfg = customcfg self.summercfg = summercfg self.summer_config = None self.session_factory = None self.ldap_session_factory = None self.l10n = None self.config = None logger.info("initializing core services") self.core_init() logger.info("core services initialized") logger.info("initializing orm") self.orm_init() logger.info("orm initialized") logger.info("custom context initialization started") self.context_init() logger.info("custom context initialization done") logger.info("context initialization done")
[docs] def __del__(self): """Calls :py:meth:`context_shutdown` to properly shutdown the context.""" self.context_shutdown()
[docs] def core_init(self): """Initializes core objects based on :py:attr:`summer_config`.""" # summer config self.summer_config = SafeConfigParser() self.summer_config.read( utils.locate_file(self.path_to_module, self.summercfg)) # session factory logger.info("creating session factory") self.session_factory = self._create_session_factory() logger.info("session factory created %s", self.session_factory) # ldap_session_facory logger.info("creating ldap session factory") self.ldap_session_factory = self._create_ldap_session_factory() logger.info("ldap session factory created %s", self.ldap_session_factory) # l10n logger.info("creating l10n services") self.l10n = self._create_l10n() logger.info("l10n services created %s", self.l10n) # custom config if self.customcfg != None: logger.info("loadig custom config file") self.config = SafeConfigParser() self.config.read( utils.locate_file(self.path_to_module, self.customcfg)) logger.info("custom config file loaded %s", self.config)
[docs] def _create_session_factory(self): """Can be overriden by subclasses (if ORM is in use). Creates session factory. Returns: SessionFactory: :py:class:`summer.sf.SessionFactory` instance. """ config = self.summer_config session_factory = None if config.has_section(SQLALCHEMY): uri = config.get(SQLALCHEMY, "uri") echo = config.getboolean(SQLALCHEMY, "echo") autoflush = config.getboolean(SQLALCHEMY, "autoflush") autocommit = config.getboolean(SQLALCHEMY, "autocommit") session_factory = sf.SessionFactory(uri, echo, autoflush, autocommit) else: logger.info("session factory not initialized -- no config") return session_factory
[docs] def _create_ldap_session_factory(self): """Can be overriden by subclasses (if LDAP is in use). Creates ldap session factory. Returns: LdapSessionFactory: :py:class:`summer.lsf.LdapSessionFactory` instance. """ config = self.summer_config ldap_session_factory = None if config.has_section(LDAP): hostname = config.get(LDAP, "hostname") port = config.getint(LDAP, "port") base = config.get(LDAP, "base") login = config.get(LDAP, "login") passwd = config.get(LDAP, "passwd") ldap_session_factory = lsf.LdapSessionFactory(hostname, port, base, login, passwd) else: logger.info("ldap session factory not initialized -- no config") return ldap_session_factory
[docs] def _create_l10n(self): """Can be overriden by subclasses (if localization is in use). Creates l10n. Returns: Localization: :py:class:`summer.l10n.Localization` instance. """ config = self.summer_config localization = None if config.has_section(L10N): domain = config.get(L10N, "domain") l10n_dir = config.get(L10N, "l10n_dir") languages = str(config.get(L10N, "languages")).split(",") if not os.access(l10n_dir, os.F_OK): logger.warning("not found path '%s'", l10n_dir) l10n_dir = utils.locate_file(self.path_to_module, l10n_dir) if not os.access(l10n_dir, os.F_OK): raise SummerConfigurationException(l10n_dir=l10n_dir) localization = l10n.Localization(domain, l10n_dir, languages) else: logger.info("localization not initialized -- no config") return localization
[docs] def orm_init(self): """Should be overriden by subclasses to initialize ORM managed tables and class mappings. You should define your custom table definitions based on :py:class:`summer.sf.AbstractTableDefinitions` and mappings based on :py:class:`summer.sf.AbstractClassMappings`. Usually you have just those lines there:: self.session_factory.set_table_definitions(TableDefinitions()) self.session_factory.set_class_mappings(ClassMappings()) """ pass
[docs] def context_init(self): """Should be overriden by subclasses to initialize custom objects in context. This is the last stage of context initialization, this method gets called after #. :py:meth:`core_init` #. :py:meth:`orm_init` are called. You can safely access those attributes: * :py:attr:`summer_config` -- Python's :py:class:`configparser.SafeConfigParser` instance of the summer framework itself * :py:attr:`config` -- Python's :py:class:`configparser.SafeConfigParser` of your custom config (if provided) * :py:attr:`l10n` -- :py:class:`summer.l10n.Localization` instance, not very interesting in itself, but summer's localization module installs the famous :py:meth:`_` gettext function into global namespace (as normal gettext module does) and configures your localization based on whatever you provided in *summer* framework config (:py:attr:`summer_config` attribute) * :py:attr:`session_factory` -- :py:class:`summer.sf.SessionFactory` instance, which provides thread-safe access to :py:class:`sqlalchemy.session` -- any data aware object (ie. each DAO at least) should have access to it. By convention, name all the references to this object as **session_factory**. AOP depends on this. You can override it, but why would you do it? Remember: *convention over configuration*. * :py:attr:`ldap_session_factory` :py:class:`summer.lsf.LdapSessionFactory` instance, which provides thread-safe access to :py:class:`summer.lsf.LdapSessionFactory.Local` instance -- a simple wrapper around actual :py:class:`ldap3.Connection`, again, convention dictates to name reference to this object as **ldap_session_factory**. AOP depends on this. """ pass
[docs] def context_shutdown(self): """Handles context shutdown. Called from :py:meth:`__del__`.""" # FIXME martin.slouf -- ValueError is thrown by Python stdlib?! # logger.info("context shutdown") pass
def __len__(self): return len(self.__dict__) def __getitem__(self, key): if key in self.__dict__: return self.__dict__[key] else: raise NoObjectFoundException(object_name=key) def __setitem__(self, key, value): self.__dict__[key] = value def __delitem__(self, key): del self.__dict__[key] def __iter__(self): return iter(self.__dict__)