opensanctions-pravda 0.1.0__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.
- opensanctions_pravda-0.1.0/LICENSE +21 -0
- opensanctions_pravda-0.1.0/PKG-INFO +211 -0
- opensanctions_pravda-0.1.0/README.md +182 -0
- opensanctions_pravda-0.1.0/pyproject.toml +67 -0
- opensanctions_pravda-0.1.0/src/pravda/__init__.py +32 -0
- opensanctions_pravda-0.1.0/src/pravda/capture.py +424 -0
- opensanctions_pravda-0.1.0/src/pravda/db.py +43 -0
- opensanctions_pravda-0.1.0/src/pravda/http_archive.py +105 -0
- opensanctions_pravda-0.1.0/src/pravda/migrate.py +56 -0
- opensanctions_pravda-0.1.0/src/pravda/migrations/env.py +71 -0
- opensanctions_pravda-0.1.0/src/pravda/migrations/script.py.mako +28 -0
- opensanctions_pravda-0.1.0/src/pravda/migrations/versions/06eb381b8ec5_initial_schema.py +49 -0
- opensanctions_pravda-0.1.0/src/pravda/pravda.py +617 -0
- opensanctions_pravda-0.1.0/src/pravda/py.typed +0 -0
- opensanctions_pravda-0.1.0/src/pravda/snapshots.py +65 -0
- opensanctions_pravda-0.1.0/src/pravda/storage.py +120 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenSanctions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opensanctions-pravda
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Durable web evidence capture with a remote browser, Postgres, and content-addressed storage
|
|
5
|
+
Keywords: evidence,web-archiving,web-capture,playwright,har,forensics
|
|
6
|
+
Author: OpenSanctions
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Dist: alembic>=1.18.5
|
|
18
|
+
Requires-Dist: asyncpg>=0.31.0
|
|
19
|
+
Requires-Dist: beautifulsoup4>=4.15.0
|
|
20
|
+
Requires-Dist: fsspec[gcs]>=2026.4.0
|
|
21
|
+
Requires-Dist: playwright>=1.60.0
|
|
22
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.50
|
|
23
|
+
Requires-Python: >=3.13
|
|
24
|
+
Project-URL: Homepage, https://github.com/opensanctions/pravda
|
|
25
|
+
Project-URL: Documentation, https://github.com/opensanctions/pravda#readme
|
|
26
|
+
Project-URL: Repository, https://github.com/opensanctions/pravda
|
|
27
|
+
Project-URL: Issues, https://github.com/opensanctions/pravda/issues
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# Pravda
|
|
31
|
+
|
|
32
|
+
Pravda is a Python library for durable web evidence capture. It drives a
|
|
33
|
+
remote Playwright browser to preserve rendered HTML, plaintext, full-page
|
|
34
|
+
screenshots, metadata, and HAR recordings with response bodies. Snapshots are
|
|
35
|
+
recorded in Postgres and on any fsspec-compatible backend for later inspection
|
|
36
|
+
or comparison.
|
|
37
|
+
|
|
38
|
+
Pravda is a **library, not a service**: it connects directly from the caller's
|
|
39
|
+
process to the browser, database, and storage backend. Applications own that
|
|
40
|
+
infrastructure (see [Infrastructure](#infrastructure)).
|
|
41
|
+
|
|
42
|
+
- **Python** 3.13+
|
|
43
|
+
- **Browser**: a remote Playwright Chromium WebSocket endpoint (headed Chrome
|
|
44
|
+
under xvfb). The browser is a client connection; Pravda does not launch one.
|
|
45
|
+
- **Database**: PostgreSQL, upgraded to Pravda's schema with the
|
|
46
|
+
[migration helper](#database-migrations).
|
|
47
|
+
- **Storage**: any fsspec URL (local path, `s3://`, `gs://`, …) for
|
|
48
|
+
content-addressed artifacts.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install opensanctions-pravda
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
Construct a [`PravdaConfig`](#configuration), build a long-lived `Pravda`, and
|
|
59
|
+
use it as an async context manager so its database engine is disposed on
|
|
60
|
+
teardown. Reuse a single instance across captures — it owns the pooled engine,
|
|
61
|
+
session factory, and storage backend, while each capture opens its own browser
|
|
62
|
+
connection.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from pravda import Pravda, PravdaConfig
|
|
66
|
+
|
|
67
|
+
config = PravdaConfig(
|
|
68
|
+
database_url="postgresql+asyncpg://pravda:pravda@localhost:5432/pravda",
|
|
69
|
+
browser_ws_url="ws://localhost:3000",
|
|
70
|
+
storage_base_path="./data",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def capture_example():
|
|
74
|
+
async with Pravda(config) as pravda:
|
|
75
|
+
snapshot = await pravda.snapshot("https://example.com")
|
|
76
|
+
print(snapshot.id, snapshot.http_status, snapshot.rendered_html)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
`PravdaConfig` takes three settings, supplied explicitly per instance:
|
|
82
|
+
|
|
83
|
+
- `database_url` — async SQLAlchemy Postgres URL
|
|
84
|
+
(`postgresql+asyncpg://user:pass@host/db`).
|
|
85
|
+
- `browser_ws_url` — remote Playwright WebSocket URL.
|
|
86
|
+
- `storage_base_path` — fsspec storage URL, such as `./data`, `s3://bucket`,
|
|
87
|
+
or `gs://bucket`.
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
### Capture a page
|
|
92
|
+
|
|
93
|
+
By default, Pravda navigates to the URL, waits for the normal `load` state,
|
|
94
|
+
captures the evidence, and persists the result. The complete pipeline is
|
|
95
|
+
bounded by a wall-clock timeout:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
async def capture_example():
|
|
99
|
+
async with Pravda(config) as pravda:
|
|
100
|
+
snapshot = await pravda.snapshot("https://example.com")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For custom navigation or interaction, pass an async `drive(page, url)`
|
|
104
|
+
callback. The callback owns the initial navigation; Pravda owns capture,
|
|
105
|
+
persistence, and cleanup, and records the state it leaves behind.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
async def drive(page, url):
|
|
109
|
+
await page.goto(url, wait_until="commit")
|
|
110
|
+
await page.wait_for_selector(".results")
|
|
111
|
+
|
|
112
|
+
async def capture_results():
|
|
113
|
+
async with Pravda(config) as pravda:
|
|
114
|
+
snapshot = await pravda.snapshot("https://example.com", drive=drive)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`page` is a real `playwright.async_api.Page`, so selectors, clicks, form
|
|
118
|
+
fills, and other Playwright operations are available. Playwright errors and
|
|
119
|
+
timeouts from `drive` are persisted as failed snapshots. Other callback
|
|
120
|
+
exceptions propagate and persist nothing.
|
|
121
|
+
|
|
122
|
+
Chrome is configured to download PDFs instead of opening its viewer. In custom
|
|
123
|
+
callbacks, `page.goto()` may therefore raise `Download is starting`; catch
|
|
124
|
+
that Playwright error if the download is expected. Pravda recovers the
|
|
125
|
+
downloaded body into the HAR.
|
|
126
|
+
|
|
127
|
+
### Query history
|
|
128
|
+
|
|
129
|
+
The configured instance returns all snapshots for an exact URL, newest first:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
async def print_history():
|
|
133
|
+
async with Pravda(config) as pravda:
|
|
134
|
+
history = await pravda.snapshots("https://example.com")
|
|
135
|
+
for snapshot in history:
|
|
136
|
+
print(snapshot.captured_at, snapshot.http_status)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Database migrations
|
|
140
|
+
|
|
141
|
+
Alembic owns the Pravda schema; the migration scripts ship inside the
|
|
142
|
+
distribution. Bring a database up to the current schema head from application
|
|
143
|
+
startup — the database URL is passed explicitly and **no** `DATABASE_URL`
|
|
144
|
+
environment variable is required:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import pravda
|
|
148
|
+
|
|
149
|
+
async def setup():
|
|
150
|
+
await pravda.migrate("postgresql+asyncpg://user:pass@host/db")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`migrate()` runs the packaged revisions through Alembic (not
|
|
154
|
+
`metadata.create_all`), is safe to call repeatedly (a database already at head
|
|
155
|
+
is a no-op), works from inside a running event loop, and disposes the engine
|
|
156
|
+
it creates. Database and migration failures propagate. There is no downgrade
|
|
157
|
+
or automatic-startup behavior: call `migrate()` where and when you want the
|
|
158
|
+
schema applied.
|
|
159
|
+
|
|
160
|
+
## Storage
|
|
161
|
+
|
|
162
|
+
Artifacts are content-addressed files under `Snapshot.prefix`. The `plaintext`,
|
|
163
|
+
`rendered_html`, and `screenshot` fields contain filenames; HAR
|
|
164
|
+
`response.content._file` fields refer to stored response bodies. Consumers
|
|
165
|
+
read these files directly from the shared fsspec backend.
|
|
166
|
+
|
|
167
|
+
## Infrastructure
|
|
168
|
+
|
|
169
|
+
Applications own the external infrastructure Pravda talks to; Pravda does not
|
|
170
|
+
launch or manage it:
|
|
171
|
+
|
|
172
|
+
- **Browser** — a remote Playwright Chromium server exposed over WebSocket.
|
|
173
|
+
See the repository for a reference Docker image that runs headed Chrome under
|
|
174
|
+
xvfb and accepts launch options through the `x-playwright-launch-options`
|
|
175
|
+
WebSocket header.
|
|
176
|
+
- **Postgres** — a database the application provisions and
|
|
177
|
+
[migrates](#database-migrations).
|
|
178
|
+
- **Storage** — an fsspec backend the application points at via
|
|
179
|
+
`storage_base_path`.
|
|
180
|
+
|
|
181
|
+
## Development
|
|
182
|
+
|
|
183
|
+
Requires [uv](https://docs.astral.sh/uv/) and Docker.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Start the remote browser and Postgres
|
|
187
|
+
docker compose up -d
|
|
188
|
+
|
|
189
|
+
# Install dependencies
|
|
190
|
+
uv sync
|
|
191
|
+
|
|
192
|
+
# Validate
|
|
193
|
+
uv run pytest
|
|
194
|
+
uv run ruff check .
|
|
195
|
+
uv run ruff format --check .
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The migration scripts live inside the package at `pravda/migrations`. After
|
|
199
|
+
changing models in `pravda/db.py`, the developer `alembic` command reads
|
|
200
|
+
`DATABASE_URL` and points at the packaged scripts via `alembic.ini`:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
DATABASE_URL=postgresql+asyncpg://pravda:pravda@localhost:5432/pravda \
|
|
204
|
+
uv run alembic upgrade head
|
|
205
|
+
DATABASE_URL=postgresql+asyncpg://pravda:pravda@localhost:5432/pravda \
|
|
206
|
+
uv run alembic revision --autogenerate -m "describe the change"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Pravda
|
|
2
|
+
|
|
3
|
+
Pravda is a Python library for durable web evidence capture. It drives a
|
|
4
|
+
remote Playwright browser to preserve rendered HTML, plaintext, full-page
|
|
5
|
+
screenshots, metadata, and HAR recordings with response bodies. Snapshots are
|
|
6
|
+
recorded in Postgres and on any fsspec-compatible backend for later inspection
|
|
7
|
+
or comparison.
|
|
8
|
+
|
|
9
|
+
Pravda is a **library, not a service**: it connects directly from the caller's
|
|
10
|
+
process to the browser, database, and storage backend. Applications own that
|
|
11
|
+
infrastructure (see [Infrastructure](#infrastructure)).
|
|
12
|
+
|
|
13
|
+
- **Python** 3.13+
|
|
14
|
+
- **Browser**: a remote Playwright Chromium WebSocket endpoint (headed Chrome
|
|
15
|
+
under xvfb). The browser is a client connection; Pravda does not launch one.
|
|
16
|
+
- **Database**: PostgreSQL, upgraded to Pravda's schema with the
|
|
17
|
+
[migration helper](#database-migrations).
|
|
18
|
+
- **Storage**: any fsspec URL (local path, `s3://`, `gs://`, …) for
|
|
19
|
+
content-addressed artifacts.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install opensanctions-pravda
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
Construct a [`PravdaConfig`](#configuration), build a long-lived `Pravda`, and
|
|
30
|
+
use it as an async context manager so its database engine is disposed on
|
|
31
|
+
teardown. Reuse a single instance across captures — it owns the pooled engine,
|
|
32
|
+
session factory, and storage backend, while each capture opens its own browser
|
|
33
|
+
connection.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from pravda import Pravda, PravdaConfig
|
|
37
|
+
|
|
38
|
+
config = PravdaConfig(
|
|
39
|
+
database_url="postgresql+asyncpg://pravda:pravda@localhost:5432/pravda",
|
|
40
|
+
browser_ws_url="ws://localhost:3000",
|
|
41
|
+
storage_base_path="./data",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
async def capture_example():
|
|
45
|
+
async with Pravda(config) as pravda:
|
|
46
|
+
snapshot = await pravda.snapshot("https://example.com")
|
|
47
|
+
print(snapshot.id, snapshot.http_status, snapshot.rendered_html)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
`PravdaConfig` takes three settings, supplied explicitly per instance:
|
|
53
|
+
|
|
54
|
+
- `database_url` — async SQLAlchemy Postgres URL
|
|
55
|
+
(`postgresql+asyncpg://user:pass@host/db`).
|
|
56
|
+
- `browser_ws_url` — remote Playwright WebSocket URL.
|
|
57
|
+
- `storage_base_path` — fsspec storage URL, such as `./data`, `s3://bucket`,
|
|
58
|
+
or `gs://bucket`.
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### Capture a page
|
|
63
|
+
|
|
64
|
+
By default, Pravda navigates to the URL, waits for the normal `load` state,
|
|
65
|
+
captures the evidence, and persists the result. The complete pipeline is
|
|
66
|
+
bounded by a wall-clock timeout:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
async def capture_example():
|
|
70
|
+
async with Pravda(config) as pravda:
|
|
71
|
+
snapshot = await pravda.snapshot("https://example.com")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For custom navigation or interaction, pass an async `drive(page, url)`
|
|
75
|
+
callback. The callback owns the initial navigation; Pravda owns capture,
|
|
76
|
+
persistence, and cleanup, and records the state it leaves behind.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
async def drive(page, url):
|
|
80
|
+
await page.goto(url, wait_until="commit")
|
|
81
|
+
await page.wait_for_selector(".results")
|
|
82
|
+
|
|
83
|
+
async def capture_results():
|
|
84
|
+
async with Pravda(config) as pravda:
|
|
85
|
+
snapshot = await pravda.snapshot("https://example.com", drive=drive)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`page` is a real `playwright.async_api.Page`, so selectors, clicks, form
|
|
89
|
+
fills, and other Playwright operations are available. Playwright errors and
|
|
90
|
+
timeouts from `drive` are persisted as failed snapshots. Other callback
|
|
91
|
+
exceptions propagate and persist nothing.
|
|
92
|
+
|
|
93
|
+
Chrome is configured to download PDFs instead of opening its viewer. In custom
|
|
94
|
+
callbacks, `page.goto()` may therefore raise `Download is starting`; catch
|
|
95
|
+
that Playwright error if the download is expected. Pravda recovers the
|
|
96
|
+
downloaded body into the HAR.
|
|
97
|
+
|
|
98
|
+
### Query history
|
|
99
|
+
|
|
100
|
+
The configured instance returns all snapshots for an exact URL, newest first:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
async def print_history():
|
|
104
|
+
async with Pravda(config) as pravda:
|
|
105
|
+
history = await pravda.snapshots("https://example.com")
|
|
106
|
+
for snapshot in history:
|
|
107
|
+
print(snapshot.captured_at, snapshot.http_status)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Database migrations
|
|
111
|
+
|
|
112
|
+
Alembic owns the Pravda schema; the migration scripts ship inside the
|
|
113
|
+
distribution. Bring a database up to the current schema head from application
|
|
114
|
+
startup — the database URL is passed explicitly and **no** `DATABASE_URL`
|
|
115
|
+
environment variable is required:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import pravda
|
|
119
|
+
|
|
120
|
+
async def setup():
|
|
121
|
+
await pravda.migrate("postgresql+asyncpg://user:pass@host/db")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`migrate()` runs the packaged revisions through Alembic (not
|
|
125
|
+
`metadata.create_all`), is safe to call repeatedly (a database already at head
|
|
126
|
+
is a no-op), works from inside a running event loop, and disposes the engine
|
|
127
|
+
it creates. Database and migration failures propagate. There is no downgrade
|
|
128
|
+
or automatic-startup behavior: call `migrate()` where and when you want the
|
|
129
|
+
schema applied.
|
|
130
|
+
|
|
131
|
+
## Storage
|
|
132
|
+
|
|
133
|
+
Artifacts are content-addressed files under `Snapshot.prefix`. The `plaintext`,
|
|
134
|
+
`rendered_html`, and `screenshot` fields contain filenames; HAR
|
|
135
|
+
`response.content._file` fields refer to stored response bodies. Consumers
|
|
136
|
+
read these files directly from the shared fsspec backend.
|
|
137
|
+
|
|
138
|
+
## Infrastructure
|
|
139
|
+
|
|
140
|
+
Applications own the external infrastructure Pravda talks to; Pravda does not
|
|
141
|
+
launch or manage it:
|
|
142
|
+
|
|
143
|
+
- **Browser** — a remote Playwright Chromium server exposed over WebSocket.
|
|
144
|
+
See the repository for a reference Docker image that runs headed Chrome under
|
|
145
|
+
xvfb and accepts launch options through the `x-playwright-launch-options`
|
|
146
|
+
WebSocket header.
|
|
147
|
+
- **Postgres** — a database the application provisions and
|
|
148
|
+
[migrates](#database-migrations).
|
|
149
|
+
- **Storage** — an fsspec backend the application points at via
|
|
150
|
+
`storage_base_path`.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
Requires [uv](https://docs.astral.sh/uv/) and Docker.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Start the remote browser and Postgres
|
|
158
|
+
docker compose up -d
|
|
159
|
+
|
|
160
|
+
# Install dependencies
|
|
161
|
+
uv sync
|
|
162
|
+
|
|
163
|
+
# Validate
|
|
164
|
+
uv run pytest
|
|
165
|
+
uv run ruff check .
|
|
166
|
+
uv run ruff format --check .
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The migration scripts live inside the package at `pravda/migrations`. After
|
|
170
|
+
changing models in `pravda/db.py`, the developer `alembic` command reads
|
|
171
|
+
`DATABASE_URL` and points at the packaged scripts via `alembic.ini`:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
DATABASE_URL=postgresql+asyncpg://pravda:pravda@localhost:5432/pravda \
|
|
175
|
+
uv run alembic upgrade head
|
|
176
|
+
DATABASE_URL=postgresql+asyncpg://pravda:pravda@localhost:5432/pravda \
|
|
177
|
+
uv run alembic revision --autogenerate -m "describe the change"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.11.26,<0.12.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "opensanctions-pravda"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Durable web evidence capture with a remote browser, Postgres, and content-addressed storage"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "OpenSanctions" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"evidence",
|
|
16
|
+
"web-archiving",
|
|
17
|
+
"web-capture",
|
|
18
|
+
"playwright",
|
|
19
|
+
"har",
|
|
20
|
+
"forensics",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Topic :: Internet :: WWW/HTTP :: Browsers",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"alembic>=1.18.5",
|
|
34
|
+
"asyncpg>=0.31.0",
|
|
35
|
+
"beautifulsoup4>=4.15.0",
|
|
36
|
+
"fsspec[gcs]>=2026.4.0",
|
|
37
|
+
"playwright>=1.60.0",
|
|
38
|
+
"sqlalchemy[asyncio]>=2.0.50",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/opensanctions/pravda"
|
|
43
|
+
Documentation = "https://github.com/opensanctions/pravda#readme"
|
|
44
|
+
Repository = "https://github.com/opensanctions/pravda"
|
|
45
|
+
Issues = "https://github.com/opensanctions/pravda/issues"
|
|
46
|
+
|
|
47
|
+
[tool.uv.build-backend]
|
|
48
|
+
module-name = "pravda"
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
target-version = "py313"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["E", "F", "I", "W"]
|
|
55
|
+
|
|
56
|
+
[dependency-groups]
|
|
57
|
+
dev = [
|
|
58
|
+
"pre-commit>=4.6.0",
|
|
59
|
+
"pytest>=9.0.3",
|
|
60
|
+
"pytest-asyncio>=1.4.0",
|
|
61
|
+
"ruff>=0.15.16",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.pytest.ini_options]
|
|
65
|
+
asyncio_mode = "auto"
|
|
66
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
67
|
+
asyncio_default_test_loop_scope = "session"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Durable evidence capture for web pages.
|
|
2
|
+
|
|
3
|
+
Construct a :class:`Pravda` with explicit :class:`PravdaConfig` settings::
|
|
4
|
+
|
|
5
|
+
from pravda import Pravda, PravdaConfig
|
|
6
|
+
|
|
7
|
+
config = PravdaConfig(
|
|
8
|
+
database_url=...,
|
|
9
|
+
browser_ws_url=...,
|
|
10
|
+
storage_base_path=...,
|
|
11
|
+
)
|
|
12
|
+
async with Pravda(config) as pravda:
|
|
13
|
+
snapshot = await pravda.snapshot(url)
|
|
14
|
+
history = await pravda.snapshots(url)
|
|
15
|
+
|
|
16
|
+
Applications own the Postgres database; bring it to the packaged schema head
|
|
17
|
+
from startup with the migration helper::
|
|
18
|
+
|
|
19
|
+
import pravda
|
|
20
|
+
await pravda.migrate(database_url)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from pravda.migrate import migrate
|
|
24
|
+
from pravda.pravda import Pravda, PravdaConfig
|
|
25
|
+
from pravda.snapshots import Snapshot
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Pravda",
|
|
29
|
+
"PravdaConfig",
|
|
30
|
+
"Snapshot",
|
|
31
|
+
"migrate",
|
|
32
|
+
]
|