Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve comment and info in Drop/CreateTableOp #881

Conversation

cansjt
Copy link
Contributor

@cansjt cansjt commented Jul 31, 2021

This is a fix for issue #879

Description

The DropTableOp.from_table() method loses the table's SchemaItem.info and comment attributes while the CreateTableOp.from_table() only loses the info attribute.

This means the following assertion does not hold, when a table goes through a from_table() to_table() round trip:

assert table == Op.from_table(table).to_table()  # Symbolic (see note below)

This commit fixes both issues.

Notes:

  1. While fixing this I found the implementations of the Create/DropTableOp to be strangely assymetrical, in particular wrt. the way each deals with extra keywords one can pass to a table. Happy to refactor this a little if you'd like me to.

  2. There is table equality notion implemented. But it seems reliant on the identity of the tables MetaData though, which kind of makes sense if you want to be 100% it is the same object, in the exact same database (given the DB URI+schema name+table name, etc.).

>>> from sqlalchemy import Table, MetaData
>>> t1 = Table('name', MetaData(schema='plop'))
>>> t2 = Table('name', MetaData(schema='plop'))
>>> t1 == t2
False
>>> t3 = Table('name', t1.metadata)
>>> t1 == t3
True

Given that the MetaData object is going "bind-free" in SQLAlchemy 2.0, I wonder if this definition of table equality is still relevant, though.

Checklist

This pull request is:

  • A documentation / typographical error fix
    • Good to go, no issue or tests are needed
  • A short code fix
    • please include the issue number, and create an issue if none exists, which
      must include a complete example of the issue. one line code fixes without an
      issue and demonstration will not be accepted.
    • Please include: Fixes: #<issue number> in the commit message
    • please include tests. one line code fixes without tests will not be accepted.
  • A new feature implementation
    • please include the issue number, and create an issue if none exists, which must
      include a complete example of how the feature would look.
    • Please include: Fixes: #<issue number> in the commit message
    • please include tests.

Have a nice day!

Thanks, you too !

@zzzeek
Copy link
Member

zzzeek commented Jul 31, 2021

hello there -

thanks for this! has a test and everything. I am on vacation this week so have limited time to check into things. will run this through CI.

@zzzeek zzzeek requested a review from sqla-tester July 31, 2021 22:35
Copy link
Collaborator

@sqla-tester sqla-tester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this is sqla-tester setting up my work on behalf of zzzeek to try to get revision 6ed4ed0 of this pull request into gerrit so we can run tests and reviews and stuff

@sqla-tester
Copy link
Collaborator

New Gerrit review created for change 6ed4ed0: https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

Copy link
Collaborator

@sqla-tester sqla-tester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mike bayer (zzzeek) wrote:

hi and thanks for doing this!

changes we need include:

  1. a changelog file docs/build/unreleased/879.rst needs to be added

  2. we do need some factoring as you mention to make DropTableOp look like CreateTableOp but I think it should be for now that CreateTableOp->**kw and DropTableOp->table_kw are treated equivalently for now, no new arguments added

  3. for pep8/flake8, black formatting needs to be run on the files. Best way to do this is to use pre-commit:

    $ cd /path/to/alembic
    $ pre-commit install

the correct formatting steps will take place when you next commit. do a "git commit --amend" and force-push the PR to make sure it gets all the files that were changed.

im on vacation this week so i may not respond quickly.

View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

  • /COMMIT_MSG (line 24): that's public API and while I can see DropTable is different, that's because the arguments sent to Table are currently only used during CREATE TABLE; I believe table_kw was added to better support "reversal" of the DropTableOp back into CreateTableOp. would not change any of that within this change.

  • /COMMIT_MSG (line 36): this definition of "==" is still relevant; if you want a comparison of two Table objects based on their contents, use the .compare() method, 1.4 and above:

    from sqlalchemy import Table, MetaData
    t1 = Table('name', MetaData(schema='plop'))
    t2 = Table('name', MetaData(schema='plop'))
    t1.compare(t2)
    True
    t3 = Table('notname', MetaData(schema='plop'))
    t1.compare(t3)
    False

  • /COMMIT_MSG (line 62): please add:

    Fixes: Drop/CreateTableOp.from_table() no longer copy the table's SchemaItem.info #879

@@ -959,6 +959,7 @@ def __init__(
self.comment = kw.pop("comment", None)
self.prefixes = kw.pop("prefixes", None)
self.kw = kw
self.table_info = kw.pop('info', {})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mike bayer (zzzeek) wrote:

call this ".info"

View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

def __init__(self,
table_name,
schema=None,
table_comment=None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mike bayer (zzzeek) wrote:

I think these both need to come from table_kw using the "pop" form. if we were to refactor this further, "table_kw" would become "**kw" just like CreateTableOp.

Also we need to add "prefixes" here.

View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

self.table_kw = table_kw or {}
self.table_info = table_info or {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mike bayer (zzzeek) wrote:

implement as:

self.info = table_kw.pop("info", None)

View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

op = ops.DropTableOp.from_table(table)
is_not_(op.to_table(), table)
eq_(op.to_table().comment, table.comment)
eq_(op.to_table().info, table.info)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mike bayer (zzzeek) wrote:

see if we can add "prefixes"

View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

op = ops.CreateTableOp.from_table(table)
is_not_(op.to_table(), table)
eq_(op.to_table().comment, table.comment)
eq_(op.to_table().info, table.info)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mike bayer (zzzeek) wrote:

see if we can add "prefixes"

View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

@cans cans force-pushed the issue-879-from-table-schemaitem-info-copy branch 2 times, most recently from a022835 to 9f711a0 Compare August 4, 2021 19:43
table_kw=table.kwargs,
comment=table.comment,
info=table.info,
prefixes=table._prefixes,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is a list and is hence mutable. shouldn't we copy it ? Same for info, which is a dict.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a good idea, seems to not be critical but then i may not be envisioning how these structures may be used, so sure.

@cans cans force-pushed the issue-879-from-table-schemaitem-info-copy branch from 9f711a0 to 300e862 Compare August 4, 2021 19:58
Comment on lines +1117 to +1122
table_kw={
"comment": table.comment,
"info": table.info,
"prefixes": table._prefixes,
**table.kwargs,
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only way I can see to not break the class' __init__() public API.

I cannot help but wonder how much of public API this is though. Indeed, even when reverting a CreateTableOp, for example, DropTableOp.from_table() is used (see l. 967). But on principle I agree, we should not break it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are on the edge of a new "minor" series 0.7 so it's a better time to change the API but I felt for the moment it's best to make this PR work without changing any public API, then make public API changes between these two objects something separate.

alembic/operations/ops.py Outdated Show resolved Hide resolved
@cans cans force-pushed the issue-879-from-table-schemaitem-info-copy branch from 300e862 to 257b987 Compare August 4, 2021 20:21
Copy link
Collaborator

@sqla-tester sqla-tester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this is sqla-tester setting up my work on behalf of CaselIT to try to get revision 257b987 of this pull request into gerrit so we can run tests and reviews and stuff

@sqla-tester
Copy link
Collaborator

Patchset 257b987 added to existing Gerrit review https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

@cans cans force-pushed the issue-879-from-table-schemaitem-info-copy branch from 257b987 to f01d5f4 Compare August 5, 2021 06:21
The `DropTableOp.from_table()` method loses the table's
`SchemaItem.info` and `comment` attributes while the
`CreateTableOp` only loses the `info` attribute.

This commit fixes both issues.

Fixes sqlalchemy#879.
@cans cans force-pushed the issue-879-from-table-schemaitem-info-copy branch from f01d5f4 to e509b9e Compare August 5, 2021 06:34
Copy link
Collaborator

@sqla-tester sqla-tester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this is sqla-tester setting up my work on behalf of zzzeek to try to get revision e509b9e of this pull request into gerrit so we can run tests and reviews and stuff

@sqla-tester
Copy link
Collaborator

Patchset e509b9e added to existing Gerrit review https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

@zzzeek
Copy link
Member

zzzeek commented Aug 10, 2021

specifically, i want to look at the **kw situation for all the operations objects, like indexes and all that. every object has dialect-specific kw so i want to apply one consistent approach to the whole thing and do real deprecation etc.

@sqla-tester
Copy link
Collaborator

mike bayer (zzzeek) wrote:

this looks good to me as is, im going to add a changelog file and also revert that formatting change for now, if we want to work with the kw API to make it make more sense we can approach that separately.

View this in Gerrit at https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/2989

@zzzeek
Copy link
Member

zzzeek commented Aug 10, 2021

also #849 was a similar issue

@zzzeek
Copy link
Member

zzzeek commented Aug 10, 2021

oh i guess black has made those formatting changes, OK.

@zzzeek
Copy link
Member

zzzeek commented Aug 10, 2021

i will also dict-/list- ify info and prefixes.

@CaselIT
Copy link
Member

CaselIT commented Aug 10, 2021

oh i guess black has made those formatting changes, OK.

not sure why there where not there already

@cansjt
Copy link
Contributor Author

cansjt commented Aug 10, 2021

Just added the copy of .prefixes and .info. is it what you meant here ?

diff --git a/alembic/operations/ops.py b/alembic/operations/ops.py
index 5d04a8e..31da81d 100644
--- a/alembic/operations/ops.py
+++ b/alembic/operations/ops.py
@@ -1,3 +1,4 @@
+import copy
 import re
 
 from sqlalchemy.types import NULLTYPE
@@ -956,9 +957,9 @@ class CreateTableOp(MigrateOperation):
         self.table_name = table_name
         self.columns = columns
         self.schema = schema
-        self.info = kw.pop("info", {})
         self.comment = kw.pop("comment", None)
-        self.prefixes = kw.pop("prefixes", None)
+        self.info = copy.deepcopy(kw.pop("info", None))
+        self.prefixes = copy.copy(kw.pop("prefixes", None))
         self.kw = kw
         self._namespace_metadata = _namespace_metadata
         self._constraints_included = _constraints_included
@@ -1099,8 +1100,8 @@ class DropTableOp(MigrateOperation):
         self.schema = schema
         self.table_kw = table_kw or {}
         self.comment = self.table_kw.pop("comment", None)
-        self.info = self.table_kw.pop("info", None)
-        self.prefixes = self.table_kw.pop("prefixes", None)
+        self.info = copy.deepcopy(self.table_kw.pop("info", None))
+        self.prefixes = copy.copy(self.table_kw.pop("prefixes", None))
         self._reverse = _reverse
 
     def to_diff_tuple(self):

@cansjt
Copy link
Contributor Author

cansjt commented Aug 10, 2021

I guess I won't... couldn't push to github.

@zzzeek
Copy link
Member

zzzeek commented Aug 10, 2021

yeah i wouldnt use deepcopy() here. this is pushed. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants