Skip to content

Compare Enum types #11

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

Merged
merged 7 commits into from
Jan 25, 2018
Merged

Compare Enum types #11

merged 7 commits into from
Jan 25, 2018

Conversation

charness
Copy link
Contributor

Add checks for changes to SQLAlchemy Enums, implemented as native enum types and/or as check constraints.

In our use case, we failed to catch missing migrations for enum columns.

I've verified these changes locally against PostgreSQL, as well, but haven't yet found a clean way to abstract the differences in expected_errors for MySQL vs PostgreSQL.

Makefile Outdated
@@ -3,7 +3,7 @@
test: flake8 pylint pytest

pylint:
pylint sqlalchemydiff -E
pylint sqlalchemydiff -E --disable=E1102
Copy link
Contributor Author

Choose a reason for hiding this comment

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

without:

************* Module sqlalchemydiff.comparer
E:137,22: inspector.get_table_names is not callable (not-callable)
E:200, 8: ignore_manager.get is not callable (not-callable)
E:207, 8: ignore_manager.get is not callable (not-callable)
E:214, 8: ignore_manager.get is not callable (not-callable)
E:221, 8: ignore_manager.get is not callable (not-callable)
E:228, 8: ignore_manager.get is not callable (not-callable)
E:289,11: inspector.get_foreign_keys is not callable (not-callable)
E:309,11: inspector.get_primary_keys is not callable (not-callable)
E:327,11: inspector.get_indexes is not callable (not-callable)
E:349,11: inspector.get_columns is not callable (not-callable)
E:373,15: inspector.get_check_constraints is not callable (not-callable)
E:394,15: inspector.get_enums is not callable (not-callable)
E:420,11: type_.compile is not callable (not-callable)
E:448,30: errors['tables_data'].setdefault is not callable (not-callable)
E:449,20: table_d.setdefault is not callable (not-callable)
************* Module sqlalchemydiff.util
E: 71,12: stream.write is not callable (not-callable)
E: 98,14: uri.rsplit is not callable (not-callable)
E:106, 4: sqlalchemy_base.metadata.create_all is not callable (not-callable)
E:124,27: data.strip is not callable (not-callable)
E:138,15: data.count is not callable (not-callable)
E:145,15: data.split is not callable (not-callable)
E:152,16: item.strip is not callable (not-callable)
E:152,41: data.split is not callable (not-callable)

@mattbennett
Copy link
Collaborator

@charness this looks awesome! Sorry to leave this PR hanging for such a long time.

I've just updated the travis configuration so the tests run on their new infrastructure. Do you want to re-up this branch so we can get it merged?

Dave Charness added 2 commits November 15, 2017 16:29
Compare enum types and/or check constraints where supported by the
database.
Besides the test changes themselves,

- Update mysql-connector-python to 2.1.6 that's currently available
  from Oracle.
- Suppress "not-callable" errors from pylint.
- Fix flake8 indentation errors.
@charness
Copy link
Contributor Author

Thanks for the update. It was an easy rebase.

@mattbennett mattbennett requested a review from timbu November 17, 2017 16:48
Copy link

@timbu timbu left a comment

Choose a reason for hiding this comment

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

@charness This looks great! Sorry it's taken me so long to get to it.

There's just a couple of suggestions and we have a failing test.


def _get_constraints_data(inspector, table_name):
try:
return inspector.get_check_constraints(table_name)
Copy link

Choose a reason for hiding this comment

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

This is new in sqlalchemy 1.1.0 - could you add the sqlalchemy>=1.1.0 requirement to setup.py?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe, instead, catch AttributeError alongside NotImplementedError and return the same empty list? (A comment mentioning 1.1.0, similar to the one you request below for get_enums, would make sense.)

I think better to leave the sqlalchemy version unconstrained if doing so requires so little.

Copy link

Choose a reason for hiding this comment

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

ah yes - good idea! - let's do the error catching instead.

@@ -215,6 +248,8 @@ def test_errors_dict_catches_all_differences(uri_left, uri_right):
}
}
},
'enums': {
},
Copy link

Choose a reason for hiding this comment

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

I'm seeing an error when running this test:


test/endtoend/test_example.py:261: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test/endtoend/test_example.py:297: in compare_error_dicts
    assert_items_equal(walk_dict(err1, path), walk_dict(err2, path))
../../.pyenv/versions/3.4.4/lib/python3.4/unittest/case.py:1153: in assertCountEqual
    self.fail(msg)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <unittest.case.TestCase testMethod=runTest>
msg = "Element counts were not equal:\nFirst has 1, Second has 0:  {'key': 'name', 'right': {'type': 'VARCHAR(200)', 'name':...llable': True}, 'left': {'type': 'VARCHAR(200)', 'name': 'name', 'default': None, 'comment': None, 'nullable': False}}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Element counts were not equal:
E       First has 1, Second has 0:  {'key': 'name', 'right': {'type': 'VARCHAR(200)', 'name': 'name', 'default': None, 'nullable': True}, 'left': {'type': 'VARCHAR(200)', 'name': 'name', 'default': None, 'nullable': False}}
E       First has 0, Second has 1:  {'key': 'name', 'right': {'type': 'VARCHAR(200)', 'name': 'name', 'default': None, 'comment': None, 'nullable': True}, 'left': {'type': 'VARCHAR(200)', 'name': 'name', 'default': None, 'comment': None, 'nullable': False}}

It looks like we are now getting an extra 'comment': None field - is this expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(TIL) Comment support is new in SQLAlchemy 1.2.0: http://docs.sqlalchemy.org/en/latest/changelog/migration_12.html#change-1546

The change adds a dialect.supports_comments.

I think the most straightforward way to go here is to key off that attribute (e.g., getattr(dialect, 'supports_comments', False)) and add 'comment': None to the expected errors when supported.

Perhaps, though, test_errors_dict_catches_all_differences should compare diff results more selectively? Check that a given key's value differs as expected between left and right, and that the rest match. That would ignore the matching, but unexpected, "comment".

That selectivity should also help make the tests work for a dialect other than mysql (e.g., postgresql), though left_only and right_only would still fail without further work.

Your thoughts?

Copy link

Choose a reason for hiding this comment

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

Agree it would be better to be more selective in the assertions rather than comparing the whole result. But if that's too big a change, your first suggestion will do the job well enough for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For now, I've implemented the simpler change. I didn't want the more-directed tests to hold up this PR.


def _get_enums_data(inspector):
try:
return inspector.get_enums()
Copy link

Choose a reason for hiding this comment

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

perhaps we could add a comment here mentioning that this is specific to the postgres inspector?

Explain the differences in comments and tolerate via try...except.
SQLAlchemy 1.2.0 added support for SQL comments. Since the test
example models include no comments, these come back as None in the
inspection output.

When the dialect indicates it `supports_comments` (a new attribute in
1.2.0), add `"comment": None` to each column in the expected_errors.
SQLAlchemy supports PEP 435 Enum classes as of 1.1.

In order to exercise get_check_constraints-related code aimed
at < 1.1.0, adapt the Polarity (native enum) columns to the
1.0 Enum API when using 1.0.
Testing with sqlalchemy 1.2 and mysql raises neither the
AttributeError nor the NotImplementedError in _get_constraints_data.
Disable coverage checking for the except clause so tests can pass.
@mattbennett
Copy link
Collaborator

Thanks @charness for the contribution! I'll release this in the next cut

@mattbennett mattbennett merged commit f54fe4c into gianchub:master Jan 25, 2018
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.

3 participants