api-dock 0.0.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.
- api_dock-0.0.1/LICENSE.md +28 -0
- api_dock-0.0.1/PKG-INFO +616 -0
- api_dock-0.0.1/README.md +586 -0
- api_dock-0.0.1/api_dock/__init__.py +26 -0
- api_dock-0.0.1/api_dock/cli.py +318 -0
- api_dock-0.0.1/api_dock/config.py +469 -0
- api_dock-0.0.1/api_dock/config_discovery.py +143 -0
- api_dock-0.0.1/api_dock/database_config.py +264 -0
- api_dock-0.0.1/api_dock/fast_api.py +161 -0
- api_dock-0.0.1/api_dock/flask_api.py +198 -0
- api_dock-0.0.1/api_dock/route_mapper.py +449 -0
- api_dock-0.0.1/api_dock/sql_builder.py +163 -0
- api_dock-0.0.1/api_dock.egg-info/PKG-INFO +616 -0
- api_dock-0.0.1/api_dock.egg-info/SOURCES.txt +23 -0
- api_dock-0.0.1/api_dock.egg-info/dependency_links.txt +1 -0
- api_dock-0.0.1/api_dock.egg-info/entry_points.txt +2 -0
- api_dock-0.0.1/api_dock.egg-info/requires.txt +7 -0
- api_dock-0.0.1/api_dock.egg-info/top_level.txt +1 -0
- api_dock-0.0.1/pyproject.toml +76 -0
- api_dock-0.0.1/setup.cfg +8 -0
- api_dock-0.0.1/tests/test_config_syntax.py +61 -0
- api_dock-0.0.1/tests/test_curl_fixes.py +47 -0
- api_dock-0.0.1/tests/test_restrictions.py +98 -0
- api_dock-0.0.1/tests/test_root_endpoint.py +44 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Regents of the University of California (Schmidt Center for Data Science and Environment at UC Berkeley)
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
api_dock-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: api_dock
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A flexible API gateway that allows you to proxy requests to multiple remote APIs and Databases
|
|
5
|
+
Author-email: Brookie Guzder-Williams <bguzder-williams@berkeley.edu>
|
|
6
|
+
License: BSd 3-clause
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Database
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE.md
|
|
22
|
+
Requires-Dist: click
|
|
23
|
+
Requires-Dist: fastapi
|
|
24
|
+
Requires-Dist: uvicorn
|
|
25
|
+
Requires-Dist: pyyaml
|
|
26
|
+
Requires-Dist: httpx
|
|
27
|
+
Requires-Dist: pyarrow
|
|
28
|
+
Requires-Dist: duckdb
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# API Dock
|
|
32
|
+
|
|
33
|
+
API Dock (API(s) + (data)Base(s)/base-(for)-API(s)) a flexible API gateway that allows you to proxy requests to multiple remote APIs and Databases through a single endpoint. The proxy can easily be launched as a FastAPI or Flask app, or integrated into any existing python based API.
|
|
34
|
+
|
|
35
|
+
## Table of Contents
|
|
36
|
+
|
|
37
|
+
- [Features](#features)
|
|
38
|
+
- [Quick Example](#quick-example)
|
|
39
|
+
- [CLI](#cli)
|
|
40
|
+
- [Commands](#commands)
|
|
41
|
+
- [Examples](#examples)
|
|
42
|
+
- [CONFIGURATION AND SYNTAX](#configuration-and-syntax)
|
|
43
|
+
- [Main Configuration](#main-configuration-api_dock_configconfigyaml)
|
|
44
|
+
- [Remote Configurations](#remote-configurations)
|
|
45
|
+
- [SQL Database Support](#sql-database-support)
|
|
46
|
+
- [Using RouteMapper in Your Own Projects](#using-routemapper-in-your-own-projects)
|
|
47
|
+
- [Basic Integration](#basic-integration)
|
|
48
|
+
- [Framework Examples](#framework-examples)
|
|
49
|
+
- [Database Integration](#database-integration)
|
|
50
|
+
- [INSTALL/REQUIREMENTS](#installrequirements)
|
|
51
|
+
- [License](#license)
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **Multi-API Proxying**: Route requests to different remote APIs based on configuration
|
|
56
|
+
- **SQL Database Support**: Query Parquet files and databases using DuckDB via REST endpoints
|
|
57
|
+
- **Cloud Storage Support**: Native support for S3, GCS, HTTPS, and local file paths
|
|
58
|
+
- **YAML Configuration**: Simple, human-readable configuration files
|
|
59
|
+
- **Access Control**: Define allowed/restricted routes per remote API
|
|
60
|
+
- **Version Support**: Handle API versioning in URL paths
|
|
61
|
+
- **Flexibility**: Quickly launch FastAPI or Flask apps, or easily integrate into any existing framework
|
|
62
|
+
|
|
63
|
+
## Quick Example
|
|
64
|
+
|
|
65
|
+
Suppose we have these 3 config files (and similar ones for service2 and service3)
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
# api_dock_config/config.yaml
|
|
69
|
+
name: "My API Dock"
|
|
70
|
+
description: "API proxy for multiple services"
|
|
71
|
+
authors: ["Your Name"]
|
|
72
|
+
|
|
73
|
+
# Remote APIs to proxy
|
|
74
|
+
remotes:
|
|
75
|
+
- "service1"
|
|
76
|
+
- "service2"
|
|
77
|
+
- "service3"
|
|
78
|
+
|
|
79
|
+
# SQL databases to query
|
|
80
|
+
databases:
|
|
81
|
+
- "db_example"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
# api_dock_config/remotes/service1.yaml
|
|
86
|
+
name: service1
|
|
87
|
+
description: Example showing all routing features
|
|
88
|
+
url: http://api.example.com
|
|
89
|
+
|
|
90
|
+
# Unified routes (mix of strings and dicts)
|
|
91
|
+
routes:
|
|
92
|
+
# routes with identical signatures
|
|
93
|
+
- health # GET http://api.example.com/health
|
|
94
|
+
- route: users # GET http://api.example.com/users (using explicit method)
|
|
95
|
+
method: get
|
|
96
|
+
- users/{{user_id}} # GET http://api.example.com/users/{{user_id}}
|
|
97
|
+
- route: users/{{user_id}}/posts # POST http://api.example.com/users/{{user_id}}/posts
|
|
98
|
+
method: post
|
|
99
|
+
# route with a different signature
|
|
100
|
+
- route: users/{{user_id}}/permissions # GET http://api.example.com/user-permissions/{{user_id}}
|
|
101
|
+
remote_route: user-permissions/{{user_id}}
|
|
102
|
+
method: get
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
# api_dock_config/databases/db_example.yaml
|
|
107
|
+
name: db_example
|
|
108
|
+
description: Example database with Parquet files
|
|
109
|
+
authors:
|
|
110
|
+
- API Team
|
|
111
|
+
|
|
112
|
+
# Table definitions - supports multiple storage backends
|
|
113
|
+
tables:
|
|
114
|
+
users: s3://your-bucket/users.parquet # S3
|
|
115
|
+
permissions: gs://your-bucket/permissions.parquet # Google Cloud Storage
|
|
116
|
+
posts: https://storage.googleapis.com/bucket/posts.parquet # HTTPS
|
|
117
|
+
local_data: tables/local_data.parquet # Local filesystem
|
|
118
|
+
|
|
119
|
+
# Named queries (optional)
|
|
120
|
+
queries:
|
|
121
|
+
get_permissions: >
|
|
122
|
+
SELECT [[users]].*, [[permissions]].permission_name
|
|
123
|
+
FROM [[users]]
|
|
124
|
+
JOIN [[permissions]] ON [[users]].ID = [[permissions]].ID
|
|
125
|
+
WHERE [[users]].user_id = {{user_id}}
|
|
126
|
+
|
|
127
|
+
# REST route definitions
|
|
128
|
+
routes:
|
|
129
|
+
- route: users
|
|
130
|
+
sql: SELECT [[users]].* FROM [[users]]
|
|
131
|
+
|
|
132
|
+
- route: users/{{user_id}}
|
|
133
|
+
sql: SELECT [[users]].* FROM [[users]] WHERE [[users]].user_id = {{user_id}}
|
|
134
|
+
|
|
135
|
+
- route: users/{{user_id}}/permissions
|
|
136
|
+
sql: "[[get_permissions]]"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Then just run `pixi run api-dock start` to launch a new api with following endpoints:
|
|
140
|
+
|
|
141
|
+
- list remote api names and databases: `/`
|
|
142
|
+
- list of available db_example queries: `/db_example/users`
|
|
143
|
+
- query example_db for users: `/db_example/users`
|
|
144
|
+
- query example_db for user: `/db_example/users/{{user_id}}`
|
|
145
|
+
- query example_db for user-permissions: `/db_example/users/{{user_id}}/permissions`
|
|
146
|
+
- list service1 endpoints: `/service1`
|
|
147
|
+
- proxy for http://api.example.com/health: `/service1/health`
|
|
148
|
+
- proxy for http://api.example.com/user-permissions/{{user_id}}: `/service1/users/{{user_id}}/permissions`
|
|
149
|
+
- list service2|3 endpoints: `/service2|3`
|
|
150
|
+
- ...
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
# CLI
|
|
155
|
+
|
|
156
|
+
## Commands
|
|
157
|
+
|
|
158
|
+
API Dock provides a modern Click-based CLI:
|
|
159
|
+
|
|
160
|
+
- **api-dock** (default): List all available configurations
|
|
161
|
+
- **api-dock init [--force]**: Initialize `api_dock_config/` directory with default configs
|
|
162
|
+
- **api-dock start [config_name]**: Start API Dock server with optional config name
|
|
163
|
+
- **api-dock describe [config_name]**: Display formatted configuration with expanded SQL queries
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## Examples
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Initialize local configuration directory
|
|
170
|
+
pixi run api-dock init
|
|
171
|
+
|
|
172
|
+
# List available configurations, and available commands
|
|
173
|
+
pixi run api-dock
|
|
174
|
+
|
|
175
|
+
# Start API server
|
|
176
|
+
# - default configuration (api_dock_config/config.yaml) with FastAPI
|
|
177
|
+
pixi run api-dock start
|
|
178
|
+
# - default configuration with Flask (backbone options: fastapi (default) or flask)
|
|
179
|
+
pixi run api-dock start --backbone flask
|
|
180
|
+
# - specify with host and/or port
|
|
181
|
+
pixi run api-dock start --host 0.0.0.0 --port 9000
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# these commands also work for alternative configurations (example: api_dock_config/config_v2.yaml)
|
|
185
|
+
pixi run api-dock start config_v2
|
|
186
|
+
pixi run api-dock describe config_v2
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**For more details**, see the [Configuration Wiki](https://github.com/yourusername/api_dock/wiki/Configuration).
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
# CONFIGURATION AND SYNTAX
|
|
194
|
+
|
|
195
|
+
Assume our file structure is:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
api_dock_config
|
|
199
|
+
├── config.yaml
|
|
200
|
+
├── config_v2.yaml
|
|
201
|
+
├── databases
|
|
202
|
+
│ ├── analytics_db.yaml
|
|
203
|
+
│ └── versioned_db
|
|
204
|
+
│ ├── 0.1.yaml
|
|
205
|
+
│ ├── 0.5.yaml
|
|
206
|
+
│ └── 1.1.yaml
|
|
207
|
+
└── remotes
|
|
208
|
+
├── service1.yaml
|
|
209
|
+
├── service2.yaml
|
|
210
|
+
└── versioned_service
|
|
211
|
+
├── 0.1.yaml
|
|
212
|
+
├── 0.2.yaml
|
|
213
|
+
└── 0.3.yaml
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Main Configuration (`api_dock_config/config.yaml`)
|
|
219
|
+
|
|
220
|
+
The main configuration files are stored in the top level of the CWD's `api_dock_config/` directory. By default api-dock expects there to be one called `config.yaml`, however configs with different names (such as `config_v2`) can be added and launched as shown in the CLI Examples section.
|
|
221
|
+
|
|
222
|
+
```yaml
|
|
223
|
+
# api_dock_config/config.yaml
|
|
224
|
+
name: "My API Dock"
|
|
225
|
+
description: "API proxy for multiple services"
|
|
226
|
+
authors: ["Your Name"]
|
|
227
|
+
|
|
228
|
+
# Remote APIs to proxy
|
|
229
|
+
remotes:
|
|
230
|
+
- "service1" # add configuration in "api_dock_config/remotes/service1.yaml"
|
|
231
|
+
- "service2" # add configuration in "api_dock_config/remotes/service2.yaml"
|
|
232
|
+
- "versioned_service" # add configurations in versions in "api_dock_config/remotes/versioned_service/"
|
|
233
|
+
|
|
234
|
+
# SQL databases to query
|
|
235
|
+
databases:
|
|
236
|
+
- "analytics_db" # adds database configuration in "api_dock_config/databases/analytics_db.yaml"
|
|
237
|
+
- "versioned_db" # adds database configurations in "api_dock_config/databases/versioned_db/"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Remote Configurations
|
|
243
|
+
|
|
244
|
+
The example below is a remote configuration.
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
# api_dock_config/remotes/service1.yaml
|
|
248
|
+
name: service1 # this is the slug that goes in the url (ie: /service1/users)
|
|
249
|
+
url: http://api.example.com # the base-url of the api being proxied
|
|
250
|
+
description: This is an api # included in response for /service1 route
|
|
251
|
+
|
|
252
|
+
# Here is where we define the routing
|
|
253
|
+
routes:
|
|
254
|
+
# routes with identical signatures
|
|
255
|
+
- health # GET http://api.example.com/health
|
|
256
|
+
- route: users # GET http://api.example.com/users (using explicit method)
|
|
257
|
+
method: get
|
|
258
|
+
- users/{{user_id}} # GET http://api.example.com/users/{{user_id}}
|
|
259
|
+
- route: users/{{user_id}}/posts # POST http://api.example.com/users/{{user_id}}/posts
|
|
260
|
+
method: post
|
|
261
|
+
# route with a different signature
|
|
262
|
+
- route: users/{{user_id}}/permissions # GET http://api.example.com/user-permissions/{{user_id}}
|
|
263
|
+
remote_route: user-permissions/{{user_id}}
|
|
264
|
+
method: get
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Variable Placeholders
|
|
268
|
+
|
|
269
|
+
Routes use double curly braces `{{}}` for variable placeholders:
|
|
270
|
+
|
|
271
|
+
- `users` - Matches exactly "users"
|
|
272
|
+
- `users/{{user_id}}` - Matches "users/123", "users/abc", etc.
|
|
273
|
+
- `users/{{user_id}}/profile` - Matches "users/123/profile"
|
|
274
|
+
- `{{}}` - Anonymous variable (matches any single path segment)
|
|
275
|
+
|
|
276
|
+
### String Routes (Simple GET Routes)
|
|
277
|
+
|
|
278
|
+
```yaml
|
|
279
|
+
routes:
|
|
280
|
+
- users # GET /users
|
|
281
|
+
- users/{{user_id}} # GET /users/123
|
|
282
|
+
- users/{{user_id}}/profile # GET /users/123/profile
|
|
283
|
+
- posts/{{post_id}} # GET /posts/456
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Dictionary Routes (Custom Methods and Mappings)
|
|
287
|
+
|
|
288
|
+
```yaml
|
|
289
|
+
routes:
|
|
290
|
+
# A simple GET (note this is the same as passing the string 'users/{{user_id}}')
|
|
291
|
+
- route: users/{{user_id}}
|
|
292
|
+
method: get
|
|
293
|
+
|
|
294
|
+
# Different HTTP method
|
|
295
|
+
- route: users/{{user_id}}
|
|
296
|
+
method: post # POST /users/123
|
|
297
|
+
|
|
298
|
+
# Custom remote mapping
|
|
299
|
+
- route: users/{{user_id}}/permissions
|
|
300
|
+
remote_route: user-permissions/{{user_id}}
|
|
301
|
+
method: get # Maps local route to different remote endpoint
|
|
302
|
+
|
|
303
|
+
# Complex mapping with multiple variables
|
|
304
|
+
- route: search/{{category}}/{{term}}/after/{{date}}
|
|
305
|
+
remote_route: api/v2/search/{{term}}/in/{{category}}?after={{date}}
|
|
306
|
+
method: get
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Route Restrictions
|
|
310
|
+
|
|
311
|
+
You can restrict access to specific routes using the `restricted` section:
|
|
312
|
+
|
|
313
|
+
```yaml
|
|
314
|
+
name: restricted_config
|
|
315
|
+
|
|
316
|
+
...
|
|
317
|
+
|
|
318
|
+
routes:
|
|
319
|
+
...
|
|
320
|
+
|
|
321
|
+
restricted:
|
|
322
|
+
- admin # Block all admin routes
|
|
323
|
+
- users/{{user_id}}/private # Block private user data
|
|
324
|
+
- system/{{system_id}}/config # Block system configuration
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**For more details**, see the [Routing and Restrictions Wiki](https://github.com/yourusername/api_dock/wiki/Routing-and-Restrictions).
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## SQL Database Support
|
|
332
|
+
|
|
333
|
+
API Dock can also be used to query Databases. For now only parquet support is working but we will be adding other Databases in the future.
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
### Database Configuration
|
|
337
|
+
|
|
338
|
+
Database configurations are stored in `config/databases/` directory. Each database defines:
|
|
339
|
+
- **tables**: Mapping of table names to file paths (supports S3, GCS, HTTPS, local paths)
|
|
340
|
+
- **queries**: Named SQL queries for reuse
|
|
341
|
+
- **routes**: REST endpoints mapped to SQL queries
|
|
342
|
+
|
|
343
|
+
### Syntax
|
|
344
|
+
|
|
345
|
+
As with the remote-apis, the routes to databases use double-curly-brackets {{}} to reference url variable placeholders.
|
|
346
|
+
Additionally for SQL there are double-square-brackets [[]]. These are used to reference other items in the database config, namely: table_names, named-queries.
|
|
347
|
+
|
|
348
|
+
#### Table References: `[[table_name]]`
|
|
349
|
+
|
|
350
|
+
Use double square brackets to reference tables defined in the `tables` section. If we have
|
|
351
|
+
|
|
352
|
+
```yaml
|
|
353
|
+
tables:
|
|
354
|
+
users: s3://your-bucket/users.parquet
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
then `SELECT [[users]].* FROM [[users]]` automatically expands to:
|
|
358
|
+
|
|
359
|
+
```sql
|
|
360
|
+
SELECT users.* FROM 's3://your-bucket/users.parquet' AS users
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### Named Queries: `[[query_name]]`
|
|
364
|
+
|
|
365
|
+
Similarly, you can reference named queries from the `queries` section with [[]]. This is one way to keep the routes clean even with complicated sql queries.
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
```yaml
|
|
369
|
+
queries:
|
|
370
|
+
get_user_permissions: |
|
|
371
|
+
SELECT [[users]].user_id, [[users]].name, [[user_permissions]].permission_name, [[user_permissions]].granted_date
|
|
372
|
+
FROM [[users]]
|
|
373
|
+
JOIN [[user_permissions]] ON [[users]].user_id = [[user_permissions]].user_id
|
|
374
|
+
WHERE [[users]].user_id = {{user_id}}
|
|
375
|
+
|
|
376
|
+
routes:
|
|
377
|
+
- route: users/{{user_id}}/permissions
|
|
378
|
+
sql: "[[get_permissions]]"
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
#### EXAMPLE
|
|
383
|
+
|
|
384
|
+
Here's a complete example
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
name: db_example
|
|
388
|
+
description: Example database with Parquet files
|
|
389
|
+
authors:
|
|
390
|
+
- API Team
|
|
391
|
+
|
|
392
|
+
# Table definitions - supports multiple storage backends
|
|
393
|
+
tables:
|
|
394
|
+
users: s3://your-bucket/users.parquet # S3
|
|
395
|
+
permissions: gs://your-bucket/permissions.parquet # Google Cloud Storage
|
|
396
|
+
posts: https://store-files.com/bucket/posts.parquet # HTTPS
|
|
397
|
+
local_data: tables/local_data.parquet # Local filesystem
|
|
398
|
+
|
|
399
|
+
# Named queries (optional)
|
|
400
|
+
queries:
|
|
401
|
+
get_permissions: >
|
|
402
|
+
SELECT [[users]].*, [[permissions]].permission_name
|
|
403
|
+
FROM [[users]]
|
|
404
|
+
JOIN [[permissions]] ON [[users]].ID = [[permissions]].ID
|
|
405
|
+
WHERE [[users]].user_id = {{user_id}}
|
|
406
|
+
|
|
407
|
+
# REST route definitions
|
|
408
|
+
routes:
|
|
409
|
+
- route: users
|
|
410
|
+
sql: SELECT [[users]].* FROM [[users]]
|
|
411
|
+
|
|
412
|
+
- route: users/{{user_id}}
|
|
413
|
+
sql: SELECT [[users]].* FROM [[users]] WHERE [[users]].user_id = {{user_id}}
|
|
414
|
+
|
|
415
|
+
- route: users/{{user_id}}/permissions
|
|
416
|
+
sql: "[[get_permissions]]"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**For more details**, see the [SQL Database Support Wiki](https://github.com/yourusername/api_dock/wiki/SQL-Database-Support).
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
# Using RouteMapper in Your Own Projects
|
|
424
|
+
|
|
425
|
+
The core functionality is available as a standalone `RouteMapper` class that can be integrated into any web framework:
|
|
426
|
+
|
|
427
|
+
## Basic Integration
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
from api_dock.route_mapper import RouteMapper
|
|
431
|
+
|
|
432
|
+
# Initialize with optional config path
|
|
433
|
+
route_mapper = RouteMapper(config_path="path/to/config.yaml")
|
|
434
|
+
|
|
435
|
+
# Get API metadata
|
|
436
|
+
metadata = route_mapper.get_config_metadata()
|
|
437
|
+
|
|
438
|
+
# Check configuration values
|
|
439
|
+
success, value, error = route_mapper.get_config_value("some_key")
|
|
440
|
+
|
|
441
|
+
# Route requests (async version for FastAPI, etc.)
|
|
442
|
+
success, data, status, error = await route_mapper.map_route(
|
|
443
|
+
remote_name="service1",
|
|
444
|
+
path="users/123",
|
|
445
|
+
method="GET",
|
|
446
|
+
headers={"Authorization": "Bearer token"},
|
|
447
|
+
query_params={"limit": "10"}
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Route requests (sync version for Flask, etc.)
|
|
451
|
+
success, data, status, error = route_mapper.map_route_sync(
|
|
452
|
+
remote_name="service1",
|
|
453
|
+
path="users/123",
|
|
454
|
+
method="GET"
|
|
455
|
+
)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Framework Examples
|
|
459
|
+
|
|
460
|
+
### Django Integration
|
|
461
|
+
```python
|
|
462
|
+
from django.http import JsonResponse
|
|
463
|
+
from api_dock.route_mapper import RouteMapper
|
|
464
|
+
|
|
465
|
+
route_mapper = RouteMapper()
|
|
466
|
+
|
|
467
|
+
def api_proxy(request, remote_name, path):
|
|
468
|
+
success, data, status, error = route_mapper.map_route_sync(
|
|
469
|
+
remote_name=remote_name,
|
|
470
|
+
path=path,
|
|
471
|
+
method=request.method,
|
|
472
|
+
headers=dict(request.headers),
|
|
473
|
+
body=request.body,
|
|
474
|
+
query_params=dict(request.GET)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
if not success:
|
|
478
|
+
return JsonResponse({"error": error}, status=status)
|
|
479
|
+
|
|
480
|
+
return JsonResponse(data, status=status)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Custom Framework Integration
|
|
484
|
+
```python
|
|
485
|
+
from api_dock.route_mapper import RouteMapper
|
|
486
|
+
|
|
487
|
+
route_mapper = RouteMapper()
|
|
488
|
+
|
|
489
|
+
@your_framework.route("/{remote_name}/{path:path}")
|
|
490
|
+
def proxy_handler(remote_name, path, request):
|
|
491
|
+
success, data, status, error = route_mapper.map_route_sync(
|
|
492
|
+
remote_name=remote_name,
|
|
493
|
+
path=path,
|
|
494
|
+
method=request.method,
|
|
495
|
+
headers=request.headers,
|
|
496
|
+
body=request.body,
|
|
497
|
+
query_params=request.query_params
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
return your_framework.Response(data, status=status)
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Database Integration
|
|
504
|
+
|
|
505
|
+
The `RouteMapper` also supports SQL database queries through the `map_database_route` method:
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
from api_dock.route_mapper import RouteMapper
|
|
509
|
+
import asyncio
|
|
510
|
+
|
|
511
|
+
route_mapper = RouteMapper(config_path="path/to/config.yaml")
|
|
512
|
+
|
|
513
|
+
# Query database (async version)
|
|
514
|
+
async def query_database():
|
|
515
|
+
success, data, status, error = await route_mapper.map_database_route(
|
|
516
|
+
database_name="db_example",
|
|
517
|
+
path="users/123"
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
if success:
|
|
521
|
+
print(data) # List of dictionaries from SQL query
|
|
522
|
+
else:
|
|
523
|
+
print(f"Error: {error}")
|
|
524
|
+
|
|
525
|
+
# Run async query
|
|
526
|
+
asyncio.run(query_database())
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Django Database Integration
|
|
530
|
+
|
|
531
|
+
```python
|
|
532
|
+
from django.http import JsonResponse
|
|
533
|
+
from api_dock.route_mapper import RouteMapper
|
|
534
|
+
import asyncio
|
|
535
|
+
|
|
536
|
+
route_mapper = RouteMapper()
|
|
537
|
+
|
|
538
|
+
def database_query(request, database_name, path):
|
|
539
|
+
# Run async database query in sync context
|
|
540
|
+
success, data, status, error = asyncio.run(
|
|
541
|
+
route_mapper.map_database_route(
|
|
542
|
+
database_name=database_name,
|
|
543
|
+
path=path
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
if not success:
|
|
548
|
+
return JsonResponse({"error": error}, status=status)
|
|
549
|
+
|
|
550
|
+
return JsonResponse(data, safe=False, status=status)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Flask Database Integration
|
|
554
|
+
|
|
555
|
+
```python
|
|
556
|
+
from flask import Flask, jsonify
|
|
557
|
+
from api_dock.route_mapper import RouteMapper
|
|
558
|
+
import asyncio
|
|
559
|
+
|
|
560
|
+
app = Flask(__name__)
|
|
561
|
+
route_mapper = RouteMapper()
|
|
562
|
+
|
|
563
|
+
@app.route("/<database_name>/<path:path>")
|
|
564
|
+
def database_proxy(database_name, path):
|
|
565
|
+
success, data, status, error = asyncio.run(
|
|
566
|
+
route_mapper.map_database_route(
|
|
567
|
+
database_name=database_name,
|
|
568
|
+
path=path
|
|
569
|
+
)
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
if not success:
|
|
573
|
+
return jsonify({"error": error}), status
|
|
574
|
+
|
|
575
|
+
return jsonify(data), status
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
# INSTALL/REQUIREMENTS
|
|
581
|
+
|
|
582
|
+
Requirements are managed through a [Pixi](https://pixi.sh/latest) "project" (similar to a conda environment). After pixi is installed use `pixi run <cmd>` to ensure the correct project is being used. For example,
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
# launch jupyter
|
|
586
|
+
pixi run jupyter lab .
|
|
587
|
+
|
|
588
|
+
# run a script
|
|
589
|
+
pixi run python scripts/hello_world.py
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
The first time `pixi run` is executed the project will be installed (note this means the first run will be a bit slower). Any changes to the project will be updated on the subsequent `pixi run`. It is unnecessary, but you can run `pixi install` after changes - this will update your local environment, so that it does not need to be updated on the next `pixi run`.
|
|
593
|
+
|
|
594
|
+
Note, the repo's `pyproject.toml`, and `pixi.lock` files ensure `pixi run` will just work. No need to recreate an environment. Additionally, the `pyproject.toml` file includes `api_dock = { path = ".", editable = true }`. This line is equivalent to `pip install -e .`, so there is no need to pip install this module.
|
|
595
|
+
|
|
596
|
+
The project was initially created using a `package_names.txt` and the following steps. Note that this should **NOT** be re-run as it will create a new project (potentially changing package versions).
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
#
|
|
600
|
+
# IMPORTANT: Do NOT run this unless you explicity want to create a new pixi project
|
|
601
|
+
#
|
|
602
|
+
# 1. initialize pixi project (in this case the pyproject.toml file had already existed)
|
|
603
|
+
pixi init . --format pyproject
|
|
604
|
+
# 2. add specified python version
|
|
605
|
+
pixi add python=3.11
|
|
606
|
+
# 3. add packages (note this will use pixi magic to determine/fix package version ranges)
|
|
607
|
+
pixi add $(cat package_names.txt)
|
|
608
|
+
# 4. add pypi-packages, if any (note this will use pixi magic to determine/fix package version ranges)
|
|
609
|
+
pixi add --pypi $(cat pypi_package_names.txt)
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
# License
|
|
615
|
+
|
|
616
|
+
BSD 3-Clause
|