Skip to content

Commit 64de16f

Browse files
committed
add proper server
1 parent 71e303f commit 64de16f

File tree

7 files changed

+346
-108
lines changed

7 files changed

+346
-108
lines changed

changes/3732.feature.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
Adds an experimental HTTP server that can expose `Store`, `Array`, or `Group` instances over HTTP.
2+
`store_app` and `node_app` build ASGI applications; `serve_store` and `serve_node` additionally
3+
start a Uvicorn server (blocking by default, or in a background thread with `background=True`).
24
See the [user guide](https://zarr.readthedocs.io/en/latest/user-guide/experimental.html#http-server)
35
and the [Serve v2 as v3 example](https://zarr.readthedocs.io/en/latest/user-guide/examples/serve_v2_v3.html).

docs/user-guide/experimental.md

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -290,33 +290,31 @@ Install the server dependencies with:
290290
pip install zarr[server]
291291
```
292292

293-
### Serving a Store
293+
### Building an ASGI App
294294

295-
[`zarr.experimental.serve.serve_store`][] creates an ASGI app that exposes every key
295+
[`zarr.experimental.serve.store_app`][] creates an ASGI app that exposes every key
296296
in a store:
297297

298298
```python
299299
import zarr
300-
from zarr.experimental.serve import serve_store
300+
from zarr.experimental.serve import store_app
301301

302302
store = zarr.storage.MemoryStore()
303303
zarr.create_array(store, shape=(100, 100), chunks=(10, 10), dtype="float64")
304304

305-
app = serve_store(store)
305+
app = store_app(store)
306306

307-
# Run with Uvicorn:
307+
# Run with any ASGI server, e.g. Uvicorn:
308308
# uvicorn my_module:app --host 0.0.0.0 --port 8000
309309
```
310310

311-
### Serving a Node
312-
313-
[`zarr.experimental.serve.serve_node`][] creates an ASGI app that only serves keys
311+
[`zarr.experimental.serve.node_app`][] creates an ASGI app that only serves keys
314312
belonging to a specific `Array` or `Group`. Requests for keys outside the node
315313
receive a 404, even if those keys exist in the underlying store:
316314

317315
```python
318316
import zarr
319-
from zarr.experimental.serve import serve_node
317+
from zarr.experimental.serve import node_app
320318

321319
store = zarr.storage.MemoryStore()
322320
root = zarr.open_group(store)
@@ -325,19 +323,57 @@ root.create_array("b", shape=(20,), dtype="float64")
325323

326324
# Only serve the array at "a" — requests for "b" will return 404.
327325
arr = root["a"]
328-
app = serve_node(arr)
326+
app = node_app(arr)
327+
```
328+
329+
### Running the Server
330+
331+
[`zarr.experimental.serve.serve_store`][] and [`zarr.experimental.serve.serve_node`][]
332+
build an ASGI app *and* start a [Uvicorn](https://www.uvicorn.org/) server.
333+
By default they block until the server is shut down:
334+
335+
```python
336+
from zarr.experimental.serve import serve_store
337+
338+
serve_store(store, host="127.0.0.1", port=8000)
339+
```
340+
341+
Pass `background=True` to start the server in a daemon thread and return
342+
immediately. The returned `uvicorn.Server` object can be used to shut down
343+
the server:
344+
345+
```python
346+
import numpy as np
347+
348+
import zarr
349+
from zarr.experimental.serve import serve_node
350+
from zarr.storage import MemoryStore
351+
352+
store = MemoryStore()
353+
arr = zarr.create_array(store, shape=(100,), chunks=(10,), dtype="float64")
354+
arr[:] = np.arange(100, dtype="float64")
355+
356+
server = serve_node(arr, host="127.0.0.1", port=8000, background=True)
357+
358+
# Now open the served array from another zarr client.
359+
remote = zarr.open_array("http://127.0.0.1:8000", mode="r")
360+
np.testing.assert_array_equal(remote[:], arr[:])
361+
362+
# Shut down when finished.
363+
server.should_exit = True
329364
```
330365

331366
### CORS Support
332367

333-
Both `serve_store` and `serve_node` accept a [`CorsOptions`][zarr.experimental.serve.CorsOptions]
334-
parameter to enable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
335-
middleware for browser-based clients:
368+
Both `store_app` and `node_app` (and their `serve_*` counterparts) accept a
369+
[`CorsOptions`][zarr.experimental.serve.CorsOptions] parameter to enable
370+
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) middleware for
371+
browser-based clients:
336372

337373
```python
338-
from zarr.experimental.serve import CorsOptions, serve_store
374+
from zarr.experimental.serve import CorsOptions, store_app
339375

340-
app = serve_store(
376+
app = store_app(
341377
store,
342378
cors_options=CorsOptions(
343379
allow_origins=["*"],
@@ -365,44 +401,7 @@ By default only `GET` requests are accepted. To enable writes, pass
365401
`methods={"GET", "PUT"}`:
366402

367403
```python
368-
app = serve_store(store, methods={"GET", "PUT"})
404+
app = store_app(store, methods={"GET", "PUT"})
369405
```
370406

371407
A `PUT` request stores the request body at the given path and returns 204 (No Content).
372-
373-
### Running the Server in a Background Thread
374-
375-
Because `serve_store` and `serve_node` return a standard ASGI app, you can run the
376-
server in a daemon thread and interact with it from the same process. This is
377-
useful for notebooks, scripts, and interactive exploration:
378-
379-
```python
380-
import threading
381-
382-
import numpy as np
383-
import uvicorn
384-
385-
import zarr
386-
from zarr.experimental.serve import serve_node
387-
from zarr.storage import MemoryStore
388-
389-
# Create an array with some data.
390-
store = MemoryStore()
391-
arr = zarr.create_array(store, shape=(100,), chunks=(10,), dtype="float64")
392-
arr[:] = np.arange(100, dtype="float64")
393-
394-
# Build the ASGI app and launch Uvicorn in a daemon thread.
395-
app = serve_node(arr)
396-
config = uvicorn.Config(app, host="127.0.0.1", port=8000)
397-
server = uvicorn.Server(config)
398-
thread = threading.Thread(target=server.run, daemon=True)
399-
thread.start()
400-
401-
# Now open the served array from another zarr client.
402-
remote = zarr.open_array("http://127.0.0.1:8000", mode="r")
403-
np.testing.assert_array_equal(remote[:], arr[:])
404-
405-
# Shut down when finished.
406-
server.should_exit = True
407-
thread.join()
408-
```

examples/serve_v2_v3/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This example demonstrates how to build a custom read-only `Store` that
44
translates Zarr v2 data into v3 format on the fly, and serve it over HTTP
5-
using `zarr.experimental.serve.serve_store`.
5+
using `zarr.experimental.serve.store_app`.
66

77
The example shows how to:
88

examples/serve_v2_v3/serve_v2_v3.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* The v2 metadata files (``.zarray``, ``.zattrs``) are hidden so only
2727
v3 keys are visible.
2828
29-
The translated store is then served over HTTP with ``serve_store``. A test
29+
The translated store is then served over HTTP with ``store_app``. A test
3030
at the bottom opens the served data *as a v3 array* and verifies it can
3131
read the values back.
3232
"""
@@ -272,12 +272,12 @@ def test_serve_roundtrip() -> None:
272272
"""Serve the translated store over HTTP and read it back as v3."""
273273
from starlette.testclient import TestClient
274274

275-
from zarr.experimental.serve import serve_store
275+
from zarr.experimental.serve import store_app
276276

277277
v2_store, _data = create_v2_array()
278278
v3_store = V2AsV3Store(v2_store)
279279

280-
app = serve_store(v3_store)
280+
app = store_app(v3_store)
281281
client = TestClient(app)
282282

283283
# Metadata should be valid v3 JSON.
@@ -328,7 +328,7 @@ def test_open_as_v3_array() -> None:
328328
print("\n4. Store listing")
329329
test_listing()
330330

331-
print("\n5. HTTP round-trip via serve_store")
331+
print("\n5. HTTP round-trip via store_app")
332332
test_serve_roundtrip()
333333

334334
print("\n6. Open as v3 array")

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ nav:
2929
- user-guide/experimental.md
3030
- Examples:
3131
- user-guide/examples/custom_dtype.md
32+
- user-guide/examples/serve_v2_v3.md
3233
- API Reference:
3334
- api/zarr/index.md
3435
- api/zarr/array.md

0 commit comments

Comments
 (0)