Skip to content

Commit cbe4139

Browse files
committed
Allow chaining noprocess fixtures - closes #890
This allows for more complex test suites with common base data fixture Extended for a specific group of tests that needs additional data elements in database
1 parent 43ecec6 commit cbe4139

File tree

3 files changed

+149
-6
lines changed

3 files changed

+149
-6
lines changed

README.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,27 @@ 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+
149170
Configuration
150171
=============
151172

pytest_postgresql/factories/noprocess.py

Lines changed: 27 additions & 6 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,28 +68,47 @@ 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+
base_template_dbname = None
73+
if depends_on:
74+
base = request.getfixturevalue(depends_on)
75+
pg_host = host or base.host
76+
pg_port = port or base.port
77+
pg_user = user or base.user
78+
pg_password = password or base.password
79+
pg_options = options or base.options
80+
base_template_dbname = base.template_dbname
81+
else:
82+
pg_host = host or config.host
83+
pg_port = port or config.port or 5432
84+
pg_user = user or config.user
85+
pg_password = password or config.password
86+
pg_options = options or config.options
87+
7388
pg_dbname = xdistify_dbname(dbname or config.dbname)
7489
pg_options = options or config.options
7590
pg_load = load or config.load
7691
drop_test_database = config.drop_test_database
7792

93+
if depends_on:
94+
noop_exec_dbname = f"{pg_dbname}_{depends_on}"
95+
else:
96+
noop_exec_dbname = pg_dbname
97+
7898
noop_exec = NoopExecutor(
7999
host=pg_host,
80100
port=pg_port,
81101
user=pg_user,
82102
password=pg_password,
83-
dbname=pg_dbname,
103+
dbname=noop_exec_dbname,
84104
options=pg_options,
85105
)
86106
janitor = DatabaseJanitor(
87107
user=noop_exec.user,
88108
host=noop_exec.host,
89109
port=noop_exec.port,
90-
template_dbname=noop_exec.template_dbname,
110+
template_dbname=base_template_dbname or noop_exec.template_dbname,
111+
dbname=noop_exec.template_dbname if base_template_dbname else None,
91112
version=noop_exec.version,
92113
password=noop_exec.password,
93114
)

tests/test_chaining.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""Chaining noprocess fixtures tests for pytest-postgresql."""
2+
3+
import psycopg
4+
5+
from pytest_postgresql import factories
6+
from pytest_postgresql.executor import PostgreSQLExecutor
7+
from pytest_postgresql.executor_noop import NoopExecutor
8+
9+
10+
def load_schema(host: str, port: int, user: str, dbname: str, password: str | None) -> None:
11+
"""Load schema into the database."""
12+
with psycopg.connect(host=host, port=port, user=user, dbname=dbname, password=password) as conn:
13+
with conn.cursor() as cur:
14+
cur.execute("CREATE TABLE schema_table (id serial PRIMARY KEY, name varchar);")
15+
conn.commit()
16+
17+
18+
def load_data(host: str, port: int, user: str, dbname: str, password: str | None) -> None:
19+
"""Load the first layer of data into the database."""
20+
with psycopg.connect(host=host, port=port, user=user, dbname=dbname, password=password) as conn:
21+
with conn.cursor() as cur:
22+
cur.execute("INSERT INTO schema_table (name) VALUES ('data_layer');")
23+
cur.execute("CREATE TABLE data_table (id serial PRIMARY KEY, val varchar);")
24+
conn.commit()
25+
26+
27+
def load_more_data(host: str, port: int, user: str, dbname: str, password: str | None) -> None:
28+
"""Load the second layer of data into the database."""
29+
with psycopg.connect(host=host, port=port, user=user, dbname=dbname, password=password) as conn:
30+
with conn.cursor() as cur:
31+
cur.execute("INSERT INTO schema_table (name) VALUES ('more_data_layer');")
32+
cur.execute("CREATE TABLE more_data_table (id serial PRIMARY KEY, extra varchar);")
33+
conn.commit()
34+
35+
36+
# Chaining: proc -> noproc -> client
37+
base_proc = factories.postgresql_proc(load=[load_schema])
38+
seeded_noproc = factories.postgresql_noproc(depends_on="base_proc", load=[load_data])
39+
client_layered = factories.postgresql("seeded_noproc")
40+
41+
# Deeper chaining: proc -> noproc -> noproc -> client
42+
more_seeded_noproc = factories.postgresql_noproc(depends_on="seeded_noproc", load=[load_more_data])
43+
client_deep_layered = factories.postgresql("more_seeded_noproc")
44+
45+
46+
def test_chaining_two_layers(client_layered: psycopg.Connection) -> None:
47+
"""Test that data from both proc and noproc layers is present."""
48+
with client_layered.cursor() as cur:
49+
# From base_proc (load_schema)
50+
cur.execute("SELECT count(*) FROM information_schema.tables WHERE table_name = 'schema_table';")
51+
res = cur.fetchone()
52+
assert res
53+
assert res[0] == 1
54+
55+
# From seeded_noproc (load_data)
56+
cur.execute("SELECT count(*) FROM information_schema.tables WHERE table_name = 'data_table';")
57+
res = cur.fetchone()
58+
assert res
59+
assert res[0] == 1
60+
61+
# Data inserted in seeded_noproc
62+
cur.execute("SELECT name FROM schema_table;")
63+
res = cur.fetchone()
64+
assert res
65+
assert res[0] == "data_layer"
66+
67+
68+
def test_chaining_three_layers(client_deep_layered: psycopg.Connection) -> None:
69+
"""Test that data from all three layers is present."""
70+
with client_deep_layered.cursor() as cur:
71+
# From base_proc
72+
cur.execute("SELECT count(*) FROM information_schema.tables WHERE table_name = 'schema_table';")
73+
res = cur.fetchone()
74+
assert res
75+
assert res[0] == 1
76+
77+
# From seeded_noproc
78+
cur.execute("SELECT count(*) FROM information_schema.tables WHERE table_name = 'data_table';")
79+
res = cur.fetchone()
80+
assert res
81+
assert res[0] == 1
82+
83+
# From more_seeded_noproc
84+
cur.execute("SELECT count(*) FROM information_schema.tables WHERE table_name = 'more_data_table';")
85+
res = cur.fetchone()
86+
assert res
87+
assert res[0] == 1
88+
89+
# Data from multiple layers
90+
cur.execute("SELECT name FROM schema_table ORDER BY id;")
91+
results = cur.fetchall()
92+
assert results[0][0] == "data_layer"
93+
assert results[1][0] == "more_data_layer"
94+
95+
96+
def test_inheritance(base_proc: PostgreSQLExecutor, seeded_noproc: NoopExecutor) -> None:
97+
"""Verify that connection parameters are inherited from the base fixture."""
98+
assert seeded_noproc.host == base_proc.host
99+
assert seeded_noproc.port == base_proc.port
100+
assert seeded_noproc.user == base_proc.user
101+
assert seeded_noproc.password == base_proc.password

0 commit comments

Comments
 (0)