Skip to content

Commit f478f8f

Browse files
authored
Merge pull request #1259 from dbfixtures/issue-890
Allow chaining noprocess fixtures - closes #890
2 parents 30687e3 + f6232c8 commit f478f8f

File tree

14 files changed

+221
-44
lines changed

14 files changed

+221
-44
lines changed

README.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,33 @@ To connect to an external server (e.g., running in Docker), use the ``postgresql
146146
147147
By default, it connects to ``127.0.0.1:5432``.
148148

149+
Chaining fixtures
150+
-----------------
151+
152+
You can chain multiple ``postgresql_noproc`` fixtures to layer your data pre-population. Each fixture in the chain will create its own template database based on the previous one.
153+
154+
.. code-block:: python
155+
156+
from pytest_postgresql import factories
157+
158+
# 1. Start with a process or a no-process base
159+
base_proc = factories.postgresql_proc(load=[load_schema])
160+
161+
# 2. Add a layer with some data
162+
seeded_noproc = factories.postgresql_noproc(depends_on="base_proc", load=[load_data])
163+
164+
# 3. Add another layer with more data
165+
more_seeded_noproc = factories.postgresql_noproc(depends_on="seeded_noproc", load=[load_more_data])
166+
167+
# 4. Use the final layer in your test
168+
client = factories.postgresql("more_seeded_noproc")
169+
170+
171+
172+
.. image:: https://raw.githubusercontent.com/dbfixtures/pytest-postgresql/main/docs/images/architecture_chaining.svg
173+
:alt: Fixture Chaining Diagram
174+
:align: center
175+
149176
Configuration
150177
=============
151178

docs/architecture_chaining.mmd

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
sequenceDiagram
2+
participant Test as Test
3+
participant ProcF as base_proc Fixture
4+
participant NoProc1 as seeded_noproc Fixture
5+
participant NoProc2 as more_seeded_noproc Fixture
6+
participant DB as PostgreSQL DB
7+
8+
Test->>ProcF: request base_proc
9+
ProcF->>DB: init database & run load_schema
10+
ProcF-->>Test: return PostgreSQLExecutor
11+
12+
Test->>NoProc1: request seeded_noproc (depends_on=base_proc)
13+
NoProc1->>ProcF: read connection/template info
14+
NoProc1->>DB: create layered DB / run load_data
15+
NoProc1-->>Test: return NoopExecutor
16+
17+
Test->>NoProc2: request more_seeded_noproc (depends_on=seeded_noproc)
18+
NoProc2->>NoProc1: read connection/template info
19+
NoProc2->>DB: run load_more_data on layered DB
20+
NoProc2-->>Test: return NoopExecutor
21+
22+
Test->>Test: validate tables and data across layers

newsfragments/890.break.1.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Bump the minimum supported pytest version to 8.2.
2+
3+
The previous minimum was about two years old, and older pytest versions
4+
can be flaky with fixture chaining that relies on `getfixturevalue` on
5+
Python 3.12-3.13 when used alongside xdist.

newsfragments/890.break.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor ``DatabaseJanitor`` to use explicit template management. This includes a new ``as_template`` flag and making ``dbname`` a required parameter.

newsfragments/890.docs.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a Mermaid sequence diagram to the documentation to illustrate fixture chaining and hierarchical cloning.

newsfragments/890.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add ``depends_on`` parameter to ``postgresql_noproc`` factory to allow hierarchical cloning and chaining of process fixtures.

oldest/requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
pytest == 7.4; python_version >= "3.14"
2-
pytest == 7.2; python_version < "3.14"
1+
pytest == 8.2
32
port-for == 0.7.3
43
mirakuru == 2.6.0
54
psycopg == 3.0.0

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ classifiers = [
2828
"Framework :: Pytest",
2929
]
3030
dependencies = [
31-
"pytest >= 7.2",
31+
"pytest >= 8.2",
3232
"port-for >= 0.7.3",
3333
"mirakuru >= 2.6.0",
3434
"packaging",

pytest_postgresql/factories/noprocess.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def postgresql_noproc(
4545
dbname: str | None = None,
4646
options: str = "",
4747
load: list[Callable | str | Path] | None = None,
48+
depends_on: str | None = None,
4849
) -> Callable[[FixtureRequest], Iterator[NoopExecutor]]:
4950
"""Postgresql noprocess factory.
5051
@@ -55,6 +56,7 @@ def postgresql_noproc(
5556
:param dbname: postgresql database name
5657
:param options: Postgresql connection options
5758
:param load: List of functions used to initialize database's template.
59+
:param depends_on: Optional name of the fixture to depend on.
5860
:returns: function which makes a postgresql process
5961
"""
6062

@@ -66,32 +68,53 @@ def postgresql_noproc_fixture(request: FixtureRequest) -> Iterator[NoopExecutor]
6668
:returns: tcp executor-like object
6769
"""
6870
config = get_config(request)
69-
pg_host = host or config.host
70-
pg_port = port or config.port or 5432
71-
pg_user = user or config.user
72-
pg_password = password or config.password
71+
72+
if depends_on:
73+
base = request.getfixturevalue(depends_on)
74+
pg_host = host or base.host
75+
pg_port = port or base.port
76+
pg_user = user or base.user
77+
pg_password = password or base.password
78+
pg_options = options or base.options
79+
base_template_dbname = base.template_dbname
80+
else:
81+
pg_host = host or config.host
82+
pg_port = port or config.port or 5432
83+
pg_user = user or config.user
84+
pg_password = password or config.password
85+
pg_options = options or config.options
86+
base_template_dbname = None
87+
7388
pg_dbname = xdistify_dbname(dbname or config.dbname)
74-
pg_options = options or config.options
7589
pg_load = load or config.load
7690
drop_test_database = config.drop_test_database
7791

92+
# In this case there's a risk that both seeded and depends_on fixture
93+
# might end up with the same configured dbname.
94+
if depends_on and not dbname:
95+
noop_exec_dbname = f"{pg_dbname}_{depends_on}"
96+
else:
97+
noop_exec_dbname = pg_dbname
98+
7899
noop_exec = NoopExecutor(
79100
host=pg_host,
80101
port=pg_port,
81102
user=pg_user,
82103
password=pg_password,
83-
dbname=pg_dbname,
104+
dbname=noop_exec_dbname,
84105
options=pg_options,
85106
)
86107
janitor = DatabaseJanitor(
87108
user=noop_exec.user,
88109
host=noop_exec.host,
89110
port=noop_exec.port,
90-
template_dbname=noop_exec.template_dbname,
111+
dbname=noop_exec.template_dbname,
112+
template_dbname=base_template_dbname,
113+
as_template=True,
91114
version=noop_exec.version,
92115
password=noop_exec.password,
93116
)
94-
if drop_test_database is True:
117+
if drop_test_database:
95118
janitor.drop()
96119
with janitor:
97120
for load_element in pg_load:

pytest_postgresql/factories/process.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ def postgresql_proc_fixture(
170170
user=postgresql_executor.user,
171171
host=postgresql_executor.host,
172172
port=postgresql_executor.port,
173-
template_dbname=postgresql_executor.template_dbname,
173+
dbname=postgresql_executor.template_dbname,
174+
as_template=True,
174175
version=postgresql_executor.version,
175176
password=postgresql_executor.password,
176177
)

0 commit comments

Comments
 (0)