|
1 |
| -# REST-DB-API |
2 | 1 |
|
3 |
| -This is a plugin to be used with apache-superset, for integrating with REST APIs |
| 2 | +# REST-DB-API |
| 3 | + |
| 4 | +This is a plugin to be used with apache-superset, for integrating with REST APIs. |
4 | 5 |
|
| 6 | +This converts REST APIs to be used as db-api in python, and builds on top of [shillelagh's](https://github.com/betodealmeida/shillelagh) generic json adapter. |
5 | 7 |
|
| 8 | +To get started, just do: |
| 9 | +```bash |
| 10 | +pip install rest-db-api |
| 11 | +``` |
6 | 12 |
|
7 |
| - - [x] POST requests (request body) |
8 |
| - - [x] headers |
9 |
| - - [ ] making the adapter read-write (for PUT/DELETE requests) |
| 13 | +Motivation |
| 14 | +1. Generic json adapter did not support request bodies and headers. |
| 15 | +2. Independance to specify http/https |
| 16 | +4. `rest` dialect enables this adapter to be used with apache superset. |
| 17 | +5. Dialect also enables us to set a base URL, and query multiple endpoints with the same 'connection'. |
| 18 | + |
| 19 | + |
| 20 | +# Examples |
| 21 | + |
| 22 | +#### GET requests |
| 23 | +Querying [weather api](https://www.weatherapi.com/) |
| 24 | + |
| 25 | +Lets assume I am querying for 3 days weather foreacast for Bangalore. The response gives the results by hour. You can get a free key from |
| 26 | +```curl |
| 27 | +https://api.weatherapi.com/v1/forecast.json?key={{your_api_key}}&q=Bangalore&days=3&aqi=no&alerts=no |
| 28 | +``` |
| 29 | + |
| 30 | +You can refer [this file](http://somelink.com) to check the response structure. |
| 31 | +We can query this with rest-db-api: |
| 32 | +```python |
| 33 | +from sqlalchemy import create_engine |
| 34 | +from restDbApi.utils import get_virtual_table |
| 35 | + |
| 36 | +engine = create_engine("rest://api.weatherapi.com?ishttps=1") |
| 37 | + |
| 38 | +endpoint = '/v1/forecast.json' |
| 39 | +params = { |
| 40 | + 'key': 'your_key', |
| 41 | + 'q': 'Bangalore', |
| 42 | + 'days': 5 |
| 43 | +} |
| 44 | +jsonpath = "$.forecast.forecastday[*]" |
| 45 | +virtual_table = get_virtual_table(endpoint=endpoint, |
| 46 | + params=params, |
| 47 | + jsonpath=jsonpath) |
| 48 | +connection = engine.connect() |
| 49 | +for i in connection.execute(f'SELECT * FROM "{virtual_table}"'): |
| 50 | + print(i) |
| 51 | +``` |
| 52 | + |
| 53 | +The response should return an array of objects/primitives. If not, we need to specify where in the response the array is (using `jsonpath`). In this case that is at `$.forecast.forecastday[*]` |
| 54 | + |
| 55 | +Now, as the shillelagh's `Adapter` class uses in memory storage - `sqllite` , we can query the data using `sqllite` syntax, like querying inside a nested JSON: |
| 56 | +```python |
| 57 | +query = f""" |
| 58 | +SELECT |
| 59 | + date, |
| 60 | + json_extract(day, "$.maxtemp_c") as max_temp_celsius, |
| 61 | + json_extract(hour, "$[6].temp_c") as six_am_celsius, |
| 62 | + json_extract(hour, "$[6].will_it_rain") as will_it_rain |
| 63 | +FROM |
| 64 | + "{virtual_table}" |
| 65 | +""" |
| 66 | +for i in connection.execute(query): |
| 67 | + print(i) |
| 68 | +``` |
| 69 | + |
| 70 | +#### POST request with headers and request body |
| 71 | + |
| 72 | +Consider this sample request |
| 73 | +```javascript |
| 74 | +curl --location -g --request POST 'https://some.api.com/some/api/path?a=60&c=someQuery&b=-50#$[*]' \ |
| 75 | +--header 'Content-Type: application/json' \ |
| 76 | +--header 'IAM_ID: satvik' \ |
| 77 | +--header 'ENVIRONMENT: staging:1.5.3' \ |
| 78 | +--header 'NAME: MY-REST-SERVICE' \ |
| 79 | +--data-raw '{ |
| 80 | + "name": "satvik", |
| 81 | + "interests": [ |
| 82 | + { |
| 83 | + "name": "badminton", |
| 84 | + "category": "sports", |
| 85 | + "stats": { |
| 86 | + "racket": "intermediate", |
| 87 | + "shuttle": "yonex mavis 500" |
| 88 | + } |
| 89 | + }, |
| 90 | + { |
| 91 | + "name": "programming", |
| 92 | + "category": "computers", |
| 93 | + "stats": { |
| 94 | + "laptop": "yw", |
| 95 | + "mouse": "5D ergonomic", |
| 96 | + "keyboard": "broken" |
| 97 | + } |
| 98 | + } |
| 99 | + ] |
| 100 | +}' |
| 101 | +``` |
| 102 | + |
| 103 | +To query this with db-api, follow the snippet: |
| 104 | +```python |
| 105 | +from sqlalchemy import create_engine |
| 106 | +from restDbApi.utils import get_virtual_table |
| 107 | + |
| 108 | +engine = create_engine("rest://some.api.com?ishttps=1") |
| 109 | + |
| 110 | +endpoint = '/some/api/path' |
| 111 | + |
| 112 | +params = { |
| 113 | + "a": 60, |
| 114 | + "b": -50, |
| 115 | + "c": "someQuery" |
| 116 | +} |
| 117 | + |
| 118 | +headers = { |
| 119 | + 'Content-Type': 'application/json', |
| 120 | + 'IAM_ID': 'satvik', |
| 121 | + 'ENVIRONMENT': 'staging:1.5.3', |
| 122 | + 'NAME': 'MY-REST-SERVICE', |
| 123 | +} |
| 124 | + |
| 125 | +body = { |
| 126 | + "name": "satvik", |
| 127 | + "interests": [ |
| 128 | + { |
| 129 | + "name": "badminton", |
| 130 | + "category": "sports", |
| 131 | + "stats": { "racket": "intermediate", "shuttle": "yonex mavis 500" } |
| 132 | + }, |
| 133 | + { |
| 134 | + "name": "programming", |
| 135 | + "category": "computers", |
| 136 | + "stats": { |
| 137 | + "laptop": "mac book pro", |
| 138 | + "mouse": "5D ergonomic", |
| 139 | + "keyboard": "broken" |
| 140 | + } |
| 141 | + } |
| 142 | + ] |
| 143 | +} |
| 144 | + |
| 145 | +jsonpath = "#$[*]" # set this according to your response |
| 146 | + |
| 147 | +virtual_table = get_virtual_table(endpoint=endpoint, |
| 148 | + params=params, |
| 149 | + headers=headers, |
| 150 | + body=body, |
| 151 | + jsonpath=jsonpath) |
| 152 | + |
| 153 | +for i in connection.execute(f'SELECT * FROM "{virtual_table}"'): |
| 154 | + print(i) |
| 155 | +``` |
| 156 | + |
| 157 | +#### Usage with apache-superset |
| 158 | +1. Go to Connect database |
| 159 | +  |
| 160 | +2. Select Shillelagh |
| 161 | +3. add the connection string with `rest://` prefix |
| 162 | + eg: `rest://api.weatherapi.com?ishttps=1` |
| 163 | +4. Gice your connection a name: eg `Weather API` |
| 164 | +1. Click test connection and then add |
| 165 | +2. Go to SQL lab and select `Weather API` from database. |
| 166 | +3. You can leave schema empty and query directly! |
| 167 | + |
| 168 | +Query is: |
| 169 | +```SQL |
| 170 | +SELECT date, |
| 171 | + json_extract(day, "$.maxtemp_c") as max_temp_celsius, |
| 172 | + json_extract(hour, "$[6].temp_c") as six_am_celsius, |
| 173 | + json_extract(hour, "$[6].will_it_rain") as will_it_rain |
| 174 | +FROM "/v1/forecast.json?key={your_key}&q=Bangalore&days=5#$.forecast.forecastday[*]"; |
| 175 | +``` |
| 176 | +Tables and schema is empty, because there's no concept for tables in REST APIs. |
| 177 | +It returns a default message. That message is configured in `rest_api_dialect.py` |
| 178 | + |
| 179 | + |
| 180 | +### Getting the virtual table |
| 181 | +In the superset's SQL lab, we're directly using |
| 182 | +```python |
| 183 | +/v1/forecast.json?key={your_key}&q=Bangalore&days=5#$.forecast.forecastday[*] |
| 184 | +``` |
| 185 | +To get the similar virtual table address for your endpoint (it may have headers or even body), use the utility `restDbApi.utils.get_virtual_table` and pass in your configs. |
| 186 | + |
| 187 | + - [x] POST requests (request body) |
| 188 | + - [x] headers |
| 189 | + - [ ] adding write support to adapter (for PUT/DELETE requests) |
0 commit comments