foundrydb-sdk 0.5.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 (37) hide show
  1. foundrydb_sdk-0.5.0/.github/workflows/publish.yml +55 -0
  2. foundrydb_sdk-0.5.0/.gitignore +11 -0
  3. foundrydb_sdk-0.5.0/LICENSE +21 -0
  4. foundrydb_sdk-0.5.0/PKG-INFO +433 -0
  5. foundrydb_sdk-0.5.0/README.md +403 -0
  6. foundrydb_sdk-0.5.0/RELEASING.md +31 -0
  7. foundrydb_sdk-0.5.0/foundrydb/__init__.py +280 -0
  8. foundrydb_sdk-0.5.0/foundrydb/ai_actions.py +242 -0
  9. foundrydb_sdk-0.5.0/foundrydb/app_jobs.py +494 -0
  10. foundrydb_sdk-0.5.0/foundrydb/app_services.py +606 -0
  11. foundrydb_sdk-0.5.0/foundrydb/backups.py +43 -0
  12. foundrydb_sdk-0.5.0/foundrydb/client.py +354 -0
  13. foundrydb_sdk-0.5.0/foundrydb/data_pipelines.py +155 -0
  14. foundrydb_sdk-0.5.0/foundrydb/edge.py +166 -0
  15. foundrydb_sdk-0.5.0/foundrydb/embedding_pipelines.py +415 -0
  16. foundrydb_sdk-0.5.0/foundrydb/file_services.py +333 -0
  17. foundrydb_sdk-0.5.0/foundrydb/inference.py +325 -0
  18. foundrydb_sdk-0.5.0/foundrydb/monitoring.py +117 -0
  19. foundrydb_sdk-0.5.0/foundrydb/organizations.py +76 -0
  20. foundrydb_sdk-0.5.0/foundrydb/queues.py +242 -0
  21. foundrydb_sdk-0.5.0/foundrydb/services.py +286 -0
  22. foundrydb_sdk-0.5.0/foundrydb/types.py +2297 -0
  23. foundrydb_sdk-0.5.0/foundrydb/users.py +47 -0
  24. foundrydb_sdk-0.5.0/foundrydb/vector_search.py +141 -0
  25. foundrydb_sdk-0.5.0/foundrydb/webhooks.py +239 -0
  26. foundrydb_sdk-0.5.0/pyproject.toml +47 -0
  27. foundrydb_sdk-0.5.0/test_live.py +209 -0
  28. foundrydb_sdk-0.5.0/tests/__init__.py +0 -0
  29. foundrydb_sdk-0.5.0/tests/conftest.py +12 -0
  30. foundrydb_sdk-0.5.0/tests/test_app_services.py +429 -0
  31. foundrydb_sdk-0.5.0/tests/test_backups.py +298 -0
  32. foundrydb_sdk-0.5.0/tests/test_client.py +428 -0
  33. foundrydb_sdk-0.5.0/tests/test_edge.py +435 -0
  34. foundrydb_sdk-0.5.0/tests/test_monitoring.py +349 -0
  35. foundrydb_sdk-0.5.0/tests/test_organizations.py +197 -0
  36. foundrydb_sdk-0.5.0/tests/test_services.py +420 -0
  37. foundrydb_sdk-0.5.0/tests/test_users.py +237 -0
@@ -0,0 +1,55 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*'
7
+ workflow_dispatch:
8
+ inputs:
9
+ tag:
10
+ description: 'Version tag to publish (e.g. v1.2.3)'
11
+ required: true
12
+
13
+ jobs:
14
+ publish:
15
+ name: Build and publish foundrydb
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: '3.11'
26
+
27
+ - name: Derive version from tag
28
+ run: |
29
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
30
+ TAG="${{ github.event.inputs.tag }}"
31
+ else
32
+ TAG="${GITHUB_REF_NAME}"
33
+ fi
34
+ VERSION="${TAG#v}"
35
+ echo "VERSION=$VERSION" >> "$GITHUB_ENV"
36
+ echo "Publishing version: $VERSION"
37
+
38
+ - name: Patch pyproject.toml version (ephemeral, in CI only)
39
+ # Replaces the hard-coded version line so the built wheel/sdist carries
40
+ # the tag version. The change is never committed.
41
+ run: |
42
+ sed -i "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
43
+ grep "^version" pyproject.toml
44
+
45
+ - name: Install build tools
46
+ run: pip install build twine
47
+
48
+ - name: Build distributions
49
+ run: python -m build
50
+
51
+ - name: Publish to PyPI
52
+ run: twine upload dist/*
53
+ env:
54
+ TWINE_USERNAME: __token__
55
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ venv/
8
+ .DS_Store
9
+ *.whl
10
+ *.tar.gz
11
+ .pytest_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anorph
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,433 @@
1
+ Metadata-Version: 2.4
2
+ Name: foundrydb-sdk
3
+ Version: 0.5.0
4
+ Summary: Python SDK for the FoundryDB managed database platform
5
+ Project-URL: Homepage, https://foundrydb.com
6
+ Project-URL: Repository, https://github.com/anorph/foundrydb-sdk-python
7
+ Project-URL: Bug Tracker, https://github.com/anorph/foundrydb-sdk-python/issues
8
+ Author: Anorph
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: foundrydb,kafka,managed-database,mongodb,mssql,mysql,opensearch,postgresql,valkey
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Database
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: httpx>=0.24.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: hatchling; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # foundrydb
32
+
33
+ Official Python SDK for the [FoundryDB](https://foundrydb.com) managed database platform.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install foundrydb-sdk
39
+ ```
40
+
41
+ The distribution is published as `foundrydb-sdk`; the import package is `foundrydb`:
42
+
43
+ ```python
44
+ import foundrydb
45
+ ```
46
+
47
+ ## Requirements
48
+
49
+ - Python 3.9+
50
+ - [`httpx`](https://www.python-httpx.org/) (automatically installed)
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ from foundrydb import FoundryDB
56
+
57
+ client = FoundryDB(
58
+ api_url="https://api.foundrydb.com",
59
+ username="admin",
60
+ password="admin",
61
+ )
62
+
63
+ services = client.services.list()
64
+ for svc in services:
65
+ print(svc.id, svc.name, svc.status)
66
+ ```
67
+
68
+ ## Organizations
69
+
70
+ FoundryDB supports personal and team organizations. You can list all organizations
71
+ your account belongs to, and scope a client (or individual service creation requests)
72
+ to a specific organization.
73
+
74
+ ### List organizations
75
+
76
+ ```python
77
+ orgs = client.organizations.list()
78
+ for org in orgs:
79
+ print(org.id, org.name, org.slug, "personal=" + str(org.is_personal))
80
+ ```
81
+
82
+ Each entry is an `Organization` dataclass with fields: `id`, `name`, `slug`, `is_personal`.
83
+
84
+ ### Scope client to an organization
85
+
86
+ Pass `organization_id` when constructing the client. Every request will then include
87
+ the `X-Active-Org-ID` header automatically.
88
+
89
+ ```python
90
+ client = FoundryDB(
91
+ api_url="https://api.foundrydb.com",
92
+ username="admin",
93
+ password="admin",
94
+ organization_id="org_abc123",
95
+ )
96
+ ```
97
+
98
+ ### Override organization per request
99
+
100
+ You can also pass `organization_id` directly to `services.create()` to override the
101
+ client-level setting for a single call:
102
+
103
+ ```python
104
+ service = client.services.create(
105
+ name="team-pg",
106
+ database_type="postgresql",
107
+ version="17",
108
+ plan_name="tier-2",
109
+ zone="se-sto1",
110
+ storage_size_gb=50,
111
+ storage_tier="maxiops",
112
+ organization_id="org_team456",
113
+ )
114
+ ```
115
+
116
+ ## Supported Database Types
117
+
118
+ | Type | Versions |
119
+ |------|----------|
120
+ | `postgresql` | 14, 15, 16, 17, 18 |
121
+ | `mysql` | 8.4 |
122
+ | `mongodb` | 6.0, 7.0, 8.0 |
123
+ | `valkey` | 7.2, 8.0, 8.1, 9.0 |
124
+ | `kafka` | 3.6, 3.7, 3.8, 3.9, 4.0 |
125
+ | `opensearch: 2 |
126
+ | `mssql` | 4.8 |
127
+
128
+ ## Usage
129
+
130
+ ### Services
131
+
132
+ ```python
133
+ # List all managed services
134
+ services = client.services.list()
135
+
136
+ # Create a single-node PostgreSQL 17 service
137
+ service = client.services.create(
138
+ name="my-pg",
139
+ database_type="postgresql",
140
+ version="17",
141
+ plan_name="tier-2",
142
+ zone="se-sto1",
143
+ storage_size_gb=50,
144
+ storage_tier="maxiops",
145
+ )
146
+ print("Created:", service.id, service.status)
147
+
148
+ # Create a 3-node HA PostgreSQL cluster with auto-failover
149
+ ha_service = client.services.create(
150
+ name="my-pg-ha",
151
+ database_type="postgresql",
152
+ version="17",
153
+ plan_name="tier-2",
154
+ zone="se-sto1",
155
+ storage_size_gb=100,
156
+ storage_tier="maxiops",
157
+ node_count=3,
158
+ auto_failover_enabled=True,
159
+ replication_mode="async",
160
+ encryption_enabled=True,
161
+ allowed_cidrs=["203.0.113.0/24"],
162
+ )
163
+
164
+ # Create an OpenSearch service
165
+ search_svc = client.services.create(
166
+ name="my-search",
167
+ database_type="opensearch",
168
+ version="2",
169
+ plan_name="tier-2",
170
+ zone="se-sto1",
171
+ storage_size_gb=50,
172
+ storage_tier="maxiops",
173
+ )
174
+
175
+ # Create a Kafka cluster
176
+ kafka_svc = client.services.create(
177
+ name="my-kafka",
178
+ database_type="kafka",
179
+ version="4.0",
180
+ plan_name="tier-2",
181
+ zone="se-sto1",
182
+ storage_size_gb=100,
183
+ storage_tier="maxiops",
184
+ node_count=3,
185
+ )
186
+
187
+ # Get a service by ID
188
+ svc = client.services.get(service.id)
189
+
190
+ # Update a service (e.g. change allowed CIDRs)
191
+ client.services.update(service.id, allowed_cidrs=["203.0.113.0/24"])
192
+
193
+ # Delete a service
194
+ client.services.delete(service.id)
195
+ ```
196
+
197
+ #### `create()` parameters
198
+
199
+ | Parameter | Type | Required | Description |
200
+ |-----------|------|----------|-------------|
201
+ | `name` | str | yes | Display name |
202
+ | `database_type` | DatabaseType | yes | Engine (see table above) |
203
+ | `version` | str | yes | Engine version string |
204
+ | `plan_name` | str | yes | Compute plan (e.g. `"tier-2"`) |
205
+ | `zone` | str | yes | Deployment zone (e.g. `"se-sto1"`) |
206
+ | `storage_size_gb` | int | yes | Data disk size in GB |
207
+ | `storage_tier` | str | yes | `"standard"` or `"maxiops"` |
208
+ | `organization_id` | str | no | Override active org for this request |
209
+ | `node_count` | int | no | Number of nodes (default 1) |
210
+ | `auto_failover_enabled` | bool | no | Enable automatic failover |
211
+ | `replication_mode` | str | no | `"async"` or `"sync"` |
212
+ | `encryption_enabled` | bool | no | At-rest encryption |
213
+ | `allowed_cidrs` | list[str] | no | Allowed source CIDRs |
214
+ | `maintenance_window` | str | no | Preferred maintenance window |
215
+
216
+ ### Database Users and Credentials
217
+
218
+ ```python
219
+ # List users
220
+ users = client.users.list(service_id)
221
+ for user in users:
222
+ print(user.username)
223
+
224
+ # Reveal password and connection string
225
+ creds = client.users.reveal_password(service_id, "admin")
226
+ print(creds.connection_string)
227
+ # postgresql://admin:s3cret@my-pg.foundrydb.com:5432/defaultdb?sslmode=require
228
+ ```
229
+
230
+ ### Backups
231
+
232
+ ```python
233
+ # List backups
234
+ backups = client.backups.list(service_id)
235
+ for b in backups:
236
+ print(b.id, b.status, b.backup_type)
237
+
238
+ # Trigger an on-demand backup
239
+ result = client.backups.trigger(service_id)
240
+ ```
241
+
242
+ ### Monitoring
243
+
244
+ ```python
245
+ # Get current metrics
246
+ metrics = client.monitoring.get_metrics(service_id)
247
+ print(f"CPU: {metrics.cpu_usage_percent}%")
248
+ print(f"Memory: {metrics.memory_usage_percent}%")
249
+
250
+ # Request logs and poll manually
251
+ task = client.monitoring.request_logs(service_id, lines=200)
252
+ result = client.monitoring.get_logs(service_id, task.task_id)
253
+ print(result.logs)
254
+
255
+ # Or use the convenience wrapper (auto-polls until done)
256
+ logs = client.monitoring.fetch_logs(service_id, lines=500)
257
+ print(logs)
258
+ ```
259
+
260
+ ### Edge Gateway
261
+
262
+ The edge gateway sits in front of app services and provides custom domains with automated TLS, path-based caching, a token-bucket rate limiter, and a WAF. All methods are on `client.edge`:
263
+
264
+ ```python
265
+ from foundrydb import EdgeCacheRule, EdgeRateLimit
266
+
267
+ # Add a custom domain (starts in pending_verification; platform verifies CNAME and issues TLS)
268
+ domain = client.edge.create_domain(app.id, "shop.acme.com")
269
+ print(domain.cname_target) # "edge.foundrydb.com" -- point your CNAME here
270
+
271
+ # Trigger an immediate verification pass instead of waiting for the background worker
272
+ domain = client.edge.verify_domain(app.id, domain.id)
273
+
274
+ # List all domains attached to the app
275
+ domains = client.edge.list_domains(app.id)
276
+ for d in domains:
277
+ print(d.domain, d.status)
278
+
279
+ # Remove a domain (idempotent: 404 is treated as success)
280
+ client.edge.delete_domain(app.id, domain.id)
281
+
282
+ # Inspect the edge overview: enabled flag, home PoP, CNAME target, per-PoP convergence
283
+ status = client.edge.get_status(app.id)
284
+ print(status.edge_enabled, status.home_pop, status.config_version)
285
+ for pop in status.applications:
286
+ print(pop.zone, pop.status, pop.applied_version)
287
+
288
+ # Update customer-tunable edge settings (cache rules, rate limit, WAF mode)
289
+ settings = client.edge.update_settings(
290
+ app.id,
291
+ cache_rules=[EdgeCacheRule(path_prefix="/static/", ttl_seconds=86400)],
292
+ rate_limit=EdgeRateLimit(requests_per_second=100, burst=200, key="ip"),
293
+ waf_mode="detect",
294
+ )
295
+ print(settings.config_version) # version the fleet will converge on
296
+ ```
297
+
298
+ #### `update_settings()` parameters
299
+
300
+ | Parameter | Type | Description |
301
+ |-----------|------|-------------|
302
+ | `cache_rules` | `list[EdgeCacheRule]` or `None` | Path-prefix cache rules. Pass `[]` to clear. |
303
+ | `rate_limit` | `EdgeRateLimit` or `None` | Token-bucket rate limit. |
304
+ | `waf_mode` | `str` or `None` | `"off"` or `"detect"`. |
305
+
306
+ #### Edge models
307
+
308
+ | Model | Fields |
309
+ |-------|--------|
310
+ | `EdgeDomain` | `id`, `service_id`, `user_id`, `domain`, `status`, `cname_target`, `certificate_id`, `verification_checked_at`, `error_message`, `created_at`, `updated_at` |
311
+ | `EdgeStatus` | `edge_enabled`, `home_pop`, `cname_target`, `config_version`, `applications` |
312
+ | `EdgeAppApplication` | `zone`, `applied_version`, `status`, `error_message` |
313
+ | `EdgeSettings` | `waf_mode`, `config_version`, `cache_rules`, `rate_limit` |
314
+ | `EdgeCacheRule` | `path_prefix`, `ttl_seconds` |
315
+ | `EdgeRateLimit` | `requests_per_second`, `burst`, `key` |
316
+
317
+ `EdgeDomainStatus` values: `pending_verification`, `verifying`, `issuing_certificate`, `propagating`, `active`, `failed`, `deleting`.
318
+
319
+ ## Async Client
320
+
321
+ All methods have async equivalents. Use `AsyncFoundryDB` as an async context manager:
322
+
323
+ ```python
324
+ import asyncio
325
+ from foundrydb import AsyncFoundryDB
326
+
327
+ async def main():
328
+ async with AsyncFoundryDB(
329
+ api_url="https://api.foundrydb.com",
330
+ username="admin",
331
+ password="admin",
332
+ organization_id="org_abc123", # optional org scoping
333
+ ) as client:
334
+ # List organizations
335
+ orgs = await client.organizations.list()
336
+
337
+ # Create a service
338
+ service = await client.services.create(
339
+ name="async-pg",
340
+ database_type="postgresql",
341
+ version="17",
342
+ plan_name="tier-2",
343
+ zone="se-sto1",
344
+ storage_size_gb=50,
345
+ storage_tier="maxiops",
346
+ )
347
+
348
+ creds = await client.users.reveal_password(service.id, "admin")
349
+ print(creds.connection_string)
350
+
351
+ logs = await client.monitoring.fetch_logs(service.id)
352
+ print(logs)
353
+
354
+ asyncio.run(main())
355
+ ```
356
+
357
+ ## Error Handling
358
+
359
+ API errors raise `FoundryDBError` with `status_code` and `body` attributes:
360
+
361
+ ```python
362
+ from foundrydb import FoundryDB, FoundryDBError
363
+
364
+ try:
365
+ client.services.get("non-existent-id")
366
+ except FoundryDBError as e:
367
+ print(f"API error {e.status_code}: {e}")
368
+ print(e.body) # raw dict from the API
369
+ ```
370
+
371
+ ## Configuration
372
+
373
+ ```python
374
+ client = FoundryDB(
375
+ api_url="https://api.foundrydb.com", # required
376
+ username="admin", # required
377
+ password="admin", # required
378
+ timeout=30.0, # optional, default 30s
379
+ organization_id="org_abc123", # optional, scopes all requests
380
+ )
381
+ ```
382
+
383
+ ## Typed Models
384
+
385
+ All responses are typed dataclasses:
386
+
387
+ ```python
388
+ from foundrydb import (
389
+ Organization,
390
+ Service,
391
+ DatabaseUser,
392
+ Backup,
393
+ ServiceMetrics,
394
+ CreateServiceRequest,
395
+ )
396
+ ```
397
+
398
+ Each model also exposes `.raw` for direct access to the full JSON response dict.
399
+
400
+ ### `Organization`
401
+
402
+ | Field | Type | Description |
403
+ |-------|------|-------------|
404
+ | `id` | str | Unique organization ID |
405
+ | `name` | str | Display name |
406
+ | `slug` | str | URL-safe identifier |
407
+ | `is_personal` | bool | True for a user's personal org |
408
+
409
+ ### `CreateServiceRequest`
410
+
411
+ A dataclass that mirrors the `create()` keyword arguments. You can construct it
412
+ directly and call `.to_dict()` to get the request payload:
413
+
414
+ ```python
415
+ from foundrydb import CreateServiceRequest
416
+
417
+ req = CreateServiceRequest(
418
+ name="my-valkey",
419
+ database_type="valkey",
420
+ version="8.1",
421
+ plan_name="tier-2",
422
+ zone="se-sto1",
423
+ storage_size_gb=20,
424
+ storage_tier="maxiops",
425
+ node_count=3,
426
+ auto_failover_enabled=True,
427
+ )
428
+ print(req.to_dict())
429
+ ```
430
+
431
+ ## License
432
+
433
+ MIT