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.
@@ -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.
@@ -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