quillsql 2.0__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 +17 -6
- quillsql-2.0.1.dist-info/METADATA +54 -0
- quillsql-2.0.1.dist-info/RECORD +11 -0
- {quillsql-2.0.dist-info → quillsql-2.0.1.dist-info}/WHEEL +1 -1
- quillsql-2.0.dist-info/METADATA +0 -35
- quillsql-2.0.dist-info/RECORD +0 -12
- tests/staging_test.py +0 -168
- {quillsql-2.0.dist-info → quillsql-2.0.1.dist-info}/LICENSE +0 -0
- {quillsql-2.0.dist-info → quillsql-2.0.1.dist-info}/top_level.txt +0 -0
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
|
-
|
|
11
|
-
|
|
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(
|
|
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 [
|
|
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
|
|
@@ -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
|
+
[](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,,
|
quillsql-2.0.dist-info/METADATA
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: quillsql
|
|
3
|
-
Version: 2.0
|
|
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
|
-
```
|
quillsql-2.0.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
quillsql/__init__.py,sha256=IgJnPRLx5GcenVt82mMQo0ydvXYB_-39Ft4Of5sEEGA,39
|
|
2
|
-
quillsql/core.py,sha256=dl6ry6_C6NUZKlU4gVaneD8zwEvdndcYLTEn_XgaDQk,12129
|
|
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
|
-
tests/staging_test.py,sha256=zdDV6BvJRyNKCy80bxDNfK1F1oqM6oIv7ul7JPKi51Q,4962
|
|
8
|
-
quillsql-2.0.dist-info/LICENSE,sha256=f2-T2fNvArbqfNW8RwsOu2IWEwpsaRX6G-AJvLJKuzg,1068
|
|
9
|
-
quillsql-2.0.dist-info/METADATA,sha256=Atl3xFsavnxO6DWo4BaeJTLo37rtHffvC4csuVldGFA,570
|
|
10
|
-
quillsql-2.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
11
|
-
quillsql-2.0.dist-info/top_level.txt,sha256=DCsUgdO5twxI1s5sw-euqMt1NDDtIGMR-t9A68P3gZs,15
|
|
12
|
-
quillsql-2.0.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
|
|
File without changes
|
|
File without changes
|