chalk-remote-call-python 0.0.0__tar.gz → 1.1.2__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.
Files changed (34) hide show
  1. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/PKG-INFO +11 -7
  2. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/README.md +10 -6
  3. chalk_remote_call_python-1.1.2/chalk_remote_call/__init__.py +4 -0
  4. chalk_remote_call_python-1.1.2/chalk_remote_call/_version.py +1 -0
  5. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/cli.py +20 -3
  6. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/handler_loader.py +19 -8
  7. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/server.py +15 -8
  8. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call_python.egg-info/PKG-INFO +11 -7
  9. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call_python.egg-info/SOURCES.txt +2 -0
  10. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/pyproject.toml +3 -0
  11. chalk_remote_call_python-1.1.2/tests/test_handler_loader.py +99 -0
  12. chalk_remote_call_python-0.0.0/chalk_remote_call/__init__.py +0 -6
  13. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/__main__.py +0 -0
  14. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/_gen/__init__.py +0 -0
  15. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/_gen/chalk/__init__.py +0 -0
  16. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/_gen/chalk/runtime/__init__.py +0 -0
  17. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/_gen/chalk/runtime/v1/__init__.py +0 -0
  18. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2.py +0 -0
  19. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/_gen/chalk/runtime/v1/remote_python_call_pb2_grpc.py +0 -0
  20. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/_native.pyi +0 -0
  21. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/arrow_utils.py +0 -0
  22. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/input_transform.py +0 -0
  23. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call/servicer.py +0 -0
  24. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call_python.egg-info/dependency_links.txt +0 -0
  25. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call_python.egg-info/entry_points.txt +0 -0
  26. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call_python.egg-info/requires.txt +0 -0
  27. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/chalk_remote_call_python.egg-info/top_level.txt +0 -0
  28. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/setup.cfg +0 -0
  29. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/setup.py +0 -0
  30. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/tests/test_arrow_utils.py +0 -0
  31. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/tests/test_end_to_end.py +0 -0
  32. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/tests/test_error_paths.py +0 -0
  33. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/tests/test_input_transform.py +0 -0
  34. {chalk_remote_call_python-0.0.0 → chalk_remote_call_python-1.1.2}/tests/test_servicer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chalk-remote-call-python
3
- Version: 0.0.0
3
+ Version: 1.1.2
4
4
  Summary: Chalk remote call Python runtime interface client
5
5
  Author: Chalk AI, Inc.
6
6
  Project-URL: Homepage, https://chalk.ai
@@ -65,15 +65,18 @@ def handler(event: dict[str, pa.Array], context: dict) -> pa.Array:
65
65
 
66
66
  Args:
67
67
  event: dict mapping column names to pyarrow.Array values.
68
- context: dict with request metadata:
69
- - "peer": remote address string
70
- - "metadata": dict of gRPC headers
68
+ context: dict with request metadata
71
69
 
72
70
  Returns:
73
71
  A pyarrow.Array, pyarrow.RecordBatch, pyarrow.Table, list, dict, or scalar.
74
72
  The framework auto-converts the result to Arrow IPC for the response.
75
73
  """
76
74
  return pc.multiply(event["x"], event["y"])
75
+
76
+
77
+ def on_shutdown():
78
+ """Optional -- runs once after the server stops accepting requests."""
79
+ print("Releasing resources...")
77
80
  ```
78
81
 
79
82
  ### 2. Start the server
@@ -97,6 +100,7 @@ python -m chalk_remote_call --handler my_handler.handler
97
100
  | `--host` | `CHALK_REMOTE_CALL_HOST` | `[::]` | Host to bind to |
98
101
  | `--workers` | `CHALK_REMOTE_CALL_WORKERS` | `10` | Tokio runtime worker threads |
99
102
  | `--on-startup` | | | Dotted path to a startup function |
103
+ | `--on-shutdown` | | | Dotted path to a shutdown function |
100
104
  | `--log-level` | | `INFO` | `DEBUG`, `INFO`, `WARNING`, or `ERROR` |
101
105
 
102
106
  ### Environment variables
@@ -115,12 +119,12 @@ WORKDIR /app
115
119
  RUN pip install chalk-remote-call-python
116
120
 
117
121
  # Copy your handler code
118
- COPY my_handler.py .
122
+ COPY my_handler.py /app
119
123
 
120
- # The server listens on port 6666 by default
121
124
  EXPOSE 6666
122
125
 
123
- ENTRYPOINT ["chalk-remote-call", "--handler", "my_handler.handler"]
126
+ ENTRYPOINT ["chalk-remote-call"]
127
+ CMD ["--handler", "handler.handler"]
124
128
  ```
125
129
 
126
130
  Build and run:
@@ -32,15 +32,18 @@ def handler(event: dict[str, pa.Array], context: dict) -> pa.Array:
32
32
 
33
33
  Args:
34
34
  event: dict mapping column names to pyarrow.Array values.
35
- context: dict with request metadata:
36
- - "peer": remote address string
37
- - "metadata": dict of gRPC headers
35
+ context: dict with request metadata
38
36
 
39
37
  Returns:
40
38
  A pyarrow.Array, pyarrow.RecordBatch, pyarrow.Table, list, dict, or scalar.
41
39
  The framework auto-converts the result to Arrow IPC for the response.
42
40
  """
43
41
  return pc.multiply(event["x"], event["y"])
42
+
43
+
44
+ def on_shutdown():
45
+ """Optional -- runs once after the server stops accepting requests."""
46
+ print("Releasing resources...")
44
47
  ```
45
48
 
46
49
  ### 2. Start the server
@@ -64,6 +67,7 @@ python -m chalk_remote_call --handler my_handler.handler
64
67
  | `--host` | `CHALK_REMOTE_CALL_HOST` | `[::]` | Host to bind to |
65
68
  | `--workers` | `CHALK_REMOTE_CALL_WORKERS` | `10` | Tokio runtime worker threads |
66
69
  | `--on-startup` | | | Dotted path to a startup function |
70
+ | `--on-shutdown` | | | Dotted path to a shutdown function |
67
71
  | `--log-level` | | `INFO` | `DEBUG`, `INFO`, `WARNING`, or `ERROR` |
68
72
 
69
73
  ### Environment variables
@@ -82,12 +86,12 @@ WORKDIR /app
82
86
  RUN pip install chalk-remote-call-python
83
87
 
84
88
  # Copy your handler code
85
- COPY my_handler.py .
89
+ COPY my_handler.py /app
86
90
 
87
- # The server listens on port 6666 by default
88
91
  EXPOSE 6666
89
92
 
90
- ENTRYPOINT ["chalk-remote-call", "--handler", "my_handler.handler"]
93
+ ENTRYPOINT ["chalk-remote-call"]
94
+ CMD ["--handler", "handler.handler"]
91
95
  ```
92
96
 
93
97
  Build and run:
@@ -0,0 +1,4 @@
1
+ from chalk_remote_call._version import __version__
2
+ from chalk_remote_call.server import serve
3
+
4
+ __all__ = ["__version__", "serve"]
@@ -0,0 +1 @@
1
+ __version__ = "1.1.2"
@@ -5,7 +5,7 @@ import logging
5
5
  import os
6
6
  import sys
7
7
 
8
- from chalk_remote_call.handler_loader import load_handler, load_startup_function
8
+ from chalk_remote_call.handler_loader import load_function, load_handler
9
9
  from chalk_remote_call.input_transform import parse_input_args
10
10
  from chalk_remote_call.server import serve
11
11
 
@@ -42,6 +42,11 @@ def main() -> None:
42
42
  default=None,
43
43
  help="Dotted path to a startup function (e.g. my_module.setup)",
44
44
  )
45
+ parser.add_argument(
46
+ "--on-shutdown",
47
+ default=None,
48
+ help="Dotted path to a shutdown function (e.g. my_module.cleanup)",
49
+ )
45
50
  parser.add_argument(
46
51
  "--log-level",
47
52
  default="INFO",
@@ -57,7 +62,7 @@ def main() -> None:
57
62
 
58
63
  # Load handler
59
64
  try:
60
- handler, auto_startup = load_handler(args.handler)
65
+ handler, auto_startup, auto_shutdown = load_handler(args.handler)
61
66
  except Exception as e:
62
67
  print(f"Error loading handler: {e}", file=sys.stderr)
63
68
  sys.exit(1)
@@ -66,13 +71,24 @@ def main() -> None:
66
71
  on_startup = None
67
72
  if args.on_startup:
68
73
  try:
69
- on_startup = load_startup_function(args.on_startup)
74
+ on_startup = load_function(args.on_startup, label="Startup")
70
75
  except Exception as e:
71
76
  print(f"Error loading startup function: {e}", file=sys.stderr)
72
77
  sys.exit(1)
73
78
  elif auto_startup is not None:
74
79
  on_startup = auto_startup
75
80
 
81
+ # Determine shutdown function
82
+ on_shutdown = None
83
+ if args.on_shutdown:
84
+ try:
85
+ on_shutdown = load_function(args.on_shutdown, label="Shutdown")
86
+ except Exception as e:
87
+ print(f"Error loading shutdown function: {e}", file=sys.stderr)
88
+ sys.exit(1)
89
+ elif auto_shutdown is not None:
90
+ on_shutdown = auto_shutdown
91
+
76
92
  # Parse input args
77
93
  arg_names = parse_input_args()
78
94
 
@@ -82,5 +98,6 @@ def main() -> None:
82
98
  port=args.port,
83
99
  workers=args.workers,
84
100
  on_startup=on_startup,
101
+ on_shutdown=on_shutdown,
85
102
  arg_names=arg_names,
86
103
  )
@@ -5,12 +5,14 @@ from collections.abc import Callable
5
5
  from typing import Any
6
6
 
7
7
 
8
- def load_handler(dotted_path: str) -> tuple[Callable[..., Any], Callable[[], None] | None]:
8
+ def load_handler(
9
+ dotted_path: str,
10
+ ) -> tuple[Callable[..., Any], Callable[[], None] | None, Callable[[], None] | None]:
9
11
  """Load a handler function from a dotted module path.
10
12
 
11
- Returns (handler_fn, on_startup_fn_or_None).
12
- The on_startup function is auto-discovered if the handler's module defines
13
- a top-level `on_startup` callable.
13
+ Returns (handler_fn, on_startup_fn_or_None, on_shutdown_fn_or_None).
14
+ The on_startup/on_shutdown functions are auto-discovered if the handler's
15
+ module defines top-level callables with those names.
14
16
  """
15
17
  if "." not in dotted_path:
16
18
  raise ValueError(f"Handler path must be a dotted path like 'my_module.handler', got: {dotted_path!r}")
@@ -27,13 +29,22 @@ def load_handler(dotted_path: str) -> tuple[Callable[..., Any], Callable[[], Non
27
29
  if on_startup is not None and not callable(on_startup):
28
30
  on_startup = None
29
31
 
30
- return handler, on_startup
32
+ on_shutdown = getattr(module, "on_shutdown", None)
33
+ if on_shutdown is not None and not callable(on_shutdown):
34
+ on_shutdown = None
31
35
 
36
+ return handler, on_startup, on_shutdown
32
37
 
33
- def load_startup_function(dotted_path: str) -> Callable[[], None]:
34
- """Load a startup function from a dotted module path."""
38
+
39
+ def load_function(dotted_path: str, label: str = "Function") -> Callable[[], None]:
40
+ """Load a callable from a dotted module path.
41
+
42
+ Args:
43
+ dotted_path: e.g. 'my_module.setup'
44
+ label: Human-readable label for error messages (e.g. "Startup", "Shutdown").
45
+ """
35
46
  if "." not in dotted_path:
36
- raise ValueError(f"Startup path must be a dotted path like 'my_module.setup', got: {dotted_path!r}")
47
+ raise ValueError(f"{label} path must be a dotted path like 'my_module.func', got: {dotted_path!r}")
37
48
 
38
49
  module_path, func_name = dotted_path.rsplit(".", 1)
39
50
  module = importlib.import_module(module_path)
@@ -16,6 +16,7 @@ def serve(
16
16
  port: int = 6666,
17
17
  workers: int = 10,
18
18
  on_startup: Callable[[], None] | None = None,
19
+ on_shutdown: Callable[[], None] | None = None,
19
20
  arg_names: list[str] | None = None,
20
21
  ) -> None:
21
22
  """Start the gRPC server implementing RemoteCallService.
@@ -26,6 +27,7 @@ def serve(
26
27
  port: The port to bind to.
27
28
  workers: Number of worker threads for the tokio runtime.
28
29
  on_startup: Optional startup hook called before the server starts accepting requests.
30
+ on_shutdown: Optional shutdown hook called after the server stops accepting requests.
29
31
  arg_names: Parsed CHALK_INPUT_ARGS column names, or None.
30
32
  """
31
33
  # Run startup hook before starting the Rust server
@@ -34,11 +36,16 @@ def serve(
34
36
  on_startup()
35
37
 
36
38
  logger.info("Starting server on %s:%d", host, port)
37
- start_server(
38
- handler=handler,
39
- process_fn=process_batches,
40
- host=host,
41
- port=port,
42
- workers=workers,
43
- arg_names=arg_names,
44
- )
39
+ try:
40
+ start_server(
41
+ handler=handler,
42
+ process_fn=process_batches,
43
+ host=host,
44
+ port=port,
45
+ workers=workers,
46
+ arg_names=arg_names,
47
+ )
48
+ finally:
49
+ if on_shutdown is not None:
50
+ logger.info("Running shutdown hook...")
51
+ on_shutdown()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chalk-remote-call-python
3
- Version: 0.0.0
3
+ Version: 1.1.2
4
4
  Summary: Chalk remote call Python runtime interface client
5
5
  Author: Chalk AI, Inc.
6
6
  Project-URL: Homepage, https://chalk.ai
@@ -65,15 +65,18 @@ def handler(event: dict[str, pa.Array], context: dict) -> pa.Array:
65
65
 
66
66
  Args:
67
67
  event: dict mapping column names to pyarrow.Array values.
68
- context: dict with request metadata:
69
- - "peer": remote address string
70
- - "metadata": dict of gRPC headers
68
+ context: dict with request metadata
71
69
 
72
70
  Returns:
73
71
  A pyarrow.Array, pyarrow.RecordBatch, pyarrow.Table, list, dict, or scalar.
74
72
  The framework auto-converts the result to Arrow IPC for the response.
75
73
  """
76
74
  return pc.multiply(event["x"], event["y"])
75
+
76
+
77
+ def on_shutdown():
78
+ """Optional -- runs once after the server stops accepting requests."""
79
+ print("Releasing resources...")
77
80
  ```
78
81
 
79
82
  ### 2. Start the server
@@ -97,6 +100,7 @@ python -m chalk_remote_call --handler my_handler.handler
97
100
  | `--host` | `CHALK_REMOTE_CALL_HOST` | `[::]` | Host to bind to |
98
101
  | `--workers` | `CHALK_REMOTE_CALL_WORKERS` | `10` | Tokio runtime worker threads |
99
102
  | `--on-startup` | | | Dotted path to a startup function |
103
+ | `--on-shutdown` | | | Dotted path to a shutdown function |
100
104
  | `--log-level` | | `INFO` | `DEBUG`, `INFO`, `WARNING`, or `ERROR` |
101
105
 
102
106
  ### Environment variables
@@ -115,12 +119,12 @@ WORKDIR /app
115
119
  RUN pip install chalk-remote-call-python
116
120
 
117
121
  # Copy your handler code
118
- COPY my_handler.py .
122
+ COPY my_handler.py /app
119
123
 
120
- # The server listens on port 6666 by default
121
124
  EXPOSE 6666
122
125
 
123
- ENTRYPOINT ["chalk-remote-call", "--handler", "my_handler.handler"]
126
+ ENTRYPOINT ["chalk-remote-call"]
127
+ CMD ["--handler", "handler.handler"]
124
128
  ```
125
129
 
126
130
  Build and run:
@@ -4,6 +4,7 @@ setup.py
4
4
  chalk_remote_call/__init__.py
5
5
  chalk_remote_call/__main__.py
6
6
  chalk_remote_call/_native.pyi
7
+ chalk_remote_call/_version.py
7
8
  chalk_remote_call/arrow_utils.py
8
9
  chalk_remote_call/cli.py
9
10
  chalk_remote_call/handler_loader.py
@@ -25,5 +26,6 @@ chalk_remote_call_python.egg-info/top_level.txt
25
26
  tests/test_arrow_utils.py
26
27
  tests/test_end_to_end.py
27
28
  tests/test_error_paths.py
29
+ tests/test_handler_loader.py
28
30
  tests/test_input_transform.py
29
31
  tests/test_servicer.py
@@ -52,6 +52,9 @@ Changelog = "https://docs.chalk.ai/docs/changelog"
52
52
  [project.scripts]
53
53
  chalk-remote-call = "chalk_remote_call.cli:main"
54
54
 
55
+ [tool.setuptools.dynamic]
56
+ version = {attr = "chalk_remote_call._version.__version__"}
57
+
55
58
  [tool.setuptools.packages.find]
56
59
  where = ["."]
57
60
  include = ["chalk_remote_call*"]
@@ -0,0 +1,99 @@
1
+ """Tests for handler_loader: load_handler and load_function."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import types
6
+
7
+ import pytest
8
+
9
+ from chalk_remote_call.handler_loader import load_function, load_handler
10
+
11
+
12
+ def _make_module(name: str, **attrs: object) -> types.ModuleType:
13
+ """Create a throwaway module with the given attributes."""
14
+ mod = types.ModuleType(name)
15
+ for k, v in attrs.items():
16
+ setattr(mod, k, v)
17
+ return mod
18
+
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # load_function
22
+ # ---------------------------------------------------------------------------
23
+
24
+
25
+ class TestLoadFunction:
26
+ def test_missing_dot_raises(self):
27
+ with pytest.raises(ValueError, match="dotted path"):
28
+ load_function("nodot", label="Test")
29
+
30
+ def test_nonexistent_module_raises(self):
31
+ with pytest.raises(ModuleNotFoundError):
32
+ load_function("nonexistent_module_xyz.func", label="Test")
33
+
34
+ def test_nonexistent_attr_raises(self):
35
+ with pytest.raises(AttributeError, match="no attribute"):
36
+ load_function("os.nonexistent_func_xyz", label="Test")
37
+
38
+ def test_not_callable_raises(self):
39
+ with pytest.raises(TypeError, match="not callable"):
40
+ load_function("os.sep", label="Test")
41
+
42
+ def test_loads_callable(self):
43
+ fn = load_function("os.path.exists", label="Test")
44
+ assert callable(fn)
45
+
46
+ def test_label_appears_in_error(self):
47
+ with pytest.raises(ValueError, match="Shutdown"):
48
+ load_function("nodot", label="Shutdown")
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # load_handler — auto-discovery of on_startup / on_shutdown
53
+ # ---------------------------------------------------------------------------
54
+
55
+
56
+ class TestLoadHandlerAutoDiscovery:
57
+ def test_discovers_on_startup(self, monkeypatch):
58
+ startup = lambda: None # noqa: E731
59
+ mod = _make_module("fake_mod", handler=lambda e, c: e, on_startup=startup)
60
+ monkeypatch.setitem(__import__("sys").modules, "fake_mod", mod)
61
+
62
+ _handler, on_startup, on_shutdown = load_handler("fake_mod.handler")
63
+ assert on_startup is startup
64
+ assert on_shutdown is None
65
+
66
+ def test_discovers_on_shutdown(self, monkeypatch):
67
+ shutdown = lambda: None # noqa: E731
68
+ mod = _make_module("fake_mod", handler=lambda e, c: e, on_shutdown=shutdown)
69
+ monkeypatch.setitem(__import__("sys").modules, "fake_mod", mod)
70
+
71
+ _handler, on_startup, on_shutdown = load_handler("fake_mod.handler")
72
+ assert on_startup is None
73
+ assert on_shutdown is shutdown
74
+
75
+ def test_discovers_both(self, monkeypatch):
76
+ startup = lambda: None # noqa: E731
77
+ shutdown = lambda: None # noqa: E731
78
+ mod = _make_module("fake_mod", handler=lambda e, c: e, on_startup=startup, on_shutdown=shutdown)
79
+ monkeypatch.setitem(__import__("sys").modules, "fake_mod", mod)
80
+
81
+ _handler, on_startup, on_shutdown = load_handler("fake_mod.handler")
82
+ assert on_startup is startup
83
+ assert on_shutdown is shutdown
84
+
85
+ def test_ignores_non_callable(self, monkeypatch):
86
+ mod = _make_module("fake_mod", handler=lambda e, c: e, on_startup="not callable", on_shutdown=42)
87
+ monkeypatch.setitem(__import__("sys").modules, "fake_mod", mod)
88
+
89
+ _handler, on_startup, on_shutdown = load_handler("fake_mod.handler")
90
+ assert on_startup is None
91
+ assert on_shutdown is None
92
+
93
+ def test_neither_defined(self, monkeypatch):
94
+ mod = _make_module("fake_mod", handler=lambda e, c: e)
95
+ monkeypatch.setitem(__import__("sys").modules, "fake_mod", mod)
96
+
97
+ _handler, on_startup, on_shutdown = load_handler("fake_mod.handler")
98
+ assert on_startup is None
99
+ assert on_shutdown is None
@@ -1,6 +0,0 @@
1
- from importlib.metadata import version
2
-
3
- from chalk_remote_call.server import serve
4
-
5
- __version__ = version("chalk-remote-call-python")
6
- __all__ = ["serve"]