quillsql 1.9__py3-none-any.whl → 2.0.1__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,14 +1,18 @@
1
+ import os
2
+ import json
3
+
1
4
  import requests
2
5
  import psycopg2
3
- import psycopg2.extras
4
- import json
5
6
  import redis
6
7
 
7
8
  from psycopg2.extensions import make_dsn
8
9
 
10
+
9
11
  ## The host url of the Quill metadata server
10
- HOST = "https://quill-344421.uc.r.appspot.com" # or "http://localhost:8080"
11
- # HOST = "http://localhost:8080"
12
+ ENV = os.environ.get("PYTHON_ENV")
13
+ DEV_HOST = "http://localhost:8080"
14
+ PROD_HOST = "https://quill-344421.uc.r.appspot.com"
15
+ HOST = DEV_HOST if ENV == "development" else PROD_HOST
12
16
 
13
17
 
14
18
  ## The TTL for new cache entries (default: 1h)
@@ -24,7 +28,7 @@ class CachedPool:
24
28
  self.pool = psycopg2.connect(config)
25
29
  self.cache = self.get_cache(cache_config)
26
30
  self.ttl = cache_config and cache_config.get("ttl") or DEFAULT_CACHE_TTL
27
- self.cur = self.pool.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
31
+ self.cur = self.pool.cursor()
28
32
  self.orgId = None
29
33
 
30
34
  def get_cache(self, cache_config):
@@ -44,7 +48,7 @@ class CachedPool:
44
48
  def exec(self, sql):
45
49
  self.cur.execute(sql)
46
50
  rows = self.cur.fetchall()
47
- return [json.loads(json.dumps(row, default=str)) for row in rows]
51
+ return [self._build_dict(self.cur, row) for row in rows]
48
52
 
49
53
  def query(self, sql):
50
54
  if not self.cache:
@@ -59,6 +63,13 @@ class CachedPool:
59
63
  new_result_string = json.dumps(new_result)
60
64
  self.cache.set(key, new_result_string, "EX", DEFAULT_CACHE_TTL)
61
65
  return new_result
66
+
67
+ # Parses the row from a tuple to a dict using the cursor description.
68
+ def _build_dict(self, cursor, row):
69
+ dict_row = {}
70
+ for key, col in enumerate(cursor.description):
71
+ dict_row[col[0]] = row[key]
72
+ return json.loads(json.dumps(dict_row, default=str))
62
73
 
63
74
 
64
75
  ## handles a query task
@@ -209,12 +220,12 @@ def handleItemTask(org_id, metadata, private_key, target_pool):
209
220
  row = {key: value for key, value in row.items() if key != field_to_remove}
210
221
  if field_to_remove in row:
211
222
  del row[field_to_remove]
212
-
223
+
213
224
  return {
214
225
  **resp_data,
215
226
  "fields": fields,
216
227
  "rows": rows,
217
- **({ "comparisonRows": compare_rows } if compare_rows else {})
228
+ **({"comparisonRows": compare_rows} if compare_rows else {}),
218
229
  }
219
230
 
220
231
 
@@ -254,7 +265,8 @@ def handleViewTask(org_id, metadata, private_key, target_pool):
254
265
  table_post = {"id": id, "deleted": deleted, "clientId": client_id}
255
266
  elif id:
256
267
  fields = [
257
- {"name": desc[0], "dataTypeID": desc[1]} for desc in cursor.description
268
+ {"name": desc[0], "dataTypeID": desc[1]}
269
+ for desc in cursor.description
258
270
  ]
259
271
  table_post = {
260
272
  "id": id,
@@ -294,9 +306,9 @@ def handleViewTask(org_id, metadata, private_key, target_pool):
294
306
  {
295
307
  "fieldType": next(
296
308
  (
297
- type[0]
309
+ dict(type)["typname"]
298
310
  for type in types_query
299
- if field["dataTypeID"] == type[2]
311
+ if field["dataTypeID"] == dict(type)["oid"]
300
312
  ),
301
313
  None,
302
314
  ),
@@ -308,17 +320,12 @@ def handleViewTask(org_id, metadata, private_key, target_pool):
308
320
  ],
309
321
  }
310
322
 
311
- response = requests.post(
323
+ return requests.post(
312
324
  f"{HOST}/createtable",
313
325
  json=table_post,
314
326
  headers={"Authorization": f"Bearer {private_key}"},
315
327
  ).json()
316
328
 
317
- if dict(response)["ok"] == 1:
318
- return {"code": 201, "message": "OK"}
319
- else:
320
- return {"errorMessage": "Failed to create table."}
321
-
322
329
 
323
330
  ## handle delete tasks
324
331
  def handleDeleteTask(org_id, metadata, private_key, target_pool):
@@ -383,5 +390,5 @@ class Quill:
383
390
  else:
384
391
  return {"error": 400, "errorMessage": "unknown task"}
385
392
  except Exception as err:
386
- # err.with_traceback()
393
+ # print(err, err.__traceback__)
387
394
  return {"error": str(err), "errorMessage": str(err) if err else ""}
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.1
2
+ Name: quillsql
3
+ Version: 2.0.1
4
+ Summary: Quill SDK for Python.
5
+ Home-page: https://github.com/quill-sql/quill-python
6
+ Author: Quill
7
+ Author-email: shawn@quillsql.com
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: psycopg2-binary
11
+ Requires-Dist: requests
12
+ Requires-Dist: redis
13
+ Requires-Dist: python-dotenv
14
+ Requires-Dist: pytest
15
+
16
+ # Quill Python SDK
17
+ [![Quill SDK](https://github.com/quill-sql/quill-python/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/quill-sql/quill-python/actions/workflows/ci.yaml)
18
+
19
+ ## Quickstart
20
+
21
+ First, install the quillsql package by running:
22
+
23
+ ```bash
24
+ $ pip install quillsql
25
+ ```
26
+
27
+ Then, add a `/quill` endpoint to your existing python server. For example, if
28
+ you were running a FASTAPI app, you would just add the endpoint like this:
29
+
30
+ ```python
31
+ from quillsql import Quill
32
+ from fastapi import FastAPI, Request
33
+
34
+ app = FastAPI()
35
+
36
+ quill = Quill(
37
+ private_key=<YOUR_PRIVATE_KEY_HERE>,
38
+ database_connection_string=<YOUR_DB_CONNECTION_STRING_HERE>,
39
+ )
40
+
41
+ # ... your existing endpoints here ...
42
+
43
+ @app.post("/quill")
44
+ async def quill_post(data: Request):
45
+ body = await data.json()
46
+ return quill.query(org_id="2", data=body)
47
+ ```
48
+
49
+ Then you can run your app like normally. Pass in this route to our react library
50
+ on the frontend and you all set!
51
+
52
+
53
+ ## Questions
54
+ If you have any questions, please reach out to us!
@@ -0,0 +1,11 @@
1
+ quillsql/__init__.py,sha256=IgJnPRLx5GcenVt82mMQo0ydvXYB_-39Ft4Of5sEEGA,39
2
+ quillsql/core.py,sha256=WkVwPfbhH_b3mdWGP3rYRNk1wFAVOo8DFlJwnrlLozA,12428
3
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ tests/cache_test.py,sha256=5MvAAyGF37wiRejOwG8m4uG9jxnEOseV5GtXTtjAk-8,11092
5
+ tests/external_test.py,sha256=pTHIiWMA0JFTapdHzoNR7ud1uCXw1r8P0kBbVLWxaXk,7466
6
+ tests/simple_test.py,sha256=DTGOFHvljEJs6akVIrDwLr9shYIP68t7-JrF_d17O2Y,7378
7
+ quillsql-2.0.1.dist-info/LICENSE,sha256=f2-T2fNvArbqfNW8RwsOu2IWEwpsaRX6G-AJvLJKuzg,1068
8
+ quillsql-2.0.1.dist-info/METADATA,sha256=IeQgdsc33ygynQnGt75syOXbxIAtkSjtmmbMAtctYzA,1395
9
+ quillsql-2.0.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
10
+ quillsql-2.0.1.dist-info/top_level.txt,sha256=DCsUgdO5twxI1s5sw-euqMt1NDDtIGMR-t9A68P3gZs,15
11
+ quillsql-2.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
tests/external_test.py CHANGED
@@ -271,26 +271,3 @@ def test_view_works_correctly_with_clientId():
271
271
  print(res)
272
272
  assert res is not None
273
273
  assert "errorMessage" not in res
274
- assert "code" in res
275
- assert "message" in res
276
- assert res["code"] == 201
277
- assert res["message"] == "OK"
278
-
279
-
280
- # TODO: flaky vvvv
281
- # def test_delete_works_correctly_with_clientId():
282
- # res = quill.query(
283
- # org_id="2",
284
- # data={
285
- # "metadata": {
286
- # "task": "delete",
287
- # "id": "6580d3aea2caa9000b1c1b06",
288
- # "clientId": "62cda15d7c9fcca7bc0a3689",
289
- # },
290
- # },
291
- # )
292
-
293
- # # TODO: lifecycle tests
294
- # # returns '' if not found
295
- # assert res is not None
296
- # assert "errorMessage" not in res
tests/simple_test.py CHANGED
@@ -268,7 +268,3 @@ def test_view_works_correctly_with_clientId():
268
268
 
269
269
  assert res is not None
270
270
  assert "errorMessage" not in res
271
- assert "code" in res
272
- assert "message" in res
273
- assert res["code"] == 201
274
- assert res["message"] == "OK"
@@ -1,35 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: quillsql
3
- Version: 1.9
4
- Summary: Quill SDK for Python.
5
- Home-page: https://github.com/quill-sql/quill-python
6
- Author: Quill
7
- Author-email: shawn@quillsql.com
8
- Description-Content-Type: text/markdown
9
- License-File: LICENSE
10
- Requires-Dist: psycopg2-binary
11
- Requires-Dist: requests
12
- Requires-Dist: redis
13
- Requires-Dist: python-dotenv
14
- Requires-Dist: pytest
15
-
16
- Quill SDK for Python
17
-
18
-
19
- ## Quickstart
20
-
21
- Install dependencies
22
- ```bash
23
- $ pip install .
24
- ```
25
-
26
- Run unit tests
27
- ```bash
28
- $ pytest
29
- ```
30
-
31
- ## Troubleshooting
32
-
33
- ```
34
- $ python3 -m pip install --upgrade setuptools
35
- ```
@@ -1,12 +0,0 @@
1
- quillsql/__init__.py,sha256=IgJnPRLx5GcenVt82mMQo0ydvXYB_-39Ft4Of5sEEGA,39
2
- quillsql/core.py,sha256=R2tDwVEwnU625O7etSSZpns-bXUldODygPIAiBNq2lY,12243
3
- tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- tests/cache_test.py,sha256=5MvAAyGF37wiRejOwG8m4uG9jxnEOseV5GtXTtjAk-8,11092
5
- tests/external_test.py,sha256=PxHB3Kg-V3lDHHfZVUour3aVYGPS3P1bMKqfajt9oFA,8063
6
- tests/simple_test.py,sha256=04AmSaXFkYAaATpLDFVQjKa3klMg42h-isjmBneEr0Y,7495
7
- tests/staging_test.py,sha256=zdDV6BvJRyNKCy80bxDNfK1F1oqM6oIv7ul7JPKi51Q,4962
8
- quillsql-1.9.dist-info/LICENSE,sha256=f2-T2fNvArbqfNW8RwsOu2IWEwpsaRX6G-AJvLJKuzg,1068
9
- quillsql-1.9.dist-info/METADATA,sha256=4Gv79Z5qRPSLGzc97Rr4e7CEE-uUWvOXb9C8RyxMF1k,570
10
- quillsql-1.9.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
11
- quillsql-1.9.dist-info/top_level.txt,sha256=DCsUgdO5twxI1s5sw-euqMt1NDDtIGMR-t9A68P3gZs,15
12
- quillsql-1.9.dist-info/RECORD,,
tests/staging_test.py DELETED
@@ -1,168 +0,0 @@
1
- import pytest
2
- from quillsql import Quill
3
- import os
4
- from dotenv import load_dotenv
5
-
6
- load_dotenv()
7
-
8
-
9
- quill = Quill(
10
- private_key=os.environ.get("QUILL_PRIVATE_KEY"),
11
- database_connection_string=os.environ.get("POSTGRES_STAGING_READ"),
12
- )
13
-
14
-
15
- def test_config_gets_metadata():
16
- res = quill.query(
17
- org_id="2", data={"metadata": {"task": "config", "name": "spend"}}
18
- )
19
-
20
- assert res is not None
21
- assert "sections" in res
22
- assert "newQueries" in res
23
- assert "filters" in res
24
- assert res["filters"] == []
25
- assert "fieldToRemove" in res
26
- assert res["fieldToRemove"] == "customer_id"
27
-
28
- for query in res["newQueries"]:
29
- assert "columns" in query
30
- assert any(
31
- column["field"] == "spend"
32
- and column["label"] == "spend"
33
- and column["format"] == "dollar_amount"
34
- for column in query["columns"]
35
- )
36
- assert any(
37
- column["field"] == "month"
38
- and column["label"] == "month"
39
- and column["format"] == "MMM_yyyy"
40
- for column in query["columns"]
41
- )
42
-
43
-
44
- def test_handles_empty_client_id():
45
- res = quill.query(
46
- org_id="2",
47
- data={
48
- "metadata": {
49
- "task": "item",
50
- "id": "6580d3aea2caa9000b1c1b06",
51
- "client_id": None,
52
- "filters": [],
53
- "query": "select * from transactions",
54
- }
55
- },
56
- )
57
-
58
- assert res["chartType"] == "line"
59
- assert res["clientId"] == "62cda15d7c9fcca7bc0a3689"
60
- assert res["name"] == "Total Spend Test"
61
- assert res["dashboardName"] == "spend"
62
-
63
-
64
- def test_gets_item():
65
- res = quill.query(
66
- org_id="2",
67
- data={
68
- "metadata": {
69
- "task": "item",
70
- "id": "6580d3aea2caa9000b1c1b06",
71
- "client_id": "62cda15d7c9fcca7bc0a3689",
72
- "filters": [],
73
- "query": "select * from transactions",
74
- }
75
- },
76
- )
77
-
78
- assert res["chartType"] == "line"
79
- assert res["clientId"] == "62cda15d7c9fcca7bc0a3689"
80
- assert res["name"] == "Total Spend Test"
81
- assert res["dashboardName"] == "spend"
82
-
83
-
84
- def test_query_for_data():
85
- res = quill.query(
86
- org_id="2",
87
- data={
88
- "metadata": {
89
- "task": "query",
90
- "id": "6580d48f457d7b000b7bee2c",
91
- "query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
92
- }
93
- },
94
- )
95
-
96
- assert "rows" in res
97
- assert "fields" in res
98
-
99
-
100
- def test_creates_a_chart():
101
- res = quill.query(
102
- org_id="2",
103
- data={
104
- "metadata": {
105
- "task": "create",
106
- "dateField": {"table": "transactions", "field": "created_at"},
107
- "query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
108
- "name": "Total Spend Test",
109
- "clientId": "62cda15d7c9fcca7bc0a3689",
110
- "customerId": "2",
111
- "xAxisField": "month",
112
- "xAxisLabel": "month",
113
- "yAxisFields": [
114
- {"field": "spend", "label": "spend", "format": "dollar_amount"}
115
- ],
116
- "yAxisLabel": "spend",
117
- "chartType": "line",
118
- "dashboardName": "spend",
119
- "xAxisFormat": "MMM_yyyy",
120
- "columns": [
121
- {"field": "spend", "label": "spend", "format": "dollar_amount"},
122
- {"field": "month", "label": "month", "format": "MMM_yyyy"},
123
- ],
124
- }
125
- },
126
- )
127
-
128
- assert res is not None
129
-
130
-
131
- def test_returns_different_configs_for_different_org_ids():
132
- data = {
133
- "metadata": {
134
- "task": "config",
135
- "name": "spend",
136
- }
137
- }
138
- res1 = quill.query(org_id="1", data=data)
139
- res2 = quill.query(org_id="2", data=data)
140
- assert res1 != res2
141
-
142
-
143
- def test_returns_different_items_for_different_org_ids():
144
- data = {
145
- "metadata": {
146
- "task": "item",
147
- "id": "6580d3aea2caa9000b1c1b06",
148
- "filters": [],
149
- "client_id": "62cda15d7c9fcca7bc0a3689",
150
- "query": "select * from transactions",
151
- }
152
- }
153
- res1 = quill.query(org_id="1", data=data)
154
- res2 = quill.query(org_id="2", data=data)
155
- assert res1 != res2
156
-
157
-
158
- def test_returns_different_query_data_for_different_org_ids():
159
- data = {
160
- "metadata": {
161
- "task": "query",
162
- "id": "6580d48f457d7b000b7bee2c",
163
- "query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
164
- }
165
- }
166
- res1 = quill.query(org_id="1", data=data)
167
- res2 = quill.query(org_id="2", data=data)
168
- assert res1 != res2