Skip to content

Commit 6d063cd

Browse files
committed
Merge branch 'develop' of github.com:tortoise/tortoise-orm into replace-pytz
2 parents b37813e + 2aa8e0d commit 6d063cd

File tree

150 files changed

+22856
-16240
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+22856
-16240
lines changed

CHANGELOG.rst

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,59 @@ Changelog
1212

1313
0.26.0 (unreleased)
1414
-------------------
15-
Fixed
15+
16+
.. warning::
17+
18+
This release contains **breaking changes** as part of the context-first architecture migration.
19+
Please read the :ref:`migration_guide` before upgrading.
20+
21+
Breaking Changes
22+
^^^^^^^^^^^^^^^^
23+
- **Context-first architecture**: All ORM state now lives in ``TortoiseContext`` instances
24+
- **Removed** legacy test classes: ``test.TestCase``, ``test.IsolatedTestCase``, ``test.TruncationTestCase``, ``test.SimpleTestCase``
25+
- **Removed** legacy test helpers: ``initializer()``, ``finalizer()``, ``env_initializer()``, ``getDBConfig()``
26+
- **Changed** ``Tortoise.init()`` now returns ``TortoiseContext`` (previously returned ``None``)
27+
- **Changed** Multiple separate ``asyncio.run()`` calls in sequence require explicit context management due to ContextVar scoping (uncommon pattern, see migration guide). The typical single ``asyncio.run(main())`` pattern continues to work unchanged.
28+
29+
Added
1630
^^^^^
17-
- Fix ``AttributeError`` when using ``tortoise-orm`` with Nuitka-compiled Python code (#2053)
18-
- Fix 'Self' in python standard library typing.py, but tortoise/model.py required it in 'typing_extensions' (#2051)
19-
- Fix annotations being selected in ValuesListQuery despite not specified in `.values_list` fields list (#2059)
31+
- ``TortoiseContext`` - explicit context manager for ORM state
32+
- ``tortoise_test_context()`` - modern pytest fixture helper for test isolation
33+
- ``get_connection(alias)`` - function to get connection by alias from current context
34+
- ``get_connections()`` - function to get the ConnectionHandler from current context
35+
- ``Tortoise.close_connections()`` - class method to close all connections
36+
- ``Tortoise.is_inited()`` - explicit method version of ``Tortoise._inited`` property
2037

2138
Changed
22-
^^^^^
39+
^^^^^^^
40+
- Framework integrations (FastAPI, Starlette, Sanic, etc.) now use ``Tortoise.close_connections()`` internally
41+
- ``ConnectionHandler`` now uses instance-based ContextVar storage (each context has isolated connections)
42+
- ``Tortoise.apps`` and ``Tortoise._inited`` now use ``classproperty`` descriptor (no metaclass)
2343
- feat: foreignkey to model type (#2027)
44+
- **Shell command now uses optional dependencies**: Install with ``pip install tortoise-orm[ipython]`` (recommended) or ``pip install tortoise-orm[ptpython]`` to enable. IPython is preferred over ptpython. Both shells are supported.
45+
- **DatetimeField and TimeField behavior change**: Fields with ``auto_now=True`` no longer incorrectly set ``auto_now_add=True`` internally.
46+
47+
Deprecated
48+
^^^^^^^^^^
49+
- ``from tortoise import connections`` - use ``get_connection()`` / ``get_connections()`` functions instead (still works but deprecated)
50+
51+
Fixed
52+
^^^^^
53+
- Fix annotations being selected in ValuesListQuery despite not specified in `.values_list` fields list (#2059)
2454

2555
0.25
2656
====
2757

58+
0.25.4
59+
------
60+
61+
Fixed
62+
^^^^^
63+
- Fix ``AttributeError`` when using ``tortoise-orm`` with Nuitka-compiled Python code (#2053)
64+
- Fix 'Self' in python standard library typing.py, but tortoise/model.py required it in 'typing_extensions' (#2051)
65+
- Fix examples should not be installed (#2050)
66+
67+
2868
0.25.3
2969
------
3070
Fixed

conftest.py

Lines changed: 201 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,216 @@
1+
"""
2+
Pytest configuration for Tortoise ORM tests.
3+
4+
Uses function-scoped fixtures for true test isolation.
5+
"""
6+
17
import os
28

39
import pytest
10+
import pytest_asyncio
411

5-
from tortoise.contrib.test import finalizer, initializer
12+
from tortoise.context import tortoise_test_context
613

714

815
@pytest.fixture(scope="session", autouse=True)
9-
def initialize_tests(request):
10-
# Reduce the default timeout for psycopg because the tests become very slow otherwise
16+
def configure_psycopg():
17+
"""Configure psycopg timeout for faster tests."""
1118
try:
1219
from tortoise.backends.psycopg import PsycopgClient
1320

1421
PsycopgClient.default_timeout = float(os.environ.get("TORTOISE_POSTGRES_TIMEOUT", "15"))
1522
except ImportError:
1623
pass
1724

25+
26+
# ============================================================================
27+
# HELPER FUNCTIONS
28+
# ============================================================================
29+
30+
31+
async def _truncate_all_tables(ctx) -> None:
32+
"""Truncate all tables in the given context."""
33+
if ctx.apps:
34+
for model in ctx.apps.get_models_iterable():
35+
quote_char = model._meta.db.query_class.SQL_CONTEXT.quote_char
36+
await model._meta.db.execute_script(
37+
f"DELETE FROM {quote_char}{model._meta.db_table}{quote_char}" # nosec
38+
)
39+
40+
41+
# ============================================================================
42+
# PYTEST FIXTURES FOR TESTS
43+
# These fixtures provide different isolation patterns for test scenarios
44+
# ============================================================================
45+
46+
47+
@pytest_asyncio.fixture(scope="module")
48+
async def db_module():
49+
"""
50+
Module-scoped fixture: Creates TortoiseContext once per test module.
51+
52+
This is the base fixture that creates the database schema once per module.
53+
Other fixtures build on top of this for different isolation strategies.
54+
55+
Note: Uses connection_label="models" to match standard test infrastructure.
56+
"""
1857
db_url = os.getenv("TORTOISE_TEST_DB", "sqlite://:memory:")
19-
initializer(["tests.testmodels"], db_url=db_url)
20-
request.addfinalizer(finalizer)
58+
async with tortoise_test_context(
59+
modules=["tests.testmodels"],
60+
db_url=db_url,
61+
app_label="models",
62+
connection_label="models",
63+
) as ctx:
64+
yield ctx
65+
66+
67+
@pytest_asyncio.fixture(scope="function")
68+
async def db(db_module):
69+
"""
70+
Function-scoped fixture with transaction rollback cleanup.
71+
72+
Each test runs inside a transaction that gets rolled back at the end,
73+
providing isolation without the overhead of schema recreation.
74+
75+
For databases that don't support transactions (e.g., MySQL MyISAM),
76+
falls back to truncation cleanup.
77+
78+
This is the FASTEST isolation method - use for most tests.
79+
80+
Usage:
81+
@pytest.mark.asyncio
82+
async def test_something(db):
83+
obj = await Model.create(name="test")
84+
assert obj.id is not None
85+
# Changes are rolled back after test
86+
"""
87+
# Get connection from the context using its default connection
88+
conn = db_module.db()
89+
90+
# Check if the database supports transactions
91+
if conn.capabilities.supports_transactions:
92+
# Start a savepoint/transaction
93+
transaction = conn._in_transaction()
94+
await transaction.__aenter__()
95+
96+
try:
97+
yield db_module
98+
finally:
99+
# Rollback the transaction (discards all changes made during test)
100+
class _RollbackException(Exception):
101+
pass
102+
103+
await transaction.__aexit__(_RollbackException, _RollbackException(), None)
104+
else:
105+
# For databases without transaction support (e.g., MyISAM),
106+
# fall back to truncation cleanup
107+
yield db_module
108+
await _truncate_all_tables(db_module)
109+
110+
111+
@pytest_asyncio.fixture(scope="function")
112+
async def db_simple(db_module):
113+
"""
114+
Function-scoped fixture with NO cleanup between tests.
115+
116+
Tests share state - data from one test persists to the next within the module.
117+
Use ONLY for read-only tests or tests that manage their own cleanup.
118+
119+
Usage:
120+
@pytest.mark.asyncio
121+
async def test_read_only(db_simple):
122+
# Read-only operations, no writes
123+
config = get_config()
124+
assert "host" in config
125+
"""
126+
yield db_module
127+
128+
129+
@pytest_asyncio.fixture(scope="function")
130+
async def db_isolated():
131+
"""
132+
Function-scoped fixture with full database recreation per test.
133+
134+
Creates a completely fresh database for EACH test. This is the SLOWEST
135+
method but provides maximum isolation.
136+
137+
Use when:
138+
- Testing database creation/dropping
139+
- Tests need custom model modules
140+
- Tests must have completely clean state
141+
142+
Usage:
143+
@pytest.mark.asyncio
144+
async def test_with_fresh_db(db_isolated):
145+
# Completely fresh database
146+
...
147+
"""
148+
db_url = os.getenv("TORTOISE_TEST_DB", "sqlite://:memory:")
149+
async with tortoise_test_context(
150+
modules=["tests.testmodels"],
151+
db_url=db_url,
152+
app_label="models",
153+
connection_label="models",
154+
) as ctx:
155+
yield ctx
156+
157+
158+
@pytest_asyncio.fixture(scope="function")
159+
async def db_truncate(db_module):
160+
"""
161+
Function-scoped fixture with table truncation cleanup.
162+
163+
After each test, all tables are truncated (DELETE FROM).
164+
Faster than db_isolated but slower than db (transaction rollback).
165+
166+
Use when testing transaction behavior (can't use rollback for cleanup).
167+
168+
Usage:
169+
@pytest.mark.asyncio
170+
async def test_with_transactions(db_truncate):
171+
async with in_transaction():
172+
await Model.create(name="test")
173+
# Table truncated after test
174+
"""
175+
yield db_module
176+
await _truncate_all_tables(db_module)
177+
178+
179+
# ============================================================================
180+
# HELPER FIXTURES
181+
# ============================================================================
182+
183+
184+
def make_db_fixture(
185+
modules: list[str], app_label: str = "models", connection_label: str = "models"
186+
):
187+
"""
188+
Factory function to create custom db fixtures with different modules.
189+
190+
Use this in subdirectory conftest.py files for tests that need
191+
custom model modules.
192+
193+
Example usage in tests/fields/conftest.py:
194+
db_array = make_db_fixture(["tests.fields.test_array"])
195+
196+
Args:
197+
modules: List of module paths to discover models from.
198+
app_label: The app label for the models, defaults to "models".
199+
connection_label: The connection alias name, defaults to "models".
200+
201+
Returns:
202+
An async fixture function.
203+
"""
204+
205+
@pytest_asyncio.fixture(scope="function")
206+
async def _db_fixture():
207+
db_url = os.getenv("TORTOISE_TEST_DB", "sqlite://:memory:")
208+
async with tortoise_test_context(
209+
modules=modules,
210+
db_url=db_url,
211+
app_label=app_label,
212+
connection_label=connection_label,
213+
) as ctx:
214+
yield ctx
215+
216+
return _db_fixture

docs/cli.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,33 @@ shell
109109

110110
Start an interactive shell with Tortoise initialized.
111111

112+
**Installation**
113+
114+
The shell command requires either IPython or ptpython. Install your preferred shell:
115+
116+
.. code-block:: shell
117+
118+
# Install IPython (recommended)
119+
pip install tortoise-orm[ipython]
120+
121+
# Or install ptpython
122+
pip install tortoise-orm[ptpython]
123+
124+
IPython is preferred when both are available.
125+
126+
**Usage**
127+
112128
.. code-block:: shell
113129
114130
tortoise shell
115131
132+
**Supported Shells**
133+
134+
- **IPython** (>=8.0.0) - Preferred, better async/await support
135+
- **ptpython** (>=3.0.0) - Alternative with good async support
136+
137+
If neither shell is installed, the command will display an error with installation instructions.
138+
116139
Target shorthand
117140
================
118141

docs/conf.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import json
88

99
# -- Path setup --------------------------------------------------------------
10-
1110
# If extensions (or modules to document with autodoc) are in another directory,
1211
# add these directories to sys.path here. If the directory is relative to the
1312
# documentation root, use os.path.abspath to make it absolute, like shown here.

0 commit comments

Comments
 (0)