diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ab47f3a..239a73a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,8 +16,8 @@ jobs: strategy: matrix: python-version: - - "3.7" - - "3.12" + - "3.7.17" + - "3.12.10" DB: - "sqlite" - "mssql" @@ -72,13 +72,17 @@ jobs: --health-interval 30s --health-timeout 15s --health-retries 5 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v1 - name: Install Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Install system libs for pymssql + run: | + sudo apt-get update + sudo apt-get install --yes build-essential freetds-dev freetds-bin - name: Install Project run: | python -m pip install --upgrade pip @@ -121,7 +125,7 @@ jobs: uses: chartboost/ruff-action@v1 generate-coverage-report: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: [test-sqlalchemy-history] steps: - name: Coveralls diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd2dd52..3de8371 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,23 @@ -# ref: https://dev.to/m1yag1/how-to-setup-your-project-with-pre-commit-black-and-flake8-183k repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.0.0 - hooks: - - id: flake8 - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.0.292' + rev: v0.11.10 hooks: - - id: ruff - name: ruff - files: ^(sqlalchemy_history|tests)/.*\.py$ - additional_dependencies: - - httpx~=0.24.1 - - tornado~=6.3.3 - - APScheduler~=3.10.4 - - cachetools~=5.3.1 - - aiolimiter~=1.1.0 \ No newline at end of file + # Run the linter. + - id: ruff-check + args: [ --fix ] + files: ^(sqlalchemy_history|tests)/.*\.py$ + additional_dependencies: + - httpx~=0.24.1 + - tornado~=6.3.3 + - APScheduler~=3.10.4 + - cachetools~=5.3.1 + - aiolimiter~=1.1.0 + # Run the formatter. + - id: ruff-format + files: ^(sqlalchemy_history|tests)/.*\.py$ + additional_dependencies: + - httpx~=0.24.1 + - tornado~=6.3.3 + - APScheduler~=3.10.4 + - cachetools~=5.3.1 + - aiolimiter~=1.1.0 \ No newline at end of file diff --git a/benchmark.py b/benchmark.py index 9e945e1..7cdc410 100644 --- a/benchmark.py +++ b/benchmark.py @@ -6,15 +6,16 @@ import sqlalchemy as sa from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker, close_all_sessions -from sqlalchemy_history import make_versioned, versioning_manager, remove_versioning -from sqlalchemy_history.transaction import TransactionFactory +from sqlalchemy.orm import close_all_sessions, sessionmaker +from termcolor import colored + +from sqlalchemy_history import make_versioned, remove_versioning, versioning_manager from sqlalchemy_history.plugins import ( PropertyModTrackerPlugin, - TransactionMetaPlugin, TransactionChangesPlugin, + TransactionMetaPlugin, ) -from termcolor import colored +from sqlalchemy_history.transaction import TransactionFactory warnings.simplefilter("error", sa.exc.SAWarning) @@ -80,8 +81,8 @@ class Tag(Model): start = time() - for i in range(20): - for i in range(20): + for _i in range(20): + for _i in range(20): session.add(Article(name="Article", tags=[Tag(), Tag()])) session.commit() diff --git a/pyproject.toml b/pyproject.toml index 80f6a42..ba04ab0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,9 @@ version = "0" authors = ["Corridor Platforms "] license = "Apache-2.0, BSD-3-Clause" readme = "docs/README.md" -packages = [{include = "sqlalchemy_history"}] +packages = [{ include = "sqlalchemy_history" }] repository = "https://github.com/corridor/sqlalchemy-history" -classifiers=[ +classifiers = [ "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", @@ -47,6 +47,96 @@ pytest-cov = "*" line-length = 110 target-version = ['py37'] +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Same as Black. +line-length = 110 +indent-width = 4 + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Enable flake8-bugbear (`B`) rules, in addition to the defaults +# Enable isort (`I`) rules, in addition to the defaults +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + #"UP", + # flake8-bugbear + "B", + # flake8-simplify + #"SIM", + # isort + "I", +] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" + + [tool.coverage.run] dynamic_context = "test_function" branch = true diff --git a/sqlalchemy_history/__init__.py b/sqlalchemy_history/__init__.py index 9b95757..cf6f853 100644 --- a/sqlalchemy_history/__init__.py +++ b/sqlalchemy_history/__init__.py @@ -8,6 +8,7 @@ """ import sqlalchemy as sa + from sqlalchemy_history.exc import ( # noqa: F401 ClassNotVersioned, ImproperlyConfigured, @@ -30,7 +31,6 @@ version_class, ) - __version__ = "2.1.4" diff --git a/sqlalchemy_history/builder.py b/sqlalchemy_history/builder.py index 8071c0b..44ae767 100644 --- a/sqlalchemy_history/builder.py +++ b/sqlalchemy_history/builder.py @@ -1,19 +1,19 @@ """Builder Module Detects and builds version class for models and version tables collected during instrument - phase by the manager +phase by the manager """ from copy import copy -from inspect import getmro from functools import wraps +from inspect import getmro import sqlalchemy as sa from sqlalchemy.orm.descriptor_props import ConcreteInheritedProperty from sqlalchemy_utils.functions import get_declarative_base, get_hybrid_properties -from sqlalchemy_history.utils import get_association_proxies, version_class from sqlalchemy_history.model_builder import ModelBuilder from sqlalchemy_history.relationship_builder import RelationshipBuilder from sqlalchemy_history.table_builder import TableBuilder +from sqlalchemy_history.utils import get_association_proxies, version_class def prevent_reentry(handler): diff --git a/sqlalchemy_history/expression_reflector.py b/sqlalchemy_history/expression_reflector.py index 322d08d..257294d 100644 --- a/sqlalchemy_history/expression_reflector.py +++ b/sqlalchemy_history/expression_reflector.py @@ -1,10 +1,10 @@ -"""This is ExpressionReflector used for generating expression queries. -""" +"""This is ExpressionReflector used for generating expression queries.""" + import sqlalchemy as sa from sqlalchemy.sql.expression import bindparam -from sqlalchemy_history.utils import version_table from sqlalchemy_history.exc import TableNotVersioned +from sqlalchemy_history.utils import version_table class VersionExpressionReflector(sa.sql.visitors.ReplacingCloningVisitor): diff --git a/sqlalchemy_history/fetcher.py b/sqlalchemy_history/fetcher.py index 4294b92..1d997fc 100644 --- a/sqlalchemy_history/fetcher.py +++ b/sqlalchemy_history/fetcher.py @@ -1,9 +1,11 @@ -"""Fetcher Module helps traverse across versions for a given versioned object. -""" +"""Fetcher Module helps traverse across versions for a given versioned object.""" + import operator + import sqlalchemy as sa from sqlalchemy_utils import get_primary_keys, identity -from sqlalchemy_history.utils import tx_column_name, end_tx_column_name + +from sqlalchemy_history.utils import end_tx_column_name, tx_column_name def parent_identity(obj_or_class): diff --git a/sqlalchemy_history/manager.py b/sqlalchemy_history/manager.py index b0107ef..0439e7e 100644 --- a/sqlalchemy_history/manager.py +++ b/sqlalchemy_history/manager.py @@ -6,6 +6,7 @@ and the actual versioning to UnitOfWork class. """ + from functools import wraps import sqlalchemy as sa @@ -57,7 +58,7 @@ def __init__( unit_of_work_cls=UnitOfWork, transaction_cls=None, user_cls=None, - options={}, + options=None, plugins=None, builder=None, ): @@ -66,6 +67,8 @@ def __init__( self.builder = Builder() else: self.builder = builder + if options is None: + options = {} self.builder.manager = self self.reset() if transaction_cls is not None: diff --git a/sqlalchemy_history/model_builder.py b/sqlalchemy_history/model_builder.py index 31ca21f..ef7fa50 100644 --- a/sqlalchemy_history/model_builder.py +++ b/sqlalchemy_history/model_builder.py @@ -1,9 +1,10 @@ -"""Model Builder module build Versioned Models -""" +"""Model Builder module build Versioned Models""" + from copy import copy + import sqlalchemy as sa from sqlalchemy.ext.declarative import declared_attr -from sqlalchemy.orm import column_property +from sqlalchemy.orm import MappedColumn, column_property from sqlalchemy_utils.functions import get_declarative_base, get_primary_keys from sqlalchemy_utils.models import generic_repr @@ -82,15 +83,17 @@ def copy_mapper_args(model): args[arg] = model.__mapper_args__[arg] if "polymorphic_on" in model.__mapper_args__: - column = model.__mapper_args__["polymorphic_on"] - if isinstance(column, str): - args["polymorphic_on"] = column + discriminator_column = model.__mapper_args__["polymorphic_on"] + if isinstance(discriminator_column, str): + args["polymorphic_on"] = discriminator_column + elif isinstance(discriminator_column, MappedColumn): + args["polymorphic_on"] = discriminator_column.column.key else: - args["polymorphic_on"] = column.key + args["polymorphic_on"] = discriminator_column.key return args -class ModelBuilder(object): +class ModelBuilder: """VersionedModelBuilder handles the building of Version models based on parent table attributes and versioning configuration.""" @@ -244,7 +247,10 @@ def mapper_args(cls): name = "%sVersion" % (self.model.__name__,) version_cls = type(name, self.base_classes(), args) if option(self.model, "base_classes") is None: - primary_keys = list(get_primary_keys(self.model).keys()) + ["transaction_id", "operation_type"] + primary_keys = list(get_primary_keys(self.model).keys()) + [ + "transaction_id", + "operation_type", + ] version_cls = generic_repr(*primary_keys)(version_cls) return version_cls diff --git a/sqlalchemy_history/operation.py b/sqlalchemy_history/operation.py index fea54fd..93e0f43 100644 --- a/sqlalchemy_history/operation.py +++ b/sqlalchemy_history/operation.py @@ -1,9 +1,7 @@ -"""Operations module contains Operation Class. -""" - -from copy import copy +"""Operations module contains Operation Class.""" from collections import OrderedDict +from copy import copy import sqlalchemy as sa from sqlalchemy_utils import identity diff --git a/sqlalchemy_history/plugins/activity.py b/sqlalchemy_history/plugins/activity.py index 3ff9805..04b9f9c 100644 --- a/sqlalchemy_history/plugins/activity.py +++ b/sqlalchemy_history/plugins/activity.py @@ -163,8 +163,8 @@ from sqlalchemy.inspection import inspect from sqlalchemy_utils import JSONType, generic_relationship -from sqlalchemy_history.plugins.base import Plugin from sqlalchemy_history.factory import ModelFactory +from sqlalchemy_history.plugins.base import Plugin from sqlalchemy_history.utils import version_class, version_obj diff --git a/sqlalchemy_history/plugins/null_delete.py b/sqlalchemy_history/plugins/null_delete.py index cf57786..89c8af5 100644 --- a/sqlalchemy_history/plugins/null_delete.py +++ b/sqlalchemy_history/plugins/null_delete.py @@ -1,6 +1,6 @@ -from sqlalchemy_history.plugins.base import Plugin from sqlalchemy_history.operation import Operation -from sqlalchemy_history.utils import versioned_column_properties, is_internal_column +from sqlalchemy_history.plugins.base import Plugin +from sqlalchemy_history.utils import is_internal_column, versioned_column_properties class NullDeletePlugin(Plugin): diff --git a/sqlalchemy_history/plugins/property_mod_tracker.py b/sqlalchemy_history/plugins/property_mod_tracker.py index 93e28dc..5bdf44e 100644 --- a/sqlalchemy_history/plugins/property_mod_tracker.py +++ b/sqlalchemy_history/plugins/property_mod_tracker.py @@ -15,8 +15,10 @@ """ from copy import copy + import sqlalchemy as sa from sqlalchemy_utils.functions import has_changes + from sqlalchemy_history.plugins.base import Plugin from sqlalchemy_history.utils import versioned_column_properties diff --git a/sqlalchemy_history/plugins/transaction_changes.py b/sqlalchemy_history/plugins/transaction_changes.py index bccfa56..b8f3c15 100644 --- a/sqlalchemy_history/plugins/transaction_changes.py +++ b/sqlalchemy_history/plugins/transaction_changes.py @@ -21,10 +21,11 @@ 233678 Article ================ ================= """ + import sqlalchemy as sa -from sqlalchemy_history.plugins.base import Plugin from sqlalchemy_history.factory import ModelFactory +from sqlalchemy_history.plugins.base import Plugin class TransactionChangesBase(object): diff --git a/sqlalchemy_history/plugins/transaction_meta.py b/sqlalchemy_history/plugins/transaction_meta.py index 883ade4..e008358 100644 --- a/sqlalchemy_history/plugins/transaction_meta.py +++ b/sqlalchemy_history/plugins/transaction_meta.py @@ -48,11 +48,11 @@ """ import sqlalchemy as sa -from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.orm.collections import attribute_mapped_collection -from sqlalchemy_history.plugins.base import Plugin from sqlalchemy_history.factory import ModelFactory +from sqlalchemy_history.plugins.base import Plugin class TransactionMetaBase(object): diff --git a/sqlalchemy_history/relationship_builder.py b/sqlalchemy_history/relationship_builder.py index 28de44a..8bc0f8a 100644 --- a/sqlalchemy_history/relationship_builder.py +++ b/sqlalchemy_history/relationship_builder.py @@ -1,13 +1,14 @@ """Relationship Builder builds and manages relations between versioned model built by builder - module for versioned package +module for versioned package """ + import sqlalchemy as sa from sqlalchemy_history.exc import ClassNotVersioned from sqlalchemy_history.expression_reflector import VersionExpressionReflector from sqlalchemy_history.operation import Operation from sqlalchemy_history.table_builder import TableBuilder -from sqlalchemy_history.utils import adapt_columns, version_class, option +from sqlalchemy_history.utils import adapt_columns, option, version_class class RelationshipBuilder(object): diff --git a/sqlalchemy_history/reverter.py b/sqlalchemy_history/reverter.py index 7f5fbcd..5dbfef5 100644 --- a/sqlalchemy_history/reverter.py +++ b/sqlalchemy_history/reverter.py @@ -1,8 +1,9 @@ -"""Reverter Reverts. -""" +"""Reverter Reverts.""" + import sqlalchemy as sa + from sqlalchemy_history.operation import Operation -from sqlalchemy_history.utils import versioned_column_properties, parent_class +from sqlalchemy_history.utils import parent_class, versioned_column_properties def first_level(paths): @@ -22,7 +23,7 @@ class ReverterException(Exception): class Reverter(object): - def __init__(self, obj, visited_objects=None, relations=[]): + def __init__(self, obj, visited_objects=None, relations=None): self.visited_objects = visited_objects or [] self.obj = obj self.version_parent = self.obj.version_parent @@ -30,7 +31,7 @@ def __init__(self, obj, visited_objects=None, relations=[]): self.parent_mapper = sa.inspect(self.parent_class) self.session = sa.orm.object_session(self.obj) - self.relations = list(relations) + self.relations = list(relations) if relations else [] for path in relations: subpath = path.split(".")[0] if subpath not in self.parent_mapper.relationships: diff --git a/sqlalchemy_history/table_builder.py b/sqlalchemy_history/table_builder.py index 3b02eba..a73cfb3 100644 --- a/sqlalchemy_history/table_builder.py +++ b/sqlalchemy_history/table_builder.py @@ -1,7 +1,6 @@ -"""Table Builder Builds versioned table. -""" -import sqlalchemy as sa +"""Table Builder Builds versioned table.""" +import sqlalchemy as sa from sqlalchemy.sql.sqltypes import Enum @@ -34,7 +33,7 @@ def reflect_column(self, column): if column_copy.name == self.option("transaction_column_name"): column_copy.nullable = False if isinstance(column_copy.type, Enum): - column_copy.type.name = 'history_' + column_copy.type.name + column_copy.type.name = "history_" + column_copy.type.name if not column_copy.primary_key: column_copy.nullable = True diff --git a/sqlalchemy_history/transaction.py b/sqlalchemy_history/transaction.py index 4347374..05f6b28 100644 --- a/sqlalchemy_history/transaction.py +++ b/sqlalchemy_history/transaction.py @@ -1,8 +1,8 @@ -"""Transaction model makes transactions for history tables -""" +"""Transaction model makes transactions for history tables""" -from collections import OrderedDict import datetime +from collections import OrderedDict + import sqlalchemy as sa from sqlalchemy.ext.compiler import compiles @@ -89,14 +89,14 @@ class Transaction(manager.declarative_base, TransactionBase): if isinstance(user_cls, str): try: user_cls = registry[user_cls] - except KeyError: + except KeyError as e: raise ImproperlyConfigured( "Could not build relationship between Transaction" " and %s. %s was not found in declarative class " "registry. Either configure VersioningManager to " "use different user class or disable this " "relationship " % (user_cls, user_cls) - ) + ) from e user_id = sa.Column( sa.inspect(user_cls).primary_key[0].type, @@ -113,7 +113,8 @@ def __repr__(self): ) return "" % ", ".join( ( - "%s=%r" % (field, value) if not isinstance(value, int) + "%s=%r" % (field, value) + if not isinstance(value, int) # We want the following line to ensure that longs get # shown without the ugly L suffix on python 2.x # versions diff --git a/sqlalchemy_history/unit_of_work.py b/sqlalchemy_history/unit_of_work.py index 84e78fe..618ddfd 100644 --- a/sqlalchemy_history/unit_of_work.py +++ b/sqlalchemy_history/unit_of_work.py @@ -1,19 +1,19 @@ -"""UnitOfWork module tracks all unit of transaction needed to be done to track history models trnasactions -""" +"""UnitOfWork module tracks all unit of transaction needed to be done to track history models trnasactions""" from copy import copy import sqlalchemy as sa from sqlalchemy_utils import get_primary_keys, identity + from sqlalchemy_history.operation import Operations +from sqlalchemy_history.schema import update_end_tx_column from sqlalchemy_history.utils import ( end_tx_column_name, - version_class, is_session_modified, tx_column_name, + version_class, versioned_column_properties, ) -from sqlalchemy_history.schema import update_end_tx_column class UnitOfWork(object): @@ -180,7 +180,7 @@ def create_version_objects(self, session): if not self.manager.options["versioning"]: return - for key, operation in copy(self.operations).items(): + for _key, operation in copy(self.operations).items(): if operation.processed: continue diff --git a/sqlalchemy_history/utils.py b/sqlalchemy_history/utils.py index 30e5b36..c0a5fa8 100644 --- a/sqlalchemy_history/utils.py +++ b/sqlalchemy_history/utils.py @@ -1,15 +1,11 @@ -from itertools import chain -from inspect import isclass from collections import defaultdict +from inspect import isclass +from itertools import chain import sqlalchemy as sa from sqlalchemy.orm.attributes import get_history from sqlalchemy.orm.util import AliasedClass -from sqlalchemy_utils.functions import ( - get_primary_keys, - identity, - naturally_equivalent, -) +from sqlalchemy_utils.functions import get_primary_keys, identity, naturally_equivalent from sqlalchemy_history.exc import ClassNotVersioned, TableNotVersioned @@ -40,11 +36,11 @@ def get_versioning_manager(item): try: return versioned_item.__versioning_manager__ - except AttributeError: + except AttributeError as e: if isinstance(versioned_item, sa.Table): - raise TableNotVersioned('Table "%s"' % versioned_item.name) + raise TableNotVersioned('Table "%s"' % versioned_item.name) from e else: - raise ClassNotVersioned(versioned_item.__name__) + raise ClassNotVersioned(versioned_item.__name__) from e def option(obj_or_class, option_name): @@ -85,9 +81,9 @@ def parent_class(version_cls): manager = get_versioning_manager(version_cls) try: return next((k for k, v in manager.version_class_map.items() if v == version_cls)) - except StopIteration: + except StopIteration as e: # Should raise Key Error if we can't find the parent_object of a orphaned versioned_model - raise KeyError(version_cls) + raise KeyError(version_cls) from e def parent_table(version_table): @@ -99,9 +95,9 @@ def parent_table(version_table): manager = get_versioning_manager(version_table) try: return next((k for k, v in manager.version_table_map.items() if v == version_table)) - except StopIteration: + except StopIteration as e: # Raise Key error as we couldn't find parent_object of versioned_object - raise KeyError(version_table) + raise KeyError(version_table) from e def transaction_class(cls): diff --git a/sqlalchemy_history/version.py b/sqlalchemy_history/version.py index b3f210b..2577ab8 100644 --- a/sqlalchemy_history/version.py +++ b/sqlalchemy_history/version.py @@ -61,5 +61,6 @@ def changeset(self): manager.plugins.after_construct_changeset(self, data) return data - def revert(self, relations=[]): + def revert(self, relations=None): + relations = relations or {} return Reverter(self, relations=relations)() diff --git a/tests/__init__.py b/tests/__init__.py index d65825a..bab85f1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,20 +1,27 @@ -from copy import copy import inspect import itertools as it import os +from copy import copy + import pytest import sqlalchemy as sa from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, column_property, close_all_sessions, declarative_base +from sqlalchemy.orm import ( + close_all_sessions, + column_property, + declarative_base, + sessionmaker, +) + from sqlalchemy_history import ( ClassNotVersioned, - version_class, make_versioned, - versioning_manager, remove_versioning, + version_class, + versioning_manager, ) +from sqlalchemy_history.plugins import TransactionChangesPlugin, TransactionMetaPlugin from sqlalchemy_history.transaction import TransactionFactory -from sqlalchemy_history.plugins import TransactionMetaPlugin, TransactionChangesPlugin class QueryPool(object): @@ -148,7 +155,10 @@ class Article(self.Model): __versioned__ = copy(self.options) id = sa.Column( - sa.Integer, sa.Sequence(f"{__tablename__}_seq", start=1), autoincrement=True, primary_key=True + sa.Integer, + sa.Sequence(f"{__tablename__}_seq", start=1), + autoincrement=True, + primary_key=True, ) name = sa.Column(sa.Unicode(255), nullable=False) content = sa.Column(sa.UnicodeText) @@ -162,7 +172,10 @@ class Tag(self.Model): __versioned__ = copy(self.options) id = sa.Column( - sa.Integer, sa.Sequence(f"{__tablename__}_seq", start=1), autoincrement=True, primary_key=True + sa.Integer, + sa.Sequence(f"{__tablename__}_seq", start=1), + autoincrement=True, + primary_key=True, ) name = sa.Column(sa.Unicode(255)) article_id = sa.Column(sa.Integer, sa.ForeignKey(Article.id)) diff --git a/tests/builders/test_relationship_builder.py b/tests/builders/test_relationship_builder.py index bd383b6..fbc6583 100644 --- a/tests/builders/test_relationship_builder.py +++ b/tests/builders/test_relationship_builder.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from tests import TestCase diff --git a/tests/builders/test_table_builder.py b/tests/builders/test_table_builder.py index 824cb60..9b50297 100644 --- a/tests/builders/test_table_builder.py +++ b/tests/builders/test_table_builder.py @@ -1,10 +1,12 @@ import os from copy import copy from datetime import datetime + import sqlalchemy as sa +from pytest import mark + from sqlalchemy_history import version_class from tests import TestCase -from pytest import mark class TestTableBuilder(TestCase): @@ -93,9 +95,7 @@ class Article(self.Model): last_update = sa.Column( sa.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False ) - enum_col = sa.Column( - sa.Enum('TYPE_A', 'TYPE_B', name='test_enum') - ) + enum_col = sa.Column(sa.Enum("TYPE_A", "TYPE_B", name="test_enum")) self.Article = Article @@ -139,12 +139,10 @@ class Article(self.Model): sa.Integer, sa.Sequence(f"{__tablename__}_seq", start=1), autoincrement=True, primary_key=True ) - enum_col = sa.Column( - sa.Enum('TYPE_A', 'TYPE_B', name='test_enum') - ) + enum_col = sa.Column(sa.Enum("TYPE_A", "TYPE_B", name="test_enum")) self.Article = Article def test_name_enums(self): version_model = version_class(self.Article) - assert version_model.enum_col.type.name == 'history_test_enum' \ No newline at end of file + assert version_model.enum_col.type.name == "history_test_enum" diff --git a/tests/inheritance/test_common_base_class.py b/tests/inheritance/test_common_base_class.py index 543cc51..041e457 100644 --- a/tests/inheritance/test_common_base_class.py +++ b/tests/inheritance/test_common_base_class.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from sqlalchemy_history import version_class from tests import TestCase diff --git a/tests/inheritance/test_join_table_inheritance.py b/tests/inheritance/test_join_table_inheritance.py index 6747593..18d1006 100644 --- a/tests/inheritance/test_join_table_inheritance.py +++ b/tests/inheritance/test_join_table_inheritance.py @@ -1,5 +1,6 @@ import pytest import sqlalchemy as sa + from sqlalchemy_history import version_class from tests import TestCase, create_test_cases diff --git a/tests/inheritance/test_multi_level_inheritance.py b/tests/inheritance/test_multi_level_inheritance.py index a7c2e2e..65dabc5 100644 --- a/tests/inheritance/test_multi_level_inheritance.py +++ b/tests/inheritance/test_multi_level_inheritance.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from sqlalchemy_history import version_class from tests import TestCase diff --git a/tests/inheritance/test_single_table_inheritance.py b/tests/inheritance/test_single_table_inheritance.py index bde3fe1..c014f4b 100644 --- a/tests/inheritance/test_single_table_inheritance.py +++ b/tests/inheritance/test_single_table_inheritance.py @@ -1,6 +1,7 @@ import pytest import sqlalchemy as sa -from sqlalchemy_history import versioning_manager, version_class + +from sqlalchemy_history import version_class, versioning_manager from tests import TestCase, create_test_cases diff --git a/tests/plugins/test_activity.py b/tests/plugins/test_activity.py index 12fdc6d..66ce90b 100644 --- a/tests/plugins/test_activity.py +++ b/tests/plugins/test_activity.py @@ -1,8 +1,9 @@ import pytest import sqlalchemy as sa + from sqlalchemy_history import versioning_manager from sqlalchemy_history.plugins import ActivityPlugin -from tests import TestCase, QueryPool +from tests import QueryPool, TestCase class ActivityTestCase(TestCase): diff --git a/tests/plugins/test_property_mod_tracker.py b/tests/plugins/test_property_mod_tracker.py index fbf4ceb..a7fcbb2 100644 --- a/tests/plugins/test_property_mod_tracker.py +++ b/tests/plugins/test_property_mod_tracker.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from sqlalchemy_history import version_class from sqlalchemy_history.plugins import PropertyModTrackerPlugin from tests import TestCase diff --git a/tests/relationships/test_association_table_relations.py b/tests/relationships/test_association_table_relations.py index 43349b7..3996f27 100644 --- a/tests/relationships/test_association_table_relations.py +++ b/tests/relationships/test_association_table_relations.py @@ -1,6 +1,7 @@ import sqlalchemy as sa from sqlalchemy import PrimaryKeyConstraint from sqlalchemy.orm import relationship + from tests import TestCase, create_test_cases diff --git a/tests/relationships/test_custom_condition_relations.py b/tests/relationships/test_custom_condition_relations.py index 9bbf5a2..5a9af04 100644 --- a/tests/relationships/test_custom_condition_relations.py +++ b/tests/relationships/test_custom_condition_relations.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from tests import TestCase, create_test_cases diff --git a/tests/relationships/test_dynamic_relationships.py b/tests/relationships/test_dynamic_relationships.py index 0db65c7..84d3ce6 100644 --- a/tests/relationships/test_dynamic_relationships.py +++ b/tests/relationships/test_dynamic_relationships.py @@ -1,7 +1,9 @@ from copy import copy -from tests import TestCase + import sqlalchemy as sa +from tests import TestCase + class TestDynamicOneToManyRelationships(TestCase): def create_models(self): diff --git a/tests/relationships/test_many_to_many_relations.py b/tests/relationships/test_many_to_many_relations.py index 077b0fc..cae8f42 100644 --- a/tests/relationships/test_many_to_many_relations.py +++ b/tests/relationships/test_many_to_many_relations.py @@ -3,9 +3,8 @@ import sqlalchemy as sa from pytest import mark -from sqlalchemy_history import versioning_manager - +from sqlalchemy_history import versioning_manager from tests import TestCase, create_test_cases diff --git a/tests/relationships/test_non_versioned_classes.py b/tests/relationships/test_non_versioned_classes.py index d1ee3ec..b0aacd1 100644 --- a/tests/relationships/test_non_versioned_classes.py +++ b/tests/relationships/test_non_versioned_classes.py @@ -1,7 +1,9 @@ from copy import copy -from tests import TestCase + import sqlalchemy as sa +from tests import TestCase + class TestRelationshipToNonVersionedClass(TestCase): def create_models(self): diff --git a/tests/relationships/test_one_to_many_relations.py b/tests/relationships/test_one_to_many_relations.py index 1d5f1d5..aea6ba4 100644 --- a/tests/relationships/test_one_to_many_relations.py +++ b/tests/relationships/test_one_to_many_relations.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from tests import TestCase, create_test_cases diff --git a/tests/relationships/test_one_to_one_relations.py b/tests/relationships/test_one_to_one_relations.py index 81cb9bd..798ecd4 100644 --- a/tests/relationships/test_one_to_one_relations.py +++ b/tests/relationships/test_one_to_one_relations.py @@ -1,7 +1,9 @@ from copy import copy -from tests import TestCase, create_test_cases + import sqlalchemy as sa +from tests import TestCase, create_test_cases + class OneToOneRelationshipsTestCase(TestCase): def create_models(self): diff --git a/tests/reported_bugs/test_bug_27_datetime_insertion_issue.py b/tests/reported_bugs/test_bug_27_datetime_insertion_issue.py index 7967090..acaa4bf 100644 --- a/tests/reported_bugs/test_bug_27_datetime_insertion_issue.py +++ b/tests/reported_bugs/test_bug_27_datetime_insertion_issue.py @@ -1,7 +1,8 @@ import datetime -import sqlalchemy as sa from copy import copy +import sqlalchemy as sa + from tests import TestCase @@ -20,7 +21,7 @@ def create_models(self): sa.DateTime, nullable=False, server_default=sa.func.current_timestamp(), - default=lambda: datetime.datetime.now(datetime.timezone.utc) + default=lambda: datetime.datetime.now(datetime.timezone.utc), ), ) diff --git a/tests/reported_bugs/test_bug_97_default_values_in_version_table.py b/tests/reported_bugs/test_bug_97_default_values_in_version_table.py index 157f176..828e2eb 100644 --- a/tests/reported_bugs/test_bug_97_default_values_in_version_table.py +++ b/tests/reported_bugs/test_bug_97_default_values_in_version_table.py @@ -1,5 +1,7 @@ -import sqlalchemy as sa from copy import copy + +import sqlalchemy as sa + from tests import TestCase diff --git a/tests/revert/test_one_to_one_relationship.py b/tests/revert/test_one_to_one_relationship.py index ce23c48..9751861 100644 --- a/tests/revert/test_one_to_one_relationship.py +++ b/tests/revert/test_one_to_one_relationship.py @@ -1,7 +1,9 @@ from copy import copy -from tests import TestCase + import sqlalchemy as sa +from tests import TestCase + class TestRevertOneToOneRelationship(TestCase): def create_models(self): diff --git a/tests/schema/test_update_end_transaction_id.py b/tests/schema/test_update_end_transaction_id.py index 0b7e72d..63c2583 100644 --- a/tests/schema/test_update_end_transaction_id.py +++ b/tests/schema/test_update_end_transaction_id.py @@ -1,10 +1,11 @@ import datetime + import sqlalchemy as sa + from sqlalchemy_history import version_class -from sqlalchemy_history.utils import version_table -from sqlalchemy_history.schema import update_end_tx_column from sqlalchemy_history.operation import Operation - +from sqlalchemy_history.schema import update_end_tx_column +from sqlalchemy_history.utils import version_table from tests import TestCase, create_test_cases @@ -25,7 +26,7 @@ def create_models(self): sa.DateTime, nullable=False, server_default=sa.func.current_timestamp(), - default=lambda: datetime.datetime.now(datetime.timezone.utc) + default=lambda: datetime.datetime.now(datetime.timezone.utc), ), ) diff --git a/tests/schema/test_update_property_mod_flags.py b/tests/schema/test_update_property_mod_flags.py index 80bf304..2c9cd29 100644 --- a/tests/schema/test_update_property_mod_flags.py +++ b/tests/schema/test_update_property_mod_flags.py @@ -1,6 +1,7 @@ from copy import copy import sqlalchemy as sa + from sqlalchemy_history import version_class from sqlalchemy_history.plugins import PropertyModTrackerPlugin from sqlalchemy_history.schema import update_property_mod_flags diff --git a/tests/test_accessors.py b/tests/test_accessors.py index 8a461f3..bd1bacb 100644 --- a/tests/test_accessors.py +++ b/tests/test_accessors.py @@ -1,5 +1,7 @@ from copy import copy + import sqlalchemy as sa + from sqlalchemy_history.utils import tx_column_name from tests import TestCase, create_test_cases diff --git a/tests/test_association_proxy.py b/tests/test_association_proxy.py index 01eeb86..a45779e 100644 --- a/tests/test_association_proxy.py +++ b/tests/test_association_proxy.py @@ -1,6 +1,6 @@ import sqlalchemy as sa -from sqlalchemy_history.utils import get_association_proxies, version_class +from sqlalchemy_history.utils import get_association_proxies, version_class from tests import TestCase diff --git a/tests/test_changeset.py b/tests/test_changeset.py index 1d5bcb2..c012a0f 100644 --- a/tests/test_changeset.py +++ b/tests/test_changeset.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from sqlalchemy_history import get_versioning_manager from tests import TestCase diff --git a/tests/test_column_aliases.py b/tests/test_column_aliases.py index 79ba53f..92e1946 100644 --- a/tests/test_column_aliases.py +++ b/tests/test_column_aliases.py @@ -1,7 +1,6 @@ import sqlalchemy as sa from sqlalchemy_history import version_class - from tests import TestCase, create_test_cases diff --git a/tests/test_column_inclusion_and_exclusion.py b/tests/test_column_inclusion_and_exclusion.py index 24b49e0..0d53e4c 100644 --- a/tests/test_column_inclusion_and_exclusion.py +++ b/tests/test_column_inclusion_and_exclusion.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from sqlalchemy_history import version_class from tests import TestCase diff --git a/tests/test_composite_primary_key.py b/tests/test_composite_primary_key.py index 74021da..05bdf6c 100644 --- a/tests/test_composite_primary_key.py +++ b/tests/test_composite_primary_key.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from sqlalchemy_history import version_class from tests import TestCase diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 2dde944..dada187 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1,16 +1,16 @@ -from pytest import raises import sqlalchemy as sa +from pytest import raises from sqlalchemy.orm import declarative_base + from sqlalchemy_history import ( - versioning_manager, + ClassNotVersioned, ImproperlyConfigured, + TableNotVersioned, TransactionFactory, version_class, - ClassNotVersioned, - TableNotVersioned, + versioning_manager, ) from sqlalchemy_history.utils import version_table - from tests import TestCase diff --git a/tests/test_custom_schema.py b/tests/test_custom_schema.py index c326d93..9305b06 100644 --- a/tests/test_custom_schema.py +++ b/tests/test_custom_schema.py @@ -1,7 +1,9 @@ import os + import sqlalchemy as sa from pytest import mark from sqlalchemy.orm import declarative_base + from tests import TestCase diff --git a/tests/test_custom_version_base_class.py b/tests/test_custom_version_base_class.py index 8c4ae2a..4932b88 100644 --- a/tests/test_custom_version_base_class.py +++ b/tests/test_custom_version_base_class.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from sqlalchemy_history import version_class from tests import TestCase diff --git a/tests/test_delete.py b/tests/test_delete.py index cef7fe7..9388686 100644 --- a/tests/test_delete.py +++ b/tests/test_delete.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from tests import TestCase diff --git a/tests/test_exotic_listener_chaining.py b/tests/test_exotic_listener_chaining.py index 30724f4..1ff7682 100644 --- a/tests/test_exotic_listener_chaining.py +++ b/tests/test_exotic_listener_chaining.py @@ -1,5 +1,6 @@ import pytest import sqlalchemy as sa + from sqlalchemy_history import versioning_manager from tests import TestCase diff --git a/tests/test_exotic_operation_combos.py b/tests/test_exotic_operation_combos.py index 11f388b..a87f4c9 100644 --- a/tests/test_exotic_operation_combos.py +++ b/tests/test_exotic_operation_combos.py @@ -1,4 +1,5 @@ import os + from pytest import mark from sqlalchemy_history.operation import Operation diff --git a/tests/test_hybrid_property.py b/tests/test_hybrid_property.py index b1da835..17a24b1 100644 --- a/tests/test_hybrid_property.py +++ b/tests/test_hybrid_property.py @@ -1,7 +1,8 @@ import datetime + import sqlalchemy as sa -from sqlalchemy_history.utils import version_class +from sqlalchemy_history.utils import version_class from tests import TestCase @@ -24,4 +25,4 @@ def time_from_publish(self): self.Article = Article def test_hybrid_property_mapping_for_versioned_class(self): - version_class(self.Article).time_from_publish + assert version_class(self.Article).time_from_publish diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 598def8..55ad83c 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -1,11 +1,10 @@ import pytest - import sqlalchemy as sa -from sqlalchemy_history import versioning_manager from sqlalchemy_i18n import Translatable, make_translatable, translation_base from sqlalchemy_utils import i18n -from tests.__init__ import TestCase +from sqlalchemy_history import versioning_manager +from tests.__init__ import TestCase i18n.get_locale = lambda: "en" make_translatable() diff --git a/tests/test_insert.py b/tests/test_insert.py index fbdc377..47b6512 100644 --- a/tests/test_insert.py +++ b/tests/test_insert.py @@ -1,6 +1,6 @@ import sqlalchemy as sa -from sqlalchemy_history import count_versions, versioning_manager +from sqlalchemy_history import count_versions, versioning_manager from tests import TestCase diff --git a/tests/test_revert.py b/tests/test_revert.py index 14a0197..cb9efba 100644 --- a/tests/test_revert.py +++ b/tests/test_revert.py @@ -1,7 +1,7 @@ import pytest import sqlalchemy as sa -from sqlalchemy_history.reverter import Reverter, ReverterException +from sqlalchemy_history.reverter import Reverter, ReverterException from tests import TestCase diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 5ba7137..09c4102 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -1,5 +1,6 @@ from sqlalchemy.orm.session import Session -from sqlalchemy_history import versioning_manager, UnitOfWork + +from sqlalchemy_history import UnitOfWork, versioning_manager from tests import TestCase diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 9895301..8e418cb 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -1,10 +1,12 @@ import os import time + import sqlalchemy as sa +from pytest import fixture, mark + from sqlalchemy_history import versioning_manager -from tests import TestCase -from pytest import mark, fixture from sqlalchemy_history.plugins import TransactionMetaPlugin +from tests import TestCase class TestTransaction(TestCase): @@ -45,11 +47,13 @@ def test_changed_entities(self): } def test_transaction_issued_at(self): - time.sleep(1) + time.sleep(1) self.article.name = "Some article 2" self.session.add(self.article) self.session.commit() - assert self.article.versions[0].transaction.issued_at != self.article.versions[1].transaction.issued_at + assert ( + self.article.versions[0].transaction.issued_at != self.article.versions[1].transaction.issued_at + ) # Check that the tests pass without TransactionChangesPlugin diff --git a/tests/test_update.py b/tests/test_update.py index 7b8f753..b4ab081 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -1,4 +1,5 @@ import sqlalchemy as sa + from tests import TestCase diff --git a/tests/test_vacuum.py b/tests/test_vacuum.py index a532ff9..b651e17 100644 --- a/tests/test_vacuum.py +++ b/tests/test_vacuum.py @@ -1,5 +1,4 @@ from sqlalchemy_history import vacuum - from tests import TestCase diff --git a/tests/test_validity_strategy.py b/tests/test_validity_strategy.py index 8339cbe..ea7ad89 100644 --- a/tests/test_validity_strategy.py +++ b/tests/test_validity_strategy.py @@ -1,5 +1,6 @@ import pytest import sqlalchemy as sa + from sqlalchemy_history import version_class from tests import TestCase @@ -30,7 +31,7 @@ class Article(self.Model): def test_schema_contains_end_transaction_id(self): table = version_class(self.Article).__table__ assert "end_transaction_id" in table.c - table.c.end_transaction_id + assert table.c.end_transaction_id is not None assert table.c.end_transaction_id.nullable assert not table.c.end_transaction_id.primary_key diff --git a/tests/utils/test_changeset.py b/tests/utils/test_changeset.py index 784e656..d7f201e 100644 --- a/tests/utils/test_changeset.py +++ b/tests/utils/test_changeset.py @@ -18,6 +18,6 @@ def test_changeset_for_update(self): article = self.Article(name="Some article") self.session.add(article) self.session.commit() - article.tags + assert article.tags == [] article.name = "Updated article" assert changeset(article) == {"name": ["Updated article", "Some article"]} diff --git a/tests/utils/test_is_modified.py b/tests/utils/test_is_modified.py index a00003c..65f12bf 100644 --- a/tests/utils/test_is_modified.py +++ b/tests/utils/test_is_modified.py @@ -1,7 +1,8 @@ from datetime import datetime + import sqlalchemy as sa -from sqlalchemy_history import is_modified +from sqlalchemy_history import is_modified from tests import TestCase diff --git a/tests/utils/test_option.py b/tests/utils/test_option.py index 18a43cf..9c88018 100644 --- a/tests/utils/test_option.py +++ b/tests/utils/test_option.py @@ -1,5 +1,6 @@ import pytest import sqlalchemy as sa + from sqlalchemy_history.utils import option from tests import TestCase diff --git a/tests/utils/test_parent_class.py b/tests/utils/test_parent_class.py index 3770146..cce5e41 100644 --- a/tests/utils/test_parent_class.py +++ b/tests/utils/test_parent_class.py @@ -1,6 +1,6 @@ from pytest import raises -from sqlalchemy_history import parent_class, version_class +from sqlalchemy_history import parent_class, version_class from tests import TestCase diff --git a/tests/utils/test_parent_table.py b/tests/utils/test_parent_table.py index 7724ebf..2e318e9 100644 --- a/tests/utils/test_parent_table.py +++ b/tests/utils/test_parent_table.py @@ -1,9 +1,9 @@ - import datetime + import pytest import sqlalchemy as sa -from sqlalchemy_history.utils import parent_table, version_table +from sqlalchemy_history.utils import parent_table, version_table from tests import TestCase @@ -23,7 +23,7 @@ def create_models(self): sa.DateTime, nullable=False, server_default=sa.func.current_timestamp(), - default=lambda: datetime.datetime.now(datetime.timezone.utc) + default=lambda: datetime.datetime.now(datetime.timezone.utc), ), ) diff --git a/tests/utils/test_tx_column_name.py b/tests/utils/test_tx_column_name.py index 4e54c61..410adad 100644 --- a/tests/utils/test_tx_column_name.py +++ b/tests/utils/test_tx_column_name.py @@ -2,7 +2,6 @@ from sqlalchemy_history.utils import end_tx_attr from tests import TestCase, create_test_cases - setting_variants = { "transaction_column_name": ["transaction_id", "tx_id"], } diff --git a/tests/utils/test_version_class.py b/tests/utils/test_version_class.py index 68b4edd..804a4b2 100644 --- a/tests/utils/test_version_class.py +++ b/tests/utils/test_version_class.py @@ -1,8 +1,8 @@ from pytest import raises + from sqlalchemy_history import ClassNotVersioned, version_class from sqlalchemy_history.manager import VersioningManager from sqlalchemy_history.model_builder import ModelBuilder - from tests import TestCase diff --git a/tests/utils/test_version_table.py b/tests/utils/test_version_table.py index 105a2c0..749e350 100644 --- a/tests/utils/test_version_table.py +++ b/tests/utils/test_version_table.py @@ -1,10 +1,11 @@ import datetime + import pytest import sqlalchemy as sa -from tests import TestCase -from sqlalchemy_history.utils import version_table from sqlalchemy_history.exc import TableNotVersioned +from sqlalchemy_history.utils import version_table +from tests import TestCase class TestVersionTableDefault(TestCase): @@ -23,7 +24,7 @@ def create_models(self): sa.DateTime, nullable=False, server_default=sa.func.current_timestamp(), - default=lambda: datetime.datetime.now(datetime.timezone.utc) + default=lambda: datetime.datetime.now(datetime.timezone.utc), ), ) diff --git a/tests/utils/test_versioning_manager.py b/tests/utils/test_versioning_manager.py index f805bff..091147a 100644 --- a/tests/utils/test_versioning_manager.py +++ b/tests/utils/test_versioning_manager.py @@ -1,10 +1,11 @@ from copy import copy -from pytest import raises + import sqlalchemy as sa +from pytest import raises + from sqlalchemy_history import versioning_manager -from sqlalchemy_history.exc import TableNotVersioned, ClassNotVersioned +from sqlalchemy_history.exc import ClassNotVersioned, TableNotVersioned from sqlalchemy_history.utils import get_versioning_manager - from tests import TestCase