On Tue, Mar 3, 2020, at 3:36 AM, David Siller wrote:
> Hello,
> 
> first and foremost: thank you for SQLAlchemy and Alembic. I've worked with a 
> lot of ORMs, but only with these two I feel very comfortable and I'm doing a 
> lot of crazy stuff with it.
> 
> The current problem that I have: I'm currently creating a lot of tables with 
> a lot of mixins, as most of them have e.g. an id-column or columns for 
> created_at-dates etc. However it seems that I can't control the order of the 
> columns, resulting in e.g. the primary-key-id-column to show up in the middle 
> of the other columns or the created_at-column at the beginning and the 
> created_by-column at the end, especially when I autogenerate the versioning 
> scripts.

Declarative with mixins would hopefully be able to do a little better than 
that, if the issue is just amongst a small handful of columns maybe there's a 
way to fix it at that level. This ordering is actually controlled by an 
attribute on every column called "_creation_order". It gets set when the column 
is first created based on a global counter and that is how the sorting of the 
columns works within Declarative; it sorts on that attribute and sends the 
Column objects to Table in that order. 

also, I don't know how the column ordering that comes from the mappings would 
be different if you rendered metadata.create_all() versus using alembic 
autogenerate, it's the same Table objects.



> 
> 
> 
>  But alembic has also a method in this case (as always ;-)). So I created a 
> Rewriter-method for the CreateTableOps in the env.py-file, re-order the 
> columns in the correct way and reassign this ordered list of columns to 
> op.columns. Unfortunately this doesn't work. Somehow it either uses whatever 
> is already the to_table()-method (?) or ... something else. So I tried to 
> create a new operation in the rewriter with the ordered list and returned 
> this operation instead. But then I get an the error: 
> sqlalchemy.exc.ArgumentError: Column object 'id' already assigned to Table 
> 'user'.


so the CreateTableOp and a lot of the other ops have an "_orig" attribute that 
points to the "original" object being autogenerated. For this object, it's 
"_orig_table". These objects did not anticipate being rewritten at the level at 
which you are attempting to do. So to make the columns-in-place get recognized, 
I have to delete _orig_table:

op._orig_table = None

then it tries to build the Table again, as we want it to do. but then we have 
the same problem which is that these columns are from your existing mapping and 
they are already associated with a table. 

There's a bunch of ways to go here but none of them would be something people 
could normally figure out without asking me here. I think the cleanest way is 
to copy the columns using their copy() method, and then to avoid dealing with 
_orig_table, make a new CreateTableOp:

@writer.rewrites(ops.CreateTableOp)
def order_columns(context, revision, op):

 special_names = {"id": -100, "created_at": 1001, "updated_at": 1002}

 cols_by_key = [
 (
 special_names.get(col.key, index)
 if isinstance(col, Column)
 else 2000,
 col.copy(),
 )
 for index, col in enumerate(op.columns)
 ]

 columns = [
 col for idx, col in sorted(cols_by_key, key=lambda entry: entry[0])
 ]
 return ops.CreateTableOp(op.table_name, columns, schema=op.schema, **op.kw)


let me just stick this in the recipes now because people wouldn't know to do 
this.




> 
> The code I'm using is the following:
> 
> from operator import itemgetter
> from alembic.autogenerate import rewriter
> from alembic.operations.ops import CreateTableOp
> from sqlalchemy.sql.schema import Column
> 
> 
> writer = rewriter.Rewriter()
> 
> 
> @writer.rewriter(CreateTableOp)
> def order_columns(context, revision, op):
> """Reorder the columns before creating a table."""
>  preordered = []
> for col in op.columns:
>  k = 0 # key used for ordering later on
> if not isinstance(col, Column):
>  k = 99 # All constraints or indexes should stay at the end of the definition
> elif col.primary_key and col.name=='id':
>  k = 1
> # + a lot of other ordering constraints for other columns
> else:
>  k= 2 # All non-id-columns
>  preordered.append((k, col)) # Creating my ordered list
> 
> # Now sorting the list and extracting only the column-objects.
> # This list is indeed correctly sorted, just what I want
>  ordered_column_list = [itm[1] for itm in sorted(preordered, 
> key=itemgetter(0))]
> 
> # Creating a new operation and returning it is not working, as it results in 
> an error:
> # Returning: ArgumentError: Column object '...' already assigned to Table 
> '...'
> # new_op = CreateTableOp(op.table_name, ordered_column_list, schema=op.schema)
> # return new_op
> 
> # Reassigning the ordered column list is not working either, it seems to be 
> ignored:
>  op.columns = ordered_column_list
> return op
> 
> [...]
> 
> def run_migrations_online():
> [...]
> with connectable.connect() as connection:
>  context.configure(
> [...]
>  process_revision_directives=writer
> )
> [...]
> 
> 
> The problem is similar to Altering the behavior of AddColumn 
> <https://groups.google.com/forum/#!searchin/sqlalchemy-alembic/order|sort:date/sqlalchemy-alembic/izYq2EMYotI/gMISQpjkAwAJ>,
>  but I need to order multiple columns. The ordering works perfectly fine, the 
> rewriter is also invoked (tested it e.g. by returning [] instead or 
> debugging), just the reordered list of columns is then totally ignored.
> 
> Any hint to point me in the right direction of what I'm doing wrong or any 
> other possibility to do the reordering?
> 
> Thank you in advance and thanks again
> 
> David
> 

> --
>  You received this message because you are subscribed to the Google Groups 
> "sqlalchemy-alembic" group.
>  To unsubscribe from this group and stop receiving emails from it, send an 
> email to sqlalchemy-alembic+unsubscr...@googlegroups.com.
>  To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy-alembic/ef49db6c-420c-49ec-a708-86ad5d874fc1%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/sqlalchemy-alembic/ef49db6c-420c-49ec-a708-86ad5d874fc1%40googlegroups.com?utm_medium=email&utm_source=footer>.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy-alembic" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy-alembic+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy-alembic/2fd9794d-5082-4605-89ff-6abb7ae7a51e%40www.fastmail.com.

Reply via email to