Source code for camelot.core.orm

#  ============================================================================
#
#  Copyright (C) 2007-2016 Conceptive Engineering bvba.
#  www.conceptive.be / info@conceptive.be
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#      * Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#      * Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#      * Neither the name of Conceptive Engineering nor the
#        names of its contributors may be used to endorse or promote products
#        derived from this software without specific prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#  DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#  ============================================================================

"""This module complements the sqlalchemy orm module, it contains the global
`Session` factory to create `session` objects.  Whenever a `session`
is needed it can be constructed with a call of `Session` ::
    
    session = Session
        
when using Elixir, Elixir needs to be told to use this session factory ::
    
    elixir.session = Session

when using Declarative, this module contains an `Entity` class that can
be used as a `declarative_base` and has some classes that mimic Elixir
behavior
"""

import functools
import logging

LOGGER = logging.getLogger('camelot.core.orm')

from camelot.core.sql import metadata
from sqlalchemy import orm, event
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative.clsregistry import ( _ModuleMarker,
                                                     _MultipleClassMarker )
from sqlalchemy.orm import scoped_session, sessionmaker, mapper

#
# Singleton session factory, to be used when a session is needed
#
Session = scoped_session( sessionmaker( autoflush = False,
                                        autocommit = True,
                                        expire_on_commit = False ) )

from . options import using_options
from . fields import has_field, Field
from . relationships import ( belongs_to, has_one, has_many,
                              has_and_belongs_to_many, 
                              ManyToOne, OneToOne, OneToMany, ManyToMany )
from . properties import has_property, GenericProperty, ColumnProperty

#
# Default registry for subclasses of Entity that have been mapped
#

class EntityCollection( dict ):

    __name__ = 'EntityCollection'

entities = EntityCollection()

#
# There are 2 base classes that each act in a different way
#
# * ClassMutator : DSL like statements that modify the Entity at definition
#   time
#
# * EntityBuilder : modify an Entity at construction time, in several phases, 
#   before and after mapper and table creation.
#

import six

from . entity import EntityBase, EntityMeta

@event.listens_for( mapper, 'after_configured' )
def process_deferred_properties( class_registry = entities ):
    """After all mappers have been configured, process the Deferred Properties.
    This function is called automatically for the default class_registry.
    """
    LOGGER.debug( 'process deferred properties' )
    descriptors = list()
    for cls in six.itervalues(class_registry):
        if isinstance( cls, ( _ModuleMarker, _MultipleClassMarker ) ):
            continue
        descriptor = getattr(cls, '_descriptor')
        if descriptor.processed == True:
            # because orm.class_mapper will trigger the 'after_configured' event,
            # there might be a recursive call of this function, if this function
            # was called by the application code, and not by the event.
            continue
        descriptors.append( (descriptor.counter, descriptor) )
        descriptor.processed = True
    descriptors.sort()

    for method_name in ( 'create_non_pk_cols',
                         'create_tables',
                         'append_constraints',
                         'create_properties',
                         'finalize', ):
        for counter, descriptor in descriptors:
            method = getattr(descriptor, method_name)
            method()


[docs]def setup_all( create_tables=False, *args, **kwargs ): """Create all tables that are registered in the metadata """ process_deferred_properties() if create_tables: metadata.create_all( *args, **kwargs )
Entity = declarative_base( cls = EntityBase, metadata = metadata, metaclass = EntityMeta, class_registry = entities, constructor = None, name = 'Entity' )
[docs]def transaction( original_function ): """Decorator to make methods transactional with regard to the session of the object on which they are called""" @functools.wraps( original_function ) def decorated_function( self, *args, **kwargs ): session = orm.object_session( self ) with session.begin(): return original_function( self, *args, **kwargs ) return decorated_function
__all__ = [ obj.__name__ for obj in [ Entity, EntityBase, EntityMeta, EntityCollection, Field, has_field, has_property, GenericProperty, ColumnProperty, belongs_to, has_one, has_many, has_and_belongs_to_many, ManyToOne, OneToOne, OneToMany, ManyToMany, using_options, setup_all, transaction ] ] + ['Session', 'entities']