quillsql 1.6__py3-none-any.whl → 1.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 +15 -4
- {quillsql-1.6.dist-info → quillsql-1.8.dist-info}/METADATA +1 -1
- quillsql-1.8.dist-info/RECORD +12 -0
- tests/external_test.py +296 -0
- tests/simple_test.py +32 -34
- quillsql-1.6.dist-info/RECORD +0 -11
- {quillsql-1.6.dist-info → quillsql-1.8.dist-info}/LICENSE +0 -0
- {quillsql-1.6.dist-info → quillsql-1.8.dist-info}/WHEEL +0 -0
- {quillsql-1.6.dist-info → quillsql-1.8.dist-info}/top_level.txt +0 -0
quillsql/core.py
CHANGED
|
@@ -17,8 +17,11 @@ DEFAULT_CACHE_TTL = 24 * 60 * 60
|
|
|
17
17
|
|
|
18
18
|
# A connection pool with a cache in front.
|
|
19
19
|
class CachedPool:
|
|
20
|
-
def __init__(self, config, cache_config):
|
|
21
|
-
|
|
20
|
+
def __init__(self, config, cache_config, psycopg2_connection=None):
|
|
21
|
+
if psycopg2_connection:
|
|
22
|
+
self.pool = psycopg2_connection
|
|
23
|
+
else:
|
|
24
|
+
self.pool = psycopg2.connect(config)
|
|
22
25
|
self.cache = self.get_cache(cache_config)
|
|
23
26
|
self.ttl = cache_config and cache_config.get("ttl") or DEFAULT_CACHE_TTL
|
|
24
27
|
self.cur = self.pool.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
|
@@ -337,12 +340,20 @@ def handleDeleteTask(org_id, metadata, private_key, target_pool):
|
|
|
337
340
|
|
|
338
341
|
## Quill - Fullstack API Platform for Dashboards and Reporting.
|
|
339
342
|
class Quill:
|
|
340
|
-
def __init__(
|
|
343
|
+
def __init__(
|
|
344
|
+
self,
|
|
345
|
+
private_key,
|
|
346
|
+
database_connection_string="",
|
|
347
|
+
psycopg2_connection=None,
|
|
348
|
+
cache=None,
|
|
349
|
+
):
|
|
341
350
|
# Handles both dsn-style connection strings (eg. "dbname=test password=secret" )
|
|
342
351
|
# as well as url-style connection strings (eg. "postgres://foo@db.com")
|
|
343
352
|
to_dsn = lambda conn: make_dsn(conn) if "://" in conn else conn
|
|
344
353
|
self.database_connection_string = to_dsn(database_connection_string)
|
|
345
|
-
self.main_pool = CachedPool(
|
|
354
|
+
self.main_pool = CachedPool(
|
|
355
|
+
database_connection_string, cache, psycopg2_connection
|
|
356
|
+
)
|
|
346
357
|
self.private_key = private_key
|
|
347
358
|
|
|
348
359
|
def query(self, org_id, data):
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
quillsql/__init__.py,sha256=IgJnPRLx5GcenVt82mMQo0ydvXYB_-39Ft4Of5sEEGA,39
|
|
2
|
+
quillsql/core.py,sha256=AJFzeFfSCVmFeGgfBFotPPDRXKPL3MbQz12E48LHlwg,12362
|
|
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=-ibAbgHtcwYHzL4VxQFoPmqX8_OlKfTzlkJvEbJRy3Y,7967
|
|
7
|
+
tests/staging_test.py,sha256=zdDV6BvJRyNKCy80bxDNfK1F1oqM6oIv7ul7JPKi51Q,4962
|
|
8
|
+
quillsql-1.8.dist-info/LICENSE,sha256=f2-T2fNvArbqfNW8RwsOu2IWEwpsaRX6G-AJvLJKuzg,1068
|
|
9
|
+
quillsql-1.8.dist-info/METADATA,sha256=auI6BMo1-n9ObCk65rlRtpC7td8yhgBZauKUvouEe1c,570
|
|
10
|
+
quillsql-1.8.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
11
|
+
quillsql-1.8.dist-info/top_level.txt,sha256=DCsUgdO5twxI1s5sw-euqMt1NDDtIGMR-t9A68P3gZs,15
|
|
12
|
+
quillsql-1.8.dist-info/RECORD,,
|
tests/external_test.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import psycopg2
|
|
2
|
+
import pytest
|
|
3
|
+
from quillsql import Quill
|
|
4
|
+
import os
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
|
|
7
|
+
load_dotenv()
|
|
8
|
+
|
|
9
|
+
# Tests using an external db connection
|
|
10
|
+
db = psycopg2.connect(os.environ.get("POSTGRES_STAGING_READ"))
|
|
11
|
+
quill = Quill(
|
|
12
|
+
private_key=os.environ.get("QUILL_PRIVATE_KEY"),
|
|
13
|
+
psycopg2_connection=db
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_config_gets_metadata():
|
|
18
|
+
res = quill.query(
|
|
19
|
+
org_id="2", data={"metadata": {"task": "config", "name": "spend"}}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
assert res is not None
|
|
23
|
+
assert "sections" in res
|
|
24
|
+
assert "newQueries" in res
|
|
25
|
+
assert "filters" in res
|
|
26
|
+
assert res["filters"] == []
|
|
27
|
+
assert "fieldToRemove" in res
|
|
28
|
+
assert res["fieldToRemove"] == "customer_id"
|
|
29
|
+
|
|
30
|
+
for query in res["newQueries"]:
|
|
31
|
+
assert "columns" in query
|
|
32
|
+
assert any(
|
|
33
|
+
column["field"] == "spend"
|
|
34
|
+
and column["label"] == "spend"
|
|
35
|
+
and column["format"] == "dollar_amount"
|
|
36
|
+
for column in query["columns"]
|
|
37
|
+
)
|
|
38
|
+
assert any(
|
|
39
|
+
column["field"] == "month"
|
|
40
|
+
and column["label"] == "month"
|
|
41
|
+
and column["format"] == "MMM_yyyy"
|
|
42
|
+
for column in query["columns"]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_config_gets_admin_metadata():
|
|
47
|
+
res = quill.query(
|
|
48
|
+
org_id="2",
|
|
49
|
+
data={
|
|
50
|
+
"metadata": {
|
|
51
|
+
"id": "65962cf6cfce31000b53c51c",
|
|
52
|
+
"orgId": "2",
|
|
53
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
54
|
+
"task": "item",
|
|
55
|
+
"filters": [],
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
assert res is not None
|
|
61
|
+
for row in res["rows"]:
|
|
62
|
+
assert row["id"] is not "id"
|
|
63
|
+
assert "customer_id" not in row
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_handles_empty_client_id():
|
|
67
|
+
res = quill.query(
|
|
68
|
+
org_id="2",
|
|
69
|
+
data={
|
|
70
|
+
"metadata": {
|
|
71
|
+
"task": "item",
|
|
72
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
73
|
+
"client_id": None,
|
|
74
|
+
"filters": [],
|
|
75
|
+
"query": "select * from transactions",
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
assert res["chartType"] == "line"
|
|
81
|
+
assert res["clientId"] == "62cda15d7c9fcca7bc0a3689"
|
|
82
|
+
assert res["name"] == "Total Spend Test"
|
|
83
|
+
assert res["dashboardName"] == "spend"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_gets_item():
|
|
87
|
+
res = quill.query(
|
|
88
|
+
org_id="2",
|
|
89
|
+
data={
|
|
90
|
+
"metadata": {
|
|
91
|
+
"task": "item",
|
|
92
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
93
|
+
"client_id": "62cda15d7c9fcca7bc0a3689",
|
|
94
|
+
"filters": [],
|
|
95
|
+
"query": "select * from transactions",
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert res["chartType"] == "line"
|
|
101
|
+
assert res["clientId"] == "62cda15d7c9fcca7bc0a3689"
|
|
102
|
+
assert res["name"] == "Total Spend Test"
|
|
103
|
+
assert res["dashboardName"] == "spend"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_query_for_data():
|
|
107
|
+
res = quill.query(
|
|
108
|
+
org_id="2",
|
|
109
|
+
data={
|
|
110
|
+
"metadata": {
|
|
111
|
+
"task": "query",
|
|
112
|
+
"id": "6580d48f457d7b000b7bee2c",
|
|
113
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
assert "rows" in res
|
|
119
|
+
assert "fields" in res
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_creates_a_chart():
|
|
123
|
+
res = quill.query(
|
|
124
|
+
org_id="2",
|
|
125
|
+
data={
|
|
126
|
+
"metadata": {
|
|
127
|
+
"task": "create",
|
|
128
|
+
"dateField": {"table": "transactions", "field": "created_at"},
|
|
129
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
130
|
+
"name": "Total Spend Test",
|
|
131
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
132
|
+
"customerId": "2",
|
|
133
|
+
"xAxisField": "month",
|
|
134
|
+
"xAxisLabel": "month",
|
|
135
|
+
"yAxisFields": [
|
|
136
|
+
{"field": "spend", "label": "spend", "format": "dollar_amount"}
|
|
137
|
+
],
|
|
138
|
+
"yAxisLabel": "spend",
|
|
139
|
+
"chartType": "line",
|
|
140
|
+
"dashboardName": "spend",
|
|
141
|
+
"xAxisFormat": "MMM_yyyy",
|
|
142
|
+
"columns": [
|
|
143
|
+
{"field": "spend", "label": "spend", "format": "dollar_amount"},
|
|
144
|
+
{"field": "month", "label": "month", "format": "MMM_yyyy"},
|
|
145
|
+
],
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
assert res is not None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_returns_different_configs_for_different_org_ids():
|
|
154
|
+
data = {
|
|
155
|
+
"metadata": {
|
|
156
|
+
"task": "config",
|
|
157
|
+
"name": "spend",
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
res1 = quill.query(org_id="1", data=data)
|
|
161
|
+
res2 = quill.query(org_id="2", data=data)
|
|
162
|
+
assert res1 != res2
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_returns_different_items_for_different_org_ids():
|
|
166
|
+
data = {
|
|
167
|
+
"metadata": {
|
|
168
|
+
"task": "item",
|
|
169
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
170
|
+
"filters": [],
|
|
171
|
+
"client_id": "62cda15d7c9fcca7bc0a3689",
|
|
172
|
+
"query": "select * from transactions",
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
res1 = quill.query(org_id="1", data=data)
|
|
176
|
+
res2 = quill.query(org_id="2", data=data)
|
|
177
|
+
assert res1 != res2
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_returns_different_query_data_for_different_org_ids():
|
|
181
|
+
data = {
|
|
182
|
+
"metadata": {
|
|
183
|
+
"task": "query",
|
|
184
|
+
"id": "6580d48f457d7b000b7bee2c",
|
|
185
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
res1 = quill.query(org_id="1", data=data)
|
|
189
|
+
res2 = quill.query(org_id="2", data=data)
|
|
190
|
+
assert res1 != res2
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_orgs_fails_with_no_client_id():
|
|
194
|
+
res = quill.query(
|
|
195
|
+
org_id="2",
|
|
196
|
+
data={
|
|
197
|
+
"metadata": {
|
|
198
|
+
"task": "orgs",
|
|
199
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
assert res is not None
|
|
205
|
+
assert "errorMessage" in res
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_orgs_works_correctly_with_client_id():
|
|
209
|
+
res = quill.query(
|
|
210
|
+
org_id="2",
|
|
211
|
+
data={
|
|
212
|
+
"metadata": {
|
|
213
|
+
"task": "orgs",
|
|
214
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
215
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
assert res is not None
|
|
221
|
+
assert "errorMessage" not in res
|
|
222
|
+
assert "orgs" in res
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_orgs_works_without_id():
|
|
226
|
+
res = quill.query(
|
|
227
|
+
org_id="2",
|
|
228
|
+
data={
|
|
229
|
+
"metadata": {
|
|
230
|
+
"task": "orgs",
|
|
231
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
assert res is not None
|
|
237
|
+
assert "errorMessage" not in res
|
|
238
|
+
assert "orgs" in res
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_view_fails_with_no_clientId():
|
|
242
|
+
res = quill.query(
|
|
243
|
+
org_id="2",
|
|
244
|
+
data={
|
|
245
|
+
"metadata": {
|
|
246
|
+
"task": "view",
|
|
247
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
assert res is not None
|
|
253
|
+
assert "errorMessage" in res
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_view_works_correctly_with_clientId():
|
|
257
|
+
res = quill.query(
|
|
258
|
+
org_id="2",
|
|
259
|
+
data={
|
|
260
|
+
"metadata": {
|
|
261
|
+
"task": "view",
|
|
262
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
263
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
264
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
265
|
+
"name": "Total Spend Test",
|
|
266
|
+
"deleted": False,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
print(res)
|
|
272
|
+
assert res is not None
|
|
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
|
@@ -61,23 +61,23 @@ def test_config_gets_admin_metadata():
|
|
|
61
61
|
assert "customer_id" not in row
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def test_config_gets_admin_metadata_2():
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
# def test_config_gets_admin_metadata_2():
|
|
65
|
+
# res = quill.query(
|
|
66
|
+
# org_id="2",
|
|
67
|
+
# data={
|
|
68
|
+
# "metadata": {
|
|
69
|
+
# "id": "65895d225cd2e8cb2bd8542d",
|
|
70
|
+
# "orgId": "2",
|
|
71
|
+
# "clientId": "6579031b3e41c378aa8180ec",
|
|
72
|
+
# "task": "item",
|
|
73
|
+
# "filters": [],
|
|
74
|
+
# }
|
|
75
|
+
# },
|
|
76
|
+
# )
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
# print(res)
|
|
79
|
+
# assert res is not None
|
|
80
|
+
# assert "rows" in res
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def test_handles_empty_client_id():
|
|
@@ -239,6 +239,22 @@ def test_orgs_works_correctly_with_client_id():
|
|
|
239
239
|
assert "orgs" in res
|
|
240
240
|
|
|
241
241
|
|
|
242
|
+
def test_orgs_works_without_id():
|
|
243
|
+
res = quill.query(
|
|
244
|
+
org_id="2",
|
|
245
|
+
data={
|
|
246
|
+
"metadata": {
|
|
247
|
+
"task": "orgs",
|
|
248
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
assert res is not None
|
|
254
|
+
assert "errorMessage" not in res
|
|
255
|
+
assert "orgs" in res
|
|
256
|
+
|
|
257
|
+
|
|
242
258
|
def test_view_fails_with_no_clientId():
|
|
243
259
|
res = quill.query(
|
|
244
260
|
org_id="2",
|
|
@@ -276,21 +292,3 @@ def test_view_works_correctly_with_clientId():
|
|
|
276
292
|
assert "message" in res
|
|
277
293
|
assert res["code"] == 201
|
|
278
294
|
assert res["message"] == "OK"
|
|
279
|
-
|
|
280
|
-
|
|
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
|
quillsql-1.6.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
quillsql/__init__.py,sha256=IgJnPRLx5GcenVt82mMQo0ydvXYB_-39Ft4Of5sEEGA,39
|
|
2
|
-
quillsql/core.py,sha256=q78yz2ZYvOfpBFB1QJQrCt9fjPGzGO8BfVR5wBibBh8,12123
|
|
3
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
tests/cache_test.py,sha256=5MvAAyGF37wiRejOwG8m4uG9jxnEOseV5GtXTtjAk-8,11092
|
|
5
|
-
tests/simple_test.py,sha256=ohB-dqnzxZFytXMNN_ve9TbirGPGZisBBiCo4B8H93A,8066
|
|
6
|
-
tests/staging_test.py,sha256=zdDV6BvJRyNKCy80bxDNfK1F1oqM6oIv7ul7JPKi51Q,4962
|
|
7
|
-
quillsql-1.6.dist-info/LICENSE,sha256=f2-T2fNvArbqfNW8RwsOu2IWEwpsaRX6G-AJvLJKuzg,1068
|
|
8
|
-
quillsql-1.6.dist-info/METADATA,sha256=Atk0K3akhFk4IEi6zRPhdFuLHR79EiUD5iqs2N-bdXg,570
|
|
9
|
-
quillsql-1.6.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
10
|
-
quillsql-1.6.dist-info/top_level.txt,sha256=DCsUgdO5twxI1s5sw-euqMt1NDDtIGMR-t9A68P3gZs,15
|
|
11
|
-
quillsql-1.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|