Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/switchyard-py/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Error mapping for the libsy Python binding.
//! Error mapping for the Python bindings.

use pyo3::create_exception;
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;

create_exception!(_switchyard_rust, LibsyError, PyRuntimeError);
create_exception!(_switchyard_rust, ContextWindowExceededError, PyRuntimeError);
create_exception!(_switchyard_rust, ServerConfigError, PyRuntimeError);

/// Converts libsy execution failures into one stable Python exception.
pub(crate) fn py_libsy_error(error: impl std::fmt::Display) -> PyErr {
Expand All @@ -20,5 +21,9 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add(
"ContextWindowExceededError",
module.py().get_type::<ContextWindowExceededError>(),
)?;
module.add(
"ServerConfigError",
module.py().get_type::<ServerConfigError>(),
)
}
6 changes: 5 additions & 1 deletion crates/switchyard-py/src/server_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use switchyard_server::{
use tokio::sync::oneshot;
use tokio::task::JoinHandle;

use crate::errors::ServerConfigError;

const DEFAULT_SHUTDOWN_TIMEOUT_SECS: f64 = 2.0;

/// Running loopback server backed entirely by the native Rust implementation.
Expand All @@ -38,7 +40,8 @@ impl PyServer {
#[pyo3(signature = (config, port=0))]
fn new(config: PathBuf, port: u16) -> PyResult<Self> {
initialize_observability().map_err(server_error)?;
let state = load_server_state(config).map_err(server_error)?;
let state = load_server_state(config)
.map_err(|error| ServerConfigError::new_err(error.to_string()))?;
let caller_auth_by_model = state
.models()
.map(|model| {
Expand Down Expand Up @@ -177,6 +180,7 @@ fn server_error(error: impl std::fmt::Display) -> PyErr {

pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
let server_module = PyModule::new(module.py(), "server")?;
server_module.add("ServerConfigError", module.getattr("ServerConfigError")?)?;
server_module.add_class::<PyServer>()?;
module.add_submodule(&server_module)?;
Ok(())
Expand Down
13 changes: 10 additions & 3 deletions switchyard/cli/launchers/native_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
_LOCAL_OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))


class NativeServerConfigError(RuntimeError):
"""Raised when a native server deployment configuration is invalid."""


class HttpStatsSource(StatsSource):
"""Read launcher statistics from the native server."""

Expand All @@ -39,9 +43,12 @@ class NativeServer:
"""Host one TOML deployment through the PyO3 Rust server binding."""

def __init__(self, config: Path) -> None:
from switchyard_rust.server import Server
from switchyard_rust.server import Server, ServerConfigError

self._server = Server(config, port=0)
try:
self._server = Server(config, port=0)
except ServerConfigError as exc:
raise NativeServerConfigError(str(exc)) from exc
self.port: int = self._server.port
self.base_url: str = self._server.base_url
self.stats: StatsSource = HttpStatsSource(self.base_url)
Expand All @@ -55,4 +62,4 @@ def close(self) -> None:
self._server.close()


__all__ = ["HttpStatsSource", "NativeServer"]
__all__ = ["HttpStatsSource", "NativeServer", "NativeServerConfigError"]
6 changes: 5 additions & 1 deletion switchyard/cli/switchyard_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
cmd_launch_codex,
cmd_launch_openclaw,
)
from switchyard.cli.launchers.native_server import NativeServerConfigError


def _add_launch_parser(
Expand Down Expand Up @@ -73,7 +74,10 @@ def main() -> None:
if not hasattr(args, "func"):
parser.print_help()
raise SystemExit(1)
args.func(args)
try:
args.func(args)
except NativeServerConfigError as exc:
parser.exit(1, f"error: {exc}\n")
Comment thread
ayushag-nv marked this conversation as resolved.


if __name__ == "__main__":
Expand Down
9 changes: 6 additions & 3 deletions switchyard_rust/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

if TYPE_CHECKING:

class ServerConfigError(RuntimeError):
"""Raised when a native server deployment configuration is invalid."""

@final
class Server:
"""Running loopback instance of the native Switchyard server."""
Expand Down Expand Up @@ -39,10 +42,10 @@ def __exit__(


def __getattr__(name: str) -> object:
if name == "Server":
if name in {"Server", "ServerConfigError"}:
native: Any = load_native()
return native.server.Server
return getattr(native.server, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = ["Server"]
__all__ = ["Server", "ServerConfigError"]
Loading