periplus-python-sdk 0.8.0__tar.gz → 0.11.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.
Files changed (41) hide show
  1. periplus_python_sdk-0.11.0/PKG-INFO +150 -0
  2. periplus_python_sdk-0.11.0/README.md +130 -0
  3. periplus_python_sdk-0.11.0/licensing/LICENSE +661 -0
  4. periplus_python_sdk-0.11.0/licensing/NOTICE +4 -0
  5. periplus_python_sdk-0.11.0/licensing/README.md +42 -0
  6. periplus_python_sdk-0.11.0/licensing/THIRD_PARTY_NOTICES.md +192 -0
  7. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/pyproject.toml +4 -4
  8. periplus_python_sdk-0.11.0/src/periplus_python_sdk.egg-info/PKG-INFO +150 -0
  9. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_python_sdk.egg-info/SOURCES.txt +10 -2
  10. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_sdk/__init__.py +5 -3
  11. periplus_python_sdk-0.11.0/src/periplus_sdk/async_resources.py +298 -0
  12. periplus_python_sdk-0.11.0/src/periplus_sdk/async_stream.py +104 -0
  13. periplus_python_sdk-0.11.0/src/periplus_sdk/client.py +281 -0
  14. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_sdk/dbapi.py +8 -8
  15. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_sdk/errors.py +3 -1
  16. periplus_python_sdk-0.11.0/src/periplus_sdk/resources.py +297 -0
  17. periplus_python_sdk-0.11.0/src/periplus_sdk/resources_types.py +264 -0
  18. periplus_python_sdk-0.11.0/src/periplus_sdk/selection.py +29 -0
  19. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_sdk/sql_api.py +3 -2
  20. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_sdk/sqlalchemy.py +42 -35
  21. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_sdk/stream.py +8 -2
  22. periplus_python_sdk-0.11.0/src/periplus_sdk/types.py +147 -0
  23. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/tests/test_client.py +69 -17
  24. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/tests/test_dbapi.py +14 -3
  25. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/tests/test_notebook.py +26 -11
  26. periplus_python_sdk-0.11.0/tests/test_platform.py +99 -0
  27. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/tests/test_sql_api.py +13 -2
  28. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/tests/test_stream.py +18 -1
  29. periplus_python_sdk-0.8.0/LICENSE +0 -202
  30. periplus_python_sdk-0.8.0/NOTICE +0 -2
  31. periplus_python_sdk-0.8.0/PKG-INFO +0 -70
  32. periplus_python_sdk-0.8.0/README.md +0 -52
  33. periplus_python_sdk-0.8.0/src/periplus_python_sdk.egg-info/PKG-INFO +0 -70
  34. periplus_python_sdk-0.8.0/src/periplus_sdk/client.py +0 -167
  35. periplus_python_sdk-0.8.0/src/periplus_sdk/types.py +0 -56
  36. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/setup.cfg +0 -0
  37. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_python_sdk.egg-info/dependency_links.txt +0 -0
  38. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_python_sdk.egg-info/entry_points.txt +0 -0
  39. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_python_sdk.egg-info/requires.txt +0 -0
  40. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_python_sdk.egg-info/top_level.txt +0 -0
  41. {periplus_python_sdk-0.8.0 → periplus_python_sdk-0.11.0}/src/periplus_sdk/py.typed +0 -0
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: periplus-python-sdk
3
+ Version: 0.11.0
4
+ Summary: Typed Periplus platform client with SQL and notebook integration
5
+ License-Expression: AGPL-3.0-only
6
+ Project-URL: Repository, https://github.com/elei-io/periplus
7
+ Project-URL: Issues, https://github.com/elei-io/periplus/issues
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ License-File: licensing/LICENSE
11
+ License-File: licensing/NOTICE
12
+ License-File: licensing/README.md
13
+ License-File: licensing/THIRD_PARTY_NOTICES.md
14
+ Requires-Dist: httpx>=0.28
15
+ Requires-Dist: pydantic<3,>=2.12
16
+ Requires-Dist: sqlalchemy<3,>=2.0
17
+ Provides-Extra: notebook
18
+ Requires-Dist: marimo[sql]>=0.24.1; extra == "notebook"
19
+ Dynamic: license-file
20
+
21
+ # Periplus Python SDK
22
+
23
+ Typed organization operations and SQL access through the Periplus HTTP API.
24
+ The 0.11.0 platform interface requires the matching API release. It includes
25
+ the source-snapshot metadata introduced in 0.10.0 alongside customer operations.
26
+ Install from PyPI:
27
+
28
+ ```sh
29
+ python -m pip install --upgrade periplus-python-sdk
30
+ ```
31
+
32
+ ```python
33
+ from periplus_sdk import Client
34
+
35
+ with Client("https://api.periplus.dev", api_key="ppl_…") as client:
36
+ result = client.execute(
37
+ "SELECT capture_id, url FROM public_v1.captures LIMIT ?", [10]
38
+ )
39
+ print(result.columns, result.rows)
40
+ ```
41
+
42
+ The default origin is `https://api.periplus.dev`. Use `PERIPLUS_API_URL` to override it and `PERIPLUS_API_KEY` to
43
+ supply your personal organization API key. A key with `organization:sql:exec`
44
+ is required. Database usernames/passwords and anonymous access are not supported.
45
+ Use HTTPS outside loopback development. Connect directly to the API origin,
46
+ not the public marketing site.
47
+
48
+ Version 0.9.0 requires personal API keys instead of database credentials. Create
49
+ a key in the app's Settings → My API keys. It inherits your current access in
50
+ that organization; SQL requires your membership to have `organization:sql:exec`.
51
+ For local development, install `./clients/periplus-python-sdk` from the repository
52
+ root and connect to `http://localhost:8000`.
53
+
54
+ `AsyncClient` accepts the same options. `prepare` explains a SELECT; `execute`
55
+ returns typed columns/rows for read-only queries. `schema()` returns
56
+ visible tables, column types/descriptions and helper documentation. All SQL uses
57
+ `POST /api/v1/sql`; schema discovery uses `GET /api/v1/schema`. ClickHouse enforces
58
+ permissions. The SDK never retries automatically, including failed queries.
59
+
60
+ Public HTML joins use `parse_id` and `node_index`; `document_id` identifies exact
61
+ raw bytes. Public shorthand uses the `public_v1` schema.
62
+
63
+ In 0.10.0, `result.source_snapshot` is a typed `SourceSnapshot` containing
64
+ `layout_id` (UUID) and `publication_epoch` (integer), or `None` when no build-bound
65
+ public corpus was read. Buffered, asynchronous, streamed and DB-API results share
66
+ this contract. Compare both fields, not just the epoch. The identity describes
67
+ the public corpus inputs, not any native staff/external inputs. It does not retain
68
+ the data or request historical reads. This replaces the old nullable integer field;
69
+ use this SDK version with the corresponding API release.
70
+
71
+ For notebook/SQLAlchemy integration:
72
+
73
+ ```python
74
+ from periplus_sdk import sql_api
75
+ from sqlalchemy import text
76
+
77
+ engine = sql_api.create_engine(base_url="http://localhost:8000", api_key="ppl_…")
78
+ with engine.connect() as connection:
79
+ print(connection.execute(text("SELECT url FROM public_v1.captures LIMIT 5")).all())
80
+ engine.dispose()
81
+ ```
82
+
83
+ Marimo discovers accessible tables, views, typed columns and comments through the
84
+ schema endpoint. Reflection does not execute SQL. One SQLAlchemy Inspector caches
85
+ its metadata; call `inspector.clear_cache()` to refresh it. Missing metadata raises
86
+ an error rather than presenting an apparently complete empty schema.
87
+
88
+ The DB-API connection advertises the ClickHouse dialect and converts native
89
+ nullable integer, decimal, date and datetime types. Nested types retain JSON wire
90
+ values. Writes are not exposed through the query API. There are no client
91
+ transactions; each statement is independent.
92
+ Streaming cursors expose incomplete/truncated results explicitly; configure
93
+ `allow_partial` only when partial results suit the application.
94
+
95
+ See [the public schema](../../docs/SCHEMA.md) and [query boundary](../../docs/QUERY.md).
96
+
97
+ ## Organization resources
98
+
99
+ The same key selects your organization and inherits your live membership access.
100
+ `Client` and `AsyncClient` expose the same namespaces:
101
+
102
+ | Namespace | Operations |
103
+ | --- | --- |
104
+ | `identity`, `availability` | `get` |
105
+ | `discovery` | `create`, `get`, `list`, `iter`, `arrivals`, `cancel` |
106
+ | `retention`, `monitoring` | `create`, `preview`, `activate`, `get`, `list`, `iter`, `members`, `update`, `pause`, `resume`, `delete` |
107
+ | `saved_queries` | `create`, `get`, `list`, `iter`, `rename`, `delete` |
108
+ | `members` | `list`, `update_role`, `remove` |
109
+ | `invitations` | `list`, `create`, `cancel` |
110
+ | `api_keys` | `create`, `list`, `iter`, `revoke` |
111
+ | `usage` | `get` |
112
+ | `query_history` | `list`, `iter`, `get`, `summary` |
113
+ | `audit` | `list`, `iter` |
114
+
115
+ ```python
116
+ from periplus_sdk import Client
117
+
118
+ with Client() as client:
119
+ preview = client.retention.preview(
120
+ name="Research sources", days=90,
121
+ sql="SELECT capture_id FROM captures WHERE domain(url) = ?",
122
+ parameters=["example.com"],
123
+ )
124
+ print(preview.id, preview.capture_count, preview.sample)
125
+ # Review the selection before calling:
126
+ # active = client.retention.activate(preview)
127
+ ```
128
+
129
+ SQL previews persist inactive, frozen selections; activation never reruns SQL.
130
+ Explicit IDs/URLs passed to `create` activate immediately. Monitoring URLs must
131
+ already be known in the corpus. Use `members` and its `next_cursor` for complete
132
+ membership: policy detail contains a sample. Updates/deletion accept a fetched
133
+ `Policy` or an ID with `expected_version`; conflicts are never retried.
134
+
135
+ Paginated lists return `Page[T]` (`items`, `next_cursor`). Pass cursors unchanged
136
+ or use `iter()`; membership/invitation lists are bounded snapshots instead.
137
+ Offset-based lists can shift during concurrent changes. Query history and arrivals
138
+ use keyset cursors. UTC usage ranges have an exclusive end date, at most 93 days.
139
+ Query history is best-effort and expires after 30 days; it is not a billing ledger.
140
+
141
+ `ApiError` includes HTTP status, code, optional request ID, validation fields and
142
+ Retry-After seconds. `TransportError` means the outcome of a write can be unknown.
143
+ Keep creation IDs to reconcile; never blindly retry. Key creation is one-time:
144
+ `created.secret.get_secret_value()` reveals the secret and must only be used for
145
+ secure storage. Its ordinary representation is masked. Lost secrets cannot be
146
+ recovered.
147
+
148
+ For async streaming, use `async with await client.stream(sql) as stream` followed
149
+ by `async for batch in stream`. Streams validate completion and close on early
150
+ exit, errors, and cancellation. No threads or background polling are introduced.
@@ -0,0 +1,130 @@
1
+ # Periplus Python SDK
2
+
3
+ Typed organization operations and SQL access through the Periplus HTTP API.
4
+ The 0.11.0 platform interface requires the matching API release. It includes
5
+ the source-snapshot metadata introduced in 0.10.0 alongside customer operations.
6
+ Install from PyPI:
7
+
8
+ ```sh
9
+ python -m pip install --upgrade periplus-python-sdk
10
+ ```
11
+
12
+ ```python
13
+ from periplus_sdk import Client
14
+
15
+ with Client("https://api.periplus.dev", api_key="ppl_…") as client:
16
+ result = client.execute(
17
+ "SELECT capture_id, url FROM public_v1.captures LIMIT ?", [10]
18
+ )
19
+ print(result.columns, result.rows)
20
+ ```
21
+
22
+ The default origin is `https://api.periplus.dev`. Use `PERIPLUS_API_URL` to override it and `PERIPLUS_API_KEY` to
23
+ supply your personal organization API key. A key with `organization:sql:exec`
24
+ is required. Database usernames/passwords and anonymous access are not supported.
25
+ Use HTTPS outside loopback development. Connect directly to the API origin,
26
+ not the public marketing site.
27
+
28
+ Version 0.9.0 requires personal API keys instead of database credentials. Create
29
+ a key in the app's Settings → My API keys. It inherits your current access in
30
+ that organization; SQL requires your membership to have `organization:sql:exec`.
31
+ For local development, install `./clients/periplus-python-sdk` from the repository
32
+ root and connect to `http://localhost:8000`.
33
+
34
+ `AsyncClient` accepts the same options. `prepare` explains a SELECT; `execute`
35
+ returns typed columns/rows for read-only queries. `schema()` returns
36
+ visible tables, column types/descriptions and helper documentation. All SQL uses
37
+ `POST /api/v1/sql`; schema discovery uses `GET /api/v1/schema`. ClickHouse enforces
38
+ permissions. The SDK never retries automatically, including failed queries.
39
+
40
+ Public HTML joins use `parse_id` and `node_index`; `document_id` identifies exact
41
+ raw bytes. Public shorthand uses the `public_v1` schema.
42
+
43
+ In 0.10.0, `result.source_snapshot` is a typed `SourceSnapshot` containing
44
+ `layout_id` (UUID) and `publication_epoch` (integer), or `None` when no build-bound
45
+ public corpus was read. Buffered, asynchronous, streamed and DB-API results share
46
+ this contract. Compare both fields, not just the epoch. The identity describes
47
+ the public corpus inputs, not any native staff/external inputs. It does not retain
48
+ the data or request historical reads. This replaces the old nullable integer field;
49
+ use this SDK version with the corresponding API release.
50
+
51
+ For notebook/SQLAlchemy integration:
52
+
53
+ ```python
54
+ from periplus_sdk import sql_api
55
+ from sqlalchemy import text
56
+
57
+ engine = sql_api.create_engine(base_url="http://localhost:8000", api_key="ppl_…")
58
+ with engine.connect() as connection:
59
+ print(connection.execute(text("SELECT url FROM public_v1.captures LIMIT 5")).all())
60
+ engine.dispose()
61
+ ```
62
+
63
+ Marimo discovers accessible tables, views, typed columns and comments through the
64
+ schema endpoint. Reflection does not execute SQL. One SQLAlchemy Inspector caches
65
+ its metadata; call `inspector.clear_cache()` to refresh it. Missing metadata raises
66
+ an error rather than presenting an apparently complete empty schema.
67
+
68
+ The DB-API connection advertises the ClickHouse dialect and converts native
69
+ nullable integer, decimal, date and datetime types. Nested types retain JSON wire
70
+ values. Writes are not exposed through the query API. There are no client
71
+ transactions; each statement is independent.
72
+ Streaming cursors expose incomplete/truncated results explicitly; configure
73
+ `allow_partial` only when partial results suit the application.
74
+
75
+ See [the public schema](../../docs/SCHEMA.md) and [query boundary](../../docs/QUERY.md).
76
+
77
+ ## Organization resources
78
+
79
+ The same key selects your organization and inherits your live membership access.
80
+ `Client` and `AsyncClient` expose the same namespaces:
81
+
82
+ | Namespace | Operations |
83
+ | --- | --- |
84
+ | `identity`, `availability` | `get` |
85
+ | `discovery` | `create`, `get`, `list`, `iter`, `arrivals`, `cancel` |
86
+ | `retention`, `monitoring` | `create`, `preview`, `activate`, `get`, `list`, `iter`, `members`, `update`, `pause`, `resume`, `delete` |
87
+ | `saved_queries` | `create`, `get`, `list`, `iter`, `rename`, `delete` |
88
+ | `members` | `list`, `update_role`, `remove` |
89
+ | `invitations` | `list`, `create`, `cancel` |
90
+ | `api_keys` | `create`, `list`, `iter`, `revoke` |
91
+ | `usage` | `get` |
92
+ | `query_history` | `list`, `iter`, `get`, `summary` |
93
+ | `audit` | `list`, `iter` |
94
+
95
+ ```python
96
+ from periplus_sdk import Client
97
+
98
+ with Client() as client:
99
+ preview = client.retention.preview(
100
+ name="Research sources", days=90,
101
+ sql="SELECT capture_id FROM captures WHERE domain(url) = ?",
102
+ parameters=["example.com"],
103
+ )
104
+ print(preview.id, preview.capture_count, preview.sample)
105
+ # Review the selection before calling:
106
+ # active = client.retention.activate(preview)
107
+ ```
108
+
109
+ SQL previews persist inactive, frozen selections; activation never reruns SQL.
110
+ Explicit IDs/URLs passed to `create` activate immediately. Monitoring URLs must
111
+ already be known in the corpus. Use `members` and its `next_cursor` for complete
112
+ membership: policy detail contains a sample. Updates/deletion accept a fetched
113
+ `Policy` or an ID with `expected_version`; conflicts are never retried.
114
+
115
+ Paginated lists return `Page[T]` (`items`, `next_cursor`). Pass cursors unchanged
116
+ or use `iter()`; membership/invitation lists are bounded snapshots instead.
117
+ Offset-based lists can shift during concurrent changes. Query history and arrivals
118
+ use keyset cursors. UTC usage ranges have an exclusive end date, at most 93 days.
119
+ Query history is best-effort and expires after 30 days; it is not a billing ledger.
120
+
121
+ `ApiError` includes HTTP status, code, optional request ID, validation fields and
122
+ Retry-After seconds. `TransportError` means the outcome of a write can be unknown.
123
+ Keep creation IDs to reconcile; never blindly retry. Key creation is one-time:
124
+ `created.secret.get_secret_value()` reveals the secret and must only be used for
125
+ secure storage. Its ordinary representation is masked. Lost secrets cannot be
126
+ recovered.
127
+
128
+ For async streaming, use `async with await client.stream(sql) as stream` followed
129
+ by `async for batch in stream`. Streams validate completion and close on early
130
+ exit, errors, and cancellation. No threads or background polling are introduced.