feedple-sdk 0.0.1__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,79 @@
1
+ # =========================
2
+ # Python bytecode
3
+ # =========================
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.pyo
7
+ *.pyd
8
+
9
+ # =========================
10
+ # Virtual environments
11
+ # =========================
12
+ .venv/
13
+ venv/
14
+ env/
15
+ ENV/
16
+
17
+ # pyenv
18
+ .python-version
19
+
20
+ # Hatch / virtualenv artifacts
21
+ .pytest_cache/
22
+ .hatch/
23
+ .hatch.env/
24
+ .hatch_build/
25
+
26
+ # =========================
27
+ # Build / packaging
28
+ # =========================
29
+ build/
30
+ dist/
31
+ *.egg-info/
32
+ .eggs/
33
+
34
+ # =========================
35
+ # Testing
36
+ # =========================
37
+ .coverage
38
+ coverage.xml
39
+ htmlcov/
40
+
41
+ # =========================
42
+ # MyPy / type checking
43
+ # =========================
44
+ .mypy_cache/
45
+ .dmypy.json
46
+
47
+ # =========================
48
+ # Ruff / lint caches
49
+ # =========================
50
+ .ruff_cache/
51
+
52
+ # =========================
53
+ # Pytest
54
+ # =========================
55
+ .pytest_cache/
56
+
57
+ # =========================
58
+ # OS files
59
+ # =========================
60
+ .DS_Store
61
+ Thumbs.db
62
+
63
+ # =========================
64
+ # IDEs
65
+ # =========================
66
+ .vscode/
67
+ .idea/
68
+
69
+ # =========================
70
+ # Logs
71
+ # =========================
72
+ *.log
73
+
74
+ # =========================
75
+ # Environment variables
76
+ # =========================
77
+ .env
78
+ .env.*
79
+ !.env.example
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Umar Bello Kanwa <umarbellokanwa@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,427 @@
1
+ Metadata-Version: 2.4
2
+ Name: feedple-sdk
3
+ Version: 0.0.1
4
+ Summary: Python SDK for Feedple AI
5
+ Project-URL: Documentation, https://github.com/Umar Bello Kanwa/feedple-sdk#readme
6
+ Project-URL: Issues, https://github.com/Umar Bello Kanwa/feedple-sdk/issues
7
+ Project-URL: Source, https://github.com/Umar Bello Kanwa/feedple-sdk
8
+ Author-email: Umar Bello Kanwa <umarbellokanwa@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE.txt
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: Implementation :: CPython
19
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
20
+ Requires-Python: >=3.8
21
+ Requires-Dist: sqlalchemy
22
+ Requires-Dist: sqlglot
23
+ Requires-Dist: websockets
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Feedple Python SDK
27
+
28
+ [![PyPI - Version](https://img.shields.io/pypi/v/feedple-sdk.svg)](https://pypi.org/project/feedple-sdk)
29
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/feedple-sdk.svg)](https://pypi.org/project/feedple-sdk)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://spdx.org/licenses/MIT.html)
31
+
32
+ The official Python SDK for the [Feedple AI](https://feedple.ai) platform. Connect your database to Feedple with a single class — the SDK handles authentication, schema sync, and query execution automatically.
33
+
34
+ ---
35
+
36
+ ## Table of Contents
37
+
38
+ - [How It Works](#how-it-works)
39
+ - [Requirements](#requirements)
40
+ - [Installation](#installation)
41
+ - [Quick Start](#quick-start)
42
+ - [Configuration](#configuration)
43
+ - [Identity & Access Control](#identity--access-control)
44
+ - [Schema Sync](#schema-sync)
45
+ - [IR Query Execution](#ir-query-execution)
46
+ - [Connection Management](#connection-management)
47
+ - [Utilities](#utilities)
48
+ - [API Reference](#api-reference)
49
+ - [License](#license)
50
+
51
+ ---
52
+
53
+ ## How It Works
54
+
55
+ ```
56
+ Your App Feedple SDK Feedple API
57
+ │ │ │
58
+ │ FeedpleSDK(api_key, db, …) │ │
59
+ │─────────────────────────────>│ │
60
+ │ │── connect() ─────────────────>│
61
+ │ │<─ auth.ack (session_id) ──────│
62
+ │ │ │
63
+ │ │── schema.started ────────────>│
64
+ │ │── schema.data (chunks) ──────>│
65
+ │ │── schema.completed ──────────>│
66
+ │ │ │
67
+ │ │<─ ir.request (IR payload) ────│
68
+ │ │── ir.ack ────────────────────>│
69
+ │ │ [executes SQL against DB] │
70
+ │ │── ir.result ─────────────────>│
71
+ │ │ │
72
+ ```
73
+
74
+ 1. **Connect & Authenticate** — Opens a persistent WebSocket connection and sends your API key. The server responds with a `session_id` used to resume after disconnects.
75
+ 2. **Sync Schema** — Inspects your database (tables, columns, PKs, FKs, indexes) and sends the schema to Feedple in chunks. Re-syncs on a configurable interval; skips if nothing changed.
76
+ 3. **Execute Queries** — Receives incoming IR query requests from Feedple, enforces RBAC, executes them safely against your database, and returns the results.
77
+
78
+ ---
79
+
80
+ ## Requirements
81
+
82
+ - Python **≥ 3.8**
83
+ - A database supported by **SQLAlchemy** (PostgreSQL, MySQL, SQLite, and others)
84
+
85
+ ---
86
+
87
+ ## Installation
88
+
89
+ ```bash
90
+ pip install feedple-sdk
91
+ ```
92
+
93
+ Install with a specific database driver:
94
+
95
+ ```bash
96
+ # PostgreSQL
97
+ pip install feedple-sdk psycopg2-binary
98
+
99
+ # MySQL
100
+ pip install feedple-sdk pymysql
101
+
102
+ # SQLite (built into Python — no extra driver needed)
103
+ pip install feedple-sdk
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Quick Start
109
+
110
+ ```python
111
+ from sqlalchemy import create_engine
112
+ from feedple_sdk import FeedpleSDK, Identity
113
+
114
+ # 1. Create your database engine (SQLAlchemy)
115
+ db = create_engine("postgresql+psycopg2://user:pass@localhost/mydb")
116
+
117
+ # 2. Define which tables Feedple can access
118
+ identity = Identity(
119
+ name="production",
120
+ all_tables=True, # expose every table, or…
121
+ # allowed_tables=["users", "orders", "products"], # …restrict to specific tables
122
+ )
123
+
124
+ # 3. Initialise the SDK — it starts immediately in the background
125
+ sdk = FeedpleSDK(
126
+ api_key="sk_live_...",
127
+ db=db,
128
+ identity=identity,
129
+ )
130
+
131
+ # Your application keeps running normally.
132
+ # The SDK manages the WebSocket connection on a background thread.
133
+ ```
134
+
135
+ > **Note:** `FeedpleSDK.__init__` starts a background daemon thread and returns immediately. Your application does not block.
136
+
137
+ ---
138
+
139
+ ## Configuration
140
+
141
+ All parameters are keyword-only.
142
+
143
+ ```python
144
+ sdk = FeedpleSDK(
145
+ # Required
146
+ api_key="sk_live_...", # Your Feedple API key
147
+ db=engine, # SQLAlchemy Engine
148
+ identity=identity, # Identity (see below)
149
+
150
+ # Schema sync
151
+ auto_sync=True, # Sync schema on startup and periodically (default: True)
152
+ sync_interval=60, # Seconds between sync cycles (default: 60)
153
+
154
+ # Connection
155
+ reconnect_enabled=True, # Reconnect on disconnect (default: True)
156
+ max_retries=None, # Max reconnect attempts; None = unlimited (default: None)
157
+ probe_before_connect=False, # HTTP probe before WS handshake for clearer errors (default: False)
158
+ )
159
+ ```
160
+
161
+ ### Parameter Reference
162
+
163
+ | Parameter | Type | Default | Description |
164
+ |-----------|------|---------|-------------|
165
+ | `api_key` | `str` | **required** | Your Feedple API key. Raises `ValueError` if empty. |
166
+ | `db` | `Engine` | **required** | SQLAlchemy database engine. |
167
+ | `identity` | `Identity` | **required** | Controls which tables are accessible. |
168
+ | `auto_sync` | `bool` | `True` | Periodically re-inspect and send the schema. |
169
+ | `sync_interval` | `int` | `60` | Seconds between schema re-sync cycles. |
170
+ | `reconnect_enabled` | `bool` | `True` | Automatically reconnect on connection loss. |
171
+ | `max_retries` | `int \| None` | `None` | Cap on reconnect attempts. `None` means unlimited. |
172
+ | `probe_before_connect` | `bool` | `False` | Perform an HTTP GET before the WS handshake to surface clearer server error messages (e.g. 403 bodies). |
173
+
174
+ ---
175
+
176
+ ## Identity & Access Control
177
+
178
+ `Identity` controls which database tables Feedple can see and query.
179
+
180
+ ```python
181
+ from feedple_sdk import Identity
182
+
183
+ # Grant access to all tables
184
+ admin_identity = Identity(
185
+ name="admin",
186
+ all_tables=True,
187
+ )
188
+
189
+ # Restrict to specific tables only
190
+ restricted_identity = Identity(
191
+ name="analytics-service",
192
+ allowed_tables=["users", "orders", "products", "events"],
193
+ all_tables=False, # default
194
+ )
195
+ ```
196
+
197
+ ### Identity Fields
198
+
199
+ | Field | Type | Default | Description |
200
+ |-------|------|---------|-------------|
201
+ | `name` | `str \| None` | — | Human-readable label for this identity. |
202
+ | `allowed_tables` | `list[str]` | `[]` | Tables this identity may access. Ignored when `all_tables=True`. |
203
+ | `all_tables` | `bool` | `False` | When `True`, all current and future tables are accessible. |
204
+
205
+ > **Security:** The SDK enforces RBAC on every IR query request. If the IR references a table not in `allowed_tables`, a `PermissionError` is raised and an `ir.error` is returned to the server — the query never reaches the database.
206
+
207
+ ---
208
+
209
+ ## Schema Sync
210
+
211
+ The SDK automatically syncs your schema on startup and then every `sync_interval` seconds. You can also trigger a sync manually:
212
+
213
+ ```python
214
+ import asyncio
215
+
216
+ # Manually trigger a schema sync (async — must be called from an async context)
217
+ asyncio.run(sdk.sync_schema())
218
+ ```
219
+
220
+ ### What gets synced
221
+
222
+ For each table the identity can access, the SDK sends:
223
+
224
+ - **Columns** — name, type string, nullable flag, default value
225
+ - **Primary key** — constrained column names
226
+ - **Foreign keys** — local columns, referenced table, referenced columns
227
+ - **Indexes** — name, column names, unique flag
228
+ - **Unique constraints** — name, column names
229
+
230
+ ### Sensitive column filtering
231
+
232
+ The following column names are **never sent** to Feedple, regardless of the identity setting:
233
+
234
+ `password`, `token`, `secret`, `hash`, `salt`, `ssn`, `credit_card`
235
+
236
+ You can call `filter_sensitive_columns` manually:
237
+
238
+ ```python
239
+ from feedple_sdk.core.schema_services import filter_sensitive_columns
240
+
241
+ safe_schema = filter_sensitive_columns(raw_schema)
242
+ ```
243
+
244
+ ---
245
+
246
+ ## IR Query Execution
247
+
248
+ The SDK receives IR (Intermediate Representation) query objects from the Feedple server and executes them against your database. You do not call this yourself — it is invoked automatically via the WebSocket.
249
+
250
+ ### IR Schema
251
+
252
+ ```python
253
+ ir = {
254
+ "operation": "query", # always "query"
255
+ "table": "orders", # primary FROM table
256
+ "fields": [ # columns to SELECT
257
+ {"column": "orders.id", "expression": None, "alias": None},
258
+ {"column": "orders.amount", "expression": "sum", "alias": "total"},
259
+ {"column": "orders.user_id","expression": "count(distinct)", "alias": "unique_users"},
260
+ ],
261
+ "joins": [ # JOIN clauses
262
+ {
263
+ "table": "users",
264
+ "on_left": "orders.user_id",
265
+ "on_right": "users.id",
266
+ "join_type": "INNER", # "INNER" or "LEFT"
267
+ }
268
+ ],
269
+ "filters": [ # WHERE conditions (ANDed together)
270
+ {"column": "orders.status", "operator": "eq", "value": "active"},
271
+ {"column": "orders.amount", "operator": "gte", "value": 100},
272
+ ],
273
+ "group_by": ["orders.status"],
274
+ "having": [{"column": "orders.id", "operator": "gt", "value": 5}],
275
+ "order_by": ["orders.created_at DESC"],
276
+ "limit": 100,
277
+ "offset": 0,
278
+ }
279
+ ```
280
+
281
+ ### Supported filter operators
282
+
283
+ | Operator | Aliases | SQL |
284
+ |----------|---------|-----|
285
+ | `eq` | `=` | `col = ?` |
286
+ | `neq` | `!=` | `col != ?` |
287
+ | `gt` | `>` | `col > ?` |
288
+ | `gte` | `>=` | `col >= ?` |
289
+ | `lt` | `<` | `col < ?` |
290
+ | `lte` | `<=` | `col <= ?` |
291
+ | `in` | `in_` | `col IN (?, ?, …)` |
292
+ | `like` | — | `col LIKE ?` |
293
+ | `ilike` | — | `col ILIKE ?` |
294
+
295
+ ### Supported aggregate expressions
296
+
297
+ `count`, `count(distinct)`, `sum`, `avg`, `min`, `max`
298
+
299
+ ---
300
+
301
+ ## Connection Management
302
+
303
+ ### Reconnect behaviour
304
+
305
+ The SDK reconnects automatically with exponential back-off:
306
+
307
+ | Attempt | Delay |
308
+ |---------|-------|
309
+ | 1 | 5 s |
310
+ | 2 | 10 s |
311
+ | 3 | 20 s |
312
+ | 4+ | 40 s → capped at 60 s |
313
+
314
+ Authentication failures (`auth.error`) are **not** retried — the SDK stops immediately and logs the error.
315
+
316
+ ### Session resume
317
+
318
+ The `session_id` received in `auth.ack` is stored and re-sent on every reconnect attempt. The server resumes the session if it is still within TTL, or issues a new session ID if it has expired.
319
+
320
+ ### Stopping the SDK
321
+
322
+ ```python
323
+ sdk.stop()
324
+ ```
325
+
326
+ Signals the background thread to stop, closes the WebSocket, and halts the event loop. Idempotent — safe to call multiple times.
327
+
328
+ ---
329
+
330
+ ## Utilities
331
+
332
+ ### SQLCompiler
333
+
334
+ Validate and RBAC-check raw SQL strings before executing them yourself:
335
+
336
+ ```python
337
+ compiler = sdk._build_compiler()
338
+
339
+ # Raises PermissionError if the SQL references a denied table
340
+ safe_sql = compiler.compile("SELECT id, name FROM users WHERE active = 1")
341
+ ```
342
+
343
+ ### Schema utilities
344
+
345
+ ```python
346
+ from feedple_sdk.core.schema_services import (
347
+ get_schema,
348
+ generate_schema_hash,
349
+ should_sync_schema,
350
+ filter_sensitive_columns,
351
+ )
352
+
353
+ schema = get_schema(db=engine, identity=identity)
354
+ hash_ = generate_schema_hash(schema)
355
+ changed = should_sync_schema(old_schema, new_schema)
356
+ ```
357
+
358
+ ---
359
+
360
+ ## API Reference
361
+
362
+ ### `FeedpleSDK`
363
+
364
+ ```python
365
+ class FeedpleSDK:
366
+ def __init__(
367
+ self,
368
+ *,
369
+ api_key: str,
370
+ db: Engine,
371
+ identity: Identity,
372
+ auto_sync: bool = True,
373
+ sync_interval: int = 60,
374
+ reconnect_enabled: bool = True,
375
+ max_retries: Optional[int] = None,
376
+ probe_before_connect: bool = False,
377
+ ) -> None: ...
378
+
379
+ async def sync_schema(self) -> None: ...
380
+ def stop(self) -> None: ...
381
+ ```
382
+
383
+ ### `Identity`
384
+
385
+ ```python
386
+ @dataclass
387
+ class Identity:
388
+ name: Optional[str]
389
+ allowed_tables: List[str] = field(default_factory=list)
390
+ all_tables: bool = False
391
+ ```
392
+
393
+ ### `PolicyEngine`
394
+
395
+ ```python
396
+ class PolicyEngine:
397
+ def __init__(self, identity: Identity): ...
398
+ def can_access_table(self, table: str) -> bool: ...
399
+ def validate_ir_access(self, ir: dict) -> None: ...
400
+ ```
401
+
402
+ ### `SQLCompiler`
403
+
404
+ ```python
405
+ class SQLCompiler:
406
+ def __init__(self, policy: PolicyEngine, dialect: str = "postgres"): ...
407
+ def parse(self, sql: str) -> exp.Expression: ...
408
+ def extract_tables(self, ast: exp.Expression) -> List[str]: ...
409
+ def validate_access(self, tables: List[str]) -> None: ...
410
+ def compile(self, sql: str) -> str: ...
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Environment URLs
416
+
417
+ By default the SDK targets the **development** server (`localhost:8000`). To target production, set `DEV_ENV = False` in `src/feedple_sdk/core/request.py`:
418
+
419
+ ```python
420
+ DEV_ENV = False # uses wss://feedple-ai.onrender.com/api/v1/tenants/ws
421
+ ```
422
+
423
+ ---
424
+
425
+ ## License
426
+
427
+ `feedple-sdk` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.