periplus-python-sdk 0.12.1__tar.gz → 0.13.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 (33) hide show
  1. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/PKG-INFO +2 -2
  2. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/README.md +1 -1
  3. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/pyproject.toml +1 -1
  4. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_python_sdk.egg-info/PKG-INFO +2 -2
  5. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/async_resources.py +7 -0
  6. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/resources.py +7 -0
  7. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/tests/test_platform.py +30 -0
  8. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/licensing/LICENSE +0 -0
  9. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/licensing/NOTICE +0 -0
  10. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/licensing/README.md +0 -0
  11. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/licensing/THIRD_PARTY_NOTICES.md +0 -0
  12. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/setup.cfg +0 -0
  13. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_python_sdk.egg-info/SOURCES.txt +0 -0
  14. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_python_sdk.egg-info/dependency_links.txt +0 -0
  15. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_python_sdk.egg-info/entry_points.txt +0 -0
  16. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_python_sdk.egg-info/requires.txt +0 -0
  17. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_python_sdk.egg-info/top_level.txt +0 -0
  18. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/__init__.py +0 -0
  19. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/async_stream.py +0 -0
  20. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/client.py +0 -0
  21. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/dbapi.py +0 -0
  22. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/errors.py +0 -0
  23. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/py.typed +0 -0
  24. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/resources_types.py +0 -0
  25. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/sql_api.py +0 -0
  26. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/sqlalchemy.py +0 -0
  27. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/stream.py +0 -0
  28. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/src/periplus_sdk/types.py +0 -0
  29. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/tests/test_client.py +0 -0
  30. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/tests/test_dbapi.py +0 -0
  31. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/tests/test_notebook.py +0 -0
  32. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/tests/test_sql_api.py +0 -0
  33. {periplus_python_sdk-0.12.1 → periplus_python_sdk-0.13.0}/tests/test_stream.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: periplus-python-sdk
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: Typed Periplus platform client with SQL and notebook integration
5
5
  License-Expression: AGPL-3.0-only
6
6
  Project-URL: Repository, https://github.com/elei-io/periplus
@@ -109,7 +109,7 @@ A Developer key selects your organization and uses your live membership access.
109
109
  | `captures`, `crawls` | `create`, `get`, `list`, `iter`, `pages`, `cancel` |
110
110
  | `pins` | `create`, `get`, `list`, `iter`, `captures`, `update`, `delete` |
111
111
  | `schedules` | `create`, `get`, `list`, `iter`, `pages`, `captures`, `update`, `pause`, `resume`, `delete` |
112
- | `saved_queries` | `create`, `get`, `list`, `iter`, `rename`, `delete` |
112
+ | `saved_queries` | `create`, `get`, `list`, `iter`, `update`, `rename`, `delete` |
113
113
  | `members` | `list`, `update_role`, `remove` |
114
114
  | `invitations` | `list`, `create`, `cancel` |
115
115
  | `api_keys` | `create`, `list`, `iter`, `revoke` |
@@ -89,7 +89,7 @@ A Developer key selects your organization and uses your live membership access.
89
89
  | `captures`, `crawls` | `create`, `get`, `list`, `iter`, `pages`, `cancel` |
90
90
  | `pins` | `create`, `get`, `list`, `iter`, `captures`, `update`, `delete` |
91
91
  | `schedules` | `create`, `get`, `list`, `iter`, `pages`, `captures`, `update`, `pause`, `resume`, `delete` |
92
- | `saved_queries` | `create`, `get`, `list`, `iter`, `rename`, `delete` |
92
+ | `saved_queries` | `create`, `get`, `list`, `iter`, `update`, `rename`, `delete` |
93
93
  | `members` | `list`, `update_role`, `remove` |
94
94
  | `invitations` | `list`, `create`, `cancel` |
95
95
  | `api_keys` | `create`, `list`, `iter`, `revoke` |
@@ -2,7 +2,7 @@
2
2
  license = "AGPL-3.0-only"
3
3
  license-files = ["licensing/LICENSE", "licensing/NOTICE", "licensing/*.md"]
4
4
  name = "periplus-python-sdk"
5
- version = "0.12.1"
5
+ version = "0.13.0"
6
6
  description = "Typed Periplus platform client with SQL and notebook integration"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: periplus-python-sdk
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: Typed Periplus platform client with SQL and notebook integration
5
5
  License-Expression: AGPL-3.0-only
6
6
  Project-URL: Repository, https://github.com/elei-io/periplus
@@ -109,7 +109,7 @@ A Developer key selects your organization and uses your live membership access.
109
109
  | `captures`, `crawls` | `create`, `get`, `list`, `iter`, `pages`, `cancel` |
110
110
  | `pins` | `create`, `get`, `list`, `iter`, `captures`, `update`, `delete` |
111
111
  | `schedules` | `create`, `get`, `list`, `iter`, `pages`, `captures`, `update`, `pause`, `resume`, `delete` |
112
- | `saved_queries` | `create`, `get`, `list`, `iter`, `rename`, `delete` |
112
+ | `saved_queries` | `create`, `get`, `list`, `iter`, `update`, `rename`, `delete` |
113
113
  | `members` | `list`, `update_role`, `remove` |
114
114
  | `invitations` | `list`, `create`, `cancel` |
115
115
  | `api_keys` | `create`, `list`, `iter`, `revoke` |
@@ -291,6 +291,13 @@ class SavedQueriesResource(PagedResource[SavedQuery]):
291
291
  return await self._client._resource_request("POST", "saved-queries", SavedQuery,
292
292
  json={"id": str(id or uuid4()), "name": name, "sql": sql})
293
293
 
294
+ async def update(self, id: str | UUID, *, name: str | None = None, sql: str | None = None) -> SavedQuery:
295
+ """Revise the shared definition without executing SQL or changing pinned results."""
296
+ payload = {key: value for key, value in {"name": name, "sql": sql}.items() if value is not None}
297
+ if not payload:
298
+ raise ValueError("Supply name, sql, or both")
299
+ return await self._client._resource_request("PATCH", f"saved-queries/{path_id(id)}", SavedQuery, json=payload)
300
+
294
301
  async def rename(self, id: str | UUID, *, name: str) -> SavedQuery:
295
302
  return await self._client._resource_request("PATCH", f"saved-queries/{path_id(id)}", SavedQuery, json={"name": name})
296
303
 
@@ -292,6 +292,13 @@ class SavedQueriesResource(PagedResource[SavedQuery]):
292
292
  return self._client._resource_request("POST", "saved-queries", SavedQuery,
293
293
  json={"id": str(id or uuid4()), "name": name, "sql": sql})
294
294
 
295
+ def update(self, id: str | UUID, *, name: str | None = None, sql: str | None = None) -> SavedQuery:
296
+ """Revise the shared definition without executing SQL or changing pinned results."""
297
+ payload = {key: value for key, value in {"name": name, "sql": sql}.items() if value is not None}
298
+ if not payload:
299
+ raise ValueError("Supply name, sql, or both")
300
+ return self._client._resource_request("PATCH", f"saved-queries/{path_id(id)}", SavedQuery, json=payload)
301
+
295
302
  def rename(self, id: str | UUID, *, name: str) -> SavedQuery:
296
303
  return self._client._resource_request("PATCH", f"saved-queries/{path_id(id)}", SavedQuery, json={"name": name})
297
304
 
@@ -21,6 +21,20 @@ SCHEDULE = dict(id=str(IDENTITY), name='Weekly', every_days=7, status='active',
21
21
 
22
22
 
23
23
  class PlatformClientTests(unittest.TestCase):
24
+ def test_saved_query_update_and_delete(self):
25
+ body = dict(id=str(IDENTITY), name='Updated', sql='SELECT 2', created_by='author',
26
+ updated_by='editor', created_at=NOW, updated_at=NOW)
27
+ client, calls = self.recorded(body)
28
+ self.assertEqual(client.saved_queries.update(IDENTITY, sql='SELECT 2').sql, 'SELECT 2')
29
+ self.assertEqual((calls[-1].method, calls[-1].url.path), ('PATCH', f'/saved-queries/{IDENTITY}'))
30
+ self.assertEqual(json.loads(calls[-1].content), {'sql': 'SELECT 2'})
31
+ client.saved_queries.update(IDENTITY, name='Updated', sql='SELECT 2')
32
+ self.assertEqual(json.loads(calls[-1].content), {'name': 'Updated', 'sql': 'SELECT 2'})
33
+ with self.assertRaises(ValueError):
34
+ client.saved_queries.update(IDENTITY)
35
+ client.saved_queries.delete(IDENTITY)
36
+ self.assertEqual(calls[-1].method, 'DELETE')
37
+
24
38
  def test_capture_creation_does_not_retry_gateway_errors(self):
25
39
  calls = []
26
40
  def handler(request):
@@ -166,6 +180,22 @@ class AsyncPlatformTests(unittest.IsolatedAsyncioTestCase):
166
180
  self.addAsyncCleanup(client.aclose)
167
181
  return client
168
182
 
183
+ async def test_async_saved_query_update_and_delete(self):
184
+ calls = []
185
+ body = dict(id=str(IDENTITY), name='Updated', sql='SELECT 2', created_by='author',
186
+ updated_by='editor', created_at=NOW, updated_at=NOW)
187
+ def handler(request):
188
+ calls.append(request)
189
+ return httpx.Response(204) if request.method == 'DELETE' else httpx.Response(200, json=body)
190
+ client = await self.client(handler)
191
+ revised = await client.saved_queries.update(IDENTITY, sql='SELECT 2')
192
+ self.assertEqual(revised.sql, 'SELECT 2')
193
+ self.assertEqual(json.loads(calls[-1].content), {'sql': 'SELECT 2'})
194
+ with self.assertRaises(ValueError):
195
+ await client.saved_queries.update(IDENTITY)
196
+ await client.saved_queries.delete(IDENTITY)
197
+ self.assertEqual(calls[-1].method, 'DELETE')
198
+
169
199
  async def test_async_resource_and_completion_checked_stream(self):
170
200
  client = await self.client(lambda request: httpx.Response(200, json={'user_id':'user_a', 'organization_id':'org_a', 'permissions':[]})
171
201
  if request.url.path == '/identity' else stream_response(dict(RESULT, truncated=False)))