quillsql 1.6__tar.gz → 1.8__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.
- {quillsql-1.6 → quillsql-1.8}/PKG-INFO +1 -1
- {quillsql-1.6 → quillsql-1.8}/quillsql/core.py +15 -4
- {quillsql-1.6 → quillsql-1.8}/quillsql.egg-info/PKG-INFO +1 -1
- {quillsql-1.6 → quillsql-1.8}/quillsql.egg-info/SOURCES.txt +1 -0
- {quillsql-1.6 → quillsql-1.8}/setup.py +1 -1
- quillsql-1.6/tests/simple_test.py → quillsql-1.8/tests/external_test.py +21 -21
- quillsql-1.8/tests/simple_test.py +294 -0
- {quillsql-1.6 → quillsql-1.8}/LICENSE +0 -0
- {quillsql-1.6 → quillsql-1.8}/README.md +0 -0
- {quillsql-1.6 → quillsql-1.8}/quillsql/__init__.py +0 -0
- {quillsql-1.6 → quillsql-1.8}/quillsql.egg-info/dependency_links.txt +0 -0
- {quillsql-1.6 → quillsql-1.8}/quillsql.egg-info/requires.txt +0 -0
- {quillsql-1.6 → quillsql-1.8}/quillsql.egg-info/top_level.txt +0 -0
- {quillsql-1.6 → quillsql-1.8}/setup.cfg +0 -0
- {quillsql-1.6 → quillsql-1.8}/tests/__init__.py +0 -0
- {quillsql-1.6 → quillsql-1.8}/tests/cache_test.py +0 -0
|
@@ -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):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import psycopg2
|
|
1
2
|
import pytest
|
|
2
3
|
from quillsql import Quill
|
|
3
4
|
import os
|
|
@@ -5,10 +6,11 @@ from dotenv import load_dotenv
|
|
|
5
6
|
|
|
6
7
|
load_dotenv()
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
# Tests using an external db connection
|
|
10
|
+
db = psycopg2.connect(os.environ.get("POSTGRES_STAGING_READ"))
|
|
9
11
|
quill = Quill(
|
|
10
12
|
private_key=os.environ.get("QUILL_PRIVATE_KEY"),
|
|
11
|
-
|
|
13
|
+
psycopg2_connection=db
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -61,25 +63,6 @@ def test_config_gets_admin_metadata():
|
|
|
61
63
|
assert "customer_id" not in row
|
|
62
64
|
|
|
63
65
|
|
|
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
|
-
|
|
78
|
-
print(res)
|
|
79
|
-
assert res is not None
|
|
80
|
-
assert "rows" in res
|
|
81
|
-
|
|
82
|
-
|
|
83
66
|
def test_handles_empty_client_id():
|
|
84
67
|
res = quill.query(
|
|
85
68
|
org_id="2",
|
|
@@ -239,6 +222,22 @@ def test_orgs_works_correctly_with_client_id():
|
|
|
239
222
|
assert "orgs" in res
|
|
240
223
|
|
|
241
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
|
+
|
|
242
241
|
def test_view_fails_with_no_clientId():
|
|
243
242
|
res = quill.query(
|
|
244
243
|
org_id="2",
|
|
@@ -278,6 +277,7 @@ def test_view_works_correctly_with_clientId():
|
|
|
278
277
|
assert res["message"] == "OK"
|
|
279
278
|
|
|
280
279
|
|
|
280
|
+
# TODO: flaky vvvv
|
|
281
281
|
# def test_delete_works_correctly_with_clientId():
|
|
282
282
|
# res = quill.query(
|
|
283
283
|
# org_id="2",
|
|
@@ -0,0 +1,294 @@
|
|
|
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_config_gets_admin_metadata():
|
|
45
|
+
res = quill.query(
|
|
46
|
+
org_id="2",
|
|
47
|
+
data={
|
|
48
|
+
"metadata": {
|
|
49
|
+
"id": "65962cf6cfce31000b53c51c",
|
|
50
|
+
"orgId": "2",
|
|
51
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
52
|
+
"task": "item",
|
|
53
|
+
"filters": [],
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
assert res is not None
|
|
59
|
+
for row in res["rows"]:
|
|
60
|
+
assert row["id"] is not "id"
|
|
61
|
+
assert "customer_id" not in row
|
|
62
|
+
|
|
63
|
+
|
|
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
|
+
|
|
78
|
+
# print(res)
|
|
79
|
+
# assert res is not None
|
|
80
|
+
# assert "rows" in res
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_handles_empty_client_id():
|
|
84
|
+
res = quill.query(
|
|
85
|
+
org_id="2",
|
|
86
|
+
data={
|
|
87
|
+
"metadata": {
|
|
88
|
+
"task": "item",
|
|
89
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
90
|
+
"client_id": None,
|
|
91
|
+
"filters": [],
|
|
92
|
+
"query": "select * from transactions",
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
assert res["chartType"] == "line"
|
|
98
|
+
assert res["clientId"] == "62cda15d7c9fcca7bc0a3689"
|
|
99
|
+
assert res["name"] == "Total Spend Test"
|
|
100
|
+
assert res["dashboardName"] == "spend"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_gets_item():
|
|
104
|
+
res = quill.query(
|
|
105
|
+
org_id="2",
|
|
106
|
+
data={
|
|
107
|
+
"metadata": {
|
|
108
|
+
"task": "item",
|
|
109
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
110
|
+
"client_id": "62cda15d7c9fcca7bc0a3689",
|
|
111
|
+
"filters": [],
|
|
112
|
+
"query": "select * from transactions",
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
assert res["chartType"] == "line"
|
|
118
|
+
assert res["clientId"] == "62cda15d7c9fcca7bc0a3689"
|
|
119
|
+
assert res["name"] == "Total Spend Test"
|
|
120
|
+
assert res["dashboardName"] == "spend"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_query_for_data():
|
|
124
|
+
res = quill.query(
|
|
125
|
+
org_id="2",
|
|
126
|
+
data={
|
|
127
|
+
"metadata": {
|
|
128
|
+
"task": "query",
|
|
129
|
+
"id": "6580d48f457d7b000b7bee2c",
|
|
130
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
assert "rows" in res
|
|
136
|
+
assert "fields" in res
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_creates_a_chart():
|
|
140
|
+
res = quill.query(
|
|
141
|
+
org_id="2",
|
|
142
|
+
data={
|
|
143
|
+
"metadata": {
|
|
144
|
+
"task": "create",
|
|
145
|
+
"dateField": {"table": "transactions", "field": "created_at"},
|
|
146
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
147
|
+
"name": "Total Spend Test",
|
|
148
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
149
|
+
"customerId": "2",
|
|
150
|
+
"xAxisField": "month",
|
|
151
|
+
"xAxisLabel": "month",
|
|
152
|
+
"yAxisFields": [
|
|
153
|
+
{"field": "spend", "label": "spend", "format": "dollar_amount"}
|
|
154
|
+
],
|
|
155
|
+
"yAxisLabel": "spend",
|
|
156
|
+
"chartType": "line",
|
|
157
|
+
"dashboardName": "spend",
|
|
158
|
+
"xAxisFormat": "MMM_yyyy",
|
|
159
|
+
"columns": [
|
|
160
|
+
{"field": "spend", "label": "spend", "format": "dollar_amount"},
|
|
161
|
+
{"field": "month", "label": "month", "format": "MMM_yyyy"},
|
|
162
|
+
],
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
assert res is not None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_returns_different_configs_for_different_org_ids():
|
|
171
|
+
data = {
|
|
172
|
+
"metadata": {
|
|
173
|
+
"task": "config",
|
|
174
|
+
"name": "spend",
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
res1 = quill.query(org_id="1", data=data)
|
|
178
|
+
res2 = quill.query(org_id="2", data=data)
|
|
179
|
+
assert res1 != res2
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_returns_different_items_for_different_org_ids():
|
|
183
|
+
data = {
|
|
184
|
+
"metadata": {
|
|
185
|
+
"task": "item",
|
|
186
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
187
|
+
"filters": [],
|
|
188
|
+
"client_id": "62cda15d7c9fcca7bc0a3689",
|
|
189
|
+
"query": "select * from transactions",
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
res1 = quill.query(org_id="1", data=data)
|
|
193
|
+
res2 = quill.query(org_id="2", data=data)
|
|
194
|
+
assert res1 != res2
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_returns_different_query_data_for_different_org_ids():
|
|
198
|
+
data = {
|
|
199
|
+
"metadata": {
|
|
200
|
+
"task": "query",
|
|
201
|
+
"id": "6580d48f457d7b000b7bee2c",
|
|
202
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
res1 = quill.query(org_id="1", data=data)
|
|
206
|
+
res2 = quill.query(org_id="2", data=data)
|
|
207
|
+
assert res1 != res2
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_orgs_fails_with_no_client_id():
|
|
211
|
+
res = quill.query(
|
|
212
|
+
org_id="2",
|
|
213
|
+
data={
|
|
214
|
+
"metadata": {
|
|
215
|
+
"task": "orgs",
|
|
216
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
assert res is not None
|
|
222
|
+
assert "errorMessage" in res
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_orgs_works_correctly_with_client_id():
|
|
226
|
+
res = quill.query(
|
|
227
|
+
org_id="2",
|
|
228
|
+
data={
|
|
229
|
+
"metadata": {
|
|
230
|
+
"task": "orgs",
|
|
231
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
232
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
assert res is not None
|
|
238
|
+
assert "errorMessage" not in res
|
|
239
|
+
assert "orgs" in res
|
|
240
|
+
|
|
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
|
+
|
|
258
|
+
def test_view_fails_with_no_clientId():
|
|
259
|
+
res = quill.query(
|
|
260
|
+
org_id="2",
|
|
261
|
+
data={
|
|
262
|
+
"metadata": {
|
|
263
|
+
"task": "view",
|
|
264
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
assert res is not None
|
|
270
|
+
assert "errorMessage" in res
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_view_works_correctly_with_clientId():
|
|
274
|
+
res = quill.query(
|
|
275
|
+
org_id="2",
|
|
276
|
+
data={
|
|
277
|
+
"metadata": {
|
|
278
|
+
"task": "view",
|
|
279
|
+
"id": "6580d3aea2caa9000b1c1b06",
|
|
280
|
+
"clientId": "62cda15d7c9fcca7bc0a3689",
|
|
281
|
+
"query": "select sum(amount) as spend, date_trunc('month', created_at) as month from transactions group by month order by max(created_at);",
|
|
282
|
+
"name": "Total Spend Test",
|
|
283
|
+
"deleted": False,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
print(res)
|
|
289
|
+
assert res is not None
|
|
290
|
+
assert "errorMessage" not in res
|
|
291
|
+
assert "code" in res
|
|
292
|
+
assert "message" in res
|
|
293
|
+
assert res["code"] == 201
|
|
294
|
+
assert res["message"] == "OK"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|