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:
Mike Bayer
2025-09-28 23:44:41 -04:00
parent 79969acaed
commit df899e94cf
10 changed files with 780 additions and 51 deletions
+9 -9
View File
@@ -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 }}
+9 -18
View File
@@ -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 }}
+1
View File
@@ -10,6 +10,7 @@
*.orig
*,cover
/.tox
/.nox
/venv/
.venv
*.egg-info
+36 -15
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
)
+12
View File
@@ -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()
+5 -4
View File
@@ -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}