Update elixir
This commit is contained in:
1 parent
b2be9ef7c4
commit
74578db933
9 files changed
+59
-547
No files matched your search
@@ -38,7 +38,7 @@ from elixir.statements import Statement
|
|||||||
from elixir.collection import EntityCollection, GlobalEntityCollection
|
from elixir.collection import EntityCollection, GlobalEntityCollection
|
||||||
|
|
||||||
|
|
||||||
__version__ = '0.7.1'
|
__version__ = '0.8.0dev'
|
||||||
|
|
||||||
__all__ = ['Entity', 'EntityBase', 'EntityMeta', 'EntityCollection',
|
__all__ = ['Entity', 'EntityBase', 'EntityMeta', 'EntityCollection',
|
||||||
'entities',
|
'entities',
|
||||||
@@ -85,11 +85,6 @@ def drop_all(*args, **kwargs):
|
|||||||
def setup_all(create_tables=False, *args, **kwargs):
|
def setup_all(create_tables=False, *args, **kwargs):
|
||||||
'''Setup the table and mapper of all entities in the default entity
|
'''Setup the table and mapper of all entities in the default entity
|
||||||
collection.
|
collection.
|
||||||
|
|
||||||
This is called automatically if any entity of the collection is configured
|
|
||||||
with the `autosetup` option and it is first accessed,
|
|
||||||
instanciated (called) or the create_all method of a metadata containing
|
|
||||||
tables from any of those entities is called.
|
|
||||||
'''
|
'''
|
||||||
setup_entities(entities)
|
setup_entities(entities)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ Default entity collection implementation
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from elixir.py23compat import rsplit
|
|
||||||
|
|
||||||
class BaseCollection(list):
|
class BaseCollection(list):
|
||||||
def __init__(self, entities=None):
|
def __init__(self, entities=None):
|
||||||
list.__init__(self)
|
list.__init__(self)
|
||||||
@@ -24,7 +22,7 @@ class BaseCollection(list):
|
|||||||
root = entity._descriptor.resolve_root
|
root = entity._descriptor.resolve_root
|
||||||
if root:
|
if root:
|
||||||
full_path = '%s.%s' % (root, full_path)
|
full_path = '%s.%s' % (root, full_path)
|
||||||
module_path, classname = rsplit(full_path, '.', 1)
|
module_path, classname = full_path.rsplit('.', 1)
|
||||||
module = sys.modules[module_path]
|
module = sys.modules[module_path]
|
||||||
res = getattr(module, classname, None)
|
res = getattr(module, classname, None)
|
||||||
if res is None:
|
if res is None:
|
||||||
|
|||||||
+32
-165
@@ -3,8 +3,6 @@ This module provides the ``Entity`` base class, as well as its metaclass
|
|||||||
``EntityMeta``.
|
``EntityMeta``.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from py23compat import sorted
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
@@ -25,11 +23,6 @@ from elixir import options
|
|||||||
from elixir.properties import Property
|
from elixir.properties import Property
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
try:
|
|
||||||
from sqlalchemy.orm import EXT_PASS
|
|
||||||
SA05orlater = False
|
|
||||||
except ImportError:
|
|
||||||
SA05orlater = True
|
|
||||||
|
|
||||||
__doc_all__ = ['Entity', 'EntityMeta']
|
__doc_all__ = ['Entity', 'EntityMeta']
|
||||||
|
|
||||||
@@ -205,6 +198,7 @@ class EntityDescriptor(object):
|
|||||||
parent_desc = self.parent._descriptor
|
parent_desc = self.parent._descriptor
|
||||||
tablename = parent_desc.table_fullname
|
tablename = parent_desc.table_fullname
|
||||||
join_clauses = []
|
join_clauses = []
|
||||||
|
fk_columns = []
|
||||||
for pk_col in parent_desc.primary_keys:
|
for pk_col in parent_desc.primary_keys:
|
||||||
colname = options.MULTIINHERITANCECOL_NAMEFORMAT % \
|
colname = options.MULTIINHERITANCECOL_NAMEFORMAT % \
|
||||||
{'entity': self.parent.__name__.lower(),
|
{'entity': self.parent.__name__.lower(),
|
||||||
@@ -214,12 +208,14 @@ class EntityDescriptor(object):
|
|||||||
# a real column object when said column is not yet
|
# a real column object when said column is not yet
|
||||||
# attached to a table
|
# attached to a table
|
||||||
pk_col_name = "%s.%s" % (tablename, pk_col.key)
|
pk_col_name = "%s.%s" % (tablename, pk_col.key)
|
||||||
fk = ForeignKey(pk_col_name, ondelete='cascade')
|
col = Column(colname, pk_col.type, primary_key=True)
|
||||||
col = Column(colname, pk_col.type, fk,
|
fk_columns.append(col)
|
||||||
primary_key=True)
|
|
||||||
self.add_column(col)
|
self.add_column(col)
|
||||||
join_clauses.append(col == pk_col)
|
join_clauses.append(col == pk_col)
|
||||||
self.join_condition = and_(*join_clauses)
|
self.join_condition = and_(*join_clauses)
|
||||||
|
self.add_constraint(
|
||||||
|
ForeignKeyConstraint(fk_columns,
|
||||||
|
parent_desc.primary_keys, ondelete='CASCADE'))
|
||||||
elif self.inheritance == 'concrete':
|
elif self.inheritance == 'concrete':
|
||||||
# Copy primary key columns from the parent.
|
# Copy primary key columns from the parent.
|
||||||
for col in self.parent._descriptor.columns:
|
for col in self.parent._descriptor.columns:
|
||||||
@@ -286,7 +282,7 @@ class EntityDescriptor(object):
|
|||||||
self.add_constraint(
|
self.add_constraint(
|
||||||
ForeignKeyConstraint(
|
ForeignKeyConstraint(
|
||||||
[e.parent.key for e in con.elements],
|
[e.parent.key for e in con.elements],
|
||||||
[e._get_colspec() for e in con.elements],
|
[e.target_fullname for e in con.elements],
|
||||||
name=con.name, #TODO: modify it
|
name=con.name, #TODO: modify it
|
||||||
onupdate=con.onupdate, ondelete=con.ondelete,
|
onupdate=con.onupdate, ondelete=con.ondelete,
|
||||||
use_alter=con.use_alter))
|
use_alter=con.use_alter))
|
||||||
@@ -370,6 +366,11 @@ class EntityDescriptor(object):
|
|||||||
|
|
||||||
order = []
|
order = []
|
||||||
for colname in order_by:
|
for colname in order_by:
|
||||||
|
#FIXME: get_column uses self.columns[key] instead of property
|
||||||
|
# names. self.columns correspond to the columns of the table if
|
||||||
|
# the table was already created and to self._columns otherwise,
|
||||||
|
# which is a ColumnCollection indexed on columns.key
|
||||||
|
# See ticket #108.
|
||||||
col = self.get_column(colname.strip('-'))
|
col = self.get_column(colname.strip('-'))
|
||||||
if colname.startswith('-'):
|
if colname.startswith('-'):
|
||||||
col = desc(col)
|
col = desc(col)
|
||||||
@@ -493,17 +494,13 @@ class EntityDescriptor(object):
|
|||||||
(col.key, self.entity.__name__))
|
(col.key, self.entity.__name__))
|
||||||
else:
|
else:
|
||||||
del self._columns[col.key]
|
del self._columns[col.key]
|
||||||
|
# are indexed on col.key
|
||||||
self._columns.add(col)
|
self._columns.add(col)
|
||||||
|
|
||||||
if col.primary_key:
|
if col.primary_key:
|
||||||
self.has_pk = True
|
self.has_pk = True
|
||||||
|
|
||||||
# Autosetup triggers shouldn't be active anymore at this point, so we
|
table = self.entity.table
|
||||||
# can theoretically access the entity's table safely. But the problem
|
|
||||||
# is that if, for some reason, the trigger removal phase didn't
|
|
||||||
# happen, we'll get an infinite loop. So we just make sure we don't
|
|
||||||
# get one in any case.
|
|
||||||
table = type.__getattribute__(self.entity, 'table')
|
|
||||||
if table is not None:
|
if table is not None:
|
||||||
if check_duplicate and col.key in table.columns.keys():
|
if check_duplicate and col.key in table.columns.keys():
|
||||||
raise Exception("Column '%s' already exist in table '%s' ! " %
|
raise Exception("Column '%s' already exist in table '%s' ! " %
|
||||||
@@ -595,6 +592,7 @@ class EntityDescriptor(object):
|
|||||||
#------------------------
|
#------------------------
|
||||||
# some useful properties
|
# some useful properties
|
||||||
|
|
||||||
|
@property
|
||||||
def table_fullname(self):
|
def table_fullname(self):
|
||||||
'''
|
'''
|
||||||
Complete name of the table for the related entity.
|
Complete name of the table for the related entity.
|
||||||
@@ -605,8 +603,8 @@ class EntityDescriptor(object):
|
|||||||
return "%s.%s" % (schema, self.tablename)
|
return "%s.%s" % (schema, self.tablename)
|
||||||
else:
|
else:
|
||||||
return self.tablename
|
return self.tablename
|
||||||
table_fullname = property(table_fullname)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def columns(self):
|
def columns(self):
|
||||||
if self.entity.table is not None:
|
if self.entity.table is not None:
|
||||||
return self.entity.table.columns
|
return self.entity.table.columns
|
||||||
@@ -615,8 +613,8 @@ class EntityDescriptor(object):
|
|||||||
# return the parent entity's columns (for example for order_by
|
# return the parent entity's columns (for example for order_by
|
||||||
# using a column defined in the parent.
|
# using a column defined in the parent.
|
||||||
return self._columns
|
return self._columns
|
||||||
columns = property(columns)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def primary_keys(self):
|
def primary_keys(self):
|
||||||
"""
|
"""
|
||||||
Returns the list of primary key columns of the entity.
|
Returns the list of primary key columns of the entity.
|
||||||
@@ -630,15 +628,15 @@ class EntityDescriptor(object):
|
|||||||
return self.parent._descriptor.primary_keys
|
return self.parent._descriptor.primary_keys
|
||||||
else:
|
else:
|
||||||
return [col for col in self.columns if col.primary_key]
|
return [col for col in self.columns if col.primary_key]
|
||||||
primary_keys = property(primary_keys)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def table(self):
|
def table(self):
|
||||||
if self.entity.table is not None:
|
if self.entity.table is not None:
|
||||||
return self.entity.table
|
return self.entity.table
|
||||||
else:
|
else:
|
||||||
return FakeTable(self)
|
return FakeTable(self)
|
||||||
table = property(table)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def primary_key_properties(self):
|
def primary_key_properties(self):
|
||||||
"""
|
"""
|
||||||
Returns the list of (mapper) properties corresponding to the primary
|
Returns the list of (mapper) properties corresponding to the primary
|
||||||
@@ -653,30 +651,32 @@ class EntityDescriptor(object):
|
|||||||
for prop in mapper.iterate_properties:
|
for prop in mapper.iterate_properties:
|
||||||
if isinstance(prop, ColumnProperty):
|
if isinstance(prop, ColumnProperty):
|
||||||
for col in prop.columns:
|
for col in prop.columns:
|
||||||
|
#XXX: Why is this extra loop necessary? What is this
|
||||||
|
# "proxy_set" supposed to mean?
|
||||||
for col in col.proxy_set:
|
for col in col.proxy_set:
|
||||||
col_to_prop[col] = prop
|
col_to_prop[col] = prop
|
||||||
pk_cols = [c for c in mapper.mapped_table.c if c.primary_key]
|
pk_cols = [c for c in mapper.mapped_table.c if c.primary_key]
|
||||||
self._pk_props = [col_to_prop[c] for c in pk_cols]
|
self._pk_props = [col_to_prop[c] for c in pk_cols]
|
||||||
return self._pk_props
|
return self._pk_props
|
||||||
primary_key_properties = property(primary_key_properties)
|
|
||||||
|
|
||||||
class FakePK(object):
|
class FakePK(object):
|
||||||
def __init__(self, descriptor):
|
def __init__(self, descriptor):
|
||||||
self.descriptor = descriptor
|
self.descriptor = descriptor
|
||||||
|
|
||||||
|
@property
|
||||||
def columns(self):
|
def columns(self):
|
||||||
return self.descriptor.primary_keys
|
return self.descriptor.primary_keys
|
||||||
columns = property(columns)
|
|
||||||
|
|
||||||
class FakeTable(object):
|
class FakeTable(object):
|
||||||
def __init__(self, descriptor):
|
def __init__(self, descriptor):
|
||||||
self.descriptor = descriptor
|
self.descriptor = descriptor
|
||||||
self.primary_key = FakePK(descriptor)
|
self.primary_key = FakePK(descriptor)
|
||||||
|
|
||||||
|
@property
|
||||||
def columns(self):
|
def columns(self):
|
||||||
return self.descriptor.columns
|
return self.descriptor.columns
|
||||||
columns = property(columns)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def fullname(self):
|
def fullname(self):
|
||||||
'''
|
'''
|
||||||
Complete name of the table for the related entity.
|
Complete name of the table for the related entity.
|
||||||
@@ -687,46 +687,8 @@ class FakeTable(object):
|
|||||||
return "%s.%s" % (schema, self.descriptor.tablename)
|
return "%s.%s" % (schema, self.descriptor.tablename)
|
||||||
else:
|
else:
|
||||||
return self.descriptor.tablename
|
return self.descriptor.tablename
|
||||||
fullname = property(fullname)
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerProxy(object):
|
|
||||||
"""
|
|
||||||
A class that serves as a "trigger" ; accessing its attributes runs
|
|
||||||
the setup_all function.
|
|
||||||
|
|
||||||
Note that the `setup_all` is called on each access of the attribute.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, class_, attrname):
|
|
||||||
self.class_ = class_
|
|
||||||
self.attrname = attrname
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
elixir.setup_all()
|
|
||||||
#FIXME: it's possible to get an infinite loop here if setup_all doesn't
|
|
||||||
#remove the triggers for this entity. This can happen if the entity is
|
|
||||||
#not in the `entities` list for some reason.
|
|
||||||
proxied_attr = getattr(self.class_, self.attrname)
|
|
||||||
return getattr(proxied_attr, name)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
proxied_attr = getattr(self.class_, self.attrname)
|
|
||||||
return "<TriggerProxy (%s)>" % (self.class_.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerAttribute(object):
|
|
||||||
|
|
||||||
def __init__(self, attrname):
|
|
||||||
self.attrname = attrname
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
|
||||||
#FIXME: it's possible to get an infinite loop here if setup_all doesn't
|
|
||||||
#remove the triggers for this entity. This can happen if the entity is
|
|
||||||
#not in the `entities` list for some reason.
|
|
||||||
elixir.setup_all()
|
|
||||||
return getattr(owner, self.attrname)
|
|
||||||
|
|
||||||
def is_entity(cls):
|
def is_entity(cls):
|
||||||
"""
|
"""
|
||||||
Scan the bases classes of `cls` to see if any is an instance of
|
Scan the bases classes of `cls` to see if any is an instance of
|
||||||
@@ -805,13 +767,6 @@ def instrument_class(cls):
|
|||||||
# setup misc options here (like tablename etc.)
|
# setup misc options here (like tablename etc.)
|
||||||
desc.setup_options()
|
desc.setup_options()
|
||||||
|
|
||||||
# create trigger proxies
|
|
||||||
# TODO: support entity_name... It makes sense only for autoloaded
|
|
||||||
# tables for now, and would make more sense if we support "external"
|
|
||||||
# tables
|
|
||||||
if desc.autosetup:
|
|
||||||
_install_autosetup_triggers(cls)
|
|
||||||
|
|
||||||
|
|
||||||
class EntityMeta(type):
|
class EntityMeta(type):
|
||||||
"""
|
"""
|
||||||
@@ -823,11 +778,6 @@ class EntityMeta(type):
|
|||||||
def __init__(cls, name, bases, dict_):
|
def __init__(cls, name, bases, dict_):
|
||||||
instrument_class(cls)
|
instrument_class(cls)
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
|
||||||
if cls._descriptor.autosetup and not hasattr(cls, '_setup_done'):
|
|
||||||
elixir.setup_all()
|
|
||||||
return type.__call__(cls, *args, **kwargs)
|
|
||||||
|
|
||||||
def __setattr__(cls, key, value):
|
def __setattr__(cls, key, value):
|
||||||
if isinstance(value, Property):
|
if isinstance(value, Property):
|
||||||
if hasattr(cls, '_setup_done'):
|
if hasattr(cls, '_setup_done'):
|
||||||
@@ -839,84 +789,6 @@ class EntityMeta(type):
|
|||||||
type.__setattr__(cls, key, value)
|
type.__setattr__(cls, key, value)
|
||||||
|
|
||||||
|
|
||||||
def _install_autosetup_triggers(cls, entity_name=None):
|
|
||||||
#TODO: move as much as possible of those "_private" values to the
|
|
||||||
# descriptor, so that we don't mess the initial class.
|
|
||||||
warnings.warn("The 'autosetup' option on entities is deprecated. "
|
|
||||||
"Please call setup_all() manually after all your entities have been "
|
|
||||||
"declared.", DeprecationWarning, stacklevel=4)
|
|
||||||
tablename = cls._descriptor.tablename
|
|
||||||
schema = cls._descriptor.table_options.get('schema', None)
|
|
||||||
cls._table_key = sqlalchemy.schema._get_table_key(tablename, schema)
|
|
||||||
|
|
||||||
table_proxy = TriggerProxy(cls, 'table')
|
|
||||||
|
|
||||||
md = cls._descriptor.metadata
|
|
||||||
md.tables[cls._table_key] = table_proxy
|
|
||||||
|
|
||||||
# We need to monkeypatch the metadata's table iterator method because
|
|
||||||
# otherwise it doesn't work if the setup is triggered by the
|
|
||||||
# metadata.create_all().
|
|
||||||
# This is because ManyToMany relationships add tables AFTER the list
|
|
||||||
# of tables that are going to be created is "computed"
|
|
||||||
# (metadata.tables.values()).
|
|
||||||
# see:
|
|
||||||
# - table_iterator method in MetaData class in sqlalchemy/schema.py
|
|
||||||
# - visit_metadata method in sqlalchemy/ansisql.py
|
|
||||||
if SA05orlater:
|
|
||||||
warnings.warn(
|
|
||||||
"The automatic setup via metadata.create_all() through "
|
|
||||||
"the autosetup option doesn't work with SQLAlchemy 0.5 and later!")
|
|
||||||
else:
|
|
||||||
# SA 0.6 does not use table_iterator anymore (it was already deprecated
|
|
||||||
# since SA 0.5.0)
|
|
||||||
original_table_iterator = md.table_iterator
|
|
||||||
if not hasattr(original_table_iterator,
|
|
||||||
'_non_elixir_patched_iterator'):
|
|
||||||
def table_iterator(*args, **kwargs):
|
|
||||||
elixir.setup_all()
|
|
||||||
return original_table_iterator(*args, **kwargs)
|
|
||||||
table_iterator.__doc__ = original_table_iterator.__doc__
|
|
||||||
table_iterator._non_elixir_patched_iterator = \
|
|
||||||
original_table_iterator
|
|
||||||
md.table_iterator = table_iterator
|
|
||||||
|
|
||||||
#TODO: we might want to add all columns that will be available as
|
|
||||||
#attributes on the class itself (in SA 0.4+). This is a pretty
|
|
||||||
#rare usecase, as people will normally hit the query attribute before the
|
|
||||||
#column attributes, but I've seen people hitting this problem...
|
|
||||||
for name in ('c', 'table', 'mapper', 'query'):
|
|
||||||
setattr(cls, name, TriggerAttribute(name))
|
|
||||||
|
|
||||||
cls._has_triggers = True
|
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_autosetup_triggers(cls):
|
|
||||||
if not hasattr(cls, '_has_triggers'):
|
|
||||||
return
|
|
||||||
|
|
||||||
for name in ('table', 'mapper'):
|
|
||||||
setattr(cls, name, None)
|
|
||||||
|
|
||||||
for name in ('c', 'query'):
|
|
||||||
delattr(cls, name)
|
|
||||||
|
|
||||||
desc = cls._descriptor
|
|
||||||
md = desc.metadata
|
|
||||||
|
|
||||||
# the fake table could have already been removed (namely in a
|
|
||||||
# single table inheritance scenario)
|
|
||||||
md.tables.pop(cls._table_key, None)
|
|
||||||
|
|
||||||
# restore original table iterator if not done already
|
|
||||||
if not SA05orlater:
|
|
||||||
if hasattr(md.table_iterator, '_non_elixir_patched_iterator'):
|
|
||||||
md.table_iterator = \
|
|
||||||
md.table_iterator._non_elixir_patched_iterator
|
|
||||||
|
|
||||||
del cls._has_triggers
|
|
||||||
|
|
||||||
|
|
||||||
def setup_entities(entities):
|
def setup_entities(entities):
|
||||||
'''Setup all entities in the list passed as argument'''
|
'''Setup all entities in the list passed as argument'''
|
||||||
|
|
||||||
@@ -928,9 +800,6 @@ def setup_entities(entities):
|
|||||||
if isinstance(attr, Property):
|
if isinstance(attr, Property):
|
||||||
delattr(entity, name)
|
delattr(entity, name)
|
||||||
|
|
||||||
if entity._descriptor.autosetup:
|
|
||||||
_cleanup_autosetup_triggers(entity)
|
|
||||||
|
|
||||||
for method_name in (
|
for method_name in (
|
||||||
'setup_autoload_table', 'create_pk_cols', 'setup_relkeys',
|
'setup_autoload_table', 'create_pk_cols', 'setup_relkeys',
|
||||||
'before_table', 'setup_table', 'setup_reltables', 'after_table',
|
'before_table', 'setup_table', 'setup_reltables', 'after_table',
|
||||||
@@ -955,8 +824,7 @@ def setup_entities(entities):
|
|||||||
def cleanup_entities(entities):
|
def cleanup_entities(entities):
|
||||||
"""
|
"""
|
||||||
Try to revert back the list of entities passed as argument to the state
|
Try to revert back the list of entities passed as argument to the state
|
||||||
they had just before their setup phase. It will not work entirely for
|
they had just before their setup phase.
|
||||||
autosetup entities as we need to remove the autosetup triggers.
|
|
||||||
|
|
||||||
As of now, this function is *not* functional in that it doesn't revert to
|
As of now, this function is *not* functional in that it doesn't revert to
|
||||||
the exact same state the entities were before setup. For example, the
|
the exact same state the entities were before setup. For example, the
|
||||||
@@ -968,8 +836,6 @@ def cleanup_entities(entities):
|
|||||||
"""
|
"""
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
desc = entity._descriptor
|
desc = entity._descriptor
|
||||||
if desc.autosetup:
|
|
||||||
_cleanup_autosetup_triggers(entity)
|
|
||||||
|
|
||||||
if hasattr(entity, '_setup_done'):
|
if hasattr(entity, '_setup_done'):
|
||||||
del entity._setup_done
|
del entity._setup_done
|
||||||
@@ -1007,6 +873,7 @@ class EntityBase(object):
|
|||||||
for key, value in kwargs.iteritems():
|
for key, value in kwargs.iteritems():
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def update_or_create(cls, data, surrogate=True):
|
def update_or_create(cls, data, surrogate=True):
|
||||||
pk_props = cls._descriptor.primary_key_properties
|
pk_props = cls._descriptor.primary_key_properties
|
||||||
|
|
||||||
@@ -1016,17 +883,16 @@ class EntityBase(object):
|
|||||||
record = cls.query.get(pk_tuple)
|
record = cls.query.get(pk_tuple)
|
||||||
if record is None:
|
if record is None:
|
||||||
if surrogate:
|
if surrogate:
|
||||||
raise Exception("cannot create surrogate with pk")
|
raise Exception("Cannot create surrogate with pk")
|
||||||
else:
|
else:
|
||||||
record = cls()
|
record = cls()
|
||||||
else:
|
else:
|
||||||
if surrogate:
|
if surrogate:
|
||||||
record = cls()
|
record = cls()
|
||||||
else:
|
else:
|
||||||
raise Exception("cannot create non surrogate without pk")
|
raise Exception("Cannot create non surrogate without pk")
|
||||||
record.from_dict(data)
|
record.from_dict(data)
|
||||||
return record
|
return record
|
||||||
update_or_create = classmethod(update_or_create)
|
|
||||||
|
|
||||||
def from_dict(self, data):
|
def from_dict(self, data):
|
||||||
"""
|
"""
|
||||||
@@ -1105,10 +971,11 @@ class EntityBase(object):
|
|||||||
|
|
||||||
# This bunch of session methods, along with all the query methods below
|
# This bunch of session methods, along with all the query methods below
|
||||||
# only make sense when using a global/scoped/contextual session.
|
# only make sense when using a global/scoped/contextual session.
|
||||||
|
@property
|
||||||
def _global_session(self):
|
def _global_session(self):
|
||||||
return self._descriptor.session.registry()
|
return self._descriptor.session.registry()
|
||||||
_global_session = property(_global_session)
|
|
||||||
|
|
||||||
|
#FIXME: remove all deprecated methods, possibly all of these
|
||||||
def merge(self, *args, **kwargs):
|
def merge(self, *args, **kwargs):
|
||||||
return self._global_session.merge(self, *args, **kwargs)
|
return self._global_session.merge(self, *args, **kwargs)
|
||||||
|
|
||||||
@@ -1126,6 +993,7 @@ class EntityBase(object):
|
|||||||
return self._global_session.save_or_update(self, *args, **kwargs)
|
return self._global_session.save_or_update(self, *args, **kwargs)
|
||||||
|
|
||||||
# query methods
|
# query methods
|
||||||
|
@classmethod
|
||||||
def get_by(cls, *args, **kwargs):
|
def get_by(cls, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns the first instance of this class matching the given criteria.
|
Returns the first instance of this class matching the given criteria.
|
||||||
@@ -1133,8 +1001,8 @@ class EntityBase(object):
|
|||||||
session.query(MyClass).filter_by(...).first()
|
session.query(MyClass).filter_by(...).first()
|
||||||
"""
|
"""
|
||||||
return cls.query.filter_by(*args, **kwargs).first()
|
return cls.query.filter_by(*args, **kwargs).first()
|
||||||
get_by = classmethod(get_by)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def get(cls, *args, **kwargs):
|
def get(cls, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return the instance of this class based on the given identifier,
|
Return the instance of this class based on the given identifier,
|
||||||
@@ -1142,7 +1010,6 @@ class EntityBase(object):
|
|||||||
session.query(MyClass).get(...)
|
session.query(MyClass).get(...)
|
||||||
"""
|
"""
|
||||||
return cls.query.get(*args, **kwargs)
|
return cls.query.get(*args, **kwargs)
|
||||||
get = classmethod(get)
|
|
||||||
|
|
||||||
|
|
||||||
class Entity(EntityBase):
|
class Entity(EntityBase):
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'before_insert',
|
'before_insert',
|
||||||
'after_insert',
|
'after_insert',
|
||||||
@@ -22,9 +24,4 @@ before_update = create_decorator('before_update')
|
|||||||
after_update = create_decorator('after_update')
|
after_update = create_decorator('after_update')
|
||||||
before_delete = create_decorator('before_delete')
|
before_delete = create_decorator('before_delete')
|
||||||
after_delete = create_decorator('after_delete')
|
after_delete = create_decorator('after_delete')
|
||||||
try:
|
|
||||||
from sqlalchemy.orm import reconstructor
|
|
||||||
except ImportError:
|
|
||||||
def reconstructor(func):
|
|
||||||
raise Exception('The reconstructor method decorator is only '
|
|
||||||
'available with SQLAlchemy 0.5 and later')
|
|
||||||
@@ -222,12 +222,12 @@ def associable(assoc_entity, plural_name=None, lazy=True):
|
|||||||
|
|
||||||
# add helper methods
|
# add helper methods
|
||||||
def select_by(cls, **kwargs):
|
def select_by(cls, **kwargs):
|
||||||
return cls.query.join([attr_name, 'targets']) \
|
return cls.query.join(attr_name, 'targets') \
|
||||||
.filter_by(**kwargs).all()
|
.filter_by(**kwargs).all()
|
||||||
setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))
|
setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))
|
||||||
|
|
||||||
def select(cls, *args, **kwargs):
|
def select(cls, *args, **kwargs):
|
||||||
return cls.query.join([attr_name, 'targets']) \
|
return cls.query.join(attr_name, 'targets') \
|
||||||
.filter(*args, **kwargs).all()
|
.filter(*args, **kwargs).all()
|
||||||
setattr(entity, 'select_%s' % self.name, classmethod(select))
|
setattr(entity, 'select_%s' % self.name, classmethod(select))
|
||||||
|
|
||||||
|
|||||||
@@ -1,251 +0,0 @@
|
|||||||
'''
|
|
||||||
This extension is DEPRECATED. Please use the orderinglist SQLAlchemy
|
|
||||||
extension instead.
|
|
||||||
|
|
||||||
For details:
|
|
||||||
http://www.sqlalchemy.org/docs/05/reference/ext/orderinglist.html
|
|
||||||
|
|
||||||
For an Elixir example:
|
|
||||||
http://elixir.ematia.de/trac/wiki/Recipes/UsingEntityForOrderedList
|
|
||||||
or
|
|
||||||
http://elixir.ematia.de/trac/browser/elixir/0.7.0/tests/test_o2m.py#L155
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An ordered-list plugin for Elixir to help you make an entity be able to be
|
|
||||||
managed in a list-like way. Much inspiration comes from the Ruby on Rails
|
|
||||||
acts_as_list plugin, which is currently more full-featured than this plugin.
|
|
||||||
|
|
||||||
Once you flag an entity with an `acts_as_list()` statement, a column will be
|
|
||||||
added to the entity called `position` which will be an integer column that is
|
|
||||||
managed for you by the plugin. You can pass an alternative column name to
|
|
||||||
the plugin using the `column_name` keyword argument.
|
|
||||||
|
|
||||||
In addition, your entity will get a series of new methods attached to it,
|
|
||||||
including:
|
|
||||||
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| Method Name | Description |
|
|
||||||
+======================+======================================================+
|
|
||||||
| ``move_lower`` | Move the item lower in the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_higher`` | Move the item higher in the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_to_bottom`` | Move the item to the bottom of the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_to_top`` | Move the item to the top of the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_to`` | Move the item to a specific position in the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
|
|
||||||
|
|
||||||
Sometimes, your entities that represent list items will be a part of different
|
|
||||||
lists. To implement this behavior, simply pass the `acts_as_list` statement a
|
|
||||||
callable that returns a "qualifier" SQLAlchemy expression. This expression will
|
|
||||||
be added to the generated WHERE clauses used by the plugin.
|
|
||||||
|
|
||||||
Example model usage:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
from elixir import *
|
|
||||||
from elixir.ext.list import acts_as_list
|
|
||||||
|
|
||||||
class ToDo(Entity):
|
|
||||||
subject = Field(String(128))
|
|
||||||
owner = ManyToOne('Person')
|
|
||||||
|
|
||||||
def qualify(self):
|
|
||||||
return ToDo.owner_id == self.owner_id
|
|
||||||
|
|
||||||
acts_as_list(qualifier=qualify)
|
|
||||||
|
|
||||||
class Person(Entity):
|
|
||||||
name = Field(String(64))
|
|
||||||
todos = OneToMany('ToDo', order_by='position')
|
|
||||||
|
|
||||||
|
|
||||||
The above example can then be used to manage ordered todo lists for people.
|
|
||||||
Note that you must set the `order_by` property on the `Person.todo` relation in
|
|
||||||
order for the relation to respect the ordering. Here is an example of using
|
|
||||||
this model in practice:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
p = Person.query.filter_by(name='Jonathan').one()
|
|
||||||
p.todos.append(ToDo(subject='Three'))
|
|
||||||
p.todos.append(ToDo(subject='Two'))
|
|
||||||
p.todos.append(ToDo(subject='One'))
|
|
||||||
session.commit(); session.clear()
|
|
||||||
|
|
||||||
p = Person.query.filter_by(name='Jonathan').one()
|
|
||||||
p.todos[0].move_to_bottom()
|
|
||||||
p.todos[2].move_to_top()
|
|
||||||
session.commit(); session.clear()
|
|
||||||
|
|
||||||
p = Person.query.filter_by(name='Jonathan').one()
|
|
||||||
assert p.todos[0].subject == 'One'
|
|
||||||
assert p.todos[1].subject == 'Two'
|
|
||||||
assert p.todos[2].subject == 'Three'
|
|
||||||
|
|
||||||
|
|
||||||
For more examples, refer to the unit tests for this plugin.
|
|
||||||
'''
|
|
||||||
|
|
||||||
from elixir.statements import Statement
|
|
||||||
from elixir.events import before_insert, before_delete
|
|
||||||
from sqlalchemy import Column, Integer, select, func, literal, and_
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
__all__ = ['acts_as_list']
|
|
||||||
__doc_all__ = []
|
|
||||||
|
|
||||||
|
|
||||||
def get_entity_where(instance):
|
|
||||||
clauses = []
|
|
||||||
for column in instance.table.primary_key.columns:
|
|
||||||
instance_value = getattr(instance, column.name)
|
|
||||||
clauses.append(column == instance_value)
|
|
||||||
return and_(*clauses)
|
|
||||||
|
|
||||||
|
|
||||||
class ListEntityBuilder(object):
|
|
||||||
|
|
||||||
def __init__(self, entity, qualifier=None, column_name='position'):
|
|
||||||
warnings.warn("The act_as_list extension is deprecated. Please use "
|
|
||||||
"SQLAlchemy's orderinglist extension instead",
|
|
||||||
DeprecationWarning, stacklevel=6)
|
|
||||||
self.entity = entity
|
|
||||||
self.qualifier_method = qualifier
|
|
||||||
self.column_name = column_name
|
|
||||||
|
|
||||||
def create_non_pk_cols(self):
|
|
||||||
if self.entity._descriptor.autoload:
|
|
||||||
for c in self.entity.table.c:
|
|
||||||
if c.name == self.column_name:
|
|
||||||
self.position_column = c
|
|
||||||
if not hasattr(self, 'position_column'):
|
|
||||||
raise Exception(
|
|
||||||
"Could not find column '%s' in autoloaded table '%s', "
|
|
||||||
"needed by entity '%s'." % (self.column_name,
|
|
||||||
self.entity.table.name, self.entity.__name__))
|
|
||||||
else:
|
|
||||||
self.position_column = Column(self.column_name, Integer)
|
|
||||||
self.entity._descriptor.add_column(self.position_column)
|
|
||||||
|
|
||||||
def after_table(self):
|
|
||||||
position_column = self.position_column
|
|
||||||
position_column_name = self.column_name
|
|
||||||
|
|
||||||
qualifier_method = self.qualifier_method
|
|
||||||
if not qualifier_method:
|
|
||||||
qualifier_method = lambda self: None
|
|
||||||
|
|
||||||
def _init_position(self):
|
|
||||||
s = select(
|
|
||||||
[(func.max(position_column)+1).label('value')],
|
|
||||||
qualifier_method(self)
|
|
||||||
).union(
|
|
||||||
select([literal(1).label('value')])
|
|
||||||
)
|
|
||||||
a = s.alias()
|
|
||||||
# we use a second func.max to get the maximum between 1 and the
|
|
||||||
# real max position if any exist
|
|
||||||
setattr(self, position_column_name, select([func.max(a.c.value)]))
|
|
||||||
|
|
||||||
# Note that this method could be rewritten more simply like below,
|
|
||||||
# but because this extension is going to be deprecated anyway,
|
|
||||||
# I don't want to risk breaking something I don't want to maintain.
|
|
||||||
# setattr(self, position_column_name, select(
|
|
||||||
# [func.coalesce(func.max(position_column), 0) + 1],
|
|
||||||
# qualifier_method(self)
|
|
||||||
# ))
|
|
||||||
_init_position = before_insert(_init_position)
|
|
||||||
|
|
||||||
def _shift_items(self):
|
|
||||||
self.table.update(
|
|
||||||
and_(
|
|
||||||
position_column > getattr(self, position_column_name),
|
|
||||||
qualifier_method(self)
|
|
||||||
),
|
|
||||||
values={
|
|
||||||
position_column : position_column - 1
|
|
||||||
}
|
|
||||||
).execute()
|
|
||||||
_shift_items = before_delete(_shift_items)
|
|
||||||
|
|
||||||
def move_to_bottom(self):
|
|
||||||
# move the items that were above this item up one
|
|
||||||
self.table.update(
|
|
||||||
and_(
|
|
||||||
position_column >= getattr(self, position_column_name),
|
|
||||||
qualifier_method(self)
|
|
||||||
),
|
|
||||||
values = {
|
|
||||||
position_column : position_column - 1
|
|
||||||
}
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
# move this item to the max position
|
|
||||||
# MySQL does not support the correlated subquery, so we need to
|
|
||||||
# execute the query (through scalar()). See ticket #34.
|
|
||||||
self.table.update(
|
|
||||||
get_entity_where(self),
|
|
||||||
values={
|
|
||||||
position_column : select(
|
|
||||||
[func.max(position_column) + 1],
|
|
||||||
qualifier_method(self)
|
|
||||||
).scalar()
|
|
||||||
}
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
def move_to_top(self):
|
|
||||||
self.move_to(1)
|
|
||||||
|
|
||||||
def move_to(self, position):
|
|
||||||
current_position = getattr(self, position_column_name)
|
|
||||||
|
|
||||||
# determine which direction we're moving
|
|
||||||
if position < current_position:
|
|
||||||
where = and_(
|
|
||||||
position <= position_column,
|
|
||||||
position_column < current_position,
|
|
||||||
qualifier_method(self)
|
|
||||||
)
|
|
||||||
modifier = 1
|
|
||||||
elif position > current_position:
|
|
||||||
where = and_(
|
|
||||||
current_position < position_column,
|
|
||||||
position_column <= position,
|
|
||||||
qualifier_method(self)
|
|
||||||
)
|
|
||||||
modifier = -1
|
|
||||||
|
|
||||||
# shift the items in between the current and new positions
|
|
||||||
self.table.update(where, values = {
|
|
||||||
position_column : position_column + modifier
|
|
||||||
}).execute()
|
|
||||||
|
|
||||||
# update this item's position to the desired position
|
|
||||||
self.table.update(get_entity_where(self)) \
|
|
||||||
.execute(**{position_column_name: position})
|
|
||||||
|
|
||||||
def move_lower(self):
|
|
||||||
# replace for ex.: p.todos.insert(x + 1, p.todos.pop(x))
|
|
||||||
self.move_to(getattr(self, position_column_name) + 1)
|
|
||||||
|
|
||||||
def move_higher(self):
|
|
||||||
self.move_to(getattr(self, position_column_name) - 1)
|
|
||||||
|
|
||||||
|
|
||||||
# attach new methods to entity
|
|
||||||
self.entity._init_position = _init_position
|
|
||||||
self.entity._shift_items = _shift_items
|
|
||||||
self.entity.move_lower = move_lower
|
|
||||||
self.entity.move_higher = move_higher
|
|
||||||
self.entity.move_to_bottom = move_to_bottom
|
|
||||||
self.entity.move_to_top = move_to_top
|
|
||||||
self.entity.move_to = move_to
|
|
||||||
|
|
||||||
|
|
||||||
acts_as_list = Statement(ListEntityBuilder)
|
|
||||||
@@ -124,16 +124,6 @@ The list of supported arguments are as follows:
|
|||||||
| | module by setting the ``__session__`` attribute of |
|
| | module by setting the ``__session__`` attribute of |
|
||||||
| | that module. |
|
| | that module. |
|
||||||
+---------------------+-------------------------------------------------------+
|
+---------------------+-------------------------------------------------------+
|
||||||
| ``autosetup`` | DEPRECATED. Specify whether that entity will contain |
|
|
||||||
| | automatic setup triggers. |
|
|
||||||
| | That is if this entity will be |
|
|
||||||
| | automatically setup (along with all other entities |
|
|
||||||
| | which were already declared) if any of the following |
|
|
||||||
| | condition happen: some of its attributes are accessed |
|
|
||||||
| | ('c', 'table', 'mapper' or 'query'), instanciated |
|
|
||||||
| | (called) or the create_all method of this entity's |
|
|
||||||
| | metadata is called. Defaults to ``False``. |
|
|
||||||
+---------------------+-------------------------------------------------------+
|
|
||||||
| ``allowcoloverride``| Specify whether it is allowed to override columns. |
|
| ``allowcoloverride``| Specify whether it is allowed to override columns. |
|
||||||
| | By default, Elixir forbids you to add a column to an |
|
| | By default, Elixir forbids you to add a column to an |
|
||||||
| | entity's table which already exist in that table. If |
|
| | entity's table which already exist in that table. If |
|
||||||
@@ -225,7 +215,6 @@ MIGRATION_TO_07_AID = False
|
|||||||
#
|
#
|
||||||
options_defaults = dict(
|
options_defaults = dict(
|
||||||
abstract=False,
|
abstract=False,
|
||||||
autosetup=False,
|
|
||||||
inheritance='single',
|
inheritance='single',
|
||||||
polymorphic=True,
|
polymorphic=True,
|
||||||
identity=None,
|
identity=None,
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
# Some helper functions to get by without Python 2.4
|
|
||||||
|
|
||||||
# set
|
|
||||||
try:
|
|
||||||
set = set
|
|
||||||
except NameError:
|
|
||||||
from sets import Set as set
|
|
||||||
|
|
||||||
orig_cmp = cmp
|
|
||||||
# [].sort
|
|
||||||
def sort_list(l, cmp=None, key=None, reverse=False):
|
|
||||||
try:
|
|
||||||
l.sort(cmp, key, reverse)
|
|
||||||
except TypeError, e:
|
|
||||||
if not str(e).startswith('sort expected at most 1 arguments'):
|
|
||||||
raise
|
|
||||||
if cmp is None:
|
|
||||||
cmp = orig_cmp
|
|
||||||
if key is not None:
|
|
||||||
# the cmp=cmp parameter is required to get the original comparator
|
|
||||||
# into the lambda namespace
|
|
||||||
cmp = lambda self, other, cmp=cmp: cmp(key(self), key(other))
|
|
||||||
if reverse:
|
|
||||||
cmp = lambda self, other, cmp=cmp: -cmp(self,other)
|
|
||||||
l.sort(cmp)
|
|
||||||
|
|
||||||
# sorted
|
|
||||||
try:
|
|
||||||
sorted = sorted
|
|
||||||
except NameError:
|
|
||||||
# global name 'sorted' doesn't exist in Python2.3
|
|
||||||
# this provides a poor-man's emulation of the sorted built-in method
|
|
||||||
def sorted(l, cmp=None, key=None, reverse=False):
|
|
||||||
sorted_list = list(l)
|
|
||||||
sort_list(sorted_list, cmp, key, reverse)
|
|
||||||
return sorted_list
|
|
||||||
|
|
||||||
# rsplit
|
|
||||||
try:
|
|
||||||
''.rsplit
|
|
||||||
def rsplit(s, delim, maxsplit):
|
|
||||||
return s.rsplit(delim, maxsplit)
|
|
||||||
|
|
||||||
except AttributeError:
|
|
||||||
def rsplit(s, delim, maxsplit):
|
|
||||||
"""Return a list of the words of the string s, scanning s
|
|
||||||
from the end. To all intents and purposes, the resulting
|
|
||||||
list of words is the same as returned by split(), except
|
|
||||||
when the optional third argument maxsplit is explicitly
|
|
||||||
specified and nonzero. When maxsplit is nonzero, at most
|
|
||||||
maxsplit number of splits - the rightmost ones - occur,
|
|
||||||
and the remainder of the string is returned as the first
|
|
||||||
element of the list (thus, the list will have at most
|
|
||||||
maxsplit+1 elements). New in version 2.4.
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 0)
|
|
||||||
['foo.bar.baz']
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 1)
|
|
||||||
['foo.bar', 'baz']
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 2)
|
|
||||||
['foo', 'bar', 'baz']
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 99)
|
|
||||||
['foo', 'bar', 'baz']
|
|
||||||
"""
|
|
||||||
assert maxsplit >= 0
|
|
||||||
|
|
||||||
if maxsplit == 0: return [s]
|
|
||||||
|
|
||||||
# the following lines perform the function, but inefficiently.
|
|
||||||
# This may be adequate for compatibility purposes
|
|
||||||
items = s.split(delim)
|
|
||||||
if maxsplit < len(items):
|
|
||||||
items[:-maxsplit] = [delim.join(items[:-maxsplit])]
|
|
||||||
return items
|
|
||||||
@@ -278,8 +278,9 @@ relationships accept the following optional (keyword) arguments:
|
|||||||
| | reference the "local"/current entity's table. |
|
| | reference the "local"/current entity's table. |
|
||||||
+--------------------+--------------------------------------------------------+
|
+--------------------+--------------------------------------------------------+
|
||||||
| ``table`` | Use a manually created table. If this argument is |
|
| ``table`` | Use a manually created table. If this argument is |
|
||||||
| | used, Elixir won't generate a table for this |
|
| | used, Elixir will not generate a table for this |
|
||||||
| | relationship, and use the one given instead. |
|
| | relationship, and use the one given instead. This |
|
||||||
|
| | argument only accepts SQLAlchemy's Table objects. |
|
||||||
+--------------------+--------------------------------------------------------+
|
+--------------------+--------------------------------------------------------+
|
||||||
| ``order_by`` | Specify which field(s) should be used to sort the |
|
| ``order_by`` | Specify which field(s) should be used to sort the |
|
||||||
| | results given by accessing the relation field. |
|
| | results given by accessing the relation field. |
|
||||||
@@ -303,13 +304,6 @@ relationships accept the following optional (keyword) arguments:
|
|||||||
| ``table_kwargs`` | A dictionary holding any other keyword argument you |
|
| ``table_kwargs`` | A dictionary holding any other keyword argument you |
|
||||||
| | might want to pass to the underlying Table object. |
|
| | might want to pass to the underlying Table object. |
|
||||||
+--------------------+--------------------------------------------------------+
|
+--------------------+--------------------------------------------------------+
|
||||||
| ``column_format`` | DEPRECATED. Specify an alternate format string for |
|
|
||||||
| | naming the |
|
|
||||||
| | columns in the mapping table. The default value is |
|
|
||||||
| | defined in ``elixir.options.M2MCOL_NAMEFORMAT``. You |
|
|
||||||
| | will be passed ``tablename``, ``key``, and ``entity`` |
|
|
||||||
| | as arguments to the format string. |
|
|
||||||
+--------------------+--------------------------------------------------------+
|
|
||||||
|
|
||||||
|
|
||||||
================
|
================
|
||||||
@@ -493,6 +487,7 @@ class Relationship(Property):
|
|||||||
self.property = relation(self.target, **kwargs)
|
self.property = relation(self.target, **kwargs)
|
||||||
self.add_mapper_property(self.name, self.property)
|
self.add_mapper_property(self.name, self.property)
|
||||||
|
|
||||||
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
if not self._target:
|
if not self._target:
|
||||||
if isinstance(self.of_kind, basestring):
|
if isinstance(self.of_kind, basestring):
|
||||||
@@ -501,8 +496,8 @@ class Relationship(Property):
|
|||||||
else:
|
else:
|
||||||
self._target = self.of_kind
|
self._target = self.of_kind
|
||||||
return self._target
|
return self._target
|
||||||
target = property(target)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def inverse(self):
|
def inverse(self):
|
||||||
if not hasattr(self, '_inverse'):
|
if not hasattr(self, '_inverse'):
|
||||||
if self.inverse_name:
|
if self.inverse_name:
|
||||||
@@ -531,7 +526,6 @@ class Relationship(Property):
|
|||||||
inverse._inverse = self
|
inverse._inverse = self
|
||||||
|
|
||||||
return self._inverse
|
return self._inverse
|
||||||
inverse = property(inverse)
|
|
||||||
|
|
||||||
def match_type_of(self, other):
|
def match_type_of(self, other):
|
||||||
return False
|
return False
|
||||||
@@ -612,12 +606,12 @@ class ManyToOne(Relationship):
|
|||||||
def match_type_of(self, other):
|
def match_type_of(self, other):
|
||||||
return isinstance(other, (OneToMany, OneToOne))
|
return isinstance(other, (OneToMany, OneToOne))
|
||||||
|
|
||||||
|
@property
|
||||||
def target_table(self):
|
def target_table(self):
|
||||||
if isinstance(self.target, EntityMeta):
|
if isinstance(self.target, EntityMeta):
|
||||||
return self.target._descriptor.table
|
return self.target._descriptor.table
|
||||||
else:
|
else:
|
||||||
return class_mapper(self.target).local_table
|
return class_mapper(self.target).local_table
|
||||||
target_table = property(target_table)
|
|
||||||
|
|
||||||
def create_keys(self, pk):
|
def create_keys(self, pk):
|
||||||
'''
|
'''
|
||||||
@@ -634,7 +628,10 @@ class ManyToOne(Relationship):
|
|||||||
source_desc = self.entity._descriptor
|
source_desc = self.entity._descriptor
|
||||||
if isinstance(self.target, EntityMeta):
|
if isinstance(self.target, EntityMeta):
|
||||||
# make sure the target has all its pk set up
|
# make sure the target has all its pk set up
|
||||||
|
#FIXME: this is not enough when specifying target_column manually,
|
||||||
|
# on unique, non-pk col, see tests/test_m2o.py:test_non_pk_forward
|
||||||
self.target._descriptor.create_pk_cols()
|
self.target._descriptor.create_pk_cols()
|
||||||
|
|
||||||
#XXX: another option, instead of the FakeTable, would be to create an
|
#XXX: another option, instead of the FakeTable, would be to create an
|
||||||
# EntityDescriptor for the SA class.
|
# EntityDescriptor for the SA class.
|
||||||
target_table = self.target_table
|
target_table = self.target_table
|
||||||
@@ -655,6 +652,12 @@ class ManyToOne(Relationship):
|
|||||||
"Couldn't find a foreign key constraint in table "
|
"Couldn't find a foreign key constraint in table "
|
||||||
"'%s' using the following columns: %s."
|
"'%s' using the following columns: %s."
|
||||||
% (self.entity.table.name, colnames))
|
% (self.entity.table.name, colnames))
|
||||||
|
else:
|
||||||
|
# in this case we let SA handle everything.
|
||||||
|
# XXX: we might want to try to build join clauses anyway so
|
||||||
|
# that we know whether there is an ambiguity or not, and
|
||||||
|
# suggest using colname if there is one
|
||||||
|
pass
|
||||||
if self.field:
|
if self.field:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"'field' argument not allowed on autoloaded table "
|
"'field' argument not allowed on autoloaded table "
|
||||||
@@ -808,8 +811,8 @@ class OneToOne(Relationship):
|
|||||||
# useless because the remote_side is already setup in the other way
|
# useless because the remote_side is already setup in the other way
|
||||||
# (ManyToOne).
|
# (ManyToOne).
|
||||||
if self.entity.table is self.target.table:
|
if self.entity.table is self.target.table:
|
||||||
#FIXME: IF this code is of any use, it will probably break for
|
# When using a manual/autoloaded table, it will be assigned
|
||||||
# autoloaded tables
|
# an empty list, which doesn't seem to upset SQLAlchemy
|
||||||
kwargs['remote_side'] = self.inverse.foreign_key
|
kwargs['remote_side'] = self.inverse.foreign_key
|
||||||
|
|
||||||
# Contrary to ManyToMany relationships, we need to specify the join
|
# Contrary to ManyToMany relationships, we need to specify the join
|
||||||
@@ -839,7 +842,6 @@ class ManyToMany(Relationship):
|
|||||||
local_colname=None, remote_colname=None,
|
local_colname=None, remote_colname=None,
|
||||||
ondelete=None, onupdate=None,
|
ondelete=None, onupdate=None,
|
||||||
table=None, schema=None,
|
table=None, schema=None,
|
||||||
column_format=None,
|
|
||||||
filter=None,
|
filter=None,
|
||||||
table_kwargs=None,
|
table_kwargs=None,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
@@ -858,14 +860,9 @@ class ManyToMany(Relationship):
|
|||||||
self.table = table
|
self.table = table
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
|
||||||
if column_format:
|
#TODO: this can probably be simplified/moved elsewhere since the
|
||||||
warnings.warn("The 'column_format' argument on ManyToMany "
|
#argument disappeared
|
||||||
"relationships is deprecated. Please use the 'local_colname' "
|
self.column_format = options.M2MCOL_NAMEFORMAT
|
||||||
"and/or 'remote_colname' arguments if you want custom "
|
|
||||||
"column names for this table only, or modify "
|
|
||||||
"options.M2MCOL_NAMEFORMAT if you want a custom format for "
|
|
||||||
"all ManyToMany tables", DeprecationWarning, stacklevel=3)
|
|
||||||
self.column_format = column_format or options.M2MCOL_NAMEFORMAT
|
|
||||||
if not hasattr(self.column_format, '__call__'):
|
if not hasattr(self.column_format, '__call__'):
|
||||||
# we need to store the format in a variable so that the
|
# we need to store the format in a variable so that the
|
||||||
# closure of the lambda is correct
|
# closure of the lambda is correct
|
||||||
@@ -891,13 +888,6 @@ class ManyToMany(Relationship):
|
|||||||
|
|
||||||
super(ManyToMany, self).__init__(of_kind, *args, **kwargs)
|
super(ManyToMany, self).__init__(of_kind, *args, **kwargs)
|
||||||
|
|
||||||
def get_table(self):
|
|
||||||
warnings.warn("The secondary_table attribute on ManyToMany objects is "
|
|
||||||
"deprecated. You should rather use the table attribute.",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
return self.table
|
|
||||||
secondary_table = property(get_table)
|
|
||||||
|
|
||||||
def match_type_of(self, other):
|
def match_type_of(self, other):
|
||||||
return isinstance(other, ManyToMany)
|
return isinstance(other, ManyToMany)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user