mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-07 01:10:52 -04:00
456727df50
Implemented initial support for free-threaded Python by adding new tests
and reworking the test harness and GitHub Actions to include Python 3.13t
and Python 3.14t in test runs. Two concurrency issues have been identified
and fixed: the first involves initialization of the ``.c`` collection on a
``FromClause``, a continuation of 🎫`12302`, where an optional mutex
under free-threading is added; the second involves synchronization of the
pool "first_connect" event, which first received thread synchronization in
🎫`2964`, however under free-threading the creation of the mutex
itself runs under the same free-threading mutex. Initial pull request and
test suite courtesy Lysandros Nikolaou.
py313t: yes
py314t: yes
Fixes: #12881
Closes: #12882
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/12882
Pull-request-sha: 53d65d96b9
Co-authored-by: Mike Bayer <mike_mp@zzzcomputng.com>
Change-Id: I2e4f2e9ac974ab6382cb0520cc446b396d9680a6
193 lines
6.0 KiB
Python
193 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
import doctest
|
|
import logging
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
from sqlalchemy.engine.url import make_url
|
|
from sqlalchemy.testing import config
|
|
from sqlalchemy.testing import fixtures
|
|
from sqlalchemy.testing import requires
|
|
from sqlalchemy.testing import skip_test
|
|
|
|
|
|
class DocTest(fixtures.TestBase):
|
|
__requires__ = ("insert_returning", "insertmanyvalues")
|
|
__only_on__ = "sqlite+pysqlite"
|
|
|
|
def _setup_logger(self):
|
|
rootlogger = logging.getLogger("sqlalchemy.engine.Engine")
|
|
|
|
class MyStream:
|
|
def write(self, string):
|
|
sys.stdout.write(string)
|
|
sys.stdout.flush()
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
self._handler = handler = logging.StreamHandler(MyStream())
|
|
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
rootlogger.addHandler(handler)
|
|
|
|
def _teardown_logger(self):
|
|
rootlogger = logging.getLogger("sqlalchemy.engine.Engine")
|
|
rootlogger.removeHandler(self._handler)
|
|
|
|
def _setup_create_table_patcher(self):
|
|
from sqlalchemy.sql import ddl
|
|
|
|
self.orig_sort = ddl.sort_tables_and_constraints
|
|
|
|
def our_sort(tables, **kw):
|
|
return self.orig_sort(sorted(tables, key=lambda t: t.key), **kw)
|
|
|
|
ddl.sort_tables_and_constraints = our_sort
|
|
|
|
def _teardown_create_table_patcher(self):
|
|
from sqlalchemy.sql import ddl
|
|
|
|
ddl.sort_tables_and_constraints = self.orig_sort
|
|
|
|
def setup_test(self):
|
|
self._setup_logger()
|
|
self._setup_create_table_patcher()
|
|
|
|
def teardown_test(self):
|
|
self._teardown_create_table_patcher()
|
|
self._teardown_logger()
|
|
|
|
def _run_doctest(self, *fnames):
|
|
here = os.path.dirname(__file__)
|
|
sqla_base = os.path.normpath(os.path.join(here, "..", ".."))
|
|
|
|
optionflags = (
|
|
doctest.ELLIPSIS
|
|
| doctest.NORMALIZE_WHITESPACE
|
|
| doctest.IGNORE_EXCEPTION_DETAIL
|
|
)
|
|
runner = doctest.DocTestRunner(
|
|
verbose=config.options.verbose >= 2, optionflags=optionflags
|
|
)
|
|
parser = doctest.DocTestParser()
|
|
globs = {"print_function": print}
|
|
|
|
for fname in fnames:
|
|
path = os.path.join(sqla_base, "doc/build", fname)
|
|
if not os.path.exists(path):
|
|
config.skip_test("Can't find documentation file %r" % path)
|
|
|
|
buf = []
|
|
|
|
with open(path, encoding="utf-8") as file_:
|
|
|
|
def load_include(m):
|
|
fname = m.group(1)
|
|
sub_path = os.path.join(os.path.dirname(path), fname)
|
|
with open(sub_path, encoding="utf-8") as file_:
|
|
for i, line in enumerate(file_, 1):
|
|
buf.append((i, line))
|
|
return fname
|
|
|
|
def run_buf(fname, is_include):
|
|
if not buf:
|
|
return
|
|
|
|
test = parser.get_doctest(
|
|
"".join(line for _, line in buf),
|
|
globs,
|
|
fname,
|
|
fname,
|
|
buf[0][0],
|
|
)
|
|
buf[:] = []
|
|
runner.run(test, clear_globs=False)
|
|
globs.update(test.globs)
|
|
|
|
doctest_enabled = True
|
|
|
|
for line_counter, line in enumerate(file_, 1):
|
|
line = re.sub(
|
|
r"{(?:stop|sql|opensql|execsql|printsql)}", "", line
|
|
)
|
|
|
|
include = re.match(r"\.\. doctest-include (.+\.rst)", line)
|
|
if include:
|
|
run_buf(fname, False)
|
|
include_fname = load_include(include)
|
|
run_buf(include_fname, True)
|
|
|
|
doctest_disable = re.match(
|
|
r"\.\. doctest-(enable|disable)", line
|
|
)
|
|
if doctest_disable:
|
|
doctest_enabled = doctest_disable.group(1) == "enable"
|
|
|
|
if doctest_enabled:
|
|
buf.append((line_counter, line))
|
|
else:
|
|
buf.append((line_counter, "\n"))
|
|
|
|
run_buf(fname, False)
|
|
|
|
runner.summarize()
|
|
assert not runner.failures
|
|
|
|
@requires.has_json_each
|
|
def test_20_style(self):
|
|
self._run_doctest(
|
|
"tutorial/index.rst",
|
|
"tutorial/engine.rst",
|
|
"tutorial/dbapi_transactions.rst",
|
|
"tutorial/metadata.rst",
|
|
"tutorial/data.rst",
|
|
"tutorial/data_insert.rst",
|
|
"tutorial/data_select.rst",
|
|
"tutorial/data_update.rst",
|
|
"tutorial/orm_data_manipulation.rst",
|
|
"tutorial/orm_related_objects.rst",
|
|
)
|
|
|
|
def test_core_operators(self):
|
|
self._run_doctest("core/operators.rst")
|
|
|
|
def test_orm_queryguide_select(self):
|
|
self._run_doctest(
|
|
"orm/queryguide/_plain_setup.rst",
|
|
"orm/queryguide/select.rst",
|
|
"orm/queryguide/api.rst",
|
|
"orm/queryguide/_end_doctest.rst",
|
|
)
|
|
|
|
def test_orm_queryguide_inheritance(self):
|
|
self._run_doctest("orm/queryguide/inheritance.rst")
|
|
|
|
@requires.update_from
|
|
def test_orm_queryguide_dml(self):
|
|
self._run_doctest("orm/queryguide/dml.rst")
|
|
|
|
def test_orm_large_collections(self):
|
|
self._run_doctest("orm/large_collections.rst")
|
|
|
|
def test_orm_queryguide_columns(self):
|
|
self._run_doctest("orm/queryguide/columns.rst")
|
|
|
|
def test_orm_quickstart(self):
|
|
self._run_doctest("orm/quickstart.rst")
|
|
|
|
# this crashes on 3.13t but passes on 3.14t.
|
|
# just requiring non-freethreaded for now
|
|
@requires.gil_enabled
|
|
@requires.greenlet
|
|
def test_asyncio(self):
|
|
try:
|
|
make_url("sqlite+aiosqlite://").get_dialect().import_dbapi()
|
|
except ImportError:
|
|
skip_test("missing aiosqile")
|
|
self._run_doctest("orm/extensions/asyncio.rst")
|
|
|
|
|
|
# increase number to force pipeline run. 1
|