wrapture-instrumentation-postgresql 1.0.0.dev1__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.
- wrapture_instrumentation_postgresql-1.0.0.dev1/CHANGES.md +9 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/Dockerfile +15 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/LICENSE +24 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/MANIFEST.in +8 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/PKG-INFO +155 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/README.md +132 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/TESTING.md +264 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/compose.yml +56 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/demo/__init__.py +0 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/demo/asyncpg.py +150 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/demo/psycopg.py +138 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/demo/psycopg2.py +138 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/pyproject.toml +125 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/setup.cfg +4 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/__init__.py +26 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/asyncpg/README.md +136 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/asyncpg/__init__.py +75 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/asyncpg/connection.py +220 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/asyncpg/cursor.py +88 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/asyncpg/prepared.py +74 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/common.py +169 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg/README.md +144 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg/__init__.py +57 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg/connection.py +168 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg/cursor.py +258 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg/transaction.py +126 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg2/README.md +131 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg2/__init__.py +49 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/psycopg2/factories.py +402 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql/py.typed +0 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql.egg-info/PKG-INFO +155 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql.egg-info/SOURCES.txt +60 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql.egg-info/dependency_links.txt +1 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql.egg-info/entry_points.txt +4 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql.egg-info/not-zip-safe +1 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql.egg-info/requires.txt +1 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/src/wrapture_instrumentation_postgresql.egg-info/top_level.txt +1 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/__init__.py +0 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/asyncpg/__init__.py +0 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/asyncpg/conftest.py +22 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/asyncpg/test_apply_remove.py +89 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/asyncpg/test_class.py +67 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/asyncpg/test_composition.py +148 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/asyncpg/test_recording.py +430 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/asyncpg/test_registration.py +82 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/conftest.py +320 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/__init__.py +0 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/conftest.py +22 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/test_apply_remove.py +94 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/test_async.py +127 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/test_class.py +70 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/test_composition.py +182 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/test_recording.py +478 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg/test_registration.py +79 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg2/__init__.py +0 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg2/conftest.py +23 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg2/test_apply_remove.py +95 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg2/test_class.py +85 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg2/test_composition.py +152 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg2/test_recording.py +526 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/psycopg2/test_registration.py +77 -0
- wrapture_instrumentation_postgresql-1.0.0.dev1/tests/test_package.py +179 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changes
|
|
2
|
+
|
|
3
|
+
## Version 1.0.0
|
|
4
|
+
|
|
5
|
+
Initial release of wrapture-instrumentation-postgresql. Everything is
|
|
6
|
+
new in this version, so rather than listing changes, see the
|
|
7
|
+
[README](README.md) for what the package provides: the table of
|
|
8
|
+
covered targets there links to each target's own notes describing
|
|
9
|
+
what it records and its settings.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# The environment the test matrix runs in: Debian with uv, and the
|
|
2
|
+
# one thing psycopg's pure Python implementation needs from the
|
|
3
|
+
# machine, a libpq. Nothing else is baked in: uv fetches whichever
|
|
4
|
+
# interpreter the run asks for and installs the test dependencies into
|
|
5
|
+
# a per-version environment, both on named volumes so they persist
|
|
6
|
+
# across runs (see compose.yml). The repository is bind-mounted at
|
|
7
|
+
# run time, so the image never goes stale as the code changes.
|
|
8
|
+
|
|
9
|
+
FROM ghcr.io/astral-sh/uv:debian-slim
|
|
10
|
+
|
|
11
|
+
RUN apt-get update \
|
|
12
|
+
&& apt-get install -y --no-install-recommends libpq5 \
|
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
14
|
+
|
|
15
|
+
WORKDIR /work
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright (c) 2026, Graham Dumpleton
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
15
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
16
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
17
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
18
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
19
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
20
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
21
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
22
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
23
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
24
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# setuptools includes test_*.py files by default but not the helpers
|
|
2
|
+
# they import (conftest.py, the package __init__.py), so the tests
|
|
3
|
+
# directory is taken whole, with the repository's other notes and the
|
|
4
|
+
# docker files the test matrix runs under.
|
|
5
|
+
graft tests
|
|
6
|
+
graft demo
|
|
7
|
+
include CHANGES.md TESTING.md compose.yml Dockerfile
|
|
8
|
+
global-exclude __pycache__ *.py[cod]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wrapture-instrumentation-postgresql
|
|
3
|
+
Version: 1.0.0.dev1
|
|
4
|
+
Summary: Instrumentation for the PostgreSQL client libraries (psycopg, psycopg2, asyncpg), applied through wrapture.
|
|
5
|
+
Author-email: Graham Dumpleton <Graham.Dumpleton@gmail.com>
|
|
6
|
+
License-Expression: BSD-2-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql
|
|
8
|
+
Project-URL: Documentation, https://wrapture.readthedocs.io
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/issues/
|
|
10
|
+
Keywords: wrapture,instrumentation,postgresql,psycopg,psycopg2,asyncpg,tracing
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: wrapture>=1.0.0a20
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# wrapture-instrumentation-postgresql
|
|
25
|
+
|
|
26
|
+
Instrumentation for the PostgreSQL client libraries, applied through
|
|
27
|
+
[wrapture](https://github.com/GrahamDumpleton/wrapture).
|
|
28
|
+
|
|
29
|
+
wrapture attaches bindings to arbitrary Python call sites without
|
|
30
|
+
modifying the code being observed, and its config layer can switch on
|
|
31
|
+
packaged instrumentation for a third-party package by name. This is
|
|
32
|
+
the PostgreSQL package in that collection: one `wrapture.Instrumentation`
|
|
33
|
+
class per client library, so tracing every query, connection and
|
|
34
|
+
transaction your application sends to PostgreSQL is one config entry
|
|
35
|
+
and no code.
|
|
36
|
+
|
|
37
|
+
> **Status: alpha, ahead of 1.0.0.** Developed against wrapture's
|
|
38
|
+
> alpha series, with pre-releases published to
|
|
39
|
+
> [PyPI](https://pypi.org/project/wrapture-instrumentation-postgresql/),
|
|
40
|
+
> and until 1.0.0 is final a plain `pip install
|
|
41
|
+
> wrapture-instrumentation-postgresql` picks up the latest pre-release
|
|
42
|
+
> automatically, so there is no need to pin a specific version.
|
|
43
|
+
|
|
44
|
+
## Why a separate package
|
|
45
|
+
|
|
46
|
+
The core
|
|
47
|
+
[wrapture-instrumentation](https://github.com/GrahamDumpleton/wrapture-instrumentation)
|
|
48
|
+
package deliberately covers only the standard library and third-party
|
|
49
|
+
packages that can be exercised in-process, with no separate backend
|
|
50
|
+
product or service needed to test against. A PostgreSQL driver is
|
|
51
|
+
exactly the kind of target the separate-package rule was drawn for:
|
|
52
|
+
its tests need a real server, so this package's suite runs one in a
|
|
53
|
+
docker container, and it carries the drivers as test dependencies
|
|
54
|
+
(some of them compiled wheels) and its own release cadence, so the
|
|
55
|
+
core package's test matrix stays light. One package covers every
|
|
56
|
+
client library for the one backend: psycopg, psycopg2 and asyncpg.
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```console
|
|
61
|
+
$ pip install wrapture-instrumentation-postgresql
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Installing it brings wrapture and nothing else. No driver is a
|
|
65
|
+
dependency: each instrumentation is inert until its driver is present,
|
|
66
|
+
and wrapture checks the installed version against the range the
|
|
67
|
+
instrumentation supports at apply time.
|
|
68
|
+
|
|
69
|
+
## Using it
|
|
70
|
+
|
|
71
|
+
An `[[instrument]]` entry in `wrapture.toml` names the target:
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
[[instrument]]
|
|
75
|
+
name = "psycopg"
|
|
76
|
+
|
|
77
|
+
[[sink]]
|
|
78
|
+
type = "printer"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
and the runner applies it before the application starts, so the patch
|
|
82
|
+
is in place before the driver is imported:
|
|
83
|
+
|
|
84
|
+
```console
|
|
85
|
+
$ python -m wrapture -m myapp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The same config works through
|
|
89
|
+
[autowrapt](https://github.com/GrahamDumpleton/autowrapt) injection
|
|
90
|
+
(`AUTOWRAPT_BOOTSTRAP=wrapture python myapp.py`); through
|
|
91
|
+
[manual setup](https://wrapture.readthedocs.io/en/latest/manual-setup.html),
|
|
92
|
+
a few lines in the application's own startup where wrapping the launch
|
|
93
|
+
from outside is awkward; and, in a test, through
|
|
94
|
+
`wrapture.instrumentation("psycopg")` scoping the instrumentation to
|
|
95
|
+
a block. The
|
|
96
|
+
[ad-hoc tracing guide](https://wrapture.readthedocs.io/en/latest/ad-hoc-tracing.html)
|
|
97
|
+
covers the config file itself.
|
|
98
|
+
|
|
99
|
+
To see what is installed, what it supports in the current
|
|
100
|
+
environment, and what settings it takes:
|
|
101
|
+
|
|
102
|
+
```console
|
|
103
|
+
$ python -m wrapture.tools instrumentation --verbose
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Provided instrumentation
|
|
107
|
+
|
|
108
|
+
| Target | Supported versions | Records | Settings |
|
|
109
|
+
| ------ | ------------------ | ------- | -------- |
|
|
110
|
+
| [`psycopg`](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/src/wrapture_instrumentation_postgresql/psycopg/README.md) | psycopg 3.1+ (3.x) | Every query as one `database` leaf, however it was issued (a cursor's `execute` or `executemany`, the connection's shortcut, a streamed query, a COPY, a server-side cursor's DECLARE), plus the connection being opened and each transaction boundary (`commit`, `rollback`, the connection's context manager, and a `transaction()` block's begin and end, savepoints included); sync and async classes alike, and connections from a pool. Each event carries the system, the operation, and the database, host and port it reached; a failing statement records the driver's exception. The SQL text is recorded only with the `statement` setting on, bound parameters never. | `statement` |
|
|
111
|
+
| [`psycopg2`](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/src/wrapture_instrumentation_postgresql/psycopg2/README.md) | psycopg2 2.9+ (2.x), psycopg2-binary alike | Every query as one `database` leaf, however it was issued (`execute`, `executemany`, `callproc`, the extras' batch helpers, a named cursor's DECLARE), each COPY (`copy_from`, `copy_to`, `copy_expert`), the connection being opened and each transaction boundary (`commit`, `rollback`, the connection's context manager); through recording subclasses injected by psycopg2's own factory mechanism, so your `cursor_factory` and `connection_factory` classes keep working and are recorded too. Each event carries the system, the operation, and the database, host and port it reached; a failing statement records the driver's exception. The SQL text (the template with its placeholders) is recorded only with the `statement` setting on, bound parameters never. | `statement` |
|
|
112
|
+
| [`asyncpg`](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/src/wrapture_instrumentation_postgresql/asyncpg/README.md) | asyncpg 0.29+ (0.x) | Every query as one `database` leaf recorded around its await, however it was issued (a connection's `execute`, `executemany`, `fetch`, `fetchrow`, `fetchval` or `fetchmany`, a prepared statement's own fetches, a server-side cursor's DECLARE and each FETCH), each COPY, the connection being opened, and each transaction boundary, which asyncpg issues through `execute`. Each event carries the system, the operation, and the database, host and port it reached; a failing statement records the driver's exception. The SQL text (with its `$n` placeholders) is recorded only with the `statement` setting on, query arguments never. A connection taken from a pool does not yet record its queries (a wrapture fix is pending). | `statement` |
|
|
113
|
+
|
|
114
|
+
The entry point name is the config's `name`""; the linked per-target
|
|
115
|
+
README is the full user documentation: what records, what the events
|
|
116
|
+
carry, the setting, and what is deliberately not traced.
|
|
117
|
+
|
|
118
|
+
## What is not traced
|
|
119
|
+
|
|
120
|
+
By design, and where it goes:
|
|
121
|
+
|
|
122
|
+
- Fetching rows: a query event closes when its execute returns, so
|
|
123
|
+
time spent iterating rows afterwards is the application's, and a
|
|
124
|
+
server-side cursor's FETCHes are not recorded (its DECLARE is).
|
|
125
|
+
|
|
126
|
+
- Pool checkouts (`psycopg_pool`): a connection taken from a pool
|
|
127
|
+
records its queries like any other, but taking and returning it are
|
|
128
|
+
not database operations and are not recorded.
|
|
129
|
+
|
|
130
|
+
- A connection taken from an asyncpg pool does not yet record its
|
|
131
|
+
queries: the pool proxy calls the connection's methods through the
|
|
132
|
+
class, a calling convention wrapture's signature check does not
|
|
133
|
+
yet handle. The fix is on wrapture's side.
|
|
134
|
+
|
|
135
|
+
- LISTEN/NOTIFY, large objects and two-phase commit are out of scope.
|
|
136
|
+
|
|
137
|
+
## Adding a target
|
|
138
|
+
|
|
139
|
+
Each client library is its own subpackage and entry point here. The
|
|
140
|
+
subpackage's `__init__.py` holds one `wrapture.Instrumentation`
|
|
141
|
+
subclass and imports only wrapture (and the package's own `common.py`,
|
|
142
|
+
which imports only wrapture too); everything that touches the driver
|
|
143
|
+
lives in sibling modules imported inside the hook. The class is
|
|
144
|
+
registered in `pyproject.toml` under
|
|
145
|
+
`[project.entry-points."wrapture.instrumentation"]`, and gets its own
|
|
146
|
+
test suite under `tests/<target>/` and a `README.md` linked from the
|
|
147
|
+
table above. The
|
|
148
|
+
[instrumentation packages](https://wrapture.readthedocs.io/en/latest/instrumentation-packages.html)
|
|
149
|
+
page of the wrapture documentation is the full contract; TESTING.md
|
|
150
|
+
here covers the tests and the server they run against.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
BSD 2-Clause. See
|
|
155
|
+
[LICENSE](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/LICENSE).
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# wrapture-instrumentation-postgresql
|
|
2
|
+
|
|
3
|
+
Instrumentation for the PostgreSQL client libraries, applied through
|
|
4
|
+
[wrapture](https://github.com/GrahamDumpleton/wrapture).
|
|
5
|
+
|
|
6
|
+
wrapture attaches bindings to arbitrary Python call sites without
|
|
7
|
+
modifying the code being observed, and its config layer can switch on
|
|
8
|
+
packaged instrumentation for a third-party package by name. This is
|
|
9
|
+
the PostgreSQL package in that collection: one `wrapture.Instrumentation`
|
|
10
|
+
class per client library, so tracing every query, connection and
|
|
11
|
+
transaction your application sends to PostgreSQL is one config entry
|
|
12
|
+
and no code.
|
|
13
|
+
|
|
14
|
+
> **Status: alpha, ahead of 1.0.0.** Developed against wrapture's
|
|
15
|
+
> alpha series, with pre-releases published to
|
|
16
|
+
> [PyPI](https://pypi.org/project/wrapture-instrumentation-postgresql/),
|
|
17
|
+
> and until 1.0.0 is final a plain `pip install
|
|
18
|
+
> wrapture-instrumentation-postgresql` picks up the latest pre-release
|
|
19
|
+
> automatically, so there is no need to pin a specific version.
|
|
20
|
+
|
|
21
|
+
## Why a separate package
|
|
22
|
+
|
|
23
|
+
The core
|
|
24
|
+
[wrapture-instrumentation](https://github.com/GrahamDumpleton/wrapture-instrumentation)
|
|
25
|
+
package deliberately covers only the standard library and third-party
|
|
26
|
+
packages that can be exercised in-process, with no separate backend
|
|
27
|
+
product or service needed to test against. A PostgreSQL driver is
|
|
28
|
+
exactly the kind of target the separate-package rule was drawn for:
|
|
29
|
+
its tests need a real server, so this package's suite runs one in a
|
|
30
|
+
docker container, and it carries the drivers as test dependencies
|
|
31
|
+
(some of them compiled wheels) and its own release cadence, so the
|
|
32
|
+
core package's test matrix stays light. One package covers every
|
|
33
|
+
client library for the one backend: psycopg, psycopg2 and asyncpg.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```console
|
|
38
|
+
$ pip install wrapture-instrumentation-postgresql
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Installing it brings wrapture and nothing else. No driver is a
|
|
42
|
+
dependency: each instrumentation is inert until its driver is present,
|
|
43
|
+
and wrapture checks the installed version against the range the
|
|
44
|
+
instrumentation supports at apply time.
|
|
45
|
+
|
|
46
|
+
## Using it
|
|
47
|
+
|
|
48
|
+
An `[[instrument]]` entry in `wrapture.toml` names the target:
|
|
49
|
+
|
|
50
|
+
```toml
|
|
51
|
+
[[instrument]]
|
|
52
|
+
name = "psycopg"
|
|
53
|
+
|
|
54
|
+
[[sink]]
|
|
55
|
+
type = "printer"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
and the runner applies it before the application starts, so the patch
|
|
59
|
+
is in place before the driver is imported:
|
|
60
|
+
|
|
61
|
+
```console
|
|
62
|
+
$ python -m wrapture -m myapp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The same config works through
|
|
66
|
+
[autowrapt](https://github.com/GrahamDumpleton/autowrapt) injection
|
|
67
|
+
(`AUTOWRAPT_BOOTSTRAP=wrapture python myapp.py`); through
|
|
68
|
+
[manual setup](https://wrapture.readthedocs.io/en/latest/manual-setup.html),
|
|
69
|
+
a few lines in the application's own startup where wrapping the launch
|
|
70
|
+
from outside is awkward; and, in a test, through
|
|
71
|
+
`wrapture.instrumentation("psycopg")` scoping the instrumentation to
|
|
72
|
+
a block. The
|
|
73
|
+
[ad-hoc tracing guide](https://wrapture.readthedocs.io/en/latest/ad-hoc-tracing.html)
|
|
74
|
+
covers the config file itself.
|
|
75
|
+
|
|
76
|
+
To see what is installed, what it supports in the current
|
|
77
|
+
environment, and what settings it takes:
|
|
78
|
+
|
|
79
|
+
```console
|
|
80
|
+
$ python -m wrapture.tools instrumentation --verbose
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Provided instrumentation
|
|
84
|
+
|
|
85
|
+
| Target | Supported versions | Records | Settings |
|
|
86
|
+
| ------ | ------------------ | ------- | -------- |
|
|
87
|
+
| [`psycopg`](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/src/wrapture_instrumentation_postgresql/psycopg/README.md) | psycopg 3.1+ (3.x) | Every query as one `database` leaf, however it was issued (a cursor's `execute` or `executemany`, the connection's shortcut, a streamed query, a COPY, a server-side cursor's DECLARE), plus the connection being opened and each transaction boundary (`commit`, `rollback`, the connection's context manager, and a `transaction()` block's begin and end, savepoints included); sync and async classes alike, and connections from a pool. Each event carries the system, the operation, and the database, host and port it reached; a failing statement records the driver's exception. The SQL text is recorded only with the `statement` setting on, bound parameters never. | `statement` |
|
|
88
|
+
| [`psycopg2`](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/src/wrapture_instrumentation_postgresql/psycopg2/README.md) | psycopg2 2.9+ (2.x), psycopg2-binary alike | Every query as one `database` leaf, however it was issued (`execute`, `executemany`, `callproc`, the extras' batch helpers, a named cursor's DECLARE), each COPY (`copy_from`, `copy_to`, `copy_expert`), the connection being opened and each transaction boundary (`commit`, `rollback`, the connection's context manager); through recording subclasses injected by psycopg2's own factory mechanism, so your `cursor_factory` and `connection_factory` classes keep working and are recorded too. Each event carries the system, the operation, and the database, host and port it reached; a failing statement records the driver's exception. The SQL text (the template with its placeholders) is recorded only with the `statement` setting on, bound parameters never. | `statement` |
|
|
89
|
+
| [`asyncpg`](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/src/wrapture_instrumentation_postgresql/asyncpg/README.md) | asyncpg 0.29+ (0.x) | Every query as one `database` leaf recorded around its await, however it was issued (a connection's `execute`, `executemany`, `fetch`, `fetchrow`, `fetchval` or `fetchmany`, a prepared statement's own fetches, a server-side cursor's DECLARE and each FETCH), each COPY, the connection being opened, and each transaction boundary, which asyncpg issues through `execute`. Each event carries the system, the operation, and the database, host and port it reached; a failing statement records the driver's exception. The SQL text (with its `$n` placeholders) is recorded only with the `statement` setting on, query arguments never. A connection taken from a pool does not yet record its queries (a wrapture fix is pending). | `statement` |
|
|
90
|
+
|
|
91
|
+
The entry point name is the config's `name`""; the linked per-target
|
|
92
|
+
README is the full user documentation: what records, what the events
|
|
93
|
+
carry, the setting, and what is deliberately not traced.
|
|
94
|
+
|
|
95
|
+
## What is not traced
|
|
96
|
+
|
|
97
|
+
By design, and where it goes:
|
|
98
|
+
|
|
99
|
+
- Fetching rows: a query event closes when its execute returns, so
|
|
100
|
+
time spent iterating rows afterwards is the application's, and a
|
|
101
|
+
server-side cursor's FETCHes are not recorded (its DECLARE is).
|
|
102
|
+
|
|
103
|
+
- Pool checkouts (`psycopg_pool`): a connection taken from a pool
|
|
104
|
+
records its queries like any other, but taking and returning it are
|
|
105
|
+
not database operations and are not recorded.
|
|
106
|
+
|
|
107
|
+
- A connection taken from an asyncpg pool does not yet record its
|
|
108
|
+
queries: the pool proxy calls the connection's methods through the
|
|
109
|
+
class, a calling convention wrapture's signature check does not
|
|
110
|
+
yet handle. The fix is on wrapture's side.
|
|
111
|
+
|
|
112
|
+
- LISTEN/NOTIFY, large objects and two-phase commit are out of scope.
|
|
113
|
+
|
|
114
|
+
## Adding a target
|
|
115
|
+
|
|
116
|
+
Each client library is its own subpackage and entry point here. The
|
|
117
|
+
subpackage's `__init__.py` holds one `wrapture.Instrumentation`
|
|
118
|
+
subclass and imports only wrapture (and the package's own `common.py`,
|
|
119
|
+
which imports only wrapture too); everything that touches the driver
|
|
120
|
+
lives in sibling modules imported inside the hook. The class is
|
|
121
|
+
registered in `pyproject.toml` under
|
|
122
|
+
`[project.entry-points."wrapture.instrumentation"]`, and gets its own
|
|
123
|
+
test suite under `tests/<target>/` and a `README.md` linked from the
|
|
124
|
+
table above. The
|
|
125
|
+
[instrumentation packages](https://wrapture.readthedocs.io/en/latest/instrumentation-packages.html)
|
|
126
|
+
page of the wrapture documentation is the full contract; TESTING.md
|
|
127
|
+
here covers the tests and the server they run against.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
BSD 2-Clause. See
|
|
132
|
+
[LICENSE](https://github.com/GrahamDumpleton/wrapture-instrumentation-postgresql/blob/develop/LICENSE).
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
## Where the tests are
|
|
4
|
+
|
|
5
|
+
Tests live in the [tests/](tests/) directory at the top of the
|
|
6
|
+
repository, separate from the package code in
|
|
7
|
+
src/wrapture_instrumentation_postgresql/. Test files are named
|
|
8
|
+
`test_*.py` and are discovered by pytest, configured via the
|
|
9
|
+
`[tool.pytest.ini_options]` section of [pyproject.toml](pyproject.toml).
|
|
10
|
+
|
|
11
|
+
The directory has two levels:
|
|
12
|
+
|
|
13
|
+
- Package-level tests directly under tests/: the version, the rule
|
|
14
|
+
that importing the package or loading any registered class never
|
|
15
|
+
imports a target, and the listing tool reporting every entry
|
|
16
|
+
cleanly.
|
|
17
|
+
|
|
18
|
+
- One subdirectory per target, `tests/<target>/` (`tests/psycopg/`,
|
|
19
|
+
`tests/psycopg2/`, `tests/asyncpg/`),
|
|
20
|
+
holding that instrumentation's suite: settings validation, applying
|
|
21
|
+
and removing the class directly, the whole path through
|
|
22
|
+
`wrapture.instrumentation()` with a timeline recording what the
|
|
23
|
+
bindings observe (sync and async), resolving the entry point by
|
|
24
|
+
name, a check that the installed driver satisfies the class's
|
|
25
|
+
`supports` range, and the composition tests with the core package's
|
|
26
|
+
sqlalchemy target.
|
|
27
|
+
|
|
28
|
+
Shared helpers and the server fixture live in
|
|
29
|
+
[tests/conftest.py](tests/conftest.py).
|
|
30
|
+
|
|
31
|
+
## The PostgreSQL server
|
|
32
|
+
|
|
33
|
+
The suites drive the real drivers against a real PostgreSQL server;
|
|
34
|
+
nothing is mocked. The session-scoped `postgresql` fixture supplies
|
|
35
|
+
it, in one of two ways:
|
|
36
|
+
|
|
37
|
+
- `WRAPTURE_POSTGRESQL_URL` in the environment names a server already
|
|
38
|
+
running (`postgresql://user:password@host:port/dbname`), and the
|
|
39
|
+
fixture uses it and starts nothing. This is how CI's service
|
|
40
|
+
container, the compose file's `postgres` service and a server of
|
|
41
|
+
your own are used.
|
|
42
|
+
|
|
43
|
+
- Otherwise the fixture runs a throwaway container from the official
|
|
44
|
+
`postgres:17` image on a random localhost port, waits until a real
|
|
45
|
+
connection from the host answers, and removes the container at the
|
|
46
|
+
end of the session. This needs the docker CLI and a running daemon
|
|
47
|
+
(Docker Desktop, or anything docker-compatible).
|
|
48
|
+
|
|
49
|
+
With neither, the session fails with a message saying so rather than
|
|
50
|
+
skipping, so a green run always means the suites ran. The fixture
|
|
51
|
+
yields a `Server` with the parts in both spellings the drivers take
|
|
52
|
+
(`server.url`, `server.kwargs`). Tests isolate themselves with
|
|
53
|
+
temporary tables (`CREATE TEMP TABLE`), private to their connection
|
|
54
|
+
and gone when it closes, so no cleanup fixtures are needed.
|
|
55
|
+
|
|
56
|
+
An interrupted run can leave its throwaway container behind;
|
|
57
|
+
`just postgresql-clean` removes any such container.
|
|
58
|
+
|
|
59
|
+
## Running the tests
|
|
60
|
+
|
|
61
|
+
All tooling in this project goes through
|
|
62
|
+
[uv](https://docs.astral.sh/uv/), which manages the project
|
|
63
|
+
environment and installs the package, its development dependencies
|
|
64
|
+
(including pytest) and the drivers the tests need.
|
|
65
|
+
|
|
66
|
+
The simplest way to run the test suite is via the Justfile target,
|
|
67
|
+
which runs natively on the default Python version and starts a
|
|
68
|
+
throwaway server unless `WRAPTURE_POSTGRESQL_URL` is set:
|
|
69
|
+
|
|
70
|
+
```console
|
|
71
|
+
just test
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Extra arguments are passed through to pytest, for example:
|
|
75
|
+
|
|
76
|
+
```console
|
|
77
|
+
just test -v
|
|
78
|
+
just test tests/psycopg/test_recording.py
|
|
79
|
+
just test -k transaction
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
One target's suite alone:
|
|
83
|
+
|
|
84
|
+
```console
|
|
85
|
+
just test-target psycopg
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Equivalently, run pytest directly with uv:
|
|
89
|
+
|
|
90
|
+
```console
|
|
91
|
+
uv run pytest
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
To run against a longer-lived server instead of a throwaway one per
|
|
95
|
+
session, start the compose file's server and export the URL it
|
|
96
|
+
prints:
|
|
97
|
+
|
|
98
|
+
```console
|
|
99
|
+
just postgresql-start
|
|
100
|
+
export WRAPTURE_POSTGRESQL_URL=postgresql://postgres:postgres@localhost:54329/postgres
|
|
101
|
+
just test
|
|
102
|
+
just postgresql-stop
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Watching what the tests record
|
|
106
|
+
|
|
107
|
+
The suites assert on tapes rather than printing anything, but the
|
|
108
|
+
recorded events can be watched live for visual verification. Setting
|
|
109
|
+
WRAPTURE_PRINTER in the environment installs a process-wide
|
|
110
|
+
wrapture.Printer sink for the session, streaming one line to stderr as
|
|
111
|
+
each operation begins and a closing line with its outcome and timing;
|
|
112
|
+
pytest captures stderr, so add -s to see it:
|
|
113
|
+
|
|
114
|
+
```console
|
|
115
|
+
WRAPTURE_PRINTER=1 just test tests/psycopg -s
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The same events can go to an OpenTelemetry backend instead, for
|
|
119
|
+
checking the spans, their kinds and their attributes in something
|
|
120
|
+
like otel-desktop-viewer. Setting WRAPTURE_OTEL installs wrapture's
|
|
121
|
+
OpenTelemetry sink for the session (exporting over OTLP to
|
|
122
|
+
http://localhost:4318, or wherever OTEL_EXPORTER_OTLP_ENDPOINT
|
|
123
|
+
points) and flushes it when the session ends; the `test-otel` recipe
|
|
124
|
+
sets the variable and overlays the wrapture[otel] dependencies the
|
|
125
|
+
sink needs:
|
|
126
|
+
|
|
127
|
+
```console
|
|
128
|
+
just test-otel tests/psycopg/test_recording.py -k transaction
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Each event the tests record arrives as its own single-span trace:
|
|
132
|
+
the tests drive the driver directly with nothing of their own around
|
|
133
|
+
the calls, and a `timeline()` roots what it records (an enclosing
|
|
134
|
+
block or observed function outside the timeline is not the parent of
|
|
135
|
+
what happens inside it), so a root span per test would have to be
|
|
136
|
+
opened inside each test's own timeline and would then appear on the
|
|
137
|
+
tape the assertions read.
|
|
138
|
+
|
|
139
|
+
For a purpose-built run rather than the tests' traffic, the demo
|
|
140
|
+
module under demo/ applies the instrumentation, drives the driver
|
|
141
|
+
against the server `WRAPTURE_POSTGRESQL_URL` names, and prints both
|
|
142
|
+
the live stream and the reconstructed tree with timings:
|
|
143
|
+
|
|
144
|
+
```console
|
|
145
|
+
just postgresql-start
|
|
146
|
+
export WRAPTURE_POSTGRESQL_URL=postgresql://postgres:postgres@localhost:54329/postgres
|
|
147
|
+
just demo-psycopg
|
|
148
|
+
just demo-psycopg2
|
|
149
|
+
just demo-asyncpg
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
With --otel the same events also export as OpenTelemetry spans over
|
|
153
|
+
OTLP (to http://localhost:4318, or wherever
|
|
154
|
+
OTEL_EXPORTER_OTLP_ENDPOINT points), for verifying the spans in a
|
|
155
|
+
local backend such as Jaeger; the Justfile target overlays the
|
|
156
|
+
wrapture[otel] dependencies for the run:
|
|
157
|
+
|
|
158
|
+
```console
|
|
159
|
+
just demo-psycopg --otel
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Testing across Python versions
|
|
163
|
+
|
|
164
|
+
The project supports Python 3.12 through 3.15. Free threaded builds
|
|
165
|
+
are not in the matrix: the instrumentation is pure Python and behaves
|
|
166
|
+
the same on them, and a free threaded row would only be testing
|
|
167
|
+
whether the drivers' own C extensions build without the GIL, which is
|
|
168
|
+
their concern. The supported list is defined at the top of the
|
|
169
|
+
[Justfile](Justfile); the default version used by plain `just test`
|
|
170
|
+
is pinned in [.python-version](.python-version).
|
|
171
|
+
|
|
172
|
+
The full matrix runs inside docker, so the machine never needs
|
|
173
|
+
PostgreSQL client libraries: psycopg's binary wheels bundle a libpq up
|
|
174
|
+
to Python 3.14, but on 3.15 psycopg runs as pure Python and needs a
|
|
175
|
+
libpq from the machine, which the image in the [Dockerfile](Dockerfile)
|
|
176
|
+
supplies. The [compose.yml](compose.yml) file defines the server and
|
|
177
|
+
a `tests` container built from that image; uv fetches the requested
|
|
178
|
+
interpreter inside the container and installs the test dependencies
|
|
179
|
+
into a per-version environment, both kept on named volumes so reruns
|
|
180
|
+
pay for neither.
|
|
181
|
+
|
|
182
|
+
```console
|
|
183
|
+
just test-all
|
|
184
|
+
just test-docker 3.15
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The first run per version downloads the interpreter and the wheels.
|
|
188
|
+
For the versions where every driver has a wheel, the suite can also
|
|
189
|
+
run natively in a per-version environment (.venv-VERSION) that leaves
|
|
190
|
+
the default .venv untouched:
|
|
191
|
+
|
|
192
|
+
```console
|
|
193
|
+
just test-python 3.13
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Testing across driver versions
|
|
197
|
+
|
|
198
|
+
The `test` dependency group installs each driver at whatever version
|
|
199
|
+
the lock resolves. The instrumentation's `supports` range is kept
|
|
200
|
+
honest by running its suite against other lines of the driver. Each
|
|
201
|
+
line has a place in `psycopg_versions`, `psycopg2_versions` or
|
|
202
|
+
`asyncpg_versions` in the Justfile, run one at a time by
|
|
203
|
+
`just test-psycopg 3.1.20`, `just test-psycopg2 2.9.9` or
|
|
204
|
+
`just test-asyncpg 0.29.0`, or all by the `-all` forms, and the CI
|
|
205
|
+
workflow runs the same matrix. These runs use an environment of their
|
|
206
|
+
own on Python 3.12 with the driver's binary distribution at the
|
|
207
|
+
requested version (`psycopg[binary]`, `psycopg2-binary`, `asyncpg`): a driver's
|
|
208
|
+
binary wheel must match its version, and the older lines ship binary
|
|
209
|
+
wheels only up to the Pythons of their day, so 3.12 is the Python
|
|
210
|
+
every line has a wheel for, and no libpq is needed from the machine. A test in the suite
|
|
211
|
+
asserts the installed driver satisfies `supports`, so a matrix entry
|
|
212
|
+
outside the range fails loudly rather than passing vacuously.
|
|
213
|
+
|
|
214
|
+
## Continuous integration
|
|
215
|
+
|
|
216
|
+
The workflow runs every test job on Linux with a PostgreSQL service
|
|
217
|
+
container (service containers are Linux-only, and the drivers do not
|
|
218
|
+
differ per OS at the instrumented layer), the URL in the environment,
|
|
219
|
+
across Python 3.12 to 3.15 and the driver version matrix.
|
|
220
|
+
|
|
221
|
+
## Testing against unreleased wrapture
|
|
222
|
+
|
|
223
|
+
The package depends on a released wrapture. To run the tests against a
|
|
224
|
+
checkout of wrapture in the sibling directory ../wrapture, without
|
|
225
|
+
editing pyproject.toml:
|
|
226
|
+
|
|
227
|
+
```console
|
|
228
|
+
just test-dev
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
which overlays that checkout as an editable install for the run.
|
|
232
|
+
|
|
233
|
+
## Writing tests
|
|
234
|
+
|
|
235
|
+
- Put new test files in tests/ (package level) or tests/<target>/
|
|
236
|
+
(for one instrumentation) and name them `test_*.py`.
|
|
237
|
+
|
|
238
|
+
- Import the package under test as
|
|
239
|
+
`wrapture_instrumentation_postgresql`, and the instrumentation
|
|
240
|
+
classes from their subpackages. The project is installed into the
|
|
241
|
+
uv-managed environment, so no path manipulation is needed.
|
|
242
|
+
|
|
243
|
+
- Guard the driver imports with `pytest.importorskip` at the top of
|
|
244
|
+
the module (`pytest.importorskip("psycopg")`), so a build without
|
|
245
|
+
that driver skips the suite rather than erroring.
|
|
246
|
+
|
|
247
|
+
- Take the `postgresql` fixture for the server and create temporary
|
|
248
|
+
tables for whatever the test needs; never leave ordinary tables
|
|
249
|
+
behind.
|
|
250
|
+
|
|
251
|
+
- Validate behaviour with wrapture's own unit testing layer:
|
|
252
|
+
`wrapture.timeline()` to record what the instrumentation's bindings
|
|
253
|
+
observe and the tape's tree and queries to assert on it, and
|
|
254
|
+
`wrapture.instrumentation()` to scope an application of a class to
|
|
255
|
+
a block, driving the real driver against the real server. Do not
|
|
256
|
+
mock a driver and do not use `unittest.mock`. If something cannot
|
|
257
|
+
be expressed that way, write it plainly with a comment naming the
|
|
258
|
+
gap, and say so when summarising the work.
|
|
259
|
+
|
|
260
|
+
- Async cases run their coroutine with `asyncio.run` inside the test;
|
|
261
|
+
no pytest-asyncio.
|
|
262
|
+
|
|
263
|
+
- Tests should not depend on anything in the scratch/ directory,
|
|
264
|
+
which is not part of the repository.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# The PostgreSQL server the tests need, and a container to run the
|
|
2
|
+
# tests in for the Python versions that need a libpq from the machine.
|
|
3
|
+
#
|
|
4
|
+
# just test-docker 3.15 one version, extra args to pytest
|
|
5
|
+
# just test-docker-all every supported version
|
|
6
|
+
# just postgresql-start the server alone, published on
|
|
7
|
+
# localhost:54329 for the demos and
|
|
8
|
+
# for running the tests natively
|
|
9
|
+
#
|
|
10
|
+
# The tests container gets the server's URL in the environment, so the
|
|
11
|
+
# suite's server fixture uses it and starts nothing itself, exactly as
|
|
12
|
+
# on CI with its service container. uv's cache and the managed
|
|
13
|
+
# interpreters live on one named volume and the per-version project
|
|
14
|
+
# environments on another, so a rerun pays for neither the download
|
|
15
|
+
# nor the install, and the container's environment never collides
|
|
16
|
+
# with the .venv the host uses in the same directory.
|
|
17
|
+
|
|
18
|
+
services:
|
|
19
|
+
postgres:
|
|
20
|
+
image: postgres:17
|
|
21
|
+
environment:
|
|
22
|
+
POSTGRES_PASSWORD: postgres
|
|
23
|
+
healthcheck:
|
|
24
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
25
|
+
interval: 2s
|
|
26
|
+
timeout: 5s
|
|
27
|
+
retries: 30
|
|
28
|
+
ports:
|
|
29
|
+
- "127.0.0.1:${WRAPTURE_POSTGRESQL_PORT:-54329}:5432"
|
|
30
|
+
|
|
31
|
+
tests:
|
|
32
|
+
build: .
|
|
33
|
+
depends_on:
|
|
34
|
+
postgres:
|
|
35
|
+
condition: service_healthy
|
|
36
|
+
environment:
|
|
37
|
+
WRAPTURE_POSTGRESQL_URL: postgresql://postgres:postgres@postgres:5432/postgres
|
|
38
|
+
UV_PYTHON: ${UV_PYTHON:-3.14}
|
|
39
|
+
UV_CACHE_DIR: /cache/uv
|
|
40
|
+
UV_PYTHON_INSTALL_DIR: /cache/python
|
|
41
|
+
UV_LINK_MODE: copy
|
|
42
|
+
volumes:
|
|
43
|
+
- .:/work
|
|
44
|
+
- uv-cache:/cache
|
|
45
|
+
- venvs:/venv
|
|
46
|
+
entrypoint:
|
|
47
|
+
- sh
|
|
48
|
+
- -c
|
|
49
|
+
- |
|
|
50
|
+
export UV_PROJECT_ENVIRONMENT="/venv/$$UV_PYTHON"
|
|
51
|
+
exec uv run --python "$$UV_PYTHON" --no-default-groups --group test pytest "$$@"
|
|
52
|
+
- tests
|
|
53
|
+
|
|
54
|
+
volumes:
|
|
55
|
+
uv-cache:
|
|
56
|
+
venvs:
|
|
File without changes
|