queryapigate 0.6.1__tar.gz → 0.7.1__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.
- queryapigate-0.7.1/LICENSE +110 -0
- {queryapigate-0.6.1/queryapigate.egg-info → queryapigate-0.7.1}/PKG-INFO +58 -31
- {queryapigate-0.6.1 → queryapigate-0.7.1}/README.md +56 -29
- {queryapigate-0.6.1 → queryapigate-0.7.1}/pyproject.toml +1 -1
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/__init__.py +1 -1
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/app.py +4 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/examples.py +6 -2
- {queryapigate-0.6.1 → queryapigate-0.7.1/queryapigate.egg-info}/PKG-INFO +58 -31
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_examples.py +40 -0
- queryapigate-0.6.1/LICENSE +0 -21
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/__main__.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/apikeys.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/bundle.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/cache.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/cli.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/collection_admin.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/config.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/cors.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/definitions.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/engine.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/errors.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/formats.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/lib/h2-2.2.224.jar +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/logging_setup.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/metrics.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/openapi.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/params.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/pool.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/postman.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/ratelimit.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/runners.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/schema.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/sqltools.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/store.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate/ui.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate.egg-info/SOURCES.txt +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate.egg-info/dependency_links.txt +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate.egg-info/entry_points.txt +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate.egg-info/requires.txt +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/queryapigate.egg-info/top_level.txt +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/setup.cfg +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_api_keys.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_audit_log.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_cli.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_collection_bundle.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_collection_postman.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_collections.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_connection_encryption.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_cors_and_rate_limit.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_duckdb.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_integration.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_jdbc.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_observability.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_params.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_queryapigate.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_release_check.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_response_cache.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_roles.py +0 -0
- {queryapigate-0.6.1 → queryapigate-0.7.1}/tests/test_sql_guard_fuzz.py +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, MIT Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-MIT
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2024-2026 Anantha Raju C
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the MIT license that is effective on the second anniversary of the date we make
|
|
91
|
+
the Software available. On or after that date, you may use the Software under
|
|
92
|
+
the MIT license, in which case the following will apply:
|
|
93
|
+
|
|
94
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
95
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
96
|
+
the Software without restriction, including without limitation the rights to
|
|
97
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
98
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
99
|
+
so, subject to the following conditions:
|
|
100
|
+
|
|
101
|
+
The above copyright notice and this permission notice shall be included in all
|
|
102
|
+
copies or substantial portions of the Software.
|
|
103
|
+
|
|
104
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
105
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
106
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
107
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
108
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
109
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
110
|
+
SOFTWARE.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queryapigate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Turn SQL into a REST API: run queries against MySQL, PostgreSQL, ClickHouse, SQLite or H2 over HTTP and get JSON, CSV, XML, YAML or XLSX back.
|
|
5
5
|
Author-email: Anantha Raju C <arcswdev@gmail.com>
|
|
6
|
-
License-Expression: MIT
|
|
6
|
+
License-Expression: FSL-1.1-MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/AnanthaRajuC/QueryAPIGate
|
|
8
8
|
Project-URL: Documentation, https://AnanthaRajuC.github.io/QueryAPIGate/
|
|
9
9
|
Project-URL: Repository, https://github.com/AnanthaRajuC/QueryAPIGate
|
|
@@ -64,7 +64,7 @@ Dynamic: license-file
|
|
|
64
64
|
[](https://codecov.io/gh/AnanthaRajuC/QueryAPIGate)
|
|
65
65
|
[](https://pypi.org/project/queryapigate/)
|
|
66
66
|
[](https://pypi.org/project/queryapigate/)
|
|
67
|
-
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
67
|
+
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
68
68
|

|
|
69
69
|
|
|
70
70
|
### Turn SQL queries into secure, governed REST APIs.
|
|
@@ -73,6 +73,12 @@ Dynamic: license-file
|
|
|
73
73
|
|
|
74
74
|
[**Full documentation**](https://AnanthaRajuC.github.io/QueryAPIGate/)
|
|
75
75
|
|
|
76
|
+
<p align="center">
|
|
77
|
+
<a href="https://www.youtube.com/watch?v=aiVbAkA2LS8">
|
|
78
|
+
<img src="https://raw.githubusercontent.com/AnanthaRajuC/QueryAPIGate/main/documentation/screenshots/demo-thumbnail.png" alt="Watch the 60-second demo: saved SQL queries as REST endpoints, scoped API keys, and the Move preview showing which keys gain or lose access" width="640">
|
|
79
|
+
</a>
|
|
80
|
+
</p>
|
|
81
|
+
|
|
76
82
|
**QueryAPIGate** is a self-hosted, single Flask service that runs SQL against your databases and returns the results as
|
|
77
83
|
JSON, NDJSON, XML, YAML, CSV, TSV or Excel. Save a query once and it becomes a versioned endpoint with typed,
|
|
78
84
|
injection-safe parameters and run history - without writing a controller, a repository layer, pagination, auth or
|
|
@@ -81,13 +87,13 @@ serialization boilerplate for it.
|
|
|
81
87
|
Write SQL. Configure the query. Apply access controls. Get an API.
|
|
82
88
|
|
|
83
89
|
~~~bash
|
|
84
|
-
$ curl 'http://127.0.0.1:5000/q/
|
|
85
|
-
[{"
|
|
90
|
+
$ curl 'http://127.0.0.1:5000/q/example_film_search?text=Harbor&page_size=2'
|
|
91
|
+
[{"film_id":48,"title":"Broken Harbor","category":"Action","rating":"PG-13"},{"film_id":44,"title":"Electric Harbor","category":"Documentary","rating":"G"}]
|
|
86
92
|
|
|
87
|
-
$ curl 'http://127.0.0.1:5000/q/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
$ curl 'http://127.0.0.1:5000/q/example_top_films?top_n=2&category=Comedy&format=csv'
|
|
94
|
+
title,category,rating,rentals,rank
|
|
95
|
+
Electric Signal,Comedy,PG,977,1
|
|
96
|
+
Crimson Garden,Comedy,PG-13,631,2
|
|
91
97
|
~~~
|
|
92
98
|
|
|
93
99
|
<p align="center">
|
|
@@ -273,11 +279,11 @@ version rather than overwriting the old one, so a change to the SQL doesn't sile
|
|
|
273
279
|
### Bound query parameters
|
|
274
280
|
|
|
275
281
|
```sql
|
|
276
|
-
SELECT * FROM
|
|
282
|
+
SELECT * FROM film WHERE film_id = :id
|
|
277
283
|
```
|
|
278
284
|
|
|
279
285
|
```http
|
|
280
|
-
GET /q/
|
|
286
|
+
GET /q/film_by_id?id=7
|
|
281
287
|
```
|
|
282
288
|
|
|
283
289
|
Values are sent to the database separately from the SQL text, so they cannot be injected into it. Parameters can
|
|
@@ -416,8 +422,8 @@ and CI on every push. See [Development](#development) below.
|
|
|
416
422
|
without building a service per query:
|
|
417
423
|
|
|
418
424
|
```http
|
|
419
|
-
GET /q/
|
|
420
|
-
GET /q/
|
|
425
|
+
GET /q/film_by_id?id=7
|
|
426
|
+
GET /q/example_top_films?top_n=5&category=Comedy
|
|
421
427
|
```
|
|
422
428
|
|
|
423
429
|
**Reporting APIs.** Turn an existing analytical SQL report into a reusable endpoint that dashboards or scheduled
|
|
@@ -483,17 +489,17 @@ From a clone: `pip install -e ".[dev]"`. Or use Docker - see [below](#docker).
|
|
|
483
489
|
Fastest: `queryapigate examples load && queryapigate serve` installs four worked [example APIs](documentation/EXAMPLES.md)
|
|
484
490
|
(reporting, dashboard, export, partner) you can try, and `queryapigate examples unload` removes them again.
|
|
485
491
|
|
|
486
|
-
Or, from a clone, the repository ships two sample SQLite databases and a couple of saved queries:
|
|
487
|
-
|
|
488
492
|
~~~bash
|
|
489
|
-
|
|
490
|
-
cp db_connections.example.json db_connections.json
|
|
493
|
+
queryapigate examples load
|
|
491
494
|
queryapigate serve # http://127.0.0.1:5000
|
|
492
495
|
~~~
|
|
493
496
|
|
|
497
|
+
The examples come with a small generated `examples` database (films, customers and rentals), so you can also run ad-hoc SQL
|
|
498
|
+
straight away:
|
|
499
|
+
|
|
494
500
|
~~~bash
|
|
495
501
|
curl -X POST 'http://127.0.0.1:5000/execute_sql?page_size=3' -H 'Content-Type: application/json' \
|
|
496
|
-
-d '{"sql": "SELECT
|
|
502
|
+
-d '{"sql": "SELECT film_id, title, rating FROM film WHERE film_id > :min", "params": {"min": 10}, "connection_name": "examples"}'
|
|
497
503
|
~~~
|
|
498
504
|
|
|
499
505
|
Open <http://127.0.0.1:5000/docs> for the interactive API reference, or <http://127.0.0.1:5000/ui> for a small
|
|
@@ -504,24 +510,26 @@ every supported database) and `saved_sql/`. Edit the file, set `"active": true`,
|
|
|
504
510
|
|
|
505
511
|
## Saving a query as an endpoint
|
|
506
512
|
|
|
513
|
+
This uses the `examples` connection that `queryapigate examples load` sets up:
|
|
514
|
+
|
|
507
515
|
~~~bash
|
|
508
516
|
curl -X PATCH http://127.0.0.1:5000/save_sql_to_file -H 'Content-Type: application/json' -d '{
|
|
509
|
-
"filename": "
|
|
510
|
-
"sql_query": "SELECT * FROM
|
|
511
|
-
"query_parameters": {"id": {"type": "int", "min": 1, "max":
|
|
512
|
-
"connection_name": "
|
|
513
|
-
"author": "me", "description": "Look up
|
|
517
|
+
"filename": "film_by_id",
|
|
518
|
+
"sql_query": "SELECT * FROM film WHERE film_id = :id",
|
|
519
|
+
"query_parameters": {"id": {"type": "int", "min": 1, "max": 60, "description": "Film id"}},
|
|
520
|
+
"connection_name": "examples",
|
|
521
|
+
"author": "me", "description": "Look up a film"
|
|
514
522
|
}'
|
|
515
523
|
|
|
516
|
-
curl 'http://127.0.0.1:5000/q/
|
|
517
|
-
curl 'http://127.0.0.1:5000/q/
|
|
524
|
+
curl 'http://127.0.0.1:5000/q/film_by_id?id=7&format=yaml'
|
|
525
|
+
curl 'http://127.0.0.1:5000/q/film_by_id?id=0'
|
|
518
526
|
# {"error": "Invalid parameters: id must be at least 1", "errors": {"id": "must be at least 1"}}
|
|
519
527
|
~~~
|
|
520
528
|
|
|
521
529
|
Rules: `type` (`int`, `float`, `str`, `bool`), `default`, `required`, `enum`, `min`/`max`, `min_length`/`max_length`,
|
|
522
530
|
`pattern` and `description` - see [the API reference](documentation/API.md#parameter-rules).
|
|
523
531
|
|
|
524
|
-
Saving again under the same name adds version 2; `DELETE /saved_sql/
|
|
532
|
+
Saving again under the same name adds version 2; `DELETE /saved_sql/film_by_id?version=1` removes one version.
|
|
525
533
|
|
|
526
534
|
Add `"cache_ttl": 60` to cache a response for that many seconds (`X-Cache: HIT`/`MISS`, `ETag`, `Cache-Control`) -
|
|
527
535
|
opt-in, and never used for a query that writes. See
|
|
@@ -608,7 +616,7 @@ instead of it - a request has to pass both. See [Per-key rate limiting](document
|
|
|
608
616
|
Every release is published to GitHub Container Registry for `linux/amd64` and `linux/arm64`:
|
|
609
617
|
|
|
610
618
|
~~~bash
|
|
611
|
-
docker run -p 5000:5000 -v
|
|
619
|
+
docker run -p 5000:5000 -v queryapigate-data:/data -e QUERYAPIGATE_API_KEY=change-me ghcr.io/anantharajuc/queryapigate:latest
|
|
612
620
|
~~~
|
|
613
621
|
|
|
614
622
|
| Tag | Contents |
|
|
@@ -617,7 +625,10 @@ docker run -p 5000:5000 -v "$PWD/data:/data" -e QUERYAPIGATE_API_KEY=change-me g
|
|
|
617
625
|
| `X.Y.Z-h2`, `latest-h2` | The same plus Java and the H2 driver - also the variant to use for a generic `jdbc` connection (mount your vendor's jar) |
|
|
618
626
|
|
|
619
627
|
The container keeps `db_connections.json` and `saved_sql/` in `/data` (create a starter with
|
|
620
|
-
`docker run --rm -v
|
|
628
|
+
`docker run --rm -v queryapigate-data:/data ghcr.io/anantharajuc/queryapigate queryapigate init`). The named volume above works
|
|
629
|
+
out of the box. To use a folder on the host instead (`-v "$PWD/data:/data"`), create it yourself first (`mkdir data`) and make sure
|
|
630
|
+
it is writable by uid 1000, the container's user - a folder that Docker creates for you is owned by root, which the container
|
|
631
|
+
cannot write to (or run with `--user "$(id -u):$(id -g)"`). It runs as a non-root user under
|
|
621
632
|
gunicorn with one worker (the files are protected by an in-process lock) and a health check on `/health`. Behind a
|
|
622
633
|
reverse proxy or load balancer, set `QUERYAPIGATE_TRUST_PROXY=1`. To build it yourself:
|
|
623
634
|
`docker build -t queryapigate .` (add `--build-arg WITH_H2=true` for H2).
|
|
@@ -715,12 +726,28 @@ observability are all useful areas. Please review [CONTRIBUTING.md](CONTRIBUTING
|
|
|
715
726
|
|
|
716
727
|
## Third-party components
|
|
717
728
|
|
|
718
|
-
The wheel bundles the [H2 Database](https://h2database.com) JDBC driver (MPL 2.0 / EPL 1.0). The
|
|
719
|
-
|
|
729
|
+
The wheel bundles the [H2 Database](https://h2database.com) JDBC driver (MPL 2.0 / EPL 1.0). The example data is generated
|
|
730
|
+
locally by `queryapigate examples load`; no third-party datasets are included.
|
|
720
731
|
|
|
721
732
|
## License
|
|
722
733
|
|
|
723
|
-
|
|
734
|
+
QueryAPIGate is **source-available**, under the
|
|
735
|
+
[Functional Source License, Version 1.1, MIT Future License](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
736
|
+
(`FSL-1.1-MIT`) © Anantha Raju C. It is not an OSI-approved open source license.
|
|
737
|
+
|
|
738
|
+
In plain terms - the [LICENSE](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE) is the only authoritative text:
|
|
739
|
+
|
|
740
|
+
- **You may** use, copy, modify and redistribute it for any purpose that is not a *Competing Use* - including running it
|
|
741
|
+
inside your own company, non-commercial education and research, and professional services you provide to someone who
|
|
742
|
+
is themselves licensed to use it.
|
|
743
|
+
- **You may not** make it available to others in a commercial product or service that substitutes for QueryAPIGate or
|
|
744
|
+
offers the same or substantially similar functionality (for example, selling it, or hosting it as a paid service).
|
|
745
|
+
- **Each version becomes MIT** on the second anniversary of the date it was released, and can then be used under MIT terms.
|
|
746
|
+
- **Versions up to and including 0.6.1** were released under the MIT license. They are no longer distributed (and the
|
|
747
|
+
repository history was reset in 0.7.0), but a copy you already obtained under MIT remains yours under those terms.
|
|
748
|
+
|
|
749
|
+
Need something the license does not cover, such as offering it as part of a hosted service? Get in touch - commercial
|
|
750
|
+
licensing may be available. This is a summary, not legal advice.
|
|
724
751
|
|
|
725
752
|
## Contact
|
|
726
753
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://codecov.io/gh/AnanthaRajuC/QueryAPIGate)
|
|
5
5
|
[](https://pypi.org/project/queryapigate/)
|
|
6
6
|
[](https://pypi.org/project/queryapigate/)
|
|
7
|
-
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
7
|
+
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
8
8
|

|
|
9
9
|
|
|
10
10
|
### Turn SQL queries into secure, governed REST APIs.
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
[**Full documentation**](https://AnanthaRajuC.github.io/QueryAPIGate/)
|
|
15
15
|
|
|
16
|
+
<p align="center">
|
|
17
|
+
<a href="https://www.youtube.com/watch?v=aiVbAkA2LS8">
|
|
18
|
+
<img src="https://raw.githubusercontent.com/AnanthaRajuC/QueryAPIGate/main/documentation/screenshots/demo-thumbnail.png" alt="Watch the 60-second demo: saved SQL queries as REST endpoints, scoped API keys, and the Move preview showing which keys gain or lose access" width="640">
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
16
22
|
**QueryAPIGate** is a self-hosted, single Flask service that runs SQL against your databases and returns the results as
|
|
17
23
|
JSON, NDJSON, XML, YAML, CSV, TSV or Excel. Save a query once and it becomes a versioned endpoint with typed,
|
|
18
24
|
injection-safe parameters and run history - without writing a controller, a repository layer, pagination, auth or
|
|
@@ -21,13 +27,13 @@ serialization boilerplate for it.
|
|
|
21
27
|
Write SQL. Configure the query. Apply access controls. Get an API.
|
|
22
28
|
|
|
23
29
|
~~~bash
|
|
24
|
-
$ curl 'http://127.0.0.1:5000/q/
|
|
25
|
-
[{"
|
|
30
|
+
$ curl 'http://127.0.0.1:5000/q/example_film_search?text=Harbor&page_size=2'
|
|
31
|
+
[{"film_id":48,"title":"Broken Harbor","category":"Action","rating":"PG-13"},{"film_id":44,"title":"Electric Harbor","category":"Documentary","rating":"G"}]
|
|
26
32
|
|
|
27
|
-
$ curl 'http://127.0.0.1:5000/q/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
$ curl 'http://127.0.0.1:5000/q/example_top_films?top_n=2&category=Comedy&format=csv'
|
|
34
|
+
title,category,rating,rentals,rank
|
|
35
|
+
Electric Signal,Comedy,PG,977,1
|
|
36
|
+
Crimson Garden,Comedy,PG-13,631,2
|
|
31
37
|
~~~
|
|
32
38
|
|
|
33
39
|
<p align="center">
|
|
@@ -213,11 +219,11 @@ version rather than overwriting the old one, so a change to the SQL doesn't sile
|
|
|
213
219
|
### Bound query parameters
|
|
214
220
|
|
|
215
221
|
```sql
|
|
216
|
-
SELECT * FROM
|
|
222
|
+
SELECT * FROM film WHERE film_id = :id
|
|
217
223
|
```
|
|
218
224
|
|
|
219
225
|
```http
|
|
220
|
-
GET /q/
|
|
226
|
+
GET /q/film_by_id?id=7
|
|
221
227
|
```
|
|
222
228
|
|
|
223
229
|
Values are sent to the database separately from the SQL text, so they cannot be injected into it. Parameters can
|
|
@@ -356,8 +362,8 @@ and CI on every push. See [Development](#development) below.
|
|
|
356
362
|
without building a service per query:
|
|
357
363
|
|
|
358
364
|
```http
|
|
359
|
-
GET /q/
|
|
360
|
-
GET /q/
|
|
365
|
+
GET /q/film_by_id?id=7
|
|
366
|
+
GET /q/example_top_films?top_n=5&category=Comedy
|
|
361
367
|
```
|
|
362
368
|
|
|
363
369
|
**Reporting APIs.** Turn an existing analytical SQL report into a reusable endpoint that dashboards or scheduled
|
|
@@ -423,17 +429,17 @@ From a clone: `pip install -e ".[dev]"`. Or use Docker - see [below](#docker).
|
|
|
423
429
|
Fastest: `queryapigate examples load && queryapigate serve` installs four worked [example APIs](documentation/EXAMPLES.md)
|
|
424
430
|
(reporting, dashboard, export, partner) you can try, and `queryapigate examples unload` removes them again.
|
|
425
431
|
|
|
426
|
-
Or, from a clone, the repository ships two sample SQLite databases and a couple of saved queries:
|
|
427
|
-
|
|
428
432
|
~~~bash
|
|
429
|
-
|
|
430
|
-
cp db_connections.example.json db_connections.json
|
|
433
|
+
queryapigate examples load
|
|
431
434
|
queryapigate serve # http://127.0.0.1:5000
|
|
432
435
|
~~~
|
|
433
436
|
|
|
437
|
+
The examples come with a small generated `examples` database (films, customers and rentals), so you can also run ad-hoc SQL
|
|
438
|
+
straight away:
|
|
439
|
+
|
|
434
440
|
~~~bash
|
|
435
441
|
curl -X POST 'http://127.0.0.1:5000/execute_sql?page_size=3' -H 'Content-Type: application/json' \
|
|
436
|
-
-d '{"sql": "SELECT
|
|
442
|
+
-d '{"sql": "SELECT film_id, title, rating FROM film WHERE film_id > :min", "params": {"min": 10}, "connection_name": "examples"}'
|
|
437
443
|
~~~
|
|
438
444
|
|
|
439
445
|
Open <http://127.0.0.1:5000/docs> for the interactive API reference, or <http://127.0.0.1:5000/ui> for a small
|
|
@@ -444,24 +450,26 @@ every supported database) and `saved_sql/`. Edit the file, set `"active": true`,
|
|
|
444
450
|
|
|
445
451
|
## Saving a query as an endpoint
|
|
446
452
|
|
|
453
|
+
This uses the `examples` connection that `queryapigate examples load` sets up:
|
|
454
|
+
|
|
447
455
|
~~~bash
|
|
448
456
|
curl -X PATCH http://127.0.0.1:5000/save_sql_to_file -H 'Content-Type: application/json' -d '{
|
|
449
|
-
"filename": "
|
|
450
|
-
"sql_query": "SELECT * FROM
|
|
451
|
-
"query_parameters": {"id": {"type": "int", "min": 1, "max":
|
|
452
|
-
"connection_name": "
|
|
453
|
-
"author": "me", "description": "Look up
|
|
457
|
+
"filename": "film_by_id",
|
|
458
|
+
"sql_query": "SELECT * FROM film WHERE film_id = :id",
|
|
459
|
+
"query_parameters": {"id": {"type": "int", "min": 1, "max": 60, "description": "Film id"}},
|
|
460
|
+
"connection_name": "examples",
|
|
461
|
+
"author": "me", "description": "Look up a film"
|
|
454
462
|
}'
|
|
455
463
|
|
|
456
|
-
curl 'http://127.0.0.1:5000/q/
|
|
457
|
-
curl 'http://127.0.0.1:5000/q/
|
|
464
|
+
curl 'http://127.0.0.1:5000/q/film_by_id?id=7&format=yaml'
|
|
465
|
+
curl 'http://127.0.0.1:5000/q/film_by_id?id=0'
|
|
458
466
|
# {"error": "Invalid parameters: id must be at least 1", "errors": {"id": "must be at least 1"}}
|
|
459
467
|
~~~
|
|
460
468
|
|
|
461
469
|
Rules: `type` (`int`, `float`, `str`, `bool`), `default`, `required`, `enum`, `min`/`max`, `min_length`/`max_length`,
|
|
462
470
|
`pattern` and `description` - see [the API reference](documentation/API.md#parameter-rules).
|
|
463
471
|
|
|
464
|
-
Saving again under the same name adds version 2; `DELETE /saved_sql/
|
|
472
|
+
Saving again under the same name adds version 2; `DELETE /saved_sql/film_by_id?version=1` removes one version.
|
|
465
473
|
|
|
466
474
|
Add `"cache_ttl": 60` to cache a response for that many seconds (`X-Cache: HIT`/`MISS`, `ETag`, `Cache-Control`) -
|
|
467
475
|
opt-in, and never used for a query that writes. See
|
|
@@ -548,7 +556,7 @@ instead of it - a request has to pass both. See [Per-key rate limiting](document
|
|
|
548
556
|
Every release is published to GitHub Container Registry for `linux/amd64` and `linux/arm64`:
|
|
549
557
|
|
|
550
558
|
~~~bash
|
|
551
|
-
docker run -p 5000:5000 -v
|
|
559
|
+
docker run -p 5000:5000 -v queryapigate-data:/data -e QUERYAPIGATE_API_KEY=change-me ghcr.io/anantharajuc/queryapigate:latest
|
|
552
560
|
~~~
|
|
553
561
|
|
|
554
562
|
| Tag | Contents |
|
|
@@ -557,7 +565,10 @@ docker run -p 5000:5000 -v "$PWD/data:/data" -e QUERYAPIGATE_API_KEY=change-me g
|
|
|
557
565
|
| `X.Y.Z-h2`, `latest-h2` | The same plus Java and the H2 driver - also the variant to use for a generic `jdbc` connection (mount your vendor's jar) |
|
|
558
566
|
|
|
559
567
|
The container keeps `db_connections.json` and `saved_sql/` in `/data` (create a starter with
|
|
560
|
-
`docker run --rm -v
|
|
568
|
+
`docker run --rm -v queryapigate-data:/data ghcr.io/anantharajuc/queryapigate queryapigate init`). The named volume above works
|
|
569
|
+
out of the box. To use a folder on the host instead (`-v "$PWD/data:/data"`), create it yourself first (`mkdir data`) and make sure
|
|
570
|
+
it is writable by uid 1000, the container's user - a folder that Docker creates for you is owned by root, which the container
|
|
571
|
+
cannot write to (or run with `--user "$(id -u):$(id -g)"`). It runs as a non-root user under
|
|
561
572
|
gunicorn with one worker (the files are protected by an in-process lock) and a health check on `/health`. Behind a
|
|
562
573
|
reverse proxy or load balancer, set `QUERYAPIGATE_TRUST_PROXY=1`. To build it yourself:
|
|
563
574
|
`docker build -t queryapigate .` (add `--build-arg WITH_H2=true` for H2).
|
|
@@ -655,12 +666,28 @@ observability are all useful areas. Please review [CONTRIBUTING.md](CONTRIBUTING
|
|
|
655
666
|
|
|
656
667
|
## Third-party components
|
|
657
668
|
|
|
658
|
-
The wheel bundles the [H2 Database](https://h2database.com) JDBC driver (MPL 2.0 / EPL 1.0). The
|
|
659
|
-
|
|
669
|
+
The wheel bundles the [H2 Database](https://h2database.com) JDBC driver (MPL 2.0 / EPL 1.0). The example data is generated
|
|
670
|
+
locally by `queryapigate examples load`; no third-party datasets are included.
|
|
660
671
|
|
|
661
672
|
## License
|
|
662
673
|
|
|
663
|
-
|
|
674
|
+
QueryAPIGate is **source-available**, under the
|
|
675
|
+
[Functional Source License, Version 1.1, MIT Future License](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
676
|
+
(`FSL-1.1-MIT`) © Anantha Raju C. It is not an OSI-approved open source license.
|
|
677
|
+
|
|
678
|
+
In plain terms - the [LICENSE](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE) is the only authoritative text:
|
|
679
|
+
|
|
680
|
+
- **You may** use, copy, modify and redistribute it for any purpose that is not a *Competing Use* - including running it
|
|
681
|
+
inside your own company, non-commercial education and research, and professional services you provide to someone who
|
|
682
|
+
is themselves licensed to use it.
|
|
683
|
+
- **You may not** make it available to others in a commercial product or service that substitutes for QueryAPIGate or
|
|
684
|
+
offers the same or substantially similar functionality (for example, selling it, or hosting it as a paid service).
|
|
685
|
+
- **Each version becomes MIT** on the second anniversary of the date it was released, and can then be used under MIT terms.
|
|
686
|
+
- **Versions up to and including 0.6.1** were released under the MIT license. They are no longer distributed (and the
|
|
687
|
+
repository history was reset in 0.7.0), but a copy you already obtained under MIT remains yours under those terms.
|
|
688
|
+
|
|
689
|
+
Need something the license does not cover, such as offering it as part of a hosted service? Get in touch - commercial
|
|
690
|
+
licensing may be available. This is a summary, not legal advice.
|
|
664
691
|
|
|
665
692
|
## Contact
|
|
666
693
|
|
|
@@ -8,7 +8,7 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "Turn SQL into a REST API: run queries against MySQL, PostgreSQL, ClickHouse, SQLite or H2 over HTTP and get JSON, CSV, XML, YAML or XLSX back."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
|
-
license = "MIT"
|
|
11
|
+
license = "FSL-1.1-MIT"
|
|
12
12
|
authors = [{ name = "Anantha Raju C", email = "arcswdev@gmail.com" }]
|
|
13
13
|
keywords = ["sql", "api", "rest", "flask", "database", "mysql", "postgresql", "clickhouse", "sqlite", "h2", "jdbc"]
|
|
14
14
|
classifiers = [
|
|
@@ -76,6 +76,10 @@ def load_examples_at_startup():
|
|
|
76
76
|
log.warning('QUERYAPIGATE_LOAD_EXAMPLES is set but the examples could not be loaded: %s',
|
|
77
77
|
getattr(error, 'message', error))
|
|
78
78
|
return
|
|
79
|
+
except Exception as error: # a convenience must never take the server down, whatever went wrong
|
|
80
|
+
log.warning('QUERYAPIGATE_LOAD_EXAMPLES is set but the examples could not be loaded (unexpected error: %s)',
|
|
81
|
+
error, exc_info=True)
|
|
82
|
+
return
|
|
79
83
|
if added['connection'] or added['queries'] or added['roles']:
|
|
80
84
|
store.record_audit('startup', 'load_examples', 'examples', added)
|
|
81
85
|
log.info('Loaded the example APIs: %d queries, %d roles', len(added['queries']), len(added['roles']))
|
|
@@ -19,6 +19,7 @@ walkthrough in ``documentation/EXAMPLES.md`` shows how to create one from the ex
|
|
|
19
19
|
"""
|
|
20
20
|
import os
|
|
21
21
|
import random
|
|
22
|
+
import sqlite3
|
|
22
23
|
from datetime import datetime, timedelta, timezone
|
|
23
24
|
from typing import Any
|
|
24
25
|
|
|
@@ -78,7 +79,6 @@ CREATE INDEX rental_film_idx ON rental(film_id);
|
|
|
78
79
|
def build_database(path, now=None, seed=42):
|
|
79
80
|
"""Create the example SQLite database at ``path`` (which must not exist). Deterministic for a given ``now`` and
|
|
80
81
|
``seed``. Dates are UTC, to match SQLite's own ``'now'`` that the example queries compare against."""
|
|
81
|
-
import sqlite3
|
|
82
82
|
rng = random.Random(seed)
|
|
83
83
|
now = now or datetime.now(timezone.utc).replace(tzinfo=None)
|
|
84
84
|
titles = [f'{a} {n}' for a in _ADJECTIVES for n in _NOUNS]
|
|
@@ -274,7 +274,11 @@ def load(now=None):
|
|
|
274
274
|
db_path = config.home() / DB_FILE
|
|
275
275
|
if _marked_connection() is None:
|
|
276
276
|
if not db_path.exists():
|
|
277
|
-
|
|
277
|
+
try:
|
|
278
|
+
build_database(str(db_path), now)
|
|
279
|
+
except sqlite3.Error as error: # SQLite's own error type - not an OSError, so callers would miss it
|
|
280
|
+
raise ApiError(f'Could not create the example database in {config.home()} ({error}). Is the data '
|
|
281
|
+
'folder writable by the user QueryAPIGate runs as?', 500) from None
|
|
278
282
|
store.update_connections({CONNECTION: {'db': 'sqlite', 'database': DB_FILE, 'active': True,
|
|
279
283
|
'example': True}})
|
|
280
284
|
added['connection'] = True
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queryapigate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Turn SQL into a REST API: run queries against MySQL, PostgreSQL, ClickHouse, SQLite or H2 over HTTP and get JSON, CSV, XML, YAML or XLSX back.
|
|
5
5
|
Author-email: Anantha Raju C <arcswdev@gmail.com>
|
|
6
|
-
License-Expression: MIT
|
|
6
|
+
License-Expression: FSL-1.1-MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/AnanthaRajuC/QueryAPIGate
|
|
8
8
|
Project-URL: Documentation, https://AnanthaRajuC.github.io/QueryAPIGate/
|
|
9
9
|
Project-URL: Repository, https://github.com/AnanthaRajuC/QueryAPIGate
|
|
@@ -64,7 +64,7 @@ Dynamic: license-file
|
|
|
64
64
|
[](https://codecov.io/gh/AnanthaRajuC/QueryAPIGate)
|
|
65
65
|
[](https://pypi.org/project/queryapigate/)
|
|
66
66
|
[](https://pypi.org/project/queryapigate/)
|
|
67
|
-
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
67
|
+
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
68
68
|

|
|
69
69
|
|
|
70
70
|
### Turn SQL queries into secure, governed REST APIs.
|
|
@@ -73,6 +73,12 @@ Dynamic: license-file
|
|
|
73
73
|
|
|
74
74
|
[**Full documentation**](https://AnanthaRajuC.github.io/QueryAPIGate/)
|
|
75
75
|
|
|
76
|
+
<p align="center">
|
|
77
|
+
<a href="https://www.youtube.com/watch?v=aiVbAkA2LS8">
|
|
78
|
+
<img src="https://raw.githubusercontent.com/AnanthaRajuC/QueryAPIGate/main/documentation/screenshots/demo-thumbnail.png" alt="Watch the 60-second demo: saved SQL queries as REST endpoints, scoped API keys, and the Move preview showing which keys gain or lose access" width="640">
|
|
79
|
+
</a>
|
|
80
|
+
</p>
|
|
81
|
+
|
|
76
82
|
**QueryAPIGate** is a self-hosted, single Flask service that runs SQL against your databases and returns the results as
|
|
77
83
|
JSON, NDJSON, XML, YAML, CSV, TSV or Excel. Save a query once and it becomes a versioned endpoint with typed,
|
|
78
84
|
injection-safe parameters and run history - without writing a controller, a repository layer, pagination, auth or
|
|
@@ -81,13 +87,13 @@ serialization boilerplate for it.
|
|
|
81
87
|
Write SQL. Configure the query. Apply access controls. Get an API.
|
|
82
88
|
|
|
83
89
|
~~~bash
|
|
84
|
-
$ curl 'http://127.0.0.1:5000/q/
|
|
85
|
-
[{"
|
|
90
|
+
$ curl 'http://127.0.0.1:5000/q/example_film_search?text=Harbor&page_size=2'
|
|
91
|
+
[{"film_id":48,"title":"Broken Harbor","category":"Action","rating":"PG-13"},{"film_id":44,"title":"Electric Harbor","category":"Documentary","rating":"G"}]
|
|
86
92
|
|
|
87
|
-
$ curl 'http://127.0.0.1:5000/q/
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
$ curl 'http://127.0.0.1:5000/q/example_top_films?top_n=2&category=Comedy&format=csv'
|
|
94
|
+
title,category,rating,rentals,rank
|
|
95
|
+
Electric Signal,Comedy,PG,977,1
|
|
96
|
+
Crimson Garden,Comedy,PG-13,631,2
|
|
91
97
|
~~~
|
|
92
98
|
|
|
93
99
|
<p align="center">
|
|
@@ -273,11 +279,11 @@ version rather than overwriting the old one, so a change to the SQL doesn't sile
|
|
|
273
279
|
### Bound query parameters
|
|
274
280
|
|
|
275
281
|
```sql
|
|
276
|
-
SELECT * FROM
|
|
282
|
+
SELECT * FROM film WHERE film_id = :id
|
|
277
283
|
```
|
|
278
284
|
|
|
279
285
|
```http
|
|
280
|
-
GET /q/
|
|
286
|
+
GET /q/film_by_id?id=7
|
|
281
287
|
```
|
|
282
288
|
|
|
283
289
|
Values are sent to the database separately from the SQL text, so they cannot be injected into it. Parameters can
|
|
@@ -416,8 +422,8 @@ and CI on every push. See [Development](#development) below.
|
|
|
416
422
|
without building a service per query:
|
|
417
423
|
|
|
418
424
|
```http
|
|
419
|
-
GET /q/
|
|
420
|
-
GET /q/
|
|
425
|
+
GET /q/film_by_id?id=7
|
|
426
|
+
GET /q/example_top_films?top_n=5&category=Comedy
|
|
421
427
|
```
|
|
422
428
|
|
|
423
429
|
**Reporting APIs.** Turn an existing analytical SQL report into a reusable endpoint that dashboards or scheduled
|
|
@@ -483,17 +489,17 @@ From a clone: `pip install -e ".[dev]"`. Or use Docker - see [below](#docker).
|
|
|
483
489
|
Fastest: `queryapigate examples load && queryapigate serve` installs four worked [example APIs](documentation/EXAMPLES.md)
|
|
484
490
|
(reporting, dashboard, export, partner) you can try, and `queryapigate examples unload` removes them again.
|
|
485
491
|
|
|
486
|
-
Or, from a clone, the repository ships two sample SQLite databases and a couple of saved queries:
|
|
487
|
-
|
|
488
492
|
~~~bash
|
|
489
|
-
|
|
490
|
-
cp db_connections.example.json db_connections.json
|
|
493
|
+
queryapigate examples load
|
|
491
494
|
queryapigate serve # http://127.0.0.1:5000
|
|
492
495
|
~~~
|
|
493
496
|
|
|
497
|
+
The examples come with a small generated `examples` database (films, customers and rentals), so you can also run ad-hoc SQL
|
|
498
|
+
straight away:
|
|
499
|
+
|
|
494
500
|
~~~bash
|
|
495
501
|
curl -X POST 'http://127.0.0.1:5000/execute_sql?page_size=3' -H 'Content-Type: application/json' \
|
|
496
|
-
-d '{"sql": "SELECT
|
|
502
|
+
-d '{"sql": "SELECT film_id, title, rating FROM film WHERE film_id > :min", "params": {"min": 10}, "connection_name": "examples"}'
|
|
497
503
|
~~~
|
|
498
504
|
|
|
499
505
|
Open <http://127.0.0.1:5000/docs> for the interactive API reference, or <http://127.0.0.1:5000/ui> for a small
|
|
@@ -504,24 +510,26 @@ every supported database) and `saved_sql/`. Edit the file, set `"active": true`,
|
|
|
504
510
|
|
|
505
511
|
## Saving a query as an endpoint
|
|
506
512
|
|
|
513
|
+
This uses the `examples` connection that `queryapigate examples load` sets up:
|
|
514
|
+
|
|
507
515
|
~~~bash
|
|
508
516
|
curl -X PATCH http://127.0.0.1:5000/save_sql_to_file -H 'Content-Type: application/json' -d '{
|
|
509
|
-
"filename": "
|
|
510
|
-
"sql_query": "SELECT * FROM
|
|
511
|
-
"query_parameters": {"id": {"type": "int", "min": 1, "max":
|
|
512
|
-
"connection_name": "
|
|
513
|
-
"author": "me", "description": "Look up
|
|
517
|
+
"filename": "film_by_id",
|
|
518
|
+
"sql_query": "SELECT * FROM film WHERE film_id = :id",
|
|
519
|
+
"query_parameters": {"id": {"type": "int", "min": 1, "max": 60, "description": "Film id"}},
|
|
520
|
+
"connection_name": "examples",
|
|
521
|
+
"author": "me", "description": "Look up a film"
|
|
514
522
|
}'
|
|
515
523
|
|
|
516
|
-
curl 'http://127.0.0.1:5000/q/
|
|
517
|
-
curl 'http://127.0.0.1:5000/q/
|
|
524
|
+
curl 'http://127.0.0.1:5000/q/film_by_id?id=7&format=yaml'
|
|
525
|
+
curl 'http://127.0.0.1:5000/q/film_by_id?id=0'
|
|
518
526
|
# {"error": "Invalid parameters: id must be at least 1", "errors": {"id": "must be at least 1"}}
|
|
519
527
|
~~~
|
|
520
528
|
|
|
521
529
|
Rules: `type` (`int`, `float`, `str`, `bool`), `default`, `required`, `enum`, `min`/`max`, `min_length`/`max_length`,
|
|
522
530
|
`pattern` and `description` - see [the API reference](documentation/API.md#parameter-rules).
|
|
523
531
|
|
|
524
|
-
Saving again under the same name adds version 2; `DELETE /saved_sql/
|
|
532
|
+
Saving again under the same name adds version 2; `DELETE /saved_sql/film_by_id?version=1` removes one version.
|
|
525
533
|
|
|
526
534
|
Add `"cache_ttl": 60` to cache a response for that many seconds (`X-Cache: HIT`/`MISS`, `ETag`, `Cache-Control`) -
|
|
527
535
|
opt-in, and never used for a query that writes. See
|
|
@@ -608,7 +616,7 @@ instead of it - a request has to pass both. See [Per-key rate limiting](document
|
|
|
608
616
|
Every release is published to GitHub Container Registry for `linux/amd64` and `linux/arm64`:
|
|
609
617
|
|
|
610
618
|
~~~bash
|
|
611
|
-
docker run -p 5000:5000 -v
|
|
619
|
+
docker run -p 5000:5000 -v queryapigate-data:/data -e QUERYAPIGATE_API_KEY=change-me ghcr.io/anantharajuc/queryapigate:latest
|
|
612
620
|
~~~
|
|
613
621
|
|
|
614
622
|
| Tag | Contents |
|
|
@@ -617,7 +625,10 @@ docker run -p 5000:5000 -v "$PWD/data:/data" -e QUERYAPIGATE_API_KEY=change-me g
|
|
|
617
625
|
| `X.Y.Z-h2`, `latest-h2` | The same plus Java and the H2 driver - also the variant to use for a generic `jdbc` connection (mount your vendor's jar) |
|
|
618
626
|
|
|
619
627
|
The container keeps `db_connections.json` and `saved_sql/` in `/data` (create a starter with
|
|
620
|
-
`docker run --rm -v
|
|
628
|
+
`docker run --rm -v queryapigate-data:/data ghcr.io/anantharajuc/queryapigate queryapigate init`). The named volume above works
|
|
629
|
+
out of the box. To use a folder on the host instead (`-v "$PWD/data:/data"`), create it yourself first (`mkdir data`) and make sure
|
|
630
|
+
it is writable by uid 1000, the container's user - a folder that Docker creates for you is owned by root, which the container
|
|
631
|
+
cannot write to (or run with `--user "$(id -u):$(id -g)"`). It runs as a non-root user under
|
|
621
632
|
gunicorn with one worker (the files are protected by an in-process lock) and a health check on `/health`. Behind a
|
|
622
633
|
reverse proxy or load balancer, set `QUERYAPIGATE_TRUST_PROXY=1`. To build it yourself:
|
|
623
634
|
`docker build -t queryapigate .` (add `--build-arg WITH_H2=true` for H2).
|
|
@@ -715,12 +726,28 @@ observability are all useful areas. Please review [CONTRIBUTING.md](CONTRIBUTING
|
|
|
715
726
|
|
|
716
727
|
## Third-party components
|
|
717
728
|
|
|
718
|
-
The wheel bundles the [H2 Database](https://h2database.com) JDBC driver (MPL 2.0 / EPL 1.0). The
|
|
719
|
-
|
|
729
|
+
The wheel bundles the [H2 Database](https://h2database.com) JDBC driver (MPL 2.0 / EPL 1.0). The example data is generated
|
|
730
|
+
locally by `queryapigate examples load`; no third-party datasets are included.
|
|
720
731
|
|
|
721
732
|
## License
|
|
722
733
|
|
|
723
|
-
|
|
734
|
+
QueryAPIGate is **source-available**, under the
|
|
735
|
+
[Functional Source License, Version 1.1, MIT Future License](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE)
|
|
736
|
+
(`FSL-1.1-MIT`) © Anantha Raju C. It is not an OSI-approved open source license.
|
|
737
|
+
|
|
738
|
+
In plain terms - the [LICENSE](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/LICENSE) is the only authoritative text:
|
|
739
|
+
|
|
740
|
+
- **You may** use, copy, modify and redistribute it for any purpose that is not a *Competing Use* - including running it
|
|
741
|
+
inside your own company, non-commercial education and research, and professional services you provide to someone who
|
|
742
|
+
is themselves licensed to use it.
|
|
743
|
+
- **You may not** make it available to others in a commercial product or service that substitutes for QueryAPIGate or
|
|
744
|
+
offers the same or substantially similar functionality (for example, selling it, or hosting it as a paid service).
|
|
745
|
+
- **Each version becomes MIT** on the second anniversary of the date it was released, and can then be used under MIT terms.
|
|
746
|
+
- **Versions up to and including 0.6.1** were released under the MIT license. They are no longer distributed (and the
|
|
747
|
+
repository history was reset in 0.7.0), but a copy you already obtained under MIT remains yours under those terms.
|
|
748
|
+
|
|
749
|
+
Need something the license does not cover, such as offering it as part of a hosted service? Get in touch - commercial
|
|
750
|
+
licensing may be available. This is a summary, not legal advice.
|
|
724
751
|
|
|
725
752
|
## Contact
|
|
726
753
|
|
|
@@ -39,6 +39,14 @@ class ExamplesTestCase(unittest.TestCase):
|
|
|
39
39
|
store.save_version(name, {'sql_query': 'SELECT 1', 'author': 'me', 'description': 'mine', 'tags': [],
|
|
40
40
|
'query_parameters': {}}, collection)
|
|
41
41
|
|
|
42
|
+
def make_home_unwritable(self):
|
|
43
|
+
"""A real read-only data folder - what a Docker bind mount created by root looks like to the container's
|
|
44
|
+
non-root user. (Mocking an OSError would not do: SQLite raises its own error type here.)"""
|
|
45
|
+
if os.geteuid() == 0:
|
|
46
|
+
self.skipTest('root ignores directory permissions')
|
|
47
|
+
os.chmod(self.home, 0o555)
|
|
48
|
+
self.addCleanup(os.chmod, self.home, 0o755)
|
|
49
|
+
|
|
42
50
|
def run_cli(self, *argv):
|
|
43
51
|
out, err = io.StringIO(), io.StringIO()
|
|
44
52
|
with redirect_stdout(out), redirect_stderr(err):
|
|
@@ -47,6 +55,13 @@ class ExamplesTestCase(unittest.TestCase):
|
|
|
47
55
|
|
|
48
56
|
|
|
49
57
|
class DatabaseTests(ExamplesTestCase):
|
|
58
|
+
def test_an_unwritable_folder_is_an_apierror_with_a_hint(self):
|
|
59
|
+
self.make_home_unwritable()
|
|
60
|
+
with self.assertRaises(ApiError) as caught:
|
|
61
|
+
examples.load()
|
|
62
|
+
self.assertEqual(caught.exception.status, 500)
|
|
63
|
+
self.assertIn('writable', caught.exception.message)
|
|
64
|
+
|
|
50
65
|
def build(self, **kwargs):
|
|
51
66
|
path = os.path.join(self.home, 'x.db')
|
|
52
67
|
examples.build_database(path, FIXED_NOW, **kwargs)
|
|
@@ -450,6 +465,24 @@ class StartupTests(ExamplesTestCase):
|
|
|
450
465
|
self.assertEqual(app.test_client().get('/health').status_code, 200)
|
|
451
466
|
self.assertEqual(self.saved_files(), ['example_top_films.json'])
|
|
452
467
|
|
|
468
|
+
def test_a_really_unwritable_home_is_a_warning_not_a_crash(self):
|
|
469
|
+
# This crashed the container: examples.load() let SQLite's own OperationalError escape, which the startup hook
|
|
470
|
+
# (catching only ApiError/OSError) did not handle, so the worker died and gunicorn kept restarting it.
|
|
471
|
+
self.make_home_unwritable()
|
|
472
|
+
os.environ['QUERYAPIGATE_LOAD_EXAMPLES'] = 'yes'
|
|
473
|
+
with self.assertLogs('queryapigate', level='WARNING') as captured:
|
|
474
|
+
app = create_app()
|
|
475
|
+
self.assertTrue(any('could not be loaded' in line for line in captured.output), captured.output)
|
|
476
|
+
self.assertEqual(app.test_client().get('/health').status_code, 200)
|
|
477
|
+
|
|
478
|
+
def test_an_unexpected_failure_while_loading_is_still_only_a_warning(self):
|
|
479
|
+
os.environ['QUERYAPIGATE_LOAD_EXAMPLES'] = 'yes'
|
|
480
|
+
with mock.patch.object(examples, 'load', side_effect=RuntimeError('something new')):
|
|
481
|
+
with self.assertLogs('queryapigate', level='WARNING') as captured:
|
|
482
|
+
app = create_app()
|
|
483
|
+
self.assertTrue(any('something new' in line for line in captured.output), captured.output)
|
|
484
|
+
self.assertEqual(app.test_client().get('/health').status_code, 200)
|
|
485
|
+
|
|
453
486
|
def test_a_read_only_home_is_a_warning_not_a_startup_failure(self):
|
|
454
487
|
os.environ['QUERYAPIGATE_LOAD_EXAMPLES'] = 'yes'
|
|
455
488
|
with mock.patch.object(examples, 'load', side_effect=OSError('read-only file system')):
|
|
@@ -489,6 +522,13 @@ class CliTests(ExamplesTestCase):
|
|
|
489
522
|
_, out, _ = self.run_cli('examples', 'unload')
|
|
490
523
|
self.assertIn('partner-key', out)
|
|
491
524
|
|
|
525
|
+
def test_an_unwritable_home_is_a_clean_message_not_a_traceback(self):
|
|
526
|
+
self.make_home_unwritable()
|
|
527
|
+
code, _, err = self.run_cli('examples', 'load')
|
|
528
|
+
self.assertEqual(code, 1)
|
|
529
|
+
self.assertIn('queryapigate examples load:', err)
|
|
530
|
+
self.assertNotIn('Traceback', err)
|
|
531
|
+
|
|
492
532
|
def test_bare_command_shows_help_and_a_leftover_old_setting_still_stops_it(self):
|
|
493
533
|
code, out, _ = self.run_cli('examples')
|
|
494
534
|
self.assertEqual((code, 'load' in out), (2, True))
|
queryapigate-0.6.1/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 Anantha Raju C
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|