From: Mike Bayer Date: Sun, 2 Aug 2009 18:40:53 +0000 (+0000) Subject: merged -r6204:6233 of trunk X-Git-Tag: rel_0_6_6~46 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4e62a5f61c47d8320a6acd6d9d36c97030a82cdc;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git merged -r6204:6233 of trunk --- diff --git a/CHANGES b/CHANGES index 0a4c34b3d5..c073159da8 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,19 @@ CHANGES inheritance attributes which were based on column_property() or similar would fail to evaluate. [ticket:1480] + + - Improved support for MapperProperty objects overriding + that of an inherited mapper for non-concrete + inheritance setups - attribute extensions won't randomly + collide with each other. [ticket:1488] + + - UPDATE and DELETE do not support ORDER BY, LIMIT, OFFSET, + etc. in standard SQL. Query.update() and Query.delete() + now raise an exception if any of limit(), offset(), + order_by(), group_by(), or distinct() have been + called. [ticket:1487] + + - Added AttributeExtension to sqlalchemy.orm.__all__ - Improved error message when query() is called with a non-SQL /entity expression. [ticket:1476] @@ -45,6 +58,11 @@ CHANGES [ticket:1424]. See http://www.sqlalchemy.org/trac/wiki/UsageRecipes/PreFilteredQuery for an example. + + - Fixed a somewhat hypothetical issue which would result + in the wrong primary key being calculated for a mapper + using the old polymorphic_union function - but this + is old stuff. [ticket:1486] - sql - Fixed a bug in extract() introduced in 0.5.4 whereby @@ -59,6 +77,10 @@ CHANGES - Fixed bug in Table and Column whereby passing empty dict for "info" argument would raise an exception. [ticket:1482] + +- oracle + - Backported 0.6 fix for Oracle alias names not getting + truncated. [ticket:1309] - ext - The collection proxies produced by associationproxy are now @@ -69,6 +91,11 @@ CHANGES - Declarative will raise an informative exception if __table_args__ is passed as a tuple with no dict argument. Improved documentation. [ticket:1468] + +- test + - Added examples into the test suite so they get exercised + regularly and cleaned up a couple deprecation warnings. + 0.5.5 ======= diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/adjacencytree/__init__.py b/examples/adjacencytree/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/association/__init__.py b/examples/association/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/collections/__init__.py b/examples/collections/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/custom_attributes/__init__.py b/examples/custom_attributes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/derived_attributes/__init__.py b/examples/derived_attributes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/dynamic_dict/__init__.py b/examples/dynamic_dict/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/elementtree/__init__.py b/examples/elementtree/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/elementtree/pickle.py b/examples/elementtree/pickle.py index 220bb22953..2176512cf1 100644 --- a/examples/elementtree/pickle.py +++ b/examples/elementtree/pickle.py @@ -26,12 +26,17 @@ from xml.etree import ElementTree engine = create_engine('sqlite://') meta = MetaData(engine) +# setup a comparator for the PickleType since it's a mutable +# element. +def are_elements_equal(x, y): + return x == y + # stores a top level record of an XML document. # the "element" column will store the ElementTree document as a BLOB. documents = Table('documents', meta, Column('document_id', Integer, primary_key=True), Column('filename', String(30), unique=True), - Column('element', PickleType) + Column('element', PickleType(comparator=are_elements_equal)) ) meta.create_all() diff --git a/examples/graphs/__init__.py b/examples/graphs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/nested_sets/__init__.py b/examples/nested_sets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/pickle/__init__.py b/examples/pickle/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/pickle/custom_pickler.py b/examples/pickle/custom_pickler.py index 79a8b3fa39..a02b708e57 100644 --- a/examples/pickle/custom_pickler.py +++ b/examples/pickle/custom_pickler.py @@ -68,7 +68,13 @@ class Foo(object): class Bar(object): def __init__(self, value): self.data = value - + + def __eq__(self, other): + if not other is None: + return self.data == other.data + return NotImplemented + + mapper(Foo, foo_table, extension=MyExt()) mapper(Bar, bar_table) diff --git a/examples/poly_assoc/__init__.py b/examples/poly_assoc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/polymorph/__init__.py b/examples/polymorph/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/polymorph/polymorph.py b/examples/polymorph/polymorph.py index ea56ffed1f..60ef98f57d 100644 --- a/examples/polymorph/polymorph.py +++ b/examples/polymorph/polymorph.py @@ -66,7 +66,7 @@ mapper(Company, companies, properties={ 'employees': relation(Person, lazy=False, backref='company', cascade="all, delete-orphan") }) -session = create_session(echo_uow=False) +session = create_session() c = Company(name='company1') c.employees.append(Manager(name='pointy haired boss', status='AAB', manager_name='manager1')) c.employees.append(Engineer(name='dilbert', status='BBA', engineer_name='engineer1', primary_language='java')) diff --git a/examples/postgis/__init__.py b/examples/postgis/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/query_caching/__init__.py b/examples/query_caching/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/sharding/__init__.py b/examples/sharding/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/vertical/__init__.py b/examples/vertical/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 41a8550fff..3c39316da3 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -26,6 +26,7 @@ from sqlalchemy.orm.interfaces import ( MapperExtension, PropComparator, SessionExtension, + AttributeExtension, ) from sqlalchemy.orm.util import ( AliasedClass as aliased, @@ -61,6 +62,7 @@ __all__ = ( 'EXT_STOP', 'InstrumentationManager', 'MapperExtension', + 'AttributeExtension', 'Validator', 'PropComparator', 'Query', diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 5335a6a6d3..b502c55aa4 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -649,6 +649,9 @@ class Mapper(object): return self # initialize properties on all mappers + # note that _mapper_registry is unordered, which + # may randomly conceal/reveal issues related to + # the order of mapper compilation for mapper in list(_mapper_registry): if getattr(mapper, '_compile_failed', False): raise sa_exc.InvalidRequestError("One or more mappers failed to compile. Exception was probably " diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 0ea67f6e53..21137bc28b 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -316,6 +316,21 @@ class Query(object): "Otherwise, call %s() before limit() or offset() are applied." % (meth, meth) ) + def _no_select_modifiers(self, meth): + if not self._enable_assertions: + return + for attr, methname, notset in ( + ('_limit', 'limit()', None), + ('_offset', 'offset()', None), + ('_order_by', 'order_by()', False), + ('_group_by', 'group_by()', False), + ('_distinct', 'distinct()', False), + ): + if getattr(self, attr) is not notset: + raise sa_exc.InvalidRequestError( + "Can't call Query.%s() when %s has been called" % (meth, methname) + ) + def _get_options(self, populate_existing=None, version_check=None, only_load_props=None, @@ -1617,6 +1632,7 @@ class Query(object): if synchronize_session not in [False, 'evaluate', 'fetch']: raise sa_exc.ArgumentError("Valid strategies for session synchronization are False, 'evaluate' and 'fetch'") + self._no_select_modifiers("delete") self = self.enable_eagerloads(False) @@ -1716,6 +1732,7 @@ class Query(object): if synchronize_session not in [False, 'evaluate', 'fetch']: raise sa_exc.ArgumentError("Valid strategies for session synchronization are False, 'evaluate' and 'fetch'") + self._no_select_modifiers("update") self = self.enable_eagerloads(False) diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 8204edd674..e19e8fb31c 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -46,8 +46,10 @@ def _register_attribute(strategy, mapper, useobject, if useobject: attribute_ext.append(sessionlib.UOWEventHandler(prop.key)) + for m in mapper.polymorphic_iterator(): - if (m is prop.parent or not m.concrete) and m.has_property(prop.key): + if prop is m._props.get(prop.key): + attributes.register_attribute_impl( m.class_, prop.key, diff --git a/setup.cfg b/setup.cfg index 25ee974dba..517492791e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,4 +3,4 @@ tag_build = dev tag_svn_revision = true [nosetests] -with-sqlalchemy = true \ No newline at end of file +with-sqlalchemy = true diff --git a/test/ex/__init__.py b/test/ex/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/ex/test_examples.py b/test/ex/test_examples.py new file mode 100644 index 0000000000..411c44a0b3 --- /dev/null +++ b/test/ex/test_examples.py @@ -0,0 +1,46 @@ +from sqlalchemy.test import * +import os, re + + +def find_py_files(dirs): + for dn in dirs: + dn = os.path.abspath(dn) + for root, dirs, files in os.walk(dn): + for r in '.svn', 'CVS', '.git', '.hg': + try: + dirs.remove(r) + except ValueError: + pass + + pyfiles = [fn for fn in files if fn.endswith('.py')] + if not pyfiles: + continue + + # Find the root of the packages. + packroot = root + while 1: + if not os.path.exists(os.path.join(packroot, '__init__.py')): + break + packroot = os.path.dirname(packroot) + + for fn in pyfiles: + yield os.path.join(root[len(packroot)+1:], fn) + +def filename_to_module_name(fn): + if os.path.basename(fn) == '__init__.py': + fn = os.path.dirname(fn) + return re.sub('\.py$', '', fn.replace(os.sep, '.')) + +def find_modules(*args): + for fn in find_py_files(args or ('./examples',)): + yield filename_to_module_name(fn) + +def check_import(module): + __import__(module) + + +class ExamplesTest(TestBase): + def test_examples(self): + for module in find_modules(): + check_import.description = module + yield check_import, module diff --git a/test/ext/test_declarative.py b/test/ext/test_declarative.py index 9ca8356918..745e3b7cf8 100644 --- a/test/ext/test_declarative.py +++ b/test/ext/test_declarative.py @@ -1473,7 +1473,7 @@ class DeclarativeReflectionTest(testing.TestBase): if testing.against('oracle', 'firebird'): id = Column('id', Integer, primary_key=True, test_needs_autoincrement=True) - + u1 = User(name='u1', addresses=[ Address(email='one'), Address(email='two'), diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py index 4370088278..c34ccdbab8 100644 --- a/test/orm/test_mapper.py +++ b/test/orm/test_mapper.py @@ -2220,6 +2220,64 @@ class NoLoadTest(_fixtures.FixtureTest): ) +class AttributeExtensionTest(_base.MappedTest): + @classmethod + def define_tables(cls, metadata): + Table('t1', + metadata, + Column('id', Integer, primary_key=True), + Column('type', String(40)), + Column('data', String(50)) + + ) + + @testing.resolve_artifact_names + def test_cascading_extensions(self): + ext_msg = [] + + class Ex1(sa.orm.AttributeExtension): + def set(self, state, value, oldvalue, initiator): + ext_msg.append("Ex1 %r" % value) + return "ex1" + value + + class Ex2(sa.orm.AttributeExtension): + def set(self, state, value, oldvalue, initiator): + ext_msg.append("Ex2 %r" % value) + return "ex2" + value + + class A(_base.BasicEntity): + pass + class B(A): + pass + class C(B): + pass + + mapper(A, t1, polymorphic_on=t1.c.type, polymorphic_identity='a', properties={ + 'data':column_property(t1.c.data, extension=Ex1()) + }) + mapper(B, polymorphic_identity='b', inherits=A) + mc = mapper(C, polymorphic_identity='c', inherits=B, properties={ + 'data':column_property(t1.c.data, extension=Ex2()) + }) + + a1 = A(data='a1') + b1 = B(data='b1') + c1 = C(data='c1') + + eq_(a1.data, 'ex1a1') + eq_(b1.data, 'ex1b1') + eq_(c1.data, 'ex2c1') + + a1.data = 'a2' + b1.data='b2' + c1.data = 'c2' + eq_(a1.data, 'ex1a2') + eq_(b1.data, 'ex1b2') + eq_(c1.data, 'ex2c2') + + eq_(ext_msg, ["Ex1 'a1'", "Ex1 'b1'", "Ex2 'c1'", "Ex1 'a2'", "Ex1 'b2'", "Ex2 'c2'"]) + + class MapperExtensionTest(_fixtures.FixtureTest): run_inserts = None diff --git a/test/orm/test_query.py b/test/orm/test_query.py index cb60bef69a..8cb7ef969b 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -2879,6 +2879,22 @@ class UpdateDeleteTest(_base.MappedTest): 'user': relation(User, lazy=False, backref=backref('documents', lazy=True)) }) + @testing.resolve_artifact_names + def test_illegal_operations(self): + s = create_session() + + for q, mname in ( + (s.query(User).limit(2), "limit"), + (s.query(User).offset(2), "offset"), + (s.query(User).limit(2).offset(2), "limit"), + (s.query(User).order_by(User.id), "order_by"), + (s.query(User).group_by(User.id), "group_by"), + (s.query(User).distinct(), "distinct") + ): + assert_raises_message(sa_exc.InvalidRequestError, r"Can't call Query.update\(\) when %s\(\) has been called" % mname, q.update, {'name':'ed'}) + assert_raises_message(sa_exc.InvalidRequestError, r"Can't call Query.delete\(\) when %s\(\) has been called" % mname, q.delete) + + @testing.resolve_artifact_names def test_delete(self): sess = create_session(bind=testing.db, autocommit=False) diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 0e0cef38f2..95ca0d17bf 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -432,7 +432,6 @@ class ReduceTest(TestBase, AssertsExecutionResults): magazine_page_table.c.page_id, cast(null(), Integer).label('magazine_page_id') ]).select_from(page_table.join(magazine_page_table)), - ).alias('pjoin') eq_(