mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-06 08:56:51 -04:00
use nox
The top-level test runner has been changed to use ``nox``, adding a ``noxfile.py`` as well as some included modules. The ``tox.ini`` file remains in place so that ``tox`` runs will continue to function in the near term, however it will be eventually removed and improvements and maintenance going forward will be only towards ``noxfile.py``. Change-Id: I66639991e1dc3db582e2ff13f9348a7d6241916e
This commit is contained in:
@@ -49,14 +49,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade tox setuptools
|
||||
pip install --upgrade nox setuptools
|
||||
pip list
|
||||
|
||||
- name: Run tests
|
||||
run: tox -e github-${{ matrix.build-type }} -- ${{ matrix.pytest-args }}
|
||||
run: nox -v -s github-${{ matrix.build-type }} -- ${{ matrix.pytest-args }}
|
||||
|
||||
run-tox:
|
||||
name: ${{ matrix.tox-env }}-${{ matrix.python-version }}
|
||||
run-nox:
|
||||
name: ${{ matrix.nox-env }}-${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -64,10 +64,10 @@ jobs:
|
||||
- "ubuntu-22.04"
|
||||
python-version:
|
||||
- "3.13"
|
||||
tox-env:
|
||||
nox-env:
|
||||
- mypy
|
||||
- lint
|
||||
- pep484
|
||||
- pep8
|
||||
|
||||
fail-fast: false
|
||||
|
||||
@@ -84,8 +84,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade tox setuptools
|
||||
pip install --upgrade nox setuptools
|
||||
pip list
|
||||
|
||||
- name: Run tox
|
||||
run: tox -e ${{ matrix.tox-env }} ${{ matrix.pytest-args }}
|
||||
- name: Run nox
|
||||
run: nox -v -s ${{ matrix.nox-env }} -- ${{ matrix.pytest-args }}
|
||||
|
||||
@@ -142,18 +142,15 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade tox setuptools
|
||||
pip install --upgrade nox setuptools
|
||||
pip list
|
||||
|
||||
- name: Run tests
|
||||
run: tox -e github-${{ matrix.build-type }} -- ${{ matrix.pytest-args }}
|
||||
env:
|
||||
# under free threading, make sure to disable GIL
|
||||
PYTHON_GIL: ${{ contains(matrix.python-version, 't') && '0' || '' }}
|
||||
run: nox -v -s github-${{ matrix.build-type }} -- ${{ matrix.pytest-args }}
|
||||
continue-on-error: ${{ matrix.python-version == 'pypy-3.10' }}
|
||||
|
||||
run-tox:
|
||||
name: ${{ matrix.tox-env }}-${{ matrix.python-version }}
|
||||
run-nox:
|
||||
name: ${{ matrix.nox-env }}-${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
# run this job using this matrix, excluding some combinations below.
|
||||
@@ -161,17 +158,11 @@ jobs:
|
||||
os:
|
||||
- "ubuntu-22.04"
|
||||
python-version:
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
tox-env:
|
||||
nox-env:
|
||||
- mypy
|
||||
- pep484
|
||||
|
||||
include:
|
||||
# run lint only on 3.13
|
||||
- tox-env: lint
|
||||
python-version: "3.13"
|
||||
os: "ubuntu-22.04"
|
||||
- pep8
|
||||
|
||||
fail-fast: false
|
||||
|
||||
@@ -189,8 +180,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade tox setuptools
|
||||
pip install --upgrade nox setuptools
|
||||
pip list
|
||||
|
||||
- name: Run tox
|
||||
run: tox -e ${{ matrix.tox-env }} ${{ matrix.pytest-args }}
|
||||
- name: Run nox
|
||||
run: nox -v -e ${{ matrix.nox-env }} ${{ matrix.pytest-args }}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*.orig
|
||||
*,cover
|
||||
/.tox
|
||||
/.nox
|
||||
/venv/
|
||||
.venv
|
||||
*.egg-info
|
||||
|
||||
+36
-15
@@ -5,30 +5,51 @@ SQLALCHEMY UNIT TESTS
|
||||
Basic Test Running
|
||||
==================
|
||||
|
||||
Tox is used to run the test suite fully. For basic test runs against
|
||||
Nox is used to run the test suite fully. For basic test runs against
|
||||
a single Python interpreter::
|
||||
|
||||
nox
|
||||
|
||||
The previous runner, tox, still retains functionality in the near term however
|
||||
will eventually be removed::
|
||||
|
||||
# still works but deprecated
|
||||
tox
|
||||
|
||||
Advanced Tox Options
|
||||
The newer nox version retains most of the same kinds of functionality as the
|
||||
tox version, including a custom tagging utility that allows the nox runner
|
||||
to accept similar "tag" style arguments as were used by the tox runner.
|
||||
|
||||
Advanced Nox Options
|
||||
====================
|
||||
|
||||
For more elaborate CI-style test running, the tox script provided will
|
||||
For more elaborate CI-style test running, the nox script provided will
|
||||
run against various Python / database targets. For a basic run against
|
||||
Python 3.11 using an in-memory SQLite database::
|
||||
Python 3.13 using an in-memory SQLite database::
|
||||
|
||||
tox -e py311-sqlite
|
||||
nox -t py313-sqlite
|
||||
|
||||
The tox runner contains a series of target combinations that can run
|
||||
against various combinations of databases. The test suite can be
|
||||
run against SQLite with "backend" tests also running against a PostgreSQL
|
||||
database::
|
||||
The nox runner contains a series of target combinations that can run
|
||||
against each database backend. Unlike the previous tox runner, targets
|
||||
that refer to multiple database backends at once are no longer
|
||||
supported at the nox level, in favor of running against multiple tags
|
||||
instead. So for example to run tests for sqlite and postgresql, while
|
||||
reducing how many tests run for postgresql to just those that are sensitive
|
||||
to the database backend::
|
||||
|
||||
tox -e py311-sqlite-postgresql
|
||||
nox -t py313-sqlite py313-postgresql-backendonly
|
||||
|
||||
Or to run just "backend" tests against a MySQL database::
|
||||
Where above, the full suite will run against SQLite under Python 3.13, then
|
||||
the "backend only" version of the suite will for the PostgreSQL database.
|
||||
|
||||
tox -e py311-mysql-backendonly
|
||||
The nox runner, like the tox runner before it, has options for running the
|
||||
tests with or without the Cython extensions built, with or without greenlet
|
||||
installed, as well as tags that select or deselect various memory/threading/
|
||||
performance intensive tests; the rules for how these environments are selected
|
||||
should be much more straightforward to understand with nox's imperative
|
||||
configuration style. For advanced use of nox it's worth it
|
||||
to poke around ``noxfile.py`` to get a general sense of what varieties
|
||||
of tests it can run.
|
||||
|
||||
Running against backends other than SQLite requires that a database of that
|
||||
vendor be available at a specific URL. See "Setting Up Databases" below
|
||||
@@ -37,7 +58,7 @@ for details.
|
||||
The pytest Engine
|
||||
=================
|
||||
|
||||
The tox runner is using pytest to invoke the test suite. Within the realm of
|
||||
The nox runner uses pytest to invoke the test suite. Within the realm of
|
||||
pytest, SQLAlchemy itself is adding a large series of option and
|
||||
customizations to the pytest runner using plugin points, to allow for
|
||||
SQLAlchemy's multiple database support, database setup/teardown and
|
||||
@@ -127,13 +148,13 @@ Above, we can now run the tests with ``my_postgresql``::
|
||||
pytest --db my_postgresql
|
||||
|
||||
We can also override the existing names in our ``test.cfg`` file, so that we can run
|
||||
with the tox runner also::
|
||||
with the nox/tox runners also::
|
||||
|
||||
# test.cfg file
|
||||
[db]
|
||||
postgresql=postgresql+psycopg2://username:pass@hostname/dbname
|
||||
|
||||
Now when we run ``tox -e py311-postgresql``, it will use our custom URL instead
|
||||
Now when we run ``nox -t py313-postgresql``, it will use our custom URL instead
|
||||
of the fixed one in setup.cfg.
|
||||
|
||||
Database Configuration
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
.. change::
|
||||
:tags: change, tests
|
||||
|
||||
The top-level test runner has been changed to use ``nox``, adding a
|
||||
``noxfile.py`` as well as some included modules. The ``tox.ini`` file
|
||||
remains in place so that ``tox`` runs will continue to function in the near
|
||||
term, however it will be eventually removed and improvements and
|
||||
maintenance going forward will be only towards ``noxfile.py``.
|
||||
|
||||
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
"""Nox configuration for SQLAlchemy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Set
|
||||
|
||||
import nox
|
||||
|
||||
if True:
|
||||
sys.path.insert(0, ".")
|
||||
from tools.toxnox import extract_opts
|
||||
from tools.toxnox import tox_parameters
|
||||
|
||||
|
||||
PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.13t", "3.14", "3.14t"]
|
||||
DATABASES = ["sqlite", "sqlite_file", "postgresql", "mysql", "oracle", "mssql"]
|
||||
CEXT = ["_auto", "cext", "nocext"]
|
||||
GREENLET = ["_greenlet", "nogreenlet"]
|
||||
BACKENDONLY = ["_all", "backendonly", "memusage"]
|
||||
|
||||
# table of ``--dbdriver`` names to use on the pytest command line, which
|
||||
# match to dialect names
|
||||
DB_CLI_NAMES = {
|
||||
"sqlite": {
|
||||
"nogreenlet": {"sqlite", "pysqlite_numeric"},
|
||||
"greenlet": {"aiosqlite"},
|
||||
},
|
||||
"sqlite_file": {
|
||||
"nogreenlet": {"sqlite"},
|
||||
"greenlet": {"aiosqlite"},
|
||||
},
|
||||
"postgresql": {
|
||||
"nogreenlet": {"psycopg2", "pg8000", "psycopg"},
|
||||
"greenlet": {"asyncpg", "psycopg_async"},
|
||||
},
|
||||
"mysql": {
|
||||
"nogreenlet": {"mysqldb", "pymysql", "mariadbconnector"},
|
||||
"greenlet": {"asyncmy", "aiomysql"},
|
||||
},
|
||||
"oracle": {
|
||||
"nogreenlet": {"cx_oracle", "oracledb"},
|
||||
"greenlet": {"oracledb_async"},
|
||||
},
|
||||
"mssql": {"nogreenlet": {"pyodbc", "pymssql"}, "greenlet": {"aioodbc"}},
|
||||
}
|
||||
|
||||
|
||||
def _setup_for_driver(
|
||||
session: nox.Session,
|
||||
cmd: List[str],
|
||||
basename: str,
|
||||
greenlet: bool = False,
|
||||
) -> None:
|
||||
|
||||
# install driver deps listed out in pyproject.toml
|
||||
nogreenlet_deps = f"tests-{basename.replace("_", "-")}"
|
||||
greenlet_deps = f"tests-{basename.replace("_", "-")}-asyncio"
|
||||
|
||||
deps = nox.project.dependency_groups(
|
||||
pyproject,
|
||||
(greenlet_deps if greenlet else nogreenlet_deps),
|
||||
)
|
||||
if deps:
|
||||
session.install(*deps)
|
||||
|
||||
# set up top level ``--db`` sent to pytest command line, which looks
|
||||
# up a base URL in the [db] section of setup.cfg. Environment variable
|
||||
# substitution used by CI is also available.
|
||||
|
||||
# e.g. TOX_POSTGRESQL, TOX_MYSQL, etc.
|
||||
dburl_env = f"TOX_{basename.upper()}"
|
||||
# e.g. --db postgresql, --db mysql, etc.
|
||||
default_dburl = f"--db {basename}"
|
||||
cmd.extend(os.environ.get(dburl_env, default_dburl).split())
|
||||
|
||||
# set up extra drivers using --dbdriver. this first looks in
|
||||
# an environment variable before making use of the DB_CLI_NAMES
|
||||
# lookup table
|
||||
|
||||
# e.g. EXTRA_PG_DRIVERS, EXTRA_MYSQL_DRIVERS, etc.
|
||||
if basename == "postgresql":
|
||||
extra_driver_env = "EXTRA_PG_DRIVERS"
|
||||
else:
|
||||
extra_driver_env = f"EXTRA_{basename.upper()}_DRIVERS"
|
||||
env_dbdrivers = os.environ.get(extra_driver_env, None)
|
||||
if env_dbdrivers:
|
||||
cmd.extend(env_dbdrivers.split())
|
||||
return
|
||||
|
||||
# use fixed names in DB_CLI_NAMES
|
||||
extra_drivers: Dict[str, Set[str]] = DB_CLI_NAMES[basename]
|
||||
dbdrivers = extra_drivers["nogreenlet"]
|
||||
if greenlet:
|
||||
dbdrivers.update(extra_drivers["greenlet"])
|
||||
|
||||
for dbdriver in dbdrivers:
|
||||
cmd.extend(["--dbdriver", dbdriver])
|
||||
|
||||
|
||||
pyproject = nox.project.load_toml("pyproject.toml")
|
||||
|
||||
nox.options.sessions = ["tests"]
|
||||
nox.options.tags = ["py"]
|
||||
|
||||
|
||||
@nox.session()
|
||||
@tox_parameters(
|
||||
["python", "database", "cext", "greenlet", "backendonly"],
|
||||
[
|
||||
PYTHON_VERSIONS,
|
||||
DATABASES,
|
||||
CEXT,
|
||||
GREENLET,
|
||||
BACKENDONLY,
|
||||
],
|
||||
)
|
||||
def tests(
|
||||
session: nox.Session,
|
||||
database: str,
|
||||
greenlet: str,
|
||||
backendonly: str,
|
||||
cext: str,
|
||||
) -> None:
|
||||
"""run the main test suite"""
|
||||
|
||||
_tests(
|
||||
session,
|
||||
database,
|
||||
greenlet=greenlet == "_greenlet",
|
||||
backendonly=backendonly == "backendonly",
|
||||
platform_intensive=backendonly == "memusage",
|
||||
cext=cext,
|
||||
)
|
||||
|
||||
|
||||
@nox.session(name="coverage")
|
||||
@tox_parameters(
|
||||
["database", "cext", "backendonly"],
|
||||
[DATABASES, CEXT, ["_all", "backendonly"]],
|
||||
base_tag="coverage",
|
||||
)
|
||||
def coverage(
|
||||
session: nox.Session, database: str, cext: str, backendonly: str
|
||||
) -> None:
|
||||
"""Run tests with coverage."""
|
||||
|
||||
_tests(
|
||||
session,
|
||||
database,
|
||||
cext,
|
||||
timing_intensive=False,
|
||||
backendonly=backendonly == "backendonly",
|
||||
coverage=True,
|
||||
)
|
||||
|
||||
|
||||
@nox.session(name="github-cext-greenlet")
|
||||
def github_cext_greenlet(session: nox.Session) -> None:
|
||||
"""run tests for github actions"""
|
||||
|
||||
_tests(session, "sqlite", "cext", greenlet=True, timing_intensive=False)
|
||||
|
||||
|
||||
@nox.session(name="github-cext")
|
||||
def github_cext(session: nox.Session) -> None:
|
||||
"""run tests for github actions"""
|
||||
|
||||
_tests(session, "sqlite", "cext", greenlet=False, timing_intensive=False)
|
||||
|
||||
|
||||
@nox.session(name="github-nocext")
|
||||
def github_nocext(session: nox.Session) -> None:
|
||||
"""run tests for github actions"""
|
||||
|
||||
_tests(session, "sqlite", "cext", greenlet=False)
|
||||
|
||||
|
||||
def _tests(
|
||||
session: nox.Session,
|
||||
database: str,
|
||||
cext: str = "_auto",
|
||||
greenlet: bool = True,
|
||||
backendonly: bool = False,
|
||||
platform_intensive: bool = False,
|
||||
timing_intensive: bool = True,
|
||||
coverage: bool = False,
|
||||
mypy: bool = False,
|
||||
) -> None:
|
||||
# PYTHONNOUSERSITE - this *MUST* be set so that the ./lib/ import
|
||||
# set up explicitly in test/conftest.py is *disabled*, so that
|
||||
# when SQLAlchemy is built into the .nox area, we use that and not the
|
||||
# local checkout, at least when usedevelop=False
|
||||
session.env["PYTHONNOUSERSITE"] = "1"
|
||||
|
||||
freethreaded = isinstance(session.python, str) and session.python.endswith(
|
||||
"t"
|
||||
)
|
||||
|
||||
if freethreaded:
|
||||
session.env["PYTHON_GIL"] = "0"
|
||||
|
||||
# greenlet frequently crashes with freethreading, so omit
|
||||
# for the near future
|
||||
greenlet = False
|
||||
|
||||
session.env["SQLALCHEMY_WARN_20"] = "1"
|
||||
|
||||
if cext == "cext":
|
||||
session.env["REQUIRE_SQLALCHEMY_CEXT"] = "1"
|
||||
elif cext == "nocext":
|
||||
session.env["DISABLE_SQLALCHEMY_CEXT"] = "1"
|
||||
|
||||
includes_excludes: dict[str, list[str]] = {"k": [], "m": []}
|
||||
|
||||
if coverage:
|
||||
timing_intensive = False
|
||||
|
||||
if platform_intensive:
|
||||
# platform_intensive refers to test/aaa_profiling/test_memusage.py.
|
||||
# it's only run exclusively of all other tests. does not include
|
||||
# greenlet related tests
|
||||
greenlet = False
|
||||
# with "-m memory_intensive", only that suite will run, all
|
||||
# other tests will be deselected by pytest
|
||||
includes_excludes["m"].append("memory_intensive")
|
||||
elif backendonly:
|
||||
# with "-m backendonly", only tests with the backend pytest mark
|
||||
# (or pytestplugin equivalent, like __backend__) will be selected
|
||||
# by pytest
|
||||
includes_excludes["m"].append("backend")
|
||||
else:
|
||||
includes_excludes["m"].append("not memory_intensive")
|
||||
|
||||
# the mypy suite is also run exclusively from the test_mypy
|
||||
# session
|
||||
includes_excludes["m"].append("not mypy")
|
||||
|
||||
if not timing_intensive:
|
||||
includes_excludes["m"].append("not timing_intensive")
|
||||
|
||||
cmd = ["python", "-m", "pytest"]
|
||||
|
||||
if coverage:
|
||||
assert not platform_intensive
|
||||
cmd.extend(
|
||||
[
|
||||
"--cov=sqlalchemy",
|
||||
"--cov-append",
|
||||
"--cov-report",
|
||||
"term",
|
||||
"--cov-report",
|
||||
"xml",
|
||||
],
|
||||
)
|
||||
includes_excludes["k"].append("not aaa_profiling")
|
||||
|
||||
cmd.extend(os.environ.get("TOX_WORKERS", "-n4").split())
|
||||
|
||||
if coverage:
|
||||
session.install("-e", ".")
|
||||
session.install(*nox.project.dependency_groups(pyproject, "coverage"))
|
||||
else:
|
||||
session.install(".")
|
||||
|
||||
session.install(*nox.project.dependency_groups(pyproject, "tests"))
|
||||
|
||||
if greenlet:
|
||||
session.install(
|
||||
*nox.project.dependency_groups(pyproject, "tests_greenlet")
|
||||
)
|
||||
else:
|
||||
# note: if on SQLAlchemy 2.0, for "nogreenlet" need to do an explicit
|
||||
# uninstall of greenlet since it's included in sqlalchemy dependencies
|
||||
# in 2.1 it's an optional dependency
|
||||
session.run("pip", "uninstall", "-y", "greenlet")
|
||||
|
||||
_setup_for_driver(session, cmd, database, greenlet=greenlet)
|
||||
|
||||
for letter, collection in includes_excludes.items():
|
||||
if collection:
|
||||
cmd.extend([f"-{letter}", " and ".join(collection)])
|
||||
|
||||
posargs, opts = extract_opts(session.posargs, "generate-junit", "dry-run")
|
||||
|
||||
if opts.generate_junit:
|
||||
# produce individual junit files that are per-database
|
||||
junitfile = f"junit-{database}.xml"
|
||||
cmd.extend(["--junitxml", junitfile])
|
||||
|
||||
cmd.extend(posargs)
|
||||
|
||||
if opts.dry_run:
|
||||
print(f"DRY RUN: command is: \n{' '.join(cmd)}")
|
||||
return
|
||||
|
||||
try:
|
||||
session.run(*cmd)
|
||||
finally:
|
||||
# Run cleanup for oracle/mssql
|
||||
if database in ["oracle", "mssql"]:
|
||||
session.run("python", "reap_dbs.py", "db_idents.txt")
|
||||
|
||||
|
||||
@nox.session(name="pep484")
|
||||
def test_pep484(session: nox.Session) -> None:
|
||||
"""Run mypy type checking."""
|
||||
|
||||
session.install(*nox.project.dependency_groups(pyproject, "mypy"))
|
||||
|
||||
session.install("-e", ".")
|
||||
|
||||
session.run(
|
||||
"mypy",
|
||||
"noxfile.py",
|
||||
"./lib/sqlalchemy",
|
||||
)
|
||||
|
||||
|
||||
@nox.session(name="mypy")
|
||||
def test_mypy(session: nox.Session) -> None:
|
||||
"""run the typing integration test suite"""
|
||||
|
||||
session.install(*nox.project.dependency_groups(pyproject, "mypy"))
|
||||
|
||||
session.install("-e", ".")
|
||||
|
||||
posargs, opts = extract_opts(session.posargs, "generate-junit")
|
||||
|
||||
cmd = ["pytest", "-m", "mypy"]
|
||||
if opts.generate_junit:
|
||||
# produce individual junit files that are per-database
|
||||
junitfile = "junit-mypy.xml"
|
||||
cmd.extend(["--junitxml", junitfile])
|
||||
|
||||
session.run(*cmd, *posargs)
|
||||
|
||||
|
||||
@nox.session(name="pep8")
|
||||
def test_pep8(session: nox.Session) -> None:
|
||||
"""Run linting and formatting checks."""
|
||||
|
||||
session.install("-e", ".")
|
||||
|
||||
session.install(*nox.project.dependency_groups(pyproject, "lint"))
|
||||
|
||||
for cmd in [
|
||||
"flake8 ./lib/ ./test/ ./examples/ noxfile.py "
|
||||
"setup.py doc/build/conf.py",
|
||||
"flake8 --extend-ignore='' ./lib/sqlalchemy/ext/asyncio "
|
||||
"./lib/sqlalchemy/orm/scoping.py",
|
||||
"black --check ./lib/ ./test/ ./examples/ setup.py doc/build/conf.py",
|
||||
"slotscheck -m sqlalchemy",
|
||||
"python ./tools/format_docs_code.py --check",
|
||||
"python ./tools/generate_tuple_map_overloads.py --check",
|
||||
"python ./tools/generate_proxy_methods.py --check",
|
||||
"python ./tools/sync_test_files.py --check",
|
||||
"python ./tools/generate_sql_functions.py --check",
|
||||
"python ./tools/normalize_file_headers.py --check",
|
||||
"python ./tools/cython_imports.py --check",
|
||||
"python ./tools/walk_packages.py",
|
||||
]:
|
||||
|
||||
session.run(*cmd.split())
|
||||
+102
-5
@@ -59,7 +59,7 @@ oracle-oracledb = ["oracledb>=1.0.1"]
|
||||
postgresql = ["psycopg2>=2.7"]
|
||||
postgresql-pg8000 = ["pg8000>=1.29.3"]
|
||||
postgresql-asyncpg = [
|
||||
"greenlet>=1", # same as ".[asyncio]" if this syntax were supported
|
||||
"sqlalchemy[asyncio]",
|
||||
"asyncpg",
|
||||
]
|
||||
postgresql-psycopg2binary = ["psycopg2-binary"]
|
||||
@@ -69,19 +69,19 @@ postgresql-psycopgbinary = ["psycopg[binary]>=3.0.7,!=3.1.15"]
|
||||
pymysql = ["pymysql"]
|
||||
cymysql = ["cymysql"]
|
||||
aiomysql = [
|
||||
"greenlet>=1", # same as ".[asyncio]" if this syntax were supported
|
||||
"sqlalchemy[asyncio]",
|
||||
"aiomysql",
|
||||
]
|
||||
aioodbc = [
|
||||
"greenlet>=1", # same as ".[asyncio]" if this syntax were supported
|
||||
"sqlalchemy[asyncio]",
|
||||
"aioodbc",
|
||||
]
|
||||
asyncmy = [
|
||||
"greenlet>=1", # same as ".[asyncio]" if this syntax were supported
|
||||
"sqlalchemy[asyncio]",
|
||||
"asyncmy>=0.2.3,!=0.2.4,!=0.2.6",
|
||||
]
|
||||
aiosqlite = [
|
||||
"greenlet>=1", # same as ".[asyncio]" if this syntax were supported
|
||||
"sqlalchemy[asyncio]",
|
||||
"aiosqlite",
|
||||
]
|
||||
sqlcipher = ["sqlcipher3_binary"]
|
||||
@@ -99,6 +99,103 @@ postgresql_psycopg2cffi = ["sqlalchemy[postgresql-psycopg2cffi]"]
|
||||
postgresql_psycopg = ["sqlalchemy[postgresql-psycopg]"]
|
||||
postgresql_psycopgbinary = ["sqlalchemy[postgresql-psycopgbinary]"]
|
||||
|
||||
[dependency-groups]
|
||||
tests = [
|
||||
"pytest>=7.0.0,<8.4",
|
||||
"pytest-xdist",
|
||||
]
|
||||
|
||||
coverage = ["pytest-cov"]
|
||||
|
||||
tests-greenlet = ["sqlalchemy[asyncio]"]
|
||||
|
||||
tests-sqlite = []
|
||||
|
||||
tests-sqlite-asyncio = [
|
||||
{include-group = "tests-greenlet"},
|
||||
{include-group = "tests-sqlite"},
|
||||
"sqlalchemy[aiosqlite]"
|
||||
]
|
||||
|
||||
tests-sqlite-file = [{include-group = "tests-sqlite"}]
|
||||
tests-sqlite-file-asyncio = [{include-group = "tests-sqlite-asyncio"}]
|
||||
|
||||
tests-postgresql = [
|
||||
"sqlalchemy[postgresql]",
|
||||
"sqlalchemy[postgresql-psycopg]",
|
||||
"sqlalchemy[postgresql-pg8000]",
|
||||
]
|
||||
|
||||
tests-postgresql-asyncio = [
|
||||
{include-group = "tests-greenlet"},
|
||||
{include-group = "tests-postgresql"},
|
||||
"sqlalchemy[postgresql-asyncpg]"
|
||||
]
|
||||
|
||||
tests-mysql = [
|
||||
"sqlalchemy[mysql]",
|
||||
"sqlalchemy[pymysql]",
|
||||
"sqlalchemy[mysql-connector]",
|
||||
|
||||
# originally to fix https://jira.mariadb.org/browse/CONPY-318,
|
||||
# more recent versions still have attribute errors and other random
|
||||
# problems
|
||||
"mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10,<1.1.13",
|
||||
|
||||
]
|
||||
tests-mysql-asyncio = [
|
||||
{include-group = "tests-greenlet"},
|
||||
{include-group = "tests-mysql"},
|
||||
"sqlalchemy[aiomysql]",
|
||||
"sqlalchemy[asyncmy]",
|
||||
]
|
||||
|
||||
tests-oracle-asyncio = [
|
||||
{include-group = "tests-greenlet"},
|
||||
{include-group = "tests-oracle"},
|
||||
]
|
||||
tests-oracle = [
|
||||
"sqlalchemy[oracle]",
|
||||
"sqlalchemy[oracle-oracledb]",
|
||||
]
|
||||
|
||||
tests-mssql = [
|
||||
"sqlalchemy[mssql]",
|
||||
"sqlalchemy[mssql-pymssql]",
|
||||
]
|
||||
|
||||
tests-mssql-asyncio = [
|
||||
{include-group = "tests-greenlet"},
|
||||
{include-group = "tests-mssql"},
|
||||
"aioodbc"
|
||||
]
|
||||
|
||||
lint = [
|
||||
{include-group = "tests-greenlet"},
|
||||
"flake8>=7.2.0",
|
||||
"flake8-import-order>=0.19.2",
|
||||
"flake8-import-single==0.1.5",
|
||||
"flake8-builtins",
|
||||
"flake8-future-annotations>=0.0.5",
|
||||
"flake8-docstrings",
|
||||
"flake8-unused-arguments",
|
||||
"flake8-rst-docstrings",
|
||||
"pydocstyle<4.0.0",
|
||||
"pygments",
|
||||
"black==25.1.0",
|
||||
"slotscheck>=0.17.0",
|
||||
"zimports", # required by generate_tuple_map_overloads
|
||||
]
|
||||
|
||||
mypy = [
|
||||
{include-group = "tests-greenlet"},
|
||||
"mypy>=1.16.0",
|
||||
"nox", # because we check noxfile.py
|
||||
"pytest>8,<8.4", # alembic/testing imports pytest
|
||||
"types-greenlet",
|
||||
]
|
||||
|
||||
|
||||
[tool.setuptools]
|
||||
include-package-data = true
|
||||
|
||||
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
"""Provides the tox_parameters() utility, which generates parameterized
|
||||
sections for nox tests, which include tags that indicate various combinations
|
||||
of those parameters in such a way that it's somewhat similar to how
|
||||
we were using the tox project; where individual dash-separated tags could
|
||||
be added to add more specificity to the suite configuation, or omitting them
|
||||
would fall back to defaults.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import re
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Generator
|
||||
from typing import Sequence
|
||||
|
||||
import nox
|
||||
|
||||
OUR_PYTHON = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
|
||||
def tox_parameters(
|
||||
names: Sequence[str],
|
||||
token_lists: Sequence[Sequence[str]],
|
||||
*,
|
||||
base_tag: str | None = None,
|
||||
filter_: Callable[..., bool] | None = None,
|
||||
always_include_in_tag: Sequence[str] | None = None,
|
||||
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
||||
r"""Decorator to create a parameter/tagging structure for a nox session
|
||||
function that acts to a large degree like tox's generative environments.
|
||||
|
||||
The output is a ``nox.parametrize()`` decorator that's built up from
|
||||
individual ``nox.param()`` instances.
|
||||
|
||||
:param names: names of the parameters sent to the session function.
|
||||
These names go straight to the first argument of ``nox.parametrize()``
|
||||
and should all match argument names accepted by the decorated function
|
||||
(except for ``python``, which is optional).
|
||||
:param token_lists: a sequence of lists of values for each parameter. a
|
||||
``nox.param()`` will be created for the full product of these values,
|
||||
minus those filtered out using the ``filter_`` callable. These tokens
|
||||
are used to create the args, tags, and ids of each ``nox.param()``. The
|
||||
list of tags will be generated out including all values for a parameter
|
||||
joined by ``-``, as well as combinations that include a subset of those
|
||||
values, where the omitted elements of the tag are implicitly considered to
|
||||
match the "default" value, indicated by them being first in their
|
||||
collection (with the exception of "python", where the current python in
|
||||
use is the default). Additionally, values that start with an underscore
|
||||
are omitted from all ids and tags. Values that refer to Python versions
|
||||
wlil be expanded to the full Python executable name when passed as
|
||||
arguments to the session function, which is currently a workaround to
|
||||
allow free-threaded python interpreters to be located.
|
||||
:param base_tag: optional tag that will be appended to all tags generated,
|
||||
e.g. if the decorator yields tags like ``python314-x86-windows``, a
|
||||
``basetag`` value of ``all`` would yield the
|
||||
tag as ``python314-x86-windows-all``.
|
||||
:param filter\_: optional filtering function, must accept keyword arguments
|
||||
matching the names in ``names``. Returns True or False indicating if
|
||||
a certain tag combination should be included.
|
||||
:param always_include_in_tag: list of names from ``names`` that indicate
|
||||
parameters that should always be part of all tags, and not be omitted
|
||||
as a "default"
|
||||
|
||||
|
||||
"""
|
||||
|
||||
PY_RE = re.compile(r"(?:python)?([234]\.\d+(t?))")
|
||||
|
||||
def _is_py_version(token: str) -> bool:
|
||||
return bool(PY_RE.match(token))
|
||||
|
||||
def _expand_python_version(token: str) -> str:
|
||||
"""expand pyx.y(t) tags into executable names.
|
||||
|
||||
Works around nox issue fixed at
|
||||
https://github.com/wntrblm/nox/pull/999 by providing full executable
|
||||
name
|
||||
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
return token
|
||||
|
||||
m = PY_RE.match(token)
|
||||
|
||||
# do this matching minimally so that it only happens for the
|
||||
# free-threaded versions. on windows, the "pythonx.y" syntax doesn't
|
||||
# work due to the use of the "py" tool
|
||||
if m and m.group(2) == "t":
|
||||
return f"python{m.group(1)}"
|
||||
else:
|
||||
return token
|
||||
|
||||
def _python_to_tag(token: str) -> str:
|
||||
m = PY_RE.match(token)
|
||||
if m:
|
||||
return f"py{m.group(1).replace('.', '')}"
|
||||
else:
|
||||
return token
|
||||
|
||||
if always_include_in_tag:
|
||||
name_to_list = dict(zip(names, token_lists))
|
||||
must_be_present = [
|
||||
name_to_list[name] for name in always_include_in_tag
|
||||
]
|
||||
else:
|
||||
must_be_present = None
|
||||
|
||||
def _recur_param(
|
||||
prevtokens: list[str],
|
||||
prevtags: list[str],
|
||||
token_lists: Sequence[Sequence[str]],
|
||||
) -> Generator[tuple[list[str], list[str], str], None, None]:
|
||||
|
||||
if not token_lists:
|
||||
return
|
||||
|
||||
tokens = token_lists[0]
|
||||
remainder = token_lists[1:]
|
||||
|
||||
for i, token in enumerate(tokens):
|
||||
|
||||
if _is_py_version(token):
|
||||
is_our_python = token == OUR_PYTHON
|
||||
tokentag = _python_to_tag(token)
|
||||
is_default_token = is_our_python
|
||||
else:
|
||||
is_our_python = False
|
||||
tokentag = token
|
||||
is_default_token = i == 0
|
||||
|
||||
if is_our_python:
|
||||
our_python_tags = ["py"]
|
||||
else:
|
||||
our_python_tags = []
|
||||
|
||||
if not tokentag.startswith("_"):
|
||||
tags = (
|
||||
prevtags
|
||||
+ [tokentag]
|
||||
+ [tag + "-" + tokentag for tag in prevtags]
|
||||
+ our_python_tags
|
||||
)
|
||||
else:
|
||||
tags = prevtags + our_python_tags
|
||||
|
||||
if remainder:
|
||||
for args, newtags, ids in _recur_param(
|
||||
prevtokens + [token], tags, remainder
|
||||
):
|
||||
if not is_default_token:
|
||||
newtags = [
|
||||
t
|
||||
for t in newtags
|
||||
if tokentag in t or t in our_python_tags
|
||||
]
|
||||
|
||||
yield args, newtags, ids
|
||||
else:
|
||||
if not is_default_token:
|
||||
newtags = [
|
||||
t
|
||||
for t in tags
|
||||
if tokentag in t or t in our_python_tags
|
||||
]
|
||||
else:
|
||||
newtags = tags
|
||||
|
||||
if base_tag:
|
||||
newtags = [t + f"-{base_tag}" for t in newtags]
|
||||
if must_be_present:
|
||||
for t in list(newtags):
|
||||
for required_tokens in must_be_present:
|
||||
if not any(r in t for r in required_tokens):
|
||||
newtags.remove(t)
|
||||
break
|
||||
|
||||
yield prevtokens + [token], newtags, "-".join(
|
||||
_python_to_tag(t)
|
||||
for t in prevtokens + [token]
|
||||
if not t.startswith("_")
|
||||
)
|
||||
|
||||
params = [
|
||||
nox.param(
|
||||
*[_expand_python_version(a) for a in args], tags=tags, id=ids
|
||||
)
|
||||
for args, tags, ids in _recur_param([], [], token_lists)
|
||||
if filter_ is None or filter_(**dict(zip(names, args)))
|
||||
]
|
||||
|
||||
# for p in params:
|
||||
# print(f"PARAM {'-'.join(p.args)} TAGS {p.tags}")
|
||||
|
||||
return nox.parametrize(names, params)
|
||||
|
||||
|
||||
def extract_opts(posargs: list[str], *args: str) -> tuple[list[str], Any]:
|
||||
"""Pop individual flag options from session.posargs.
|
||||
|
||||
Returns a named tuple with the individual flag options indicated,
|
||||
as well the new posargs with those flags removed from the string list
|
||||
so that the posargs can be forwarded onto pytest.
|
||||
|
||||
Basically if nox had an option for additional environmental flags that
|
||||
didn't require putting them after ``--``, we wouldn't need this, but this
|
||||
is probably more flexible.
|
||||
|
||||
"""
|
||||
underscore_args = [arg.replace("-", "_") for arg in args]
|
||||
return_tuple = collections.namedtuple("options", underscore_args) # type: ignore # noqa: E501
|
||||
|
||||
look_for_args = {f"--{arg}": idx for idx, arg in enumerate(args)}
|
||||
return_args = [False for arg in args]
|
||||
|
||||
def extract(arg: str) -> bool:
|
||||
if arg in look_for_args:
|
||||
return_args[look_for_args[arg]] = True
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
return [arg for arg in posargs if not extract(arg)], return_tuple(
|
||||
*return_args
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
def warn_tox():
|
||||
print(
|
||||
"\n"
|
||||
+ "=" * 80
|
||||
+ "\n\033[1;31m ⚠️ NOTE: TOX IS DEPRECATED IN THIS PROJECT! ⚠️"
|
||||
"\033[0m\n\033[1;33m "
|
||||
"Please use nox instead for running tests.\033[0m\n" + "=" * 80 + "\n"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
warn_tox()
|
||||
@@ -95,7 +95,7 @@ allowlist_externals=sh
|
||||
# PYTHONPATH - erased so that we use the build that's present
|
||||
# in .tox as the SQLAlchemy library to be imported
|
||||
#
|
||||
# PYTHONUSERSITE - this *MUST* be set so that the ./lib/ import
|
||||
# PYTHONNOUSERSITE - this *MUST* be set so that the ./lib/ import
|
||||
# set up explicitly in test/conftest.py is *disabled*, again so that
|
||||
# when SQLAlchemy is built into the .tox area, we use that and not the
|
||||
# local checkout, at least when usedevelop=False
|
||||
@@ -196,6 +196,7 @@ commands=
|
||||
nogreenlet: pip uninstall -y greenlet
|
||||
{env:BASECOMMAND} {env:WORKERS} {env:SQLITE:} {env:EXTRA_SQLITE_DRIVERS:} {env:POSTGRESQL:} {env:EXTRA_PG_DRIVERS:} {env:MYSQL:} {env:EXTRA_MYSQL_DRIVERS:} {env:ORACLE:} {env:EXTRA_ORACLE_DRIVERS:} {env:MSSQL:} {env:EXTRA_MSSQL_DRIVERS:} {env:IDENTS:} {env:PYTEST_EXCLUDES:} {env:COVERAGE:} {posargs}
|
||||
oracle,mssql,sqlite_file: python reap_dbs.py db_idents.txt
|
||||
python tools/warn_tox.py
|
||||
|
||||
|
||||
[testenv:pep484]
|
||||
@@ -205,9 +206,7 @@ deps=
|
||||
types-greenlet
|
||||
commands =
|
||||
mypy {env:MYPY_COLOR} ./lib/sqlalchemy
|
||||
# pyright changes too often with not-exactly-correct errors
|
||||
# suddently appearing for it to be stable enough for CI
|
||||
# pyright
|
||||
python tools/warn_tox.py
|
||||
|
||||
extras =
|
||||
{[greenletextras]extras}
|
||||
@@ -224,6 +223,7 @@ extras=
|
||||
|
||||
commands =
|
||||
pytest {env:PYTEST_COLOR} -m mypy {posargs}
|
||||
python tools/warn_tox.py
|
||||
|
||||
[testenv:mypy-cov]
|
||||
|
||||
@@ -236,6 +236,7 @@ extras=
|
||||
|
||||
commands =
|
||||
pytest {env:PYTEST_COLOR} -m mypy {env:COVERAGE} {posargs}
|
||||
python tools/warn_tox.py
|
||||
|
||||
setenv=
|
||||
COVERAGE={[testenv]cov_args}
|
||||
|
||||
Reference in New Issue
Block a user