Skip to content

Commit 4fd1b4d

Browse files
ci: Test optional dependencies separately (#135)
1 parent 028055d commit 4fd1b4d

File tree

7 files changed

+1540
-2434
lines changed

7 files changed

+1540
-2434
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ jobs:
4141
matrix:
4242
os: [ubuntu-latest, windows-latest]
4343
environment: [py310, py311, py312, py313]
44+
with_optionals: [false]
45+
include:
46+
- os: ubuntu-latest
47+
environment: py313-optionals
48+
with_optionals: true
49+
- os: windows-latest
50+
environment: py313-optionals
51+
with_optionals: true
4452
steps:
4553
- name: Checkout branch
4654
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -51,7 +59,7 @@ jobs:
5159
- name: Install repository
5260
run: pixi run -e ${{ matrix.environment }} postinstall
5361
- name: Run pytest
54-
run: pixi run -e ${{ matrix.environment }} test-coverage --color=yes
62+
run: pixi run -e ${{ matrix.environment }} test-coverage --color=yes ${{ matrix.with_optionals && '-m with_optionals' || '-m "not with_optionals"'}} --cov=dataframely --cov-report=xml
5563
- name: Upload codecov
5664
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
5765
with:

dataframely/_compat.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ def __getattr__(self, name: str) -> Any:
1818
try:
1919
import sqlalchemy as sa
2020
import sqlalchemy.dialects.mssql as sa_mssql
21+
from sqlalchemy import Dialect
22+
from sqlalchemy.dialects.mssql.pyodbc import MSDialect_pyodbc
23+
from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
2124
from sqlalchemy.sql.type_api import TypeEngine as sa_TypeEngine
2225
except ImportError: # pragma: no cover
2326
sa = _DummyModule("sqlalchemy") # type: ignore
@@ -26,7 +29,14 @@ def __getattr__(self, name: str) -> Any:
2629
class sa_TypeEngine: # type: ignore # noqa: N801
2730
pass
2831

32+
class MSDialect_pyodbc: # type: ignore # noqa: N801
33+
pass
34+
35+
class PGDialect_psycopg2: # type: ignore # noqa: N801
36+
pass
2937

38+
class Dialect: # type: ignore # noqa: N801
39+
pass
3040
# -------------------------------------- PYARROW ------------------------------------- #
3141

3242
try:
@@ -36,4 +46,11 @@ class sa_TypeEngine: # type: ignore # noqa: N801
3646

3747
# ------------------------------------------------------------------------------------ #
3848

39-
__all__ = ["sa", "sa_mssql", "sa_TypeEngine", "pa"]
49+
__all__ = [
50+
"sa",
51+
"sa_mssql",
52+
"sa_TypeEngine",
53+
"pa",
54+
"MSDialect_pyodbc",
55+
"PGDialect_psycopg2",
56+
]

pixi.lock

Lines changed: 1488 additions & 2420 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pixi.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,18 @@ readthedocs = "rm -rf $READTHEDOCS_OUTPUT/html && cp -r docs/_build/html $READTH
4343
mypy = ">=1.13"
4444
pandas = "*"
4545
pandas-stubs = "*"
46-
pyarrow = "*"
4746
pyodbc = "*"
4847
pytest = ">=6"
4948
pytest-benchmark = "*"
5049
pytest-cov = "*"
5150
pytest-md = "*"
5251
scikit-learn = "*"
52+
53+
[feature.optionals.dependencies]
54+
pyarrow = "*"
5355
sqlalchemy = ">=2"
5456

57+
5558
[feature.test.tasks]
5659
test = "pytest"
5760
test-bench = "pytest tests/benches --benchmark-only --benchmark-sort mean --benchmark-columns min,max,mean,rounds"
@@ -102,11 +105,12 @@ install-polars-nightly = "pip install --pre --no-deps --upgrade --only-binary :a
102105

103106
[environments]
104107
build = ["build"]
105-
default = ["dev", "lint", "test"]
108+
default = ["dev", "lint", "test", "optionals"]
106109
docs = ["docs"]
107110
lint = { features = ["lint"], no-default-feature = true }
108111
nightly = ["nightly", "test"]
109112
py310 = ["py310", "test"]
110113
py311 = ["py311", "test"]
111114
py312 = ["py312", "test"]
112115
py313 = ["py313", "test"]
116+
py313-optionals = ["py313", "test", "optionals"]

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ readme = "README.md"
2727
requires-python = ">=3.10"
2828
version = "0.0.0"
2929

30+
[project.optional-dependencies]
31+
sqlalchemy = ["sqlalchemy"]
32+
pyarrow = ["pyarrow"]
33+
3034
[project.urls]
3135
Documentation = "https://dataframely.readthedocs.io/"
3236
Repository = "https://github.com/quantco/dataframely"
@@ -94,6 +98,9 @@ filterwarnings = [
9498
"ignore:The 'nullable' argument was not explicitly set:FutureWarning",
9599
]
96100
testpaths = ["tests"]
101+
markers = [
102+
"with_optionals: Tests that require optional dependencies to be installed",
103+
]
97104

98105
[tool.coverage.run]
99106
omit = ["dataframely/mypy.py", "dataframely/testing/generate.py"]

tests/columns/test_pyarrow.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
create_schema,
1515
)
1616

17+
pytestmark = pytest.mark.with_optionals
18+
1719

1820
@pytest.mark.parametrize("column_type", ALL_COLUMN_TYPES)
1921
def test_equal_to_polars_schema(column_type: type[Column]) -> None:

tests/columns/test_sql_schema.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
# SPDX-License-Identifier: BSD-3-Clause
33

44
import pytest
5-
import sqlalchemy as sa
6-
from sqlalchemy.dialects.mssql.pyodbc import MSDialect_pyodbc
7-
from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
85

96
import dataframely as dy
7+
from dataframely._compat import Dialect, MSDialect_pyodbc, PGDialect_psycopg2
108
from dataframely.columns import Column
119
from dataframely.testing import COLUMN_TYPES, create_schema
1210

11+
pytestmark = pytest.mark.with_optionals
12+
1313

1414
@pytest.mark.parametrize(
1515
("column", "datatype"),
@@ -107,7 +107,7 @@ def test_postgres_datatype(column: Column, datatype: str) -> None:
107107
@pytest.mark.parametrize("nullable", [True, False])
108108
@pytest.mark.parametrize("dialect", [MSDialect_pyodbc()])
109109
def test_sql_nullability(
110-
column_type: type[Column], nullable: bool, dialect: sa.Dialect
110+
column_type: type[Column], nullable: bool, dialect: Dialect
111111
) -> None:
112112
schema = create_schema("test", {"a": column_type(nullable=nullable)})
113113
columns = schema.sql_schema(dialect)
@@ -119,7 +119,7 @@ def test_sql_nullability(
119119
@pytest.mark.parametrize("primary_key", [True, False])
120120
@pytest.mark.parametrize("dialect", [MSDialect_pyodbc(), PGDialect_psycopg2()])
121121
def test_sql_primary_key(
122-
column_type: type[Column], primary_key: bool, dialect: sa.Dialect
122+
column_type: type[Column], primary_key: bool, dialect: Dialect
123123
) -> None:
124124
schema = create_schema("test", {"a": column_type(primary_key=primary_key)})
125125
columns = schema.sql_schema(dialect)
@@ -129,37 +129,37 @@ def test_sql_primary_key(
129129

130130

131131
@pytest.mark.parametrize("dialect", [MSDialect_pyodbc(), PGDialect_psycopg2()])
132-
def test_sql_multiple_columns(dialect: sa.Dialect) -> None:
132+
def test_sql_multiple_columns(dialect: Dialect) -> None:
133133
schema = create_schema("test", {"a": dy.Int32(nullable=False), "b": dy.Integer()})
134134
assert len(schema.sql_schema(dialect)) == 2
135135

136136

137137
@pytest.mark.parametrize("dialect", [MSDialect_pyodbc(), PGDialect_psycopg2()])
138-
def test_raise_for_list_column(dialect: sa.Dialect) -> None:
138+
def test_raise_for_list_column(dialect: Dialect) -> None:
139139
with pytest.raises(
140140
NotImplementedError, match="SQL column cannot have 'List' type."
141141
):
142142
dy.List(dy.String()).sqlalchemy_dtype(dialect)
143143

144144

145145
@pytest.mark.parametrize("dialect", [MSDialect_pyodbc(), PGDialect_psycopg2()])
146-
def test_raise_for_array_column(dialect: sa.Dialect) -> None:
146+
def test_raise_for_array_column(dialect: Dialect) -> None:
147147
with pytest.raises(
148148
NotImplementedError, match="SQL column cannot have 'Array' type."
149149
):
150150
dy.Array(dy.String(), 1).sqlalchemy_dtype(dialect)
151151

152152

153153
@pytest.mark.parametrize("dialect", [MSDialect_pyodbc(), PGDialect_psycopg2()])
154-
def test_raise_for_struct_column(dialect: sa.Dialect) -> None:
154+
def test_raise_for_struct_column(dialect: Dialect) -> None:
155155
with pytest.raises(
156156
NotImplementedError, match="SQL column cannot have 'Struct' type."
157157
):
158158
dy.Struct({"a": dy.String()}).sqlalchemy_dtype(dialect)
159159

160160

161161
@pytest.mark.parametrize("dialect", [MSDialect_pyodbc(), PGDialect_psycopg2()])
162-
def test_raise_for_object_column(dialect: sa.Dialect) -> None:
162+
def test_raise_for_object_column(dialect: Dialect) -> None:
163163
with pytest.raises(
164164
NotImplementedError, match="SQL column cannot have 'Object' type."
165165
):

0 commit comments

Comments
 (0)