From 861fbabe8a58d6f207416bfe47c2f79bef7adf3b Mon Sep 17 00:00:00 2001 From: Bruno FS Ciconelle Date: Tue, 25 Jun 2024 15:06:13 -0300 Subject: [PATCH] test(JSONObjectAgg): add option to run tests on postgresql --- poetry.lock | 34 +++++++++++++++++++++++++------- pyproject.toml | 3 ++- tests/conftest.py | 46 ++++++++++++++++++++++++++++++++++++++++--- tests/test_obj_agg.py | 21 ++++++++++++++------ 4 files changed, 87 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index da6fadf..24946c6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -940,6 +940,26 @@ files = [ "ruamel.yaml" = ">=0.15" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +[[package]] +name = "psycopg2" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg2-2.9.9-cp310-cp310-win32.whl", hash = "sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516"}, + {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, + {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, + {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, + {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, + {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, + {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, + {file = "psycopg2-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e"}, + {file = "psycopg2-2.9.9-cp39-cp39-win32.whl", hash = "sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59"}, + {file = "psycopg2-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913"}, + {file = "psycopg2-2.9.9.tar.gz", hash = "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156"}, +] + [[package]] name = "pycparser" version = "2.22" @@ -1063,13 +1083,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydoclint" -version = "0.4.1" +version = "0.4.2" description = "A Python docstring linter that checks arguments, returns, yields, and raises sections" optional = false python-versions = ">=3.8" files = [ - {file = "pydoclint-0.4.1-py2.py3-none-any.whl", hash = "sha256:4e32fdf0a47a2199377617f09af0a82a2157f80543026f919a17112a396e752f"}, - {file = "pydoclint-0.4.1.tar.gz", hash = "sha256:d39ed26a793203afadb1917011710fbf258ac3dddcd79b53212e0a2107221643"}, + {file = "pydoclint-0.4.2-py2.py3-none-any.whl", hash = "sha256:1941ba8d47b021939e47f6b79b77513449ef0565330b836173df9d6cf1606045"}, + {file = "pydoclint-0.4.2.tar.gz", hash = "sha256:559060dc1a91240ac1e28be570b3563ae66f292c7dd7124f4d4eab1483416a41"}, ] [package.dependencies] @@ -1414,13 +1434,13 @@ typing-extensions = ">=4.7.1" [[package]] name = "setuptools" -version = "70.1.0" +version = "70.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.1.0-py3-none-any.whl", hash = "sha256:d9b8b771455a97c8a9f3ab3448ebe0b29b5e105f1228bba41028be116985a267"}, - {file = "setuptools-70.1.0.tar.gz", hash = "sha256:01a1e793faa5bd89abc851fa15d0a0db26f160890c7102cd8dce643e886b47f5"}, + {file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"}, + {file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"}, ] [package.extras] @@ -1833,4 +1853,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "0d0b7374fe0ef02f8f326a9e70dbc2e0871028d52a5d3aaa42a37881d4728013" +content-hash = "83eaa059b3126da34411a08d6c1371817202676043678eb2365336b6a58111e3" diff --git a/pyproject.toml b/pyproject.toml index e52bd3c..9a72cd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ Changelog = "https://github.com/quipucords/django-json-agg/releases" python = "^3.8" django = ">=4.2" -[tool.poetry.group.dev.dependencies] +[tool.poetry.group.devel.dependencies] Pygments = ">=2.10.0" coverage = { extras = ["toml"], version = ">=6.2" } faker = "^25.9.1" @@ -30,6 +30,7 @@ furo = ">=2021.11.12" myst-parser = { version = ">=0.16.1" } pre-commit = ">=2.16.0" pre-commit-hooks = ">=4.1.0" +psycopg2 = "^2.9.9" pydoclint = "^0.4.1" pytest = ">=6.2.5" pytest-django = "^4.8.0" diff --git a/tests/conftest.py b/tests/conftest.py index 50374af..57d9999 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,32 @@ """Testing setup.""" +from __future__ import annotations + import django +import pytest +from django.conf import settings + + +def pytest_addoption(parser: pytest.Parser): + parser.addoption("--db-vendor", action="store", default="sqlite") + parser.addoption("--db-name", action="store", default="postgres") + parser.addoption("--db-user", action="store", default="postgres") + parser.addoption("--db-password", action="store", default="") + parser.addoption("--db-host", action="store", default="127.0.0.1") + parser.addoption("--db-port", action="store", default="5432") + +@pytest.fixture(scope="session") +def db_vendor(pytestconfig): + return pytestconfig.getoption("--db-vendor") -def pytest_configure(config): - from django.conf import settings +def pytest_configure(config: pytest.Config): + db_settings = get_db_settings(config) settings.configure( DEBUG_PROPAGATE_EXCEPTIONS=True, DATABASES={ - "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}, + "default": db_settings, }, SITE_ID=1, SECRET_KEY="not a secret in tests", # noqa: S106 @@ -45,3 +62,26 @@ def pytest_configure(config): ) django.setup() + + +def get_db_settings(config: pytest.Config): + valid_db_vendors = ["sqlite", "postgresql"] + vendor_name = config.getoption("--db-vendor") + if vendor_name not in valid_db_vendors: + raise pytest.UsageError( + f"Invalid db vendor ('{vendor_name}'). Valid values are {valid_db_vendors}." + ) + settings_per_vendor = { + "sqlite": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}, + "postgresql": { + "ENGINE": "django.db.backends.postgresql", + "NAME": config.getoption("--db-name"), + "USER": config.getoption("--db-user"), + "PASSWORD": config.getoption("--db-password"), + "HOST": config.getoption("--db-host"), + "PORT": config.getoption("--db-port"), + }, + } + + db_settings = settings_per_vendor[vendor_name] + return db_settings diff --git a/tests/test_obj_agg.py b/tests/test_obj_agg.py index b45038d..71b79f7 100644 --- a/tests/test_obj_agg.py +++ b/tests/test_obj_agg.py @@ -2,13 +2,13 @@ from __future__ import annotations +import datetime from functools import partial from itertools import chain from typing import TYPE_CHECKING import pytest from django.db.models import DateTimeField -from django.db.models import JSONField from json_agg import JSONObjectAgg from tests.models import Author @@ -116,12 +116,17 @@ def test_aggregate_text(faker: Faker): @pytest.mark.django_db -def test_aggregate_datetime(faker: Faker): +def test_aggregate_datetime(faker: Faker, db_vendor: str): """Test JSONObjectAgg over a datetime value (Post.updated_at).""" + kw = {} + if db_vendor == "postgresql": + # enforce tz for postgresql only - sqlite don't support it. + kw = dict(tzinfo=datetime.UTC) + expected_value_per_author_name = post_factory( faker, value_name="updated_at", - value_factory=partial(faker.date_time), + value_factory=partial(faker.date_time, **kw), ) queryset = Author.objects.annotate( @@ -142,13 +147,17 @@ def test_aggregate_json(faker: Faker): value_name="metadata", value_factory=partial(faker.pydict, allowed_types=(str, int)), ) - + # we can't use nested_output_field for JSONField because it would only work + # for sqlite, which internally stores json as text. On postgres, data is + # stored as jsonb, which is automatically translated to dict (maybe by psycopg2?). + # This distinction on how those objects are stored internally breaks JSONField + # default decoder, which doesn't expect dicts. queryset = Author.objects.annotate( post_metadata=JSONObjectAgg( "posts__title", "posts__metadata", - nested_output_field=JSONField(), - # sqlite_func="json", + # nested_output_field=JSONField(), + sqlite_func="json", ) ).all()