Source code for sqlalchemy_tree.manager.unmapped

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    sqlalchemy_tree.manager.unmapped
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    :copyright: (C) 2012-2014 the SQLAlchemy-ORM-Tree authors and contributors
                <see AUTHORS file>.
    :license: BSD, see LICENSE for more details.
"""

from __future__ import absolute_import, division, print_function, \
    with_statement, unicode_literals

import sqlalchemy

from sqlalchemy.orm.mapper import class_mapper

from ..options import TreeOptions
from ..orm import TreeMapperExtension, TreeSessionExtension
from .instance import TreeInstanceManager
from .class_ import TreeClassManager

__all__ = (
    'TreeManager',
)


[docs]class TreeManager(object): """Extension class to create required fields and access class-level and instance-level API based on context. Basic usage is simple:: class Node(object): tree = sqlalchemy_tree.TreeManager(node_table) # After Node is mapped: Node.tree.register() Now there is an ability to get an instance manager or class manager via the property `'mp'` depending on the way in which it is accessed. `Node.mp` will return the mapper extension until the class is mapped (useful for setting up parameters to pass to the mapper function itself), the class manager :class:`TreeClassManager` after mapping, and `instance_node.mp` will return instance_node's :class:`TreeInstanceManager`. See those classes for more details about their public API's. :param table: instance of `sqlalchemy.Table`. A table that will be mapped to the node class and will hold tree nodes in its rows. The adjacency-list link/self- referential foreign-key will be automatically determined, and the additional four columns “tree_id”, “left”, “right” and “depth” will automatically be added if necessary. :attr:`table` is the only one strictly required argument. :param parent_id_field=None: a self-referencing foreign key field containing the parent node's primary key. If this parameter is omitted, it will be guessed joining a `table` with itself and using the right part of join's `ON` clause as parent id field. :param tree_id_field='tree_id': the name of the tree id field, or the field object itself. The field will be created if the actual parameter value is a string and there is no such column in the table :attr:`table`. If the value provided is or names an existing SQLAlchemy column object, that object must pass some sanity checks: it must be in :attr:`table`, it should have `nullable=False`, and be of type :class:`TreeIdField`. :param left_field='tree_left': the same as for :attr:`tree_id_field`, except that the type of this column should be :class:`TreeLeftField`. :param right_field='tree_right': the same as for :attr:`tree_id_field`, except that the type of this column should be :class:`TreeRightField`. :param depth_field='tree_depth': the same as for :attr:`tree_id_field`, except that the type of this column should be :class:`TreeDepthField`. :param instance_manager_attr='_tree_instance_manager': name for node instance's attribute to cache node's instance manager. .. warning:: Do not change the values of `TreeManager` constructor's arguments after saving a first tree node. Doing so will corrupt the tree. """ def __init__(self, *args, **kwargs): self.instance_manager_attr = kwargs.pop('instance_manager_attr', '_tree_instance_manager') kwargs['instance_manager_attr'] = self.instance_manager_attr opts = TreeOptions(*args, **kwargs) self.options = opts self.class_manager = None self.mapper_extension = TreeMapperExtension(options=opts) self.session_extension = None self.root_node_class = None def __get__(self, obj, objtype): """There may be three kinds of return values from this getter. The first one is used when the class which this descriptor is attached to is not yet mapped to any table. In that case the return value is an instance of :class:`MPMapperExtension` which is intended to be used as mapper extension. The second scenario is access to :class:`TreeManager` via mapped class. The corresponding :class:`TreeClassManager` instance is returned. .. note:: If the nodes of your tree use polymorphic inheritance it is important to know that class manager is accessible only via the base class of inheritance hierarchy. And the third way is accessing it from the node instance: a :class:`TreeInstanceManager` is returned then.""" # First handle the case of class/extension access: if obj is None: try: # FIXME: Figure out what this root_node_class business is # about. root_node_class = self._get_root_node_class(objtype) except sqlalchemy.orm.exc.UnmappedClassError: # SQLAlchemy will helpfully throw an exception if the class is not # mapped. This is one of our normal use case scenarios, resulting in # the mapper extension being returned: return self.mapper_extension # FIXME: Figure out what this root_node_class business is about. assert objtype is root_node_class, \ u"TreeClassManager should be accessed via its base class in " \ u"the polymorphic inheritance hierarchy: %r" % root_node_class return self._get_class_manager(root_node_class) # Otherwise if obj is specified, an instance manager is requested: else: # The instance manager is cached on the object itself using a developer- # specified attribute name. First see if the cached manager exists: instance_manager = getattr(obj, self.instance_manager_attr, None) # If the instance manager wasn't found, we create it and cache it on the # object: if instance_manager is None: root_node_class = self._get_root_node_class(objtype) instance_manager = TreeInstanceManager( class_manager=self._get_class_manager(root_node_class), obj=obj) setattr(obj, self.instance_manager_attr, instance_manager) # ...and return the instance manager to the caller: return instance_manager def _get_class_manager(self, objtype): "Return an instance of :class:`TreeClassManager`" # We cache the class manager on first access, so that it doesn't have to # be regenerated every time. It's created on first call to this method and # not in __init__ as the class won't be mapped at that time. if self.class_manager is None: self.session_extension = TreeSessionExtension( options=self.options, node_class=objtype) self.class_manager = TreeClassManager( node_class=objtype, options=self.options, mapper_extension=self.mapper_extension, session_extension=self.session_extension) self.options.class_mapped( manager=self.class_manager) return self.class_manager def _get_root_node_class(self, objtype): "Get the root node class in the polymorphic inheritance hierarchy." # FIXME: Figure out what this root_node_class business is about. if self.root_node_class is None: mapper = class_mapper(objtype) while mapper.inherits is not None: mapper = mapper.inherits self.root_node_class = mapper.class_ return self.root_node_class