provably-sdk 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.
Files changed (31) hide show
  1. provably_sdk-0.1.0/.github/workflows/publish.yml +34 -0
  2. provably_sdk-0.1.0/.gitignore +7 -0
  3. provably_sdk-0.1.0/LICENSE.md +101 -0
  4. provably_sdk-0.1.0/PKG-INFO +92 -0
  5. provably_sdk-0.1.0/README.md +62 -0
  6. provably_sdk-0.1.0/pyproject.toml +67 -0
  7. provably_sdk-0.1.0/src/provably/__init__.py +55 -0
  8. provably_sdk-0.1.0/src/provably/_answer_model.py +78 -0
  9. provably_sdk-0.1.0/src/provably/_api.py +407 -0
  10. provably_sdk-0.1.0/src/provably/_auth_api.py +191 -0
  11. provably_sdk-0.1.0/src/provably/_config.py +136 -0
  12. provably_sdk-0.1.0/src/provably/_errors.py +162 -0
  13. provably_sdk-0.1.0/src/provably/_http.py +247 -0
  14. provably_sdk-0.1.0/src/provably/auth_service.py +149 -0
  15. provably_sdk-0.1.0/src/provably/connection.py +43 -0
  16. provably_sdk-0.1.0/src/provably/oauth_login.py +133 -0
  17. provably_sdk-0.1.0/src/provably/py.typed +0 -0
  18. provably_sdk-0.1.0/src/provably/service.py +674 -0
  19. provably_sdk-0.1.0/tests/__init__.py +0 -0
  20. provably_sdk-0.1.0/tests/test_answer_model.py +113 -0
  21. provably_sdk-0.1.0/tests/test_api.py +67 -0
  22. provably_sdk-0.1.0/tests/test_auth_api.py +110 -0
  23. provably_sdk-0.1.0/tests/test_auth_service.py +170 -0
  24. provably_sdk-0.1.0/tests/test_connection.py +20 -0
  25. provably_sdk-0.1.0/tests/test_errors.py +211 -0
  26. provably_sdk-0.1.0/tests/test_http.py +267 -0
  27. provably_sdk-0.1.0/tests/test_oauth_login.py +93 -0
  28. provably_sdk-0.1.0/tests/test_sandbox_api.py +105 -0
  29. provably_sdk-0.1.0/tests/test_sandbox_service.py +187 -0
  30. provably_sdk-0.1.0/tests/test_service.py +164 -0
  31. provably_sdk-0.1.0/uv.lock +612 -0
@@ -0,0 +1,34 @@
1
+ name: publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ runs-on: ubuntu-latest
11
+ environment:
12
+ name: pypi
13
+ url: https://pypi.org/p/provably-sdk
14
+ permissions:
15
+ id-token: write
16
+ contents: read
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v3
27
+
28
+ - name: Build package
29
+ run: uv build
30
+
31
+ - name: Publish to PyPI
32
+ uses: pypa/gh-action-pypi-publish@release/v1
33
+ with:
34
+ packages-dir: dist
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .ruff_cache/
@@ -0,0 +1,101 @@
1
+ # License
2
+
3
+ Business Source License 1.1
4
+
5
+ Copyright © 2026 Provably Technologies LTD
6
+
7
+ Use Limitation: You may not offer the Software as a commercial hosted
8
+ service without purchasing a commercial license from Provably Technologies Ltd.
9
+
10
+ Change Date: 2029-05-07
11
+ Change License: GPL-3.0-or-later
12
+
13
+ Business Source License 1.1
14
+
15
+ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
16
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
17
+
18
+
19
+ 1. Definitions
20
+ The "Licensed Work" is the software and documentation made available
21
+ under this License.
22
+ The "Licensor" is the entity offering the Licensed Work under this
23
+ License.
24
+ "You" refers to the licensee of the Licensed Work.
25
+ "Use Limitation" means the restrictions (if any) placed on your use of
26
+ the Licensed Work before the Change Date, as listed in the license
27
+ parameters at the top of this License.
28
+ "Additional Use Grant" means additional rights (if any) the Licensor
29
+ grants to you before the Change Date, as listed in the license parameters
30
+ at the top of this License.
31
+ The "Change License" is the license selected by the Licensor to replace
32
+ this License, as listed in the license parameters at the top of this
33
+ License.
34
+ The "Change Date" is the date when the license automatically changes
35
+ from this License to the Change License, as listed in the license
36
+ parameters at the top of this License.
37
+ "Distribution" and "Use" have the same meaning as under copyright law.
38
+
39
+
40
+ 2. License Grant
41
+ From the Effective Date until the Change Date, the Licensor hereby grants
42
+ you the following rights:
43
+ a) The right to copy, modify, create derivative works, redistribute, and
44
+ make non-production Use of the Licensed Work, solely in compliance
45
+ with the Use Limitation and Additional Use Grant.
46
+ b) The right to sublicense the foregoing rights to third parties acting
47
+ on your behalf (for example, cloud service providers or contractors),
48
+ subject to the same Use Limitation.
49
+
50
+ All other rights are expressly reserved by the Licensor.
51
+
52
+
53
+ 3. Change Date
54
+ On the Change Date, the rights granted in Section 2 terminate, and you
55
+ may thereafter exercise the rights granted to you under the Change
56
+ License. The Licensor will publish the Change License in the same
57
+ repository or source as the Licensed Work or in any successor location.
58
+
59
+
60
+ 4. Compliance and Termination
61
+ Any Use of the Licensed Work not expressly permitted under this License
62
+ (including violating the Use Limitation) is **prohibited**.
63
+
64
+ If you violate this License, your rights will terminate automatically,
65
+ and you must cease all Use and Distribution of the Licensed Work as soon
66
+ as possible. Your licenses from anyone else remain in force.
67
+
68
+
69
+ 5. Disclaimer of Warranty
70
+ THE LICENSED WORK IS PROVIDED "AS IS". TO THE MAXIMUM EXTENT PERMITTED
71
+ BY APPLICABLE LAW, THE LICENSOR DISCLAIMS ALL WARRANTIES, EXPRESS OR
72
+ IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF
73
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND
74
+ NON-INFRINGEMENT.
75
+
76
+
77
+ 6. Limitation of Liability
78
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE
79
+ LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
80
+ CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION WITH THE LICENSED
81
+ WORK OR THIS LICENSE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
82
+
83
+
84
+ 7. Trademarks
85
+ This License does not grant permission to use the Licensor's trademarks
86
+ or trade names, except to the extent required for reasonable and
87
+ customary use in describing the origin of the Licensed Work.
88
+
89
+
90
+ 8. Severability
91
+ If any provision of this License is held unenforceable or invalid under
92
+ applicable law, such provision shall be interpreted to accomplish the
93
+ objectives of such provision to the greatest extent possible and the
94
+ remaining provisions shall continue in full force and effect.
95
+
96
+
97
+ 9. Governing Law
98
+ This License shall be governed by and construed in accordance with the
99
+ laws of the jurisdiction specified by the Licensor, excluding its
100
+ conflicts-of-law principles. If no jurisdiction is specified, the laws
101
+ of Finland apply (excluding conflict-of-law rules).
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.5
2
+ Name: provably-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Provably API: OAuth sign-in, sandboxes, collections, queries and proofs.
5
+ Project-URL: Homepage, https://provably.ai
6
+ Project-URL: Repository, https://github.com/ProvablyAI/provably-sdk-python
7
+ Author: Provably Technologies Ltd
8
+ License: Business Source License 1.1
9
+ License-File: LICENSE.md
10
+ Keywords: oauth,proofs,provably,sdk,verifiable
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: httpx>=0.26
21
+ Requires-Dist: msgspec>=0.18.0
22
+ Requires-Dist: pydantic>=2.6
23
+ Requires-Dist: structlog>=24.1
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.10; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.3; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # provably-sdk
32
+
33
+ Python SDK for the Provably API: OAuth browser sign-in, sandboxes, middlewares,
34
+ databases, collections, preprocessing, queries and proofs.
35
+
36
+ ```bash
37
+ pip install provably-sdk
38
+ ```
39
+
40
+ ## Configure
41
+
42
+ The SDK reads nothing from the environment or disk. Tell it where the API is,
43
+ which organisation to act for and where to keep the session:
44
+
45
+ ```python
46
+ import uuid
47
+
48
+ import provably
49
+ from provably import MemoryTokenStore, ProvablyConfig
50
+
51
+ provably.configure(
52
+ config=ProvablyConfig(org_id=uuid.UUID("...")), # api_url / app_url default to production
53
+ tokens=MemoryTokenStore(),
54
+ )
55
+ ```
56
+
57
+ - `config` may be a callable, read whenever a client is built, for apps whose
58
+ settings change at runtime. It must not raise before sign-in; leave `org_id`
59
+ unset and org-scoped calls raise `ProvablyConfigError`.
60
+ - `tokens` is any object with `load()`, `save(tokens)` and
61
+ `clear_refresh_token()` (see `provably.TokenStore`). Rotated tokens are written
62
+ back to it, together with the OAuth client they were issued to.
63
+ - `egress` is an optional context manager every HTTP call runs inside, for apps
64
+ that intercept their own outbound traffic.
65
+
66
+ ## Sign in
67
+
68
+ Each app signs in as its own registered OAuth client, on its own loopback port:
69
+
70
+ ```python
71
+ tokens = await provably.browser_login(client_id="my-app", port=8911)
72
+ store.save(tokens)
73
+ ```
74
+
75
+ ## Use
76
+
77
+ ```python
78
+ from provably import service
79
+
80
+ sandbox = await service.get_sandbox()
81
+ query_id = await service.run_query(middleware_id, collection_id, "SELECT 1")
82
+ await service.wait_for_proof_computation(query_id)
83
+ ```
84
+
85
+ ## Development
86
+
87
+ ```bash
88
+ uv sync --extra dev
89
+ uv run pytest
90
+ uv run ruff check . && uv run ruff format --check .
91
+ uv run mypy
92
+ ```
@@ -0,0 +1,62 @@
1
+ # provably-sdk
2
+
3
+ Python SDK for the Provably API: OAuth browser sign-in, sandboxes, middlewares,
4
+ databases, collections, preprocessing, queries and proofs.
5
+
6
+ ```bash
7
+ pip install provably-sdk
8
+ ```
9
+
10
+ ## Configure
11
+
12
+ The SDK reads nothing from the environment or disk. Tell it where the API is,
13
+ which organisation to act for and where to keep the session:
14
+
15
+ ```python
16
+ import uuid
17
+
18
+ import provably
19
+ from provably import MemoryTokenStore, ProvablyConfig
20
+
21
+ provably.configure(
22
+ config=ProvablyConfig(org_id=uuid.UUID("...")), # api_url / app_url default to production
23
+ tokens=MemoryTokenStore(),
24
+ )
25
+ ```
26
+
27
+ - `config` may be a callable, read whenever a client is built, for apps whose
28
+ settings change at runtime. It must not raise before sign-in; leave `org_id`
29
+ unset and org-scoped calls raise `ProvablyConfigError`.
30
+ - `tokens` is any object with `load()`, `save(tokens)` and
31
+ `clear_refresh_token()` (see `provably.TokenStore`). Rotated tokens are written
32
+ back to it, together with the OAuth client they were issued to.
33
+ - `egress` is an optional context manager every HTTP call runs inside, for apps
34
+ that intercept their own outbound traffic.
35
+
36
+ ## Sign in
37
+
38
+ Each app signs in as its own registered OAuth client, on its own loopback port:
39
+
40
+ ```python
41
+ tokens = await provably.browser_login(client_id="my-app", port=8911)
42
+ store.save(tokens)
43
+ ```
44
+
45
+ ## Use
46
+
47
+ ```python
48
+ from provably import service
49
+
50
+ sandbox = await service.get_sandbox()
51
+ query_id = await service.run_query(middleware_id, collection_id, "SELECT 1")
52
+ await service.wait_for_proof_computation(query_id)
53
+ ```
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ uv sync --extra dev
59
+ uv run pytest
60
+ uv run ruff check . && uv run ruff format --check .
61
+ uv run mypy
62
+ ```
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "provably-sdk"
7
+ version = "0.1.0"
8
+ description = "Python SDK for the Provably API: OAuth sign-in, sandboxes, collections, queries and proofs."
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ requires-python = ">=3.12"
11
+ license = { text = "Business Source License 1.1" }
12
+ authors = [{ name = "Provably Technologies Ltd" }]
13
+ keywords = ["provably", "sdk", "verifiable", "proofs", "oauth"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Operating System :: OS Independent",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = [
25
+ "httpx>=0.26",
26
+ "msgspec>=0.18.0",
27
+ "pydantic>=2.6",
28
+ "structlog>=24.1",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=8.0",
34
+ "pytest-asyncio>=0.23",
35
+ "ruff>=0.3",
36
+ "mypy>=1.10",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://provably.ai"
41
+ Repository = "https://github.com/ProvablyAI/provably-sdk-python"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/provably"]
45
+
46
+ [tool.ruff]
47
+ line-length = 120
48
+ target-version = "py312"
49
+
50
+ [tool.ruff.lint]
51
+ select = ["E", "F", "I", "UP", "W"]
52
+
53
+ [tool.ruff.lint.isort]
54
+ known-first-party = ["provably"]
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ pythonpath = ["src", "."]
59
+ asyncio_mode = "auto"
60
+
61
+ [tool.mypy]
62
+ python_version = "3.12"
63
+ mypy_path = "src"
64
+ files = ["src", "tests"]
65
+ strict = true
66
+ warn_unreachable = true
67
+ plugins = ["pydantic.mypy"]
@@ -0,0 +1,55 @@
1
+ """Python SDK for the Provably API."""
2
+
3
+ from provably._answer_model import AggregateAnswer, QueryAnswer, ResultsetAnswer, TabularData
4
+ from provably._auth_api import Organization, OrganizationType
5
+ from provably._config import (
6
+ MemoryTokenStore,
7
+ OAuthTokens,
8
+ ProvablyConfig,
9
+ ProvablyConfigError,
10
+ TokenStore,
11
+ configure,
12
+ )
13
+ from provably._errors import (
14
+ ProvablyAPIError,
15
+ ProvablyAuthError,
16
+ ProvablyConnectionError,
17
+ ProvablyDataError,
18
+ ProvablyError,
19
+ ProvablyNotFoundError,
20
+ ProvablyResourceAlreadyExistsError,
21
+ ProvablyUnauthorizedError,
22
+ )
23
+ from provably.auth_service import ProvablyAuthService, auth_service
24
+ from provably.connection import ConnectionInfo
25
+ from provably.oauth_login import browser_login
26
+ from provably.service import ProvablyService, service
27
+
28
+ __all__ = [
29
+ "AggregateAnswer",
30
+ "ConnectionInfo",
31
+ "MemoryTokenStore",
32
+ "OAuthTokens",
33
+ "Organization",
34
+ "OrganizationType",
35
+ "ProvablyAPIError",
36
+ "ProvablyAuthError",
37
+ "ProvablyAuthService",
38
+ "ProvablyConfig",
39
+ "ProvablyConfigError",
40
+ "ProvablyConnectionError",
41
+ "ProvablyDataError",
42
+ "ProvablyError",
43
+ "ProvablyNotFoundError",
44
+ "ProvablyResourceAlreadyExistsError",
45
+ "ProvablyService",
46
+ "ProvablyUnauthorizedError",
47
+ "QueryAnswer",
48
+ "ResultsetAnswer",
49
+ "TabularData",
50
+ "TokenStore",
51
+ "auth_service",
52
+ "browser_login",
53
+ "configure",
54
+ "service",
55
+ ]
@@ -0,0 +1,78 @@
1
+ """Data validation and serialization contracts for the Rust API layers."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ import msgspec
6
+ import structlog
7
+ from pydantic import BaseModel, Field, model_validator
8
+
9
+ _log = structlog.get_logger(__name__)
10
+
11
+ __all__ = ["QueryAnswer", "TabularData", "AggregateAnswer", "ResultsetAnswer"]
12
+
13
+
14
+ def _safe_deserialize(cell: Any) -> Any:
15
+ """Helper to cleanly parse stringified JSON containers using high-speed C decoding."""
16
+ if isinstance(cell, str) and cell.strip().startswith(("{", "[")):
17
+ try:
18
+ return msgspec.json.decode(cell)
19
+ except Exception as e:
20
+ _log.debug("safe_deserialize_fallback", error=str(e))
21
+ return cell
22
+ return cell
23
+
24
+
25
+ class TabularData(BaseModel):
26
+ columns: list[dict[str, Any]] = Field(default_factory=list)
27
+ rows: list[list[Any]] = Field(default_factory=list)
28
+
29
+ def extract_value(self) -> Any:
30
+ """Unpacks tabular results down to the core target payload."""
31
+ if not self.rows:
32
+ return {"columns": self.columns, "rows": self.rows}
33
+
34
+ row0 = self.rows[0]
35
+ # Extracted column names are normalized to lowercase
36
+ col_names = [str(c.get("name") or "").lower() for c in self.columns]
37
+
38
+ # Target 1: Extract intercept record payloads cleanly if present
39
+ if "raw_response" in col_names:
40
+ return _safe_deserialize(row0[col_names.index("raw_response")])
41
+
42
+ # Target 2: Single cell scalar
43
+ if len(col_names) == 1 and len(row0) == 1:
44
+ return _safe_deserialize(row0[0])
45
+
46
+ return {"columns": self.columns, "rows": self.rows}
47
+
48
+
49
+ class AggregateAnswer(BaseModel):
50
+ type: Literal["aggregate"]
51
+ value: str
52
+
53
+ def extract_value(self) -> Any:
54
+ return _safe_deserialize(self.value)
55
+
56
+
57
+ class ResultsetAnswer(BaseModel):
58
+ type: Literal["resultset"]
59
+ value: TabularData
60
+
61
+ def extract_value(self) -> Any:
62
+ return self.value.extract_value()
63
+
64
+
65
+ class QueryAnswer(BaseModel):
66
+ """Wrapper mapping the structure of QueryAnswer enum."""
67
+
68
+ root: AggregateAnswer | ResultsetAnswer = Field(..., discriminator="type")
69
+
70
+ @model_validator(mode="before")
71
+ @classmethod
72
+ def wrap_root(cls, data: Any) -> Any:
73
+ if isinstance(data, dict) and "root" not in data:
74
+ return {"root": data}
75
+ return data
76
+
77
+ def flatten(self) -> Any:
78
+ return self.root.extract_value()