quillsql 2.2.7__py3-none-any.whl → 2.2.8__py3-none-any.whl

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.
quillsql/core.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import codecs
2
3
  from dotenv import load_dotenv
3
4
 
4
5
  import requests
@@ -29,7 +30,7 @@ load_dotenv()
29
30
 
30
31
  ENV = os.getenv("PYTHON_ENV")
31
32
  DEV_HOST = "http://localhost:8080"
32
- PROD_HOST = "https://quill-344421.uc.r.appspot.com"
33
+ PROD_HOST = "https://api.quill.co"
33
34
  HOST = DEV_HOST if ENV == "development" else PROD_HOST
34
35
 
35
36
  SINGLE_TENANT = "QUILL_SINGLE_TENANT"
@@ -386,6 +387,80 @@ class Quill:
386
387
  "status": "error",
387
388
  "data": responseMetadata,
388
389
  }
390
+
391
+ async def stream(
392
+ self,
393
+ tenants,
394
+ metadata,
395
+ flags=None,
396
+ ):
397
+ if not tenants:
398
+ raise ValueError("You may not pass an empty tenants array.")
399
+
400
+ if not metadata:
401
+ yield {"type": "error", "errorText": "Missing metadata."}
402
+ return
403
+
404
+ task = metadata.get("task")
405
+ if not task:
406
+ yield {"type": "error", "errorText": "Missing task."}
407
+ return
408
+
409
+ try:
410
+ # Set tenant IDs in the connection
411
+ self.target_connection.tenant_ids = extract_tenant_ids(tenants)
412
+
413
+ # Handle tenant flags synthesis
414
+ tenant_flags = None
415
+ if tenants[0] == SINGLE_TENANT and flags:
416
+ if flags and isinstance(flags[0], dict):
417
+ tenant_flags = [{'tenantField': SINGLE_TENANT, 'flags': flags}]
418
+ else:
419
+ tenant_flags = flags
420
+
421
+ payload = {
422
+ **metadata,
423
+ "tenants": tenants,
424
+ "flags": tenant_flags,
425
+ }
426
+ # Custom JSON Encoder to handle Enums
427
+ class EnumEncoder(json.JSONEncoder):
428
+ def default(self, obj):
429
+ if isinstance(obj, Enum):
430
+ return obj.value # Convert enum to its value (string in this case)
431
+ return super().default(obj)
432
+ url = f"{self.baseUrl}/sdk/{task}"
433
+ headers = {"Authorization": f"Bearer {self.private_key}", "Content-Type": "application/json","Accept": "text/event-stream"}
434
+ encoded = json.dumps(payload, cls=EnumEncoder)
435
+
436
+ resp = requests.post(url, data=encoded, headers=headers, stream=True)
437
+ decoder = codecs.getincrementaldecoder('utf-8')()
438
+ buf = ""
439
+ for chunk in resp.iter_content(chunk_size=4096):
440
+ buf += decoder.decode(chunk)
441
+ while "\n\n" in buf:
442
+ raw_event, buf = buf.split("\n\n", 1)
443
+ data_lines = []
444
+ for line in raw_event.splitlines():
445
+ if line.startswith("data:"):
446
+ data_lines.append(line[len("data:"):].strip())
447
+ if not data_lines:
448
+ continue
449
+ payload = "\n".join(data_lines)
450
+ if payload == "[DONE]":
451
+ break
452
+ yield json.loads(payload)
453
+
454
+ # flush any partial code points at the end
455
+ buf += decoder.decode(b"", final=True)
456
+ yield buf
457
+ return
458
+ except Exception as err:
459
+ yield {
460
+ "type": "error",
461
+ "errorText": str(err).splitlines()[0],
462
+ }
463
+ return
389
464
 
390
465
  def apply_limit(self, query, limit):
391
466
  # Simple logic: if query already has a limit, don't add another
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: quillsql
3
- Version: 2.2.7
3
+ Version: 2.2.8
4
4
  Summary: Quill SDK for Python.
5
5
  Home-page: https://github.com/quill-sql/quill-python
6
6
  Author: Quill
@@ -67,3 +67,44 @@ async def quill_post(data: Request, user: dict = Depends(authenticate_jwt)):
67
67
 
68
68
  Then you can run your app like normally. Pass in this route to our react library
69
69
  on the frontend and you all set!
70
+
71
+ ## Streaming
72
+
73
+ ```python
74
+ from quillsql import Quill
75
+ from fastapi.responses import StreamingResponse
76
+ import asyncio
77
+
78
+ quill = Quill(
79
+ private_key=os.getenv("QULL_PRIVATE_KEY"),
80
+ database_connection_string=os.getenv("POSTGRES_READ"),
81
+ database_type="postgresql"
82
+ )
83
+
84
+ @app.post("/quill-stream")
85
+ async def quill_post(data: Request, user: dict = Depends(authenticate_jwt)):
86
+ # assuming user fetched via auth middleware has an userId
87
+ user_id = user["user_id"]
88
+ body = await data.json()
89
+ metadata = body.get("metadata")
90
+
91
+ quill_stream = quill.stream(
92
+ tenants=[{"tenantField": "user_id", "tenantIds": [user_id]}],
93
+ metadata=metadata,
94
+ )
95
+
96
+ async def event_generator():
97
+ # Full event types list: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol#data-stream-protocol
98
+ async for event in quill_stream:
99
+ if event["type"] == "start":
100
+ pass
101
+ elif event["type"] == "text-delta":
102
+ yield event['delta']
103
+ elif event["type"] == "finish":
104
+ return
105
+ elif event["type"] == "error":
106
+ yield event['errorText']
107
+ await asyncio.sleep(0)
108
+
109
+ return StreamingResponse(event_generator(), media_type="text/event-stream")
110
+ ```
@@ -1,5 +1,5 @@
1
1
  quillsql/__init__.py,sha256=wjJfszle5vheUbgUfJMHQqtqhx2W3UaDN4ndcRIfmkQ,236
2
- quillsql/core.py,sha256=IQZlNdEAfiEuWF4PnA4hI6iK3Yk5OI8QKwKVklwW8GA,23943
2
+ quillsql/core.py,sha256=yLDGW_BZTcY5FBxJVefCxzPBgzChy_vLHfJNylz6wuo,26737
3
3
  quillsql/error.py,sha256=n9VKHw4FAgg7ZEAz2YQ8L_8FdRG_1shwGngf2iWhUSM,175
4
4
  quillsql/assets/__init__.py,sha256=oXQ2ZS5XDXkXTYjADxNfGt55cIn_rqfgWL2EDqjTyoI,45
5
5
  quillsql/assets/pgtypes.py,sha256=-B_2wUaoAsdX7_HnJhUlx4ptZQ6x-cXwuST9ACgGFdE,33820
@@ -15,7 +15,7 @@ quillsql/utils/post_quill_executor.py,sha256=DB1RHNfqHPYarMM10vSv--UjpCZqe4qYTjq
15
15
  quillsql/utils/run_query_processes.py,sha256=QwnMr5UwXdtO_W88lv5nBaf6pJ_h5oWQnYd8K9oHQ5s,1030
16
16
  quillsql/utils/schema_conversion.py,sha256=TFfMibN9nOsxNRhHw5YIFl3jGTvipG81bxX4LFDulUY,314
17
17
  quillsql/utils/tenants.py,sha256=ZD2FuKz0gjBVSsThHDv1P8PU6EL8E009NWihE5hAH-Q,2022
18
- quillsql-2.2.7.dist-info/METADATA,sha256=EE-TzuvH71mqurYprv3i-rzqd4_REL4bB14WEHqPCaA,1786
19
- quillsql-2.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- quillsql-2.2.7.dist-info/top_level.txt,sha256=eU2vHnVqwpYQJ3ADl1Q-DIBzbYejZRUhcMdN_4zMCz8,9
21
- quillsql-2.2.7.dist-info/RECORD,,
18
+ quillsql-2.2.8.dist-info/METADATA,sha256=qfMIakgsSk045GZzk7sNtLu-2gVIQ82toNiAp3lwif0,3052
19
+ quillsql-2.2.8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
20
+ quillsql-2.2.8.dist-info/top_level.txt,sha256=eU2vHnVqwpYQJ3ADl1Q-DIBzbYejZRUhcMdN_4zMCz8,9
21
+ quillsql-2.2.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5