persql 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.
@@ -0,0 +1,21 @@
1
+ # Python build artifacts
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ .eggs/
9
+
10
+ # Test / type-check caches
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+
15
+ # Virtual environments
16
+ .venv/
17
+ venv/
18
+
19
+ # Coverage
20
+ .coverage
21
+ htmlcov/
persql-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Premsan
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.
persql-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: persql
3
+ Version: 0.1.0
4
+ Summary: PerSQL Python SDK — SQLite databases on the edge for AI agents.
5
+ Project-URL: Homepage, https://persql.com
6
+ Project-URL: Documentation, https://docs.persql.com
7
+ Project-URL: Console, https://console.persql.com
8
+ Project-URL: Issues, https://persql.com/support
9
+ Author-email: Premsan <support@persql.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,cloudflare,database,edge,llm,sqlite
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Database
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: httpx>=0.24
28
+ Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.8; extra == 'dev'
31
+ Requires-Dist: ruff>=0.5; extra == 'dev'
32
+ Provides-Extra: subscribe
33
+ Requires-Dist: websockets>=11; extra == 'subscribe'
34
+ Provides-Extra: test
35
+ Requires-Dist: anyio>=3; extra == 'test'
36
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
37
+ Requires-Dist: pytest>=7; extra == 'test'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # persql
41
+
42
+ Python SDK for **[PerSQL](https://persql.com)** — SQLite databases on
43
+ the edge for AI agents. One isolated database per agent, per app, per
44
+ PR, per use case — backed by Cloudflare Durable Objects with embedded
45
+ SQLite. Prepaid billing, four meters, no plans.
46
+
47
+ ```bash
48
+ pip install persql
49
+ ```
50
+
51
+ ## Quick start
52
+
53
+ ```python
54
+ import os
55
+ from persql import PerSQL
56
+
57
+ persql = PerSQL(token=os.environ["PERSQL_TOKEN"])
58
+ db = persql.database("acme/orders")
59
+
60
+ result = db.query("SELECT id, email FROM customers WHERE id = ?", [42])
61
+ for row in result["data"]:
62
+ print(row["email"])
63
+ ```
64
+
65
+ Async:
66
+
67
+ ```python
68
+ import asyncio
69
+ from persql import AsyncPerSQL
70
+
71
+ async def main():
72
+ async with AsyncPerSQL(token=os.environ["PERSQL_TOKEN"]) as persql:
73
+ db = persql.database("acme/orders")
74
+ result = await db.query("SELECT 1 AS one")
75
+ print(result["data"])
76
+
77
+ asyncio.run(main())
78
+ ```
79
+
80
+ ## Local mode (tests, no network)
81
+
82
+ ```python
83
+ persql = PerSQL(local=":memory:")
84
+ db = persql.database("test/db")
85
+ db.query("CREATE TABLE t (id INTEGER)")
86
+ db.query("INSERT INTO t (id) VALUES (?)", [1])
87
+ print(db.query("SELECT * FROM t")["data"]) # [{"id": 1}]
88
+ ```
89
+
90
+ Local mode uses stdlib `sqlite3` — no extra dependencies. Vectors,
91
+ blobs, branches, approvals, and subscribe require a server-mode token
92
+ (`psql_live_…` or `psql_test_…`) and raise in local mode.
93
+
94
+ ## Agent tools
95
+
96
+ Generate a typed tool bundle for any LLM — one tool per table plus
97
+ discovery, safety, and branch-management tools — and let the model
98
+ drive the database:
99
+
100
+ ```python
101
+ import anthropic
102
+ from persql import PerSQL
103
+
104
+ persql = PerSQL(token=os.environ["PERSQL_TOKEN"])
105
+ db = persql.database("acme/orders")
106
+ tools = db.as_tools()
107
+
108
+ client = anthropic.Anthropic()
109
+ reply = client.messages.create(
110
+ model="claude-opus-4-7",
111
+ max_tokens=4096,
112
+ tools=tools["anthropic"],
113
+ messages=[{"role": "user", "content": "How many customers signed up last week?"}],
114
+ )
115
+ for block in reply.content:
116
+ if block.type == "tool_use":
117
+ result = tools["run"](block.name, block.input)
118
+ print(block.name, "→", result)
119
+ ```
120
+
121
+ The same bundle exposes:
122
+
123
+ - `tools["anthropic"]` — pass directly to `anthropic.messages.create`
124
+ - `tools["openai"]` — pass to `openai.chat.completions.create`
125
+ - `tools["langchain"]` — convert with `DynamicStructuredTool`
126
+ - `tools["run"](name, input)` — sync or async dispatcher
127
+
128
+ Async clients return an awaitable bundle; `await db.as_tools()` then
129
+ use `await tools["run"](...)`.
130
+
131
+ ## Branches
132
+
133
+ Each branch is its own database, forked from the parent at create
134
+ time. Idempotent by ref — call from CI with the PR number and the same
135
+ ref re-runs as a reset:
136
+
137
+ ```python
138
+ branch = db.branches.upsert("pr-42", ttl_days=7)
139
+ preview = db.branches.preview("pr-42")
140
+ print(preview["plan"]) # added / changed / removed objects
141
+ ```
142
+
143
+ ## Safety primitives
144
+
145
+ Pre-flight a write, get a single-use token, redeem only if the plan
146
+ looks right:
147
+
148
+ ```python
149
+ plan = db.proposals.propose(
150
+ "UPDATE orders SET status='shipped' WHERE created_at < ?",
151
+ params=["2026-01-01"],
152
+ )
153
+ print(plan["estimated_affected_rows"])
154
+ db.proposals.apply(plan["execution_token"])
155
+ ```
156
+
157
+ ## Approvals
158
+
159
+ When a write hits a `require_approval` rule, the SDK raises
160
+ `ApprovalRequiredError`. Either halt and surface the URL to a human,
161
+ or wait for the decision and redeem:
162
+
163
+ ```python
164
+ from persql import ApprovalRequiredError
165
+
166
+ try:
167
+ db.query("DELETE FROM customers WHERE id = ?", [42])
168
+ except ApprovalRequiredError as e:
169
+ print(f"Needs approval: {e.approval_url}")
170
+ # …after a member approves in the console:
171
+ db.approvals.redeem(e.approval_token)
172
+ ```
173
+
174
+ ## Subscribe (async, optional)
175
+
176
+ Row-change events over WebSocket. Install the optional dep:
177
+
178
+ ```bash
179
+ pip install 'persql[subscribe]'
180
+ ```
181
+
182
+ ```python
183
+ async with AsyncPerSQL(token=...) as persql:
184
+ db = persql.database("acme/orders")
185
+ async for change in await db.subscribe(tables=["orders"]):
186
+ print(change["table"], change["kind"])
187
+ ```
188
+
189
+ ## Errors
190
+
191
+ ```python
192
+ from persql import ApprovalRequiredError, PerSQLError, RateLimitError
193
+
194
+ try:
195
+ db.query("UPDATE orders SET status='shipped'")
196
+ except ApprovalRequiredError as e:
197
+ pass # human approval required — see e.approval_url
198
+ except RateLimitError as e:
199
+ time.sleep(e.retry_after_seconds)
200
+ except PerSQLError as e:
201
+ # `.detail["kind"]` is set on /v1/query SQL errors
202
+ print(e.status, e.args[0], getattr(e, "detail", None))
203
+ ```
204
+
205
+ ## Reference
206
+
207
+ | Component | Description |
208
+ | --- | --- |
209
+ | [Console](https://console.persql.com) | Manage tokens, databases, branches, billing |
210
+ | [Docs](https://docs.persql.com) | Concepts, API reference, examples |
211
+ | [TS SDK](https://www.npmjs.com/package/@persql/sdk) | TypeScript equivalent of this package |
212
+
213
+ ## License
214
+
215
+ MIT
persql-0.1.0/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # persql
2
+
3
+ Python SDK for **[PerSQL](https://persql.com)** — SQLite databases on
4
+ the edge for AI agents. One isolated database per agent, per app, per
5
+ PR, per use case — backed by Cloudflare Durable Objects with embedded
6
+ SQLite. Prepaid billing, four meters, no plans.
7
+
8
+ ```bash
9
+ pip install persql
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ ```python
15
+ import os
16
+ from persql import PerSQL
17
+
18
+ persql = PerSQL(token=os.environ["PERSQL_TOKEN"])
19
+ db = persql.database("acme/orders")
20
+
21
+ result = db.query("SELECT id, email FROM customers WHERE id = ?", [42])
22
+ for row in result["data"]:
23
+ print(row["email"])
24
+ ```
25
+
26
+ Async:
27
+
28
+ ```python
29
+ import asyncio
30
+ from persql import AsyncPerSQL
31
+
32
+ async def main():
33
+ async with AsyncPerSQL(token=os.environ["PERSQL_TOKEN"]) as persql:
34
+ db = persql.database("acme/orders")
35
+ result = await db.query("SELECT 1 AS one")
36
+ print(result["data"])
37
+
38
+ asyncio.run(main())
39
+ ```
40
+
41
+ ## Local mode (tests, no network)
42
+
43
+ ```python
44
+ persql = PerSQL(local=":memory:")
45
+ db = persql.database("test/db")
46
+ db.query("CREATE TABLE t (id INTEGER)")
47
+ db.query("INSERT INTO t (id) VALUES (?)", [1])
48
+ print(db.query("SELECT * FROM t")["data"]) # [{"id": 1}]
49
+ ```
50
+
51
+ Local mode uses stdlib `sqlite3` — no extra dependencies. Vectors,
52
+ blobs, branches, approvals, and subscribe require a server-mode token
53
+ (`psql_live_…` or `psql_test_…`) and raise in local mode.
54
+
55
+ ## Agent tools
56
+
57
+ Generate a typed tool bundle for any LLM — one tool per table plus
58
+ discovery, safety, and branch-management tools — and let the model
59
+ drive the database:
60
+
61
+ ```python
62
+ import anthropic
63
+ from persql import PerSQL
64
+
65
+ persql = PerSQL(token=os.environ["PERSQL_TOKEN"])
66
+ db = persql.database("acme/orders")
67
+ tools = db.as_tools()
68
+
69
+ client = anthropic.Anthropic()
70
+ reply = client.messages.create(
71
+ model="claude-opus-4-7",
72
+ max_tokens=4096,
73
+ tools=tools["anthropic"],
74
+ messages=[{"role": "user", "content": "How many customers signed up last week?"}],
75
+ )
76
+ for block in reply.content:
77
+ if block.type == "tool_use":
78
+ result = tools["run"](block.name, block.input)
79
+ print(block.name, "→", result)
80
+ ```
81
+
82
+ The same bundle exposes:
83
+
84
+ - `tools["anthropic"]` — pass directly to `anthropic.messages.create`
85
+ - `tools["openai"]` — pass to `openai.chat.completions.create`
86
+ - `tools["langchain"]` — convert with `DynamicStructuredTool`
87
+ - `tools["run"](name, input)` — sync or async dispatcher
88
+
89
+ Async clients return an awaitable bundle; `await db.as_tools()` then
90
+ use `await tools["run"](...)`.
91
+
92
+ ## Branches
93
+
94
+ Each branch is its own database, forked from the parent at create
95
+ time. Idempotent by ref — call from CI with the PR number and the same
96
+ ref re-runs as a reset:
97
+
98
+ ```python
99
+ branch = db.branches.upsert("pr-42", ttl_days=7)
100
+ preview = db.branches.preview("pr-42")
101
+ print(preview["plan"]) # added / changed / removed objects
102
+ ```
103
+
104
+ ## Safety primitives
105
+
106
+ Pre-flight a write, get a single-use token, redeem only if the plan
107
+ looks right:
108
+
109
+ ```python
110
+ plan = db.proposals.propose(
111
+ "UPDATE orders SET status='shipped' WHERE created_at < ?",
112
+ params=["2026-01-01"],
113
+ )
114
+ print(plan["estimated_affected_rows"])
115
+ db.proposals.apply(plan["execution_token"])
116
+ ```
117
+
118
+ ## Approvals
119
+
120
+ When a write hits a `require_approval` rule, the SDK raises
121
+ `ApprovalRequiredError`. Either halt and surface the URL to a human,
122
+ or wait for the decision and redeem:
123
+
124
+ ```python
125
+ from persql import ApprovalRequiredError
126
+
127
+ try:
128
+ db.query("DELETE FROM customers WHERE id = ?", [42])
129
+ except ApprovalRequiredError as e:
130
+ print(f"Needs approval: {e.approval_url}")
131
+ # …after a member approves in the console:
132
+ db.approvals.redeem(e.approval_token)
133
+ ```
134
+
135
+ ## Subscribe (async, optional)
136
+
137
+ Row-change events over WebSocket. Install the optional dep:
138
+
139
+ ```bash
140
+ pip install 'persql[subscribe]'
141
+ ```
142
+
143
+ ```python
144
+ async with AsyncPerSQL(token=...) as persql:
145
+ db = persql.database("acme/orders")
146
+ async for change in await db.subscribe(tables=["orders"]):
147
+ print(change["table"], change["kind"])
148
+ ```
149
+
150
+ ## Errors
151
+
152
+ ```python
153
+ from persql import ApprovalRequiredError, PerSQLError, RateLimitError
154
+
155
+ try:
156
+ db.query("UPDATE orders SET status='shipped'")
157
+ except ApprovalRequiredError as e:
158
+ pass # human approval required — see e.approval_url
159
+ except RateLimitError as e:
160
+ time.sleep(e.retry_after_seconds)
161
+ except PerSQLError as e:
162
+ # `.detail["kind"]` is set on /v1/query SQL errors
163
+ print(e.status, e.args[0], getattr(e, "detail", None))
164
+ ```
165
+
166
+ ## Reference
167
+
168
+ | Component | Description |
169
+ | --- | --- |
170
+ | [Console](https://console.persql.com) | Manage tokens, databases, branches, billing |
171
+ | [Docs](https://docs.persql.com) | Concepts, API reference, examples |
172
+ | [TS SDK](https://www.npmjs.com/package/@persql/sdk) | TypeScript equivalent of this package |
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,195 @@
1
+ """PerSQL — SQLite databases on the edge for AI agents.
2
+
3
+ Quick start::
4
+
5
+ import os
6
+ from persql import PerSQL
7
+
8
+ persql = PerSQL(token=os.environ["PERSQL_TOKEN"])
9
+ db = persql.database("acme/orders")
10
+ result = db.query("SELECT id, email FROM customers WHERE id = ?", [42])
11
+ for row in result["data"]:
12
+ print(row["email"])
13
+
14
+ Async variant::
15
+
16
+ import asyncio
17
+ from persql import AsyncPerSQL
18
+
19
+ async def main():
20
+ async with AsyncPerSQL(token="...") as persql:
21
+ db = persql.database("acme/orders")
22
+ result = await db.query("SELECT 1 AS one")
23
+ print(result["data"])
24
+
25
+ asyncio.run(main())
26
+
27
+ Local mode (tests, no network)::
28
+
29
+ persql = PerSQL(local=":memory:")
30
+ db = persql.database("test/db")
31
+ db.query("CREATE TABLE t (id INTEGER)")
32
+ db.query("INSERT INTO t (id) VALUES (?)", [1])
33
+ print(db.query("SELECT * FROM t")["data"]) # [{"id": 1}]
34
+
35
+ Agent tools — one Anthropic + OpenAI + LangChain bundle covering the
36
+ whole database::
37
+
38
+ tools = db.as_tools()
39
+ # tools["anthropic"] — pass to anthropic.messages.create
40
+ # tools["openai"] — pass to openai.chat.completions.create
41
+ # tools["langchain"] — convert with DynamicStructuredTool
42
+ # tools["run"] — dispatcher to execute model-emitted calls
43
+
44
+ Errors::
45
+
46
+ from persql import ApprovalRequiredError, PerSQLError, RateLimitError
47
+
48
+ try:
49
+ db.query("UPDATE orders SET status='shipped'")
50
+ except ApprovalRequiredError as e:
51
+ print(f"Approval needed: {e.approval_url}")
52
+ except RateLimitError as e:
53
+ time.sleep(e.retry_after_seconds)
54
+ except PerSQLError as e:
55
+ # `.detail["kind"]` is set on /v1/query and /v1/batch SQL errors
56
+ print(e.status, e.args[0], getattr(e, "detail", None))
57
+ """
58
+
59
+ from __future__ import annotations
60
+
61
+ from ._async_database import (
62
+ AsyncPerSQLApprovals,
63
+ AsyncPerSQLBlob,
64
+ AsyncPerSQLBranches,
65
+ AsyncPerSQLDatabase,
66
+ AsyncPerSQLProposals,
67
+ AsyncPerSQLVectors,
68
+ )
69
+ from ._client import AsyncPerSQL, PerSQL
70
+ from ._database import (
71
+ PerSQLApprovals,
72
+ PerSQLBlob,
73
+ PerSQLBranches,
74
+ PerSQLDatabase,
75
+ PerSQLProposals,
76
+ PerSQLVectors,
77
+ )
78
+ from ._errors import (
79
+ ApprovalRequiredError,
80
+ ApprovalRuleHit,
81
+ PerSQLError,
82
+ RateLimitError,
83
+ SqlErrorDetail,
84
+ SqlErrorKind,
85
+ )
86
+ from ._tools import DatabaseToolBundle
87
+ from ._types import (
88
+ AnthropicTool,
89
+ BlobListItem,
90
+ BlobListResponse,
91
+ BlobPutResult,
92
+ BranchInfo,
93
+ BranchListPage,
94
+ BranchMergeMode,
95
+ BranchMergePlanStep,
96
+ BranchMergeResult,
97
+ BranchMergeSummary,
98
+ BranchMigration,
99
+ ClaimedBranch,
100
+ ColumnDescription,
101
+ DatabaseSchema,
102
+ DescribeBundle,
103
+ ForeignKeyDescription,
104
+ ForkedFrom,
105
+ HandoffClaim,
106
+ LangChainTool,
107
+ OpenAiTool,
108
+ OpenAiToolFunction,
109
+ PinnedHandoff,
110
+ ProposalPlan,
111
+ QueryLogEntry,
112
+ QueryLogPage,
113
+ QueryResult,
114
+ Role,
115
+ SchemaColumn,
116
+ SchemaDoctorFinding,
117
+ SchemaDoctorReport,
118
+ SchemaSearchHit,
119
+ SchemaSearchResponse,
120
+ SchemaTable,
121
+ Statement,
122
+ SubscribeChange,
123
+ SubscribeChangeKind,
124
+ TableDescription,
125
+ TableInfo,
126
+ VectorMatch,
127
+ VectorUpsertItem,
128
+ )
129
+
130
+ __version__ = "0.1.0"
131
+
132
+ __all__ = [
133
+ "AnthropicTool",
134
+ "ApprovalRequiredError",
135
+ "ApprovalRuleHit",
136
+ "AsyncPerSQL",
137
+ "AsyncPerSQLApprovals",
138
+ "AsyncPerSQLBlob",
139
+ "AsyncPerSQLBranches",
140
+ "AsyncPerSQLDatabase",
141
+ "AsyncPerSQLProposals",
142
+ "AsyncPerSQLVectors",
143
+ "BlobListItem",
144
+ "BlobListResponse",
145
+ "BlobPutResult",
146
+ "BranchInfo",
147
+ "BranchListPage",
148
+ "BranchMergeMode",
149
+ "BranchMergePlanStep",
150
+ "BranchMergeResult",
151
+ "BranchMergeSummary",
152
+ "BranchMigration",
153
+ "ClaimedBranch",
154
+ "ColumnDescription",
155
+ "DatabaseSchema",
156
+ "DatabaseToolBundle",
157
+ "DescribeBundle",
158
+ "ForeignKeyDescription",
159
+ "ForkedFrom",
160
+ "HandoffClaim",
161
+ "LangChainTool",
162
+ "OpenAiTool",
163
+ "OpenAiToolFunction",
164
+ "PerSQL",
165
+ "PerSQLApprovals",
166
+ "PerSQLBlob",
167
+ "PerSQLBranches",
168
+ "PerSQLDatabase",
169
+ "PerSQLError",
170
+ "PerSQLProposals",
171
+ "PerSQLVectors",
172
+ "PinnedHandoff",
173
+ "ProposalPlan",
174
+ "QueryLogEntry",
175
+ "QueryLogPage",
176
+ "QueryResult",
177
+ "RateLimitError",
178
+ "Role",
179
+ "SchemaColumn",
180
+ "SchemaDoctorFinding",
181
+ "SchemaDoctorReport",
182
+ "SchemaSearchHit",
183
+ "SchemaSearchResponse",
184
+ "SchemaTable",
185
+ "SqlErrorDetail",
186
+ "SqlErrorKind",
187
+ "Statement",
188
+ "SubscribeChange",
189
+ "SubscribeChangeKind",
190
+ "TableDescription",
191
+ "TableInfo",
192
+ "VectorMatch",
193
+ "VectorUpsertItem",
194
+ "__version__",
195
+ ]