Repository: cedar-team/snapshot-queries (language: python)

Issue:
Improve testing automation



Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index a1cce8e..0000000
--- a/.flake8
+++ /dev/null
@@ -1,2 +0,0 @@
-[flake8]
-ignore = E501,W503
diff --git a/Dockerfile b/Dockerfile
index c5695c5..8f92b38 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,26 +1,21 @@
 ARG PYTHON_VERSION
+
 FROM python:$PYTHON_VERSION as base
+
 WORKDIR /python
 
-COPY requirements.txt ./
-RUN pip install -r requirements.txt
+RUN pip install tox==3.24.5
 
-COPY test.requirements.txt ./
-RUN pip install -r test.requirements.txt
+FROM base as development
+# Configures a venv with django and sqlalchemy installed to facilitate local development
+# and testing
 
-########
-# Django
-########
-FROM base as django
-RUN pip install django
-COPY . ./
-RUN pip install --no-deps -e .
+# Add the bare minimum just so tox can set up the venv
+COPY pyproject.toml /python
+COPY setup.cfg /python
+COPY tox.ini /python
+COPY snapshot_queries /python/snapshot_queries/
 
-############
-# SqlAlchemy
-############
-FROM base as sqlalchemy
-RUN pip install sqlalchemy
-COPY . ./
-RUN pip install --no-deps -e .
+ENV PATH=/venv-tox/bin:$PATH
 
+RUN tox --devenv /venv-tox -e django-and-sqlalchemy
diff --git a/docker-compose.yml b/docker-compose.yml
index fc9d1a5..5e53cb9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,10 +1,10 @@
+# docker-compose file to facilitate local development and testing in dev and CI
 version: '3'
 services:
-  test-django:
-    image: snapshot-queries-django-tests-${PYTHON_VERSION:-3.9}
+  local_development:
     build:
       context: .
-      target: django
+      target: development
       args:
         PYTHON_VERSION: ${PYTHON_VERSION:-3.9}
     volumes:
@@ -13,11 +13,9 @@ services:
     depends_on:
       - postgres-db
 
-  test-sqlalchemy:
-    image: snapshot-queries-sqlalchemy-tests-${PYTHON_VERSION:-3.9}
+  testing_environment:
     build:
       context: .
-      target: sqlalchemy
       args:
         PYTHON_VERSION: ${PYTHON_VERSION:-3.9}
     volumes:
diff --git a/pyproject.toml b/pyproject.toml
index b23e362..b3b7049 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,5 +6,5 @@ requires = [
 build-backend = "setuptools.build_meta"
 
 [tool.semantic_release]
-version_variable = "setup.py:__version__"
-branch = "main"
+version_variable = "snapshot_queries/__init__.py:VERSION"
+branch = "jprieto/update-project-setup"
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index c3b1c2d..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-sqlparse>=0.4.1
-attrs>=20.3.0
-pygments>=2.8.1"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..049c929
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,29 @@
+[metadata]
+name = snapshot-queries
+author = Cedar
+author_email = "support@cedar.com"
+version = attr: snapshot_queries.VERSION
+description = A tool for capturing queries executed in Django and SQLAlchemy
+long_description = file: README.md
+keywords = queries, testing, snapshots, orm, sql
+license_files = LICENSE.txt
+classifiers =
+    Programming Language :: Python :: 3
+    License :: OSI Approved :: MIT License
+    Operating System :: OS Independent
+url = https://github.com/cedar-team/snapshot-queries
+python_requires = >=3.6
+
+[options]
+packages = snapshot_queries
+install_requires =
+    sqlparse>=0.4.1
+    attrs>=20.3.0
+    pygments>=2.8.1
+
+[options.extras_require]
+development = snapshottest==0.5.1;  psycopg2-binary==2.8.6;  nose==1.3.7;  ipdb;
+linting = black==22.3.0; flake8==4.0.1; isort==5.10.1
+
+[flake8]
+ignore = E501,W503
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 2d1ff8d..0000000
--- a/setup.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import setuptools
-import pathlib
-
-import pkg_resources
-import setuptools
-
-__version__ = "0.1.1"
-
-setuptools.setup(
-    name="snapshot-queries",
-    version="0.0.1",
-    author="Cedar",
-    author_email="support@cedar.com",
-    license="MIT",
-    description="A tool for capturing queries executed in Django and SqlAlchemy",
-    long_description=pathlib.Path("README.md").read_text(),
-    long_description_content_type="text/markdown",
-    url="https://github.com/cedar-team/snapshot-queries",
-    project_urls={
-        "Bug Tracker": "https://github.com/cedar-team/snapshot-queries",
-    },
-    classifiers=[
-        "Programming Language :: Python :: 3",
-        "License :: OSI Approved :: MIT License",
-        "Operating System :: OS Independent",
-    ],
-    packages=setuptools.find_packages(),
-    python_requires=">=3.6",
-    install_requires=[
-        str(requirement)
-        for requirement in pkg_resources.parse_requirements(
-            pathlib.Path("requirements.txt").read_text()
-        )
-    ],
-    extras_require={
-        "test": [
-            str(requirement)
-            for requirement in pkg_resources.parse_requirements(
-                pathlib.Path("test.requirements.txt").read_text()
-            )
-        ]
-    },
-)
diff --git a/snapshot_queries/__init__.py b/snapshot_queries/__init__.py
index eb3717d..60df2ec 100644
--- a/snapshot_queries/__init__.py
+++ b/snapshot_queries/__init__.py
@@ -1,3 +1,5 @@
 from .snapshot_queries import snapshot_queries
 
+VERSION = "0.1.1"
+
 __all__ = ["snapshot_queries"]
diff --git a/snapshot_queries/query_list.py b/snapshot_queries/query_list.py
index 02e98c1..466d9c2 100644
--- a/snapshot_queries/query_list.py
+++ b/snapshot_queries/query_list.py
@@ -1,12 +1,11 @@
 import collections
 import sys
-from typing import Dict, Callable
 from collections import UserDict
-
-from .timedelta import TimeDelta
+from typing import Callable, Dict
 
 from .query import Query
 from .sliceable_list import SliceableList
+from .timedelta import TimeDelta
 
 
 class QueryList(SliceableList):
diff --git a/snapshot_queries/snapshot_queries.py b/snapshot_queries/snapshot_queries.py
index ed9c342..6cfb06e 100644
--- a/snapshot_queries/snapshot_queries.py
+++ b/snapshot_queries/snapshot_queries.py
@@ -4,8 +4,8 @@
 from contextlib import contextmanager
 from typing import Callable, Dict
 
-from .query_list import QueryList
 from .query import Query
+from .query_list import QueryList
 
 try:
     from freezegun.api import real_time
diff --git a/snapshot_queries/stacktrace.py b/snapshot_queries/stacktrace.py
index 848a229..12c5ce9 100644
--- a/snapshot_queries/stacktrace.py
+++ b/snapshot_queries/stacktrace.py
@@ -1,9 +1,10 @@
-import attr
 import os
 import pkgutil
 import traceback
 import typing
 
+import attr
+
 from .sliceable_list import SliceableList
 
 
diff --git a/tox.ini b/tox.ini
index 2934d51..869e6e6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,44 +3,49 @@ envlist =
     django-and-sqlalchemy
     sqlalchemy-{10,11,12,13}-only
     django-{111,20,21,22}-only
-    lint
+isolated_build = True
 
 [testenv:django-and-sqlalchemy]
 commands =
     {toxinidir}/tests/test_django/test.sh --no-input {posargs}
     {toxinidir}/tests/test_sqlalchemy/test.sh {posargs}
-extras = test
+extras = development
 deps =
     django>=2.2
     sqlalchemy>=1.4
-    appdirs==1.4.3
 
 [testenv:sqlalchemy{10,11,12,13,14}-only]
 commands = {toxinidir}/tests/test_sqlalchemy/test.sh {posargs}
-extras = test
+extras = development
 deps =
     sqlalchemy10: sqlalchemy>=1.0,<1.1
     sqlalchemy11: sqlalchemy>=1.1,<1.2
     sqlalchemy12: sqlalchemy>=1.2,<1.3
     sqlalchemy13: sqlalchemy>=1.3,<1.4
-    sqlalchemy14: sqlalchemy>=1.4
-    appdirs==1.4.3
+    sqlalchemy14: sqlalchemy>=1.4,<1.5
 
 [testenv:django{111,20,21,22}-only]
 commands = {toxinidir}/tests/test_django/test.sh --no-input {posargs}
-extras = test
+extras = development
 deps =
     django111: django>=1.11,<2.0
     django20: django>=2.0,<2.1
     django21: django>=2.1,<2.2
-    django22: django>=2.2
-    appdirs==1.4.3
+    django22: django>=2.2<2.3
 
+; runs all lint checks (does not change code)
 [testenv:lint]
-deps =
-    flake8
-    black
-    isort
+deps = .[linting]
+skip_install = true
 commands =
     flake8 --max-line-length=89 snapshot_queries
-    black --check snapshot_queries setup.py
+    black --check snapshot_queries
+    isort . --check
+
+; runs all formatting tasks
+[testenv:format]
+deps = .[linting]
+skip_install = true
+commands =
+    black .
+    isort .


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index bd151e8..ac5c113 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,19 +1,24 @@
 name: Tests
-
-on:
-  push:
-    branches: [ main ]
-  pull_request:
-    branches: [ main ]
+on: [pull_request]
 
 jobs:
-  all:
-
+  tests:
     runs-on: ubuntu-latest
-
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [ "3.8", "3.9" ]
     steps:
-    - uses: actions/checkout@v2
-
-    - name: Run Tests
-      run: |
-        ./test.sh --no-snapshot-update
+      - uses: actions/checkout@v2
+      - name: Build image for Python ${{ matrix.python-version }}
+        run: docker-compose build --build-arg PYTHON_VERSION="${{ matrix.python-version }}"
+      - name: Run Tests
+        run: docker-compose run --rm -e TOX_PARALLEL_NO_SPINNER=1 -w /python testing_environment tox --parallel
+  linting:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Build image for Python 3.9
+        run: docker-compose build --build-arg PYTHON_VERSION=3.9
+      - name: Run Tests
+        run: docker-compose run --rm -e TOX_PARALLEL_NO_SPINNER=1 -w /python testing_environment tox -e lint
diff --git a/test.requirements.txt b/test.requirements.txt
deleted file mode 100644
index 2600312..0000000
--- a/test.requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-snapshottest==0.5.1
-psycopg2-binary==2.8.6
-nose==1.3.7
-tox==3.24.3
-ipdb
diff --git a/test.sh b/test.sh
index ef8913a..cff46ad 100755
--- a/test.sh
+++ b/test.sh
@@ -1,4 +1,7 @@
 #!/usr/bin/env bash
+# Run tests locally against one or more versions of Python, optionally updating
+# snapshots
+
 # -e: if any command fails exit immediately
 # -u: fail if undefined variable is used
 # -o pipefail: return value of a pipeline is the return value of the rightmost command
@@ -8,24 +11,13 @@ print_in_cyan() {
     printf "\x1B[36m%b\x1B[0m\n" "$*"
 }
 
-PYTHON_VERSIONS="3.8  3.9"
-TEST_SQLALCHEMY=true
-TEST_DJANGO=true
+PYTHON_VERSIONS=('3.8' '3.9')
 SNAPSHOT_UPDATE="--snapshot-update"
 
-for i in "$@"; do
-  case $i in
+for arg in "$@"; do
+  case $arg in
     --python-version=*)
-      PYTHON_VERSIONS="${i#*=}"
-      shift # past argument=value
-      ;;
-    --django)
-      TEST_SQLALCHEMY=false
-      shift
-      ;;
-    --sqlalchemy)
-      TEST_DJANGO=false
-      shift
+      PYTHON_VERSIONS=("${arg#*=}")
       ;;
     --no-snapshot-update)
       SNAPSHOT_UPDATE=""
@@ -36,18 +28,10 @@ for i in "$@"; do
   esac
 done
 
-for PYTHON_VERSION in $PYTHON_VERSIONS
+for PYTHON_VERSION in "${PYTHON_VERSIONS[@]}"
 do
     print_in_cyan "\nTesting Python $PYTHON_VERSION"
-    export PYTHON_VERSION=$PYTHON_VERSION
-
-    if [ "$TEST_DJANGO" = true ]; then
-        docker-compose build test-django
-        docker-compose run --rm test-django tests/test_django/test.sh $SNAPSHOT_UPDATE
-    fi
 
-    if [ "$TEST_SQLALCHEMY" = true ]; then
-        docker-compose build test-sqlalchemy
-        docker-compose run --rm test-sqlalchemy tests/test_sqlalchemy/test.sh $SNAPSHOT_UPDATE
-    fi
+    docker-compose build --build-arg PYTHON_VERSION="$PYTHON_VERSION"
+    docker-compose run -e --rm testing_environment tox -- "$SNAPSHOT_UPDATE"
 done
diff --git a/tests/test_django/settings/settings.py b/tests/test_django/settings/settings.py
index 7bca241..1512b69 100644
--- a/tests/test_django/settings/settings.py
+++ b/tests/test_django/settings/settings.py
@@ -11,7 +11,6 @@
 """
 
 import os
-
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 from collections import OrderedDict
 
diff --git a/tests/test_django/tests/snapshots/snap_test_snapshot_queries.py b/tests/test_django/tests/snapshots/snap_test_snapshot_queries.py
index 712a5bc..8d97731 100644
--- a/tests/test_django/tests/snapshots/snap_test_snapshot_queries.py
+++ b/tests/test_django/tests/snapshots/snap_test_snapshot_queries.py
@@ -4,12 +4,13 @@
 
 from snapshottest import Snapshot
 
-
 snapshots = Snapshot()
 
-snapshots['SnapshotQueriesTest::test_multiple_queries_display_string 1'] = '''Query 1
+snapshots[
+    "SnapshotQueriesTest::test_multiple_queries_display_string 1"
+] = """Query 1
 ---------
-/python/tests/test_django/tests/test_snapshot_queries.py:21 in test_multiple_queries_display_string
+/python/tests/test_django/tests/test_snapshot_queries.py:22 in test_multiple_queries_display_string
 
 list(User.objects.only("id").filter(id=2))
 
@@ -20,34 +21,38 @@
 
 Query 2
 ---------
-/python/tests/test_django/tests/test_snapshot_queries.py:22 in test_multiple_queries_display_string
+/python/tests/test_django/tests/test_snapshot_queries.py:23 in test_multiple_queries_display_string
 
 list(User.objects.only("email").filter(id=1))
 
 SELECT "auth_user"."id",
        "auth_user"."email"
 FROM "auth_user"
-WHERE "auth_user"."id" = \'1\''''
+WHERE "auth_user"."id" = \'1\'"""
 
-snapshots['SnapshotQueriesTest::test_single_query_display_string 1'] = '''Query 1
+snapshots[
+    "SnapshotQueriesTest::test_single_query_display_string 1"
+] = """Query 1
 ---------
-/python/tests/test_django/tests/test_snapshot_queries.py:14 in test_single_query_display_string
+/python/tests/test_django/tests/test_snapshot_queries.py:15 in test_single_query_display_string
 
 list(User.objects.only("email").filter(id=1))
 
 SELECT "auth_user"."id",
        "auth_user"."email"
 FROM "auth_user"
-WHERE "auth_user"."id" = \'1\''''
+WHERE "auth_user"."id" = \'1\'"""
 
-snapshots['SnapshotQueriesTest::test_multiple_queries_duplicates 1'] = '''
+snapshots[
+    "SnapshotQueriesTest::test_multiple_queries_duplicates 1"
+] = """
 
 ============================
 2 duplicate queries detected
 ============================
 Query 1
 ---------
-/python/tests/test_django/tests/test_snapshot_queries.py:30 in test_multiple_queries_duplicates
+/python/tests/test_django/tests/test_snapshot_queries.py:31 in test_multiple_queries_duplicates
 
 list(User.objects.only("id").filter(id=1))
 
@@ -58,22 +63,24 @@
 
 Query 2
 ---------
-/python/tests/test_django/tests/test_snapshot_queries.py:31 in test_multiple_queries_duplicates
+/python/tests/test_django/tests/test_snapshot_queries.py:32 in test_multiple_queries_duplicates
 
 list(User.objects.only("id").filter(id=1))
 
 SELECT "auth_user"."id"
 FROM "auth_user"
-WHERE "auth_user"."id" = \'1\''''
+WHERE "auth_user"."id" = \'1\'"""
 
-snapshots['SnapshotQueriesTest::test_multiple_queries_similar 1'] = '''
+snapshots[
+    "SnapshotQueriesTest::test_multiple_queries_similar 1"
+] = """
 
 ==========================
 2 similar queries detected
 ==========================
 Query 1
 ---------
-/python/tests/test_django/tests/test_snapshot_queries.py:43 in test_multiple_queries_similar
+/python/tests/test_django/tests/test_snapshot_queries.py:44 in test_multiple_queries_similar
 
 list(User.objects.only("email").filter(id=1))
 
@@ -85,11 +92,11 @@
 
 Query 2
 ---------
-/python/tests/test_django/tests/test_snapshot_queries.py:44 in test_multiple_queries_similar
+/python/tests/test_django/tests/test_snapshot_queries.py:45 in test_multiple_queries_similar
 
 list(User.objects.only("email").filter(id=2))
 
 SELECT "auth_user"."id",
        "auth_user"."email"
 FROM "auth_user"
-WHERE "auth_user"."id" = \'2\''''
+WHERE "auth_user"."id" = \'2\'"""
diff --git a/tests/test_django/tests/test_snapshot_queries.py b/tests/test_django/tests/test_snapshot_queries.py
index 310a9ce..8f3bd04 100644
--- a/tests/test_django/tests/test_snapshot_queries.py
+++ b/tests/test_django/tests/test_snapshot_queries.py
@@ -1,7 +1,8 @@
-from snapshot_queries import snapshot_queries
 from django.contrib.auth import get_user_model
 from snapshottest.django import TestCase
 
+from snapshot_queries import snapshot_queries
+
 User = get_user_model()
 
 
diff --git a/tests/test_sqlalchemy/snapshots/snap_test_postgres.py b/tests/test_sqlalchemy/snapshots/snap_test_postgres.py
index 1fa442d..0029ba2 100644
--- a/tests/test_sqlalchemy/snapshots/snap_test_postgres.py
+++ b/tests/test_sqlalchemy/snapshots/snap_test_postgres.py
@@ -4,12 +4,13 @@
 
 from snapshottest import Snapshot
 
-
 snapshots = Snapshot()
 
-snapshots['TestPostgres::test_executing_queries 1'] = '''Query 1
+snapshots[
+    "TestPostgres::test_executing_queries 1"
+] = """Query 1
 ---------
-/python/tests/test_sqlalchemy/test_postgres.py:51 in test_executing_queries
+/python/tests/test_sqlalchemy/test_postgres.py:54 in test_executing_queries
 
 conn.execute(
 
@@ -19,7 +20,7 @@
 
 Query 2
 ---------
-/python/tests/test_sqlalchemy/test_postgres.py:57 in test_executing_queries
+/python/tests/test_sqlalchemy/test_postgres.py:60 in test_executing_queries
 
 conn.execute(
 
@@ -29,7 +30,7 @@
 
 Query 3
 ---------
-/python/tests/test_sqlalchemy/test_postgres.py:63 in test_executing_queries
+/python/tests/test_sqlalchemy/test_postgres.py:66 in test_executing_queries
 
 conn.execute(self.students.select())
 
@@ -41,11 +42,11 @@
 
 Query 4
 ---------
-/python/tests/test_sqlalchemy/test_postgres.py:64 in test_executing_queries
+/python/tests/test_sqlalchemy/test_postgres.py:67 in test_executing_queries
 
 conn.execute(self.classes.select())
 
 SELECT classes.id,
        classes.name,
        classes.start_date
-FROM classes'''
+FROM classes"""
diff --git a/tests/test_sqlalchemy/snapshots/snap_test_sqlite.py b/tests/test_sqlalchemy/snapshots/snap_test_sqlite.py
index 87cc744..6ca9320 100644
--- a/tests/test_sqlalchemy/snapshots/snap_test_sqlite.py
+++ b/tests/test_sqlalchemy/snapshots/snap_test_sqlite.py
@@ -4,12 +4,13 @@
 
 from snapshottest import Snapshot
 
-
 snapshots = Snapshot()
 
-snapshots['TestSQLite::test_executing_queries 1'] = '''Query 1
+snapshots[
+    "TestSQLite::test_executing_queries 1"
+] = """Query 1
 ---------
-/python/tests/test_sqlalchemy/test_sqlite.py:44 in test_executing_queries
+/python/tests/test_sqlalchemy/test_sqlite.py:47 in test_executing_queries
 
 conn.execute(
 
@@ -19,7 +20,7 @@
 
 Query 2
 ---------
-/python/tests/test_sqlalchemy/test_sqlite.py:50 in test_executing_queries
+/python/tests/test_sqlalchemy/test_sqlite.py:53 in test_executing_queries
 
 conn.execute(
 
@@ -29,7 +30,7 @@
 
 Query 3
 ---------
-/python/tests/test_sqlalchemy/test_sqlite.py:56 in test_executing_queries
+/python/tests/test_sqlalchemy/test_sqlite.py:59 in test_executing_queries
 
 conn.execute(self.students.select())
 
@@ -41,11 +42,11 @@
 
 Query 4
 ---------
-/python/tests/test_sqlalchemy/test_sqlite.py:57 in test_executing_queries
+/python/tests/test_sqlalchemy/test_sqlite.py:60 in test_executing_queries
 
 conn.execute(self.classes.select())
 
 SELECT classes.id,
        classes.name,
        classes.start_date
-FROM classes'''
+FROM classes"""
diff --git a/tests/test_sqlalchemy/test_postgres.py b/tests/test_sqlalchemy/test_postgres.py
index f32c41b..0ee729d 100644
--- a/tests/test_sqlalchemy/test_postgres.py
+++ b/tests/test_sqlalchemy/test_postgres.py
@@ -1,7 +1,10 @@
+from datetime import date
+
 from snapshottest import TestCase
-from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, Date
+from sqlalchemy import (Column, Date, Integer, MetaData, String, Table,
+                        create_engine)
+
 from snapshot_queries import snapshot_queries
-from datetime import date
 
 
 class TestPostgres(TestCase):
diff --git a/tests/test_sqlalchemy/test_sqlite.py b/tests/test_sqlalchemy/test_sqlite.py
index 4870bd1..d88f08b 100644
--- a/tests/test_sqlalchemy/test_sqlite.py
+++ b/tests/test_sqlalchemy/test_sqlite.py
@@ -1,8 +1,11 @@
+from datetime import date
+from pathlib import Path
+
 from snapshottest import TestCase
-from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, Date
+from sqlalchemy import (Column, Date, Integer, MetaData, String, Table,
+                        create_engine)
+
 from snapshot_queries import snapshot_queries
-from pathlib import Path
-from datetime import date
 
 
 class TestSQLite(TestCase):