Skip to content

Commit

Permalink
feat: Test with Python 3.12 🐍 (#1733)
Browse files Browse the repository at this point in the history
* feat: Support Python 3.12 🐍

* Check pendulum prerelease

* XFail duckdb tests in Python 3.12

* Update ABC exception message for 3.12
  • Loading branch information
edgarrmondragon authored Dec 11, 2023
1 parent 8e95043 commit 299acc0
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
matrix:
session: [tests]
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
sqlalchemy: ["2.*"]
include:
- { session: tests, python-version: "3.11", os: "ubuntu-latest", sqlalchemy: "1.*" }
Expand Down
12 changes: 11 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
COOKIECUTTER_REPLAY_FILES = list(Path("./e2e-tests/cookiecutters").glob("*.json"))

package = "singer_sdk"
python_versions = ["3.11", "3.10", "3.9", "3.8", "3.7"]
python_versions = ["3.12", "3.11", "3.10", "3.9", "3.8", "3.7"]
main_python_version = "3.11"
locations = "singer_sdk", "tests", "noxfile.py", "docs/conf.py"
nox.options.sessions = (
Expand All @@ -52,6 +52,13 @@
]


def _clean_py312_deps(session: Session, dependencies: list[str]) -> None:
"""Clean dependencies for Python 3.12."""
if session.python == "3.12":
dependencies.remove("duckdb")
dependencies.remove("duckdb-engine")


@session(python=main_python_version)
def mypy(session: Session) -> None:
"""Check types with mypy."""
Expand All @@ -77,6 +84,7 @@ def mypy(session: Session) -> None:
@session(python=python_versions)
def tests(session: Session) -> None:
"""Execute pytest tests and compute coverage."""
_clean_py312_deps(session, test_dependencies)
session.install(".[s3,parquet]")
session.install(*test_dependencies)

Expand Down Expand Up @@ -107,6 +115,7 @@ def tests(session: Session) -> None:
@session(python=main_python_version)
def benches(session: Session) -> None:
"""Run benchmarks."""
_clean_py312_deps(session, test_dependencies)
session.install(".[s3]")
session.install(*test_dependencies)
sqlalchemy_version = os.environ.get("SQLALCHEMY_VERSION")
Expand All @@ -129,6 +138,7 @@ def update_snapshots(session: Session) -> None:
"""Update pytest snapshots."""
args = session.posargs or ["-m", "snapshot"]

_clean_py312_deps(session, test_dependencies)
session.install(".")
session.install(*test_dependencies)
session.run("pytest", "--snapshot-update", *args)
Expand Down
187 changes: 185 additions & 2 deletions poetry.lock

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Typing :: Typed",
Expand Down Expand Up @@ -55,7 +56,10 @@ jsonschema = [
]
memoization = { version = ">=0.3.2,<0.5.0", python = "<4" }
packaging = ">=23.1"
pendulum = ">=2.1.0"
pendulum = [
{version = ">=2.1.0,<3", python = "<3.8"},
{version = ">=2.1.0,<4", python = ">=3.8", allow-prereleases = true},
]
PyJWT = "~=2.4"
python-dateutil = ">=2.8.2"
python-dotenv = ">=0.20"
Expand Down Expand Up @@ -86,7 +90,8 @@ fs-s3fs = {version = ">=1.1.1", optional = true}
# Parquet file dependencies installed as optional 'parquet' extras
numpy = [
{ version = "<1.22", python = "<3.8", optional = true },
{ version = ">=1.22", python = ">=3.8", optional = true },
{ version = ">=1.22,<1.25", python = ">=3.8,<3.9", optional = true },
{ version = ">=1.22", python = ">=3.9", optional = true },
]
pyarrow = [
{ version = ">=11,<13", python = "<3.8", optional = true },
Expand Down Expand Up @@ -120,8 +125,11 @@ coverage = [
{extras = ["toml"], version = ">=7.2,<7.3", python = "<3.8"},
{extras = ["toml"], version = ">=7.2", python = ">=3.8"},
]
duckdb = ">=0.8.0"
duckdb-engine = ">=0.9.4"

# TODO: Remove the Python 3.12 marker when DuckDB supports it
duckdb = { version = ">=0.8.0", python = "<3.12" }
duckdb-engine = { version = ">=0.9.4", python = "<3.12" }

mypy = [
{ version = ">=1.0,<1.5", python = "<3.8" },
{ version = ">=1.0", python = ">=3.8" },
Expand Down
2 changes: 1 addition & 1 deletion singer_sdk/authenticators.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ def is_token_valid(self) -> bool:
return False
if not self.expires_in:
return True
return self.expires_in > (utc_now() - self.last_refreshed).total_seconds() # type: ignore[no-any-return]
return self.expires_in > (utc_now() - self.last_refreshed).total_seconds()

# Authentication and refresh
def update_access_token(self) -> None:
Expand Down
8 changes: 4 additions & 4 deletions tests/core/rest/test_pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_paginator_base_missing_implementation():

with pytest.raises(
TypeError,
match="Can't instantiate abstract class .* get_next",
match="Can't instantiate abstract class .* '?get_next'?",
):
BaseAPIPaginator(0)

Expand All @@ -52,7 +52,7 @@ def test_paginator_page_number_missing_implementation():

with pytest.raises(
TypeError,
match="Can't instantiate abstract class .* has_more",
match="Can't instantiate abstract class .* '?has_more'?",
):
BasePageNumberPaginator(1)

Expand All @@ -62,7 +62,7 @@ def test_paginator_offset_missing_implementation():

with pytest.raises(
TypeError,
match="Can't instantiate abstract class .* has_more",
match="Can't instantiate abstract class .* '?has_more'?",
):
BaseOffsetPaginator(0, 100)

Expand All @@ -72,7 +72,7 @@ def test_paginator_hateoas_missing_implementation():

with pytest.raises(
TypeError,
match="Can't instantiate abstract class .* get_next_url",
match="Can't instantiate abstract class .* '?get_next_url'?",
):
BaseHATEOASPaginator()

Expand Down
7 changes: 7 additions & 0 deletions tests/core/test_connector_sql.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from __future__ import annotations

import sys
import typing as t
from decimal import Decimal
from unittest import mock

import pytest
import sqlalchemy
from sqlalchemy.dialects import registry, sqlite
from sqlalchemy.exc import NoSuchModuleError

from singer_sdk.connectors import SQLConnector
from singer_sdk.exceptions import ConfigValidationError
Expand Down Expand Up @@ -308,6 +310,11 @@ def get_column_alter_ddl(
)


@pytest.mark.xfail(
reason="DuckDB does not build on Python 3.12 yet",
condition=sys.version_info >= (3, 12),
raises=NoSuchModuleError,
)
class TestDuckDBConnector:
@pytest.fixture
def connector(self):
Expand Down

0 comments on commit 299acc0

Please sign in to comment.