postgresql-testing 0.0.1__tar.gz → 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {postgresql_testing-0.0.1 → postgresql_testing-0.0.2}/PKG-INFO +19 -3
- postgresql_testing-0.0.2/README.md +44 -0
- {postgresql_testing-0.0.1 → postgresql_testing-0.0.2}/pyproject.toml +1 -1
- {postgresql_testing-0.0.1 → postgresql_testing-0.0.2}/src/postgresql_testing/__init__.py +50 -50
- postgresql_testing-0.0.1/README.md +0 -28
- {postgresql_testing-0.0.1 → postgresql_testing-0.0.2}/src/postgresql_testing/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: postgresql-testing
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: PostgreSQL testing helpers
|
|
5
5
|
Author: Oli Russell
|
|
6
6
|
Author-email: Oli Russell <mrxoliver@gmail.com>
|
|
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
|
|
|
12
12
|
|
|
13
13
|
# Postgresql testing
|
|
14
14
|
|
|
15
|
-
Simple Postgres helpers for testing with Python - no docker, brew, apt, etc - uses [postgresql-binaries](https://github.com/leontrolski/postgresql-binaries).
|
|
15
|
+
Simple Postgres helpers for testing with Python - no docker, brew, apt, etc - uses [postgresql-binaries](https://github.com/leontrolski/postgresql-binaries). The interface is simple, but close enough to the metal that you can use it to eg. have a new database per testing thread/use a fresh database from a `TEMPLATE` or archive.
|
|
16
16
|
|
|
17
17
|
```shell
|
|
18
18
|
pip install postgresql-testing 'postgresql-binaries==18.*'
|
|
@@ -37,4 +37,20 @@ There are various useful flags and things - the source code is short enough to j
|
|
|
37
37
|
|
|
38
38
|
<hr>
|
|
39
39
|
|
|
40
|
-
There are a couple of helpers for creating/using template dbs and
|
|
40
|
+
There are a couple of helpers for creating/using template dbs and archives. I have some vague long term plan for some kind of "Docker layers for migrated databases" with clever caching (or not), but I'm not quite sure what it looks like yet.
|
|
41
|
+
|
|
42
|
+
Some benchmarking:
|
|
43
|
+
|
|
44
|
+
| number of tables | create from template | dump to archive | create from archive |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| 100 | 80ms | 80ms (0.2MB) | 150ms |
|
|
47
|
+
| 1000 | 500ms | 300ms (2MB) | 1100ms |
|
|
48
|
+
|
|
49
|
+
On macos using a ramdisk, it is slightly quicker:
|
|
50
|
+
|
|
51
|
+
| number of tables | create from template | dump to archive | create from archive |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| 100 | 70ms | 70ms | 120ms |
|
|
54
|
+
| 1000 | 350ms | 300ms | 1000ms |
|
|
55
|
+
|
|
56
|
+
See claims/advice from planet [Go](https://github.com/peterldowns/pgtestdb).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Postgresql testing
|
|
2
|
+
|
|
3
|
+
Simple Postgres helpers for testing with Python - no docker, brew, apt, etc - uses [postgresql-binaries](https://github.com/leontrolski/postgresql-binaries). The interface is simple, but close enough to the metal that you can use it to eg. have a new database per testing thread/use a fresh database from a `TEMPLATE` or archive.
|
|
4
|
+
|
|
5
|
+
```shell
|
|
6
|
+
pip install postgresql-testing 'postgresql-binaries==18.*'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Then to use, eg:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import postgresql_testing
|
|
13
|
+
|
|
14
|
+
@pytest.fixture(scope="session")
|
|
15
|
+
def db() -> Iterator[str]:
|
|
16
|
+
config = postgresql_testing.DatabaseConfig.default("testing-db")
|
|
17
|
+
postgresql_testing.initdb(config.directory, on_existing="use")
|
|
18
|
+
with postgresql_testing.serve(config):
|
|
19
|
+
postgresql_testing.ensure_user(config)
|
|
20
|
+
postgresql_testing.create_database(config, on_existing="replace")
|
|
21
|
+
yield config.dsn
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
There are various useful flags and things - the source code is short enough to just dive in.
|
|
25
|
+
|
|
26
|
+
<hr>
|
|
27
|
+
|
|
28
|
+
There are a couple of helpers for creating/using template dbs and archives. I have some vague long term plan for some kind of "Docker layers for migrated databases" with clever caching (or not), but I'm not quite sure what it looks like yet.
|
|
29
|
+
|
|
30
|
+
Some benchmarking:
|
|
31
|
+
|
|
32
|
+
| number of tables | create from template | dump to archive | create from archive |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| 100 | 80ms | 80ms (0.2MB) | 150ms |
|
|
35
|
+
| 1000 | 500ms | 300ms (2MB) | 1100ms |
|
|
36
|
+
|
|
37
|
+
On macos using a ramdisk, it is slightly quicker:
|
|
38
|
+
|
|
39
|
+
| number of tables | create from template | dump to archive | create from archive |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| 100 | 70ms | 70ms | 120ms |
|
|
42
|
+
| 1000 | 350ms | 300ms | 1000ms |
|
|
43
|
+
|
|
44
|
+
See claims/advice from planet [Go](https://github.com/peterldowns/pgtestdb).
|
|
@@ -8,9 +8,8 @@ import subprocess
|
|
|
8
8
|
from typing import Iterator, Literal, Self
|
|
9
9
|
import postgresql_binaries
|
|
10
10
|
import psycopg
|
|
11
|
-
import tarfile
|
|
12
11
|
|
|
13
|
-
DEFAULT_DIR = Path("/tmp
|
|
12
|
+
DEFAULT_DIR = Path("/tmp/postgresql-testing")
|
|
14
13
|
SUPERUSER = "postgres"
|
|
15
14
|
ROOT_DATABASE = "postgres"
|
|
16
15
|
|
|
@@ -72,7 +71,6 @@ def initdb(
|
|
|
72
71
|
*,
|
|
73
72
|
on_existing: Literal["raise", "use", "replace"] = "raise",
|
|
74
73
|
) -> None:
|
|
75
|
-
"""Call `initdb` with some sensible arguments for testing."""
|
|
76
74
|
if directory.exists():
|
|
77
75
|
if on_existing == "raise":
|
|
78
76
|
raise RuntimeError(f"Directory {directory} already exists")
|
|
@@ -100,7 +98,6 @@ def initdb(
|
|
|
100
98
|
|
|
101
99
|
@contextmanager
|
|
102
100
|
def serve(c: ClusterConfig) -> Iterator[None]:
|
|
103
|
-
"""Call `postgres` with some sensible arguments."""
|
|
104
101
|
c.stderr.parent.mkdir(parents=True, exist_ok=True)
|
|
105
102
|
with open(c.stderr, "wb") as stderr_f:
|
|
106
103
|
cmd = [
|
|
@@ -123,28 +120,6 @@ def serve(c: ClusterConfig) -> Iterator[None]:
|
|
|
123
120
|
process.wait()
|
|
124
121
|
|
|
125
122
|
|
|
126
|
-
@contextmanager
|
|
127
|
-
def create_template(
|
|
128
|
-
c: DatabaseConfig,
|
|
129
|
-
*,
|
|
130
|
-
template: str,
|
|
131
|
-
on_existing: Literal["raise", "replace"] = "replace",
|
|
132
|
-
) -> Iterator[DatabaseConfig]:
|
|
133
|
-
with psycopg.connect(c.superuser().dsn) as conn, conn.cursor() as cur:
|
|
134
|
-
conn.autocommit = True
|
|
135
|
-
template_exists = bool(cur.execute(f"SELECT 1 FROM pg_database WHERE datname = '{template}'").fetchall())
|
|
136
|
-
if template_exists:
|
|
137
|
-
if on_existing == "raise":
|
|
138
|
-
raise RuntimeError(f"Template database {template} already exists")
|
|
139
|
-
if on_existing == "replace":
|
|
140
|
-
cur.execute(f"UPDATE pg_database SET datistemplate = false WHERE datname='{template}'")
|
|
141
|
-
cur.execute(f'DROP DATABASE "{template}"')
|
|
142
|
-
|
|
143
|
-
cur.execute(f'CREATE DATABASE "{template}" OWNER "{c.user}"')
|
|
144
|
-
yield replace(c, database=template)
|
|
145
|
-
cur.execute(f"UPDATE pg_database SET datistemplate = true WHERE datname='{template}'")
|
|
146
|
-
|
|
147
|
-
|
|
148
123
|
def ensure_user(c: DatabaseConfig) -> None:
|
|
149
124
|
with psycopg.connect(c.superuser().dsn) as conn, conn.cursor() as cur:
|
|
150
125
|
try:
|
|
@@ -159,11 +134,6 @@ def create_database(
|
|
|
159
134
|
template: str | None = None,
|
|
160
135
|
on_existing: Literal["raise", "replace"] = "replace",
|
|
161
136
|
) -> None:
|
|
162
|
-
"""Create a Postgres database.
|
|
163
|
-
|
|
164
|
-
- Optionally pass in a template.
|
|
165
|
-
- Takes in the tens of ms for a small db.
|
|
166
|
-
"""
|
|
167
137
|
template_str = "" if template is None else f'WITH TEMPLATE "{template}"'
|
|
168
138
|
sql_create_database = f'CREATE DATABASE "{c.database}" {template_str} OWNER "{c.user}" STRATEGY=FILE_COPY'
|
|
169
139
|
|
|
@@ -179,41 +149,71 @@ def create_database(
|
|
|
179
149
|
cur.execute(sql_create_database)
|
|
180
150
|
|
|
181
151
|
|
|
152
|
+
# More experimental functions
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@contextmanager
|
|
156
|
+
def create_template(
|
|
157
|
+
c: DatabaseConfig,
|
|
158
|
+
*,
|
|
159
|
+
template: str,
|
|
160
|
+
on_existing: Literal["raise", "replace"] = "replace",
|
|
161
|
+
) -> Iterator[DatabaseConfig]:
|
|
162
|
+
with psycopg.connect(c.superuser().dsn) as conn, conn.cursor() as cur:
|
|
163
|
+
conn.autocommit = True
|
|
164
|
+
template_exists = bool(cur.execute(f"SELECT 1 FROM pg_database WHERE datname = '{template}'").fetchall())
|
|
165
|
+
if template_exists:
|
|
166
|
+
if on_existing == "raise":
|
|
167
|
+
raise RuntimeError(f"Template database {template} already exists")
|
|
168
|
+
if on_existing == "replace":
|
|
169
|
+
cur.execute(f"UPDATE pg_database SET datistemplate = false WHERE datname='{template}'")
|
|
170
|
+
cur.execute(f'DROP DATABASE "{template}"')
|
|
171
|
+
|
|
172
|
+
cur.execute(f'CREATE DATABASE "{template}" OWNER "{c.user}"')
|
|
173
|
+
yield replace(c, database=template)
|
|
174
|
+
cur.execute(f"UPDATE pg_database SET datistemplate = true WHERE datname='{template}'")
|
|
175
|
+
|
|
176
|
+
|
|
182
177
|
def dump_archive(
|
|
183
|
-
|
|
178
|
+
c: DatabaseConfig,
|
|
184
179
|
archive: Path,
|
|
185
180
|
*,
|
|
186
181
|
on_existing: Literal["raise", "replace"] = "replace",
|
|
187
182
|
) -> None:
|
|
188
|
-
"""Dump the entirety of a Postgres cluster to a tar archive.
|
|
189
|
-
|
|
190
|
-
- Archives are not very portable (between system architectures or Postgres
|
|
191
|
-
versions).
|
|
192
|
-
- Takes in the hundreds of ms for a small db.
|
|
193
|
-
"""
|
|
194
183
|
if archive.exists():
|
|
195
184
|
if on_existing == "raise":
|
|
196
185
|
raise RuntimeError(f"Archive {archive} already exists")
|
|
197
|
-
|
|
186
|
+
if on_existing == "replace":
|
|
187
|
+
archive.unlink()
|
|
198
188
|
|
|
199
|
-
|
|
200
|
-
|
|
189
|
+
cmd: list[str] = [
|
|
190
|
+
str(postgresql_binaries.bin() / "pg_dump"),
|
|
191
|
+
c.dsn,
|
|
192
|
+
*("--file", str(archive)),
|
|
193
|
+
*("--format", "t"),
|
|
194
|
+
*("--compress", "0"),
|
|
195
|
+
"--no-sync",
|
|
196
|
+
]
|
|
197
|
+
subprocess.check_call(cmd)
|
|
201
198
|
|
|
202
199
|
|
|
203
200
|
def load_archive(
|
|
204
201
|
archive: Path,
|
|
205
|
-
|
|
202
|
+
c: DatabaseConfig,
|
|
206
203
|
*,
|
|
207
204
|
on_existing: Literal["raise", "replace"] = "replace",
|
|
208
205
|
) -> None:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
206
|
+
cmd: list[str] = [
|
|
207
|
+
str(postgresql_binaries.bin() / "pg_restore"),
|
|
208
|
+
*("--host", c.host),
|
|
209
|
+
*("--port", str(c.port)),
|
|
210
|
+
*("--username", c.user),
|
|
211
|
+
*(("--password", c.password) if c.password else ()),
|
|
212
|
+
*("--dbname", c.database),
|
|
213
|
+
*(("--clean",) if on_existing == "replace" else ()),
|
|
214
|
+
str(archive),
|
|
215
|
+
]
|
|
216
|
+
subprocess.check_call(cmd)
|
|
217
217
|
|
|
218
218
|
|
|
219
219
|
def _try_connect(dsn: str) -> None:
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Postgresql testing
|
|
2
|
-
|
|
3
|
-
Simple Postgres helpers for testing with Python - no docker, brew, apt, etc - uses [postgresql-binaries](https://github.com/leontrolski/postgresql-binaries).
|
|
4
|
-
|
|
5
|
-
```shell
|
|
6
|
-
pip install postgresql-testing 'postgresql-binaries==18.*'
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
Then to use, eg:
|
|
10
|
-
|
|
11
|
-
```python
|
|
12
|
-
import postgresql_testing
|
|
13
|
-
|
|
14
|
-
@pytest.fixture(scope="session")
|
|
15
|
-
def db() -> Iterator[str]:
|
|
16
|
-
config = postgresql_testing.DatabaseConfig.default("testing-db")
|
|
17
|
-
postgresql_testing.initdb(config.directory, on_existing="use")
|
|
18
|
-
with postgresql_testing.serve(config):
|
|
19
|
-
postgresql_testing.ensure_user(config)
|
|
20
|
-
postgresql_testing.create_database(config, on_existing="replace")
|
|
21
|
-
yield config.dsn
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
There are various useful flags and things - the source code is short enough to just dive in.
|
|
25
|
-
|
|
26
|
-
<hr>
|
|
27
|
-
|
|
28
|
-
There are a couple of helpers for creating/using template dbs and tar files. I have some vague long term plan for some kind of "Docker layers for migrated databases" with clever caching, but I'm not quite sure what it looks like yet.
|
|
File without changes
|