Ticket #11 (closed defect: fixed)

Opened 5 years ago

Last modified 5 years ago

Since revision 221, dropping all tables can occasionally cause an infinitely recursive call

Reported by: guest Owned by: ged
Priority: normal Milestone: 0.4
Component: core Version:
Keywords: Cc: Chris Wagner <cw264701@…>

Description

I haven't looked very deeply into this problem yet, but if you need more info, please let me know. I will simply provide you with a stack trace, for now:

Traceback (most recent call last):
  File "/home/chris/projects/easyweaze/easyweaze/test/web_tests/test_signup.py", line 18, in setUp
    RequiresCleanDatabaseTestCase.setUp(self)
  File "/home/chris/projects/easyweaze/easyweaze/test/__init__.py", line 19, in setUp
    models.drop_all_database_tables()
  File "./../easyweaze/models.py", line 40, in drop_all_database_tables
  File "/usr/local/lib/python2.5/site-packages/elixir/__init__.py", line 103, in drop_all
    md.drop_all(*args, **kwargs)
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/schema.py", line 1250, in drop_all
    bind.drop(self, checkfirst=checkfirst, tables=tables)
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/engine/base.py", line 1046, in drop
    self._run_visitor(self.dialect.schemadropper, entity, connection=connection, **kwargs)
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/engine/base.py", line 1071, in _run_visitor
    visitorcallable(self.dialect, conn, **kwargs).traverse(element)
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/sql/visitors.py", line 63, in traverse
    meth(target)
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/sql/compiler.py", line 889, in visit_metadata
    collection = [t for t in metadata.table_iterator(reverse=True, tables=self.tables) if (not self.checkfirst or  self.dialect.has_table(self.connection, t.name, schema=t.schema))]
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 650, in table_iterator
    return original_table_iterator(*args, **kwargs)
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/schema.py", line 1146, in table_iterator
    return iter(sorter.sort(reverse=reverse))
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/sql/util.py", line 136, in sort
    self._sorted = self._do_sort()
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/sql/util.py", line 157, in _do_sort
    vis.traverse(table)
  File "/usr/local/lib/python2.5/site-packages/sqlalchemy/sql/visitors.py", line 56, in traverse
    for c in t.get_children(**self.__traverse_options__):
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 543, in __getattr__
    proxied_attr = getattr(self.class_, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
... (and on and on like that for many more frames) ...
File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 556, in __get__
    return getattr(owner, self.attrname)
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 555, in __get__
    _auto_setup_all()
  File "/usr/local/lib/python2.5/site-packages/elixir/entity.py", line 708, in _auto_setup_all
    _cleanup_autosetup_triggers(entity)
RuntimeError: maximum recursion depth exceeded

Sorry for the short description! Please let me know if you need more detail. :)

Change History

Changed 5 years ago by ged

  • owner set to ged
  • status changed from new to accepted
  • milestone set to Elixir 0.4

Do you by any chance drop the tables before setting them up?

I'd appreciate either a short test script or a description of how you ordered those operations: declare your classes, call setup_all, cleanup_all, create_all or drop_all. Thanks for the report.

Changed 5 years ago by ged

  • status changed from accepted to closed
  • resolution set to fixed

I've fixed a bunch of pretty bad bugs in r222 which are related to what you report, though I'm not sure I fixed your particular scenario. Please reopen this bug report if your situation is not fixed.

Changed 5 years ago by guest

  • status changed from closed to reopened
  • resolution deleted
  • milestone deleted

Sorry, I just had a look at this ticket, to see that you responded. Trac never seemed to email me. (I added myself to the CC field when creating the ticket.)

The problem does not seem to be fixed; not in since r222, nor since the most recent revision, r243. I am dropping the tables before setting them up, as this problem occurs when I run my test suite. (I drop them before running the test suite, as opposed to after, in case a previous test run was interrupted or something.) I'll try to write an isolated script that can reproduce the problem for you.

Thanks.

Changed 5 years ago by guest

Okay, here's a script that seems to do the trick:

# -*- coding: utf-8 -*-

from elixir import metadata, create_all, drop_all, Entity, \
  using_options, with_fields, Field, Boolean
from sqlalchemy import create_engine


def get_database_connection_string(engine, db_name, user, password, host, port):

    # TODO: test that this connection string works properly for other database
    #   engines -- especially sqlite (but also Postgres, ...).
    connection_str = engine + "://"
    if engine != "sqlite":
        connection_str += user + ":" + password
        if host == "" or host is None: host = "localhost"
        connection_str += "@" + host
        if port is not None and port != "": connection_str += ":" + port
    connection_str += "/" + db_name

    return connection_str


def connect_to_database(engine, db_name, user, password, host, port):
    metadata.bind = create_engine(get_database_connection_string(
      engine, db_name, user, password, host, port))


def create_all_database_tables():
    create_all()


def drop_all_database_tables():
    drop_all()


class User(Entity):
    using_options(tablename='users')
    with_fields(
      some_field = Field(Boolean, default=False))


connect_to_database('postgres', 'db-name', 'db-user', 'password', None, '5433')

drop_all_database_tables()
create_all_database_tables()

class MockUser(User):
    pass

drop_all_database_tables()

You'll obviously need to set up a database and plug in the right credentials.

The problem is due to the fact that I was sub-classing an existing model. I'm not sure whether you'd even consider this a valid use case, but it did work as I expected before r221.

In favor of allowing such semantics: sub-classing, as such, can be useful for testing. In my case, I was using this method to make sure my view/controller layer was not catching exceptions that it shouldn't be.

Changed 5 years ago by ged

  • status changed from reopened to closed
  • resolution set to fixed
  • milestone set to Elixir 0.4

should be fixed now, thanks for the test case.

Changed 5 years ago by guest

Seems to work! Thanks. :)

Note: See TracTickets for help on using tickets.