Skip to content
Open
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
62 changes: 55 additions & 7 deletions sdks/python/boxlite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,72 @@
warnings.warn(f"BoxLite native extension not available: {e}", ImportWarning)
__all__ = []

# Import error types (pure Python, always available independently of native extensions)
try:
from .errors import ( # noqa: F401
AlreadyExistsError,
BoxliteError,
ConfigError,
DatabaseError,
EngineError,
ExecError,
ExecutionError,
ImageError,
InternalError,
InvalidArgumentError,
InvalidStateError,
NetworkError,
NotFoundError,
ParseError,
PortalError,
ResourceExhaustedError,
RpcError,
StoppedError,
StorageError,
TimeoutError,
)

__all__.extend(
[
# Error types (base)
"BoxliteError",
# Error types (mapped from Rust)
"EngineError",
"ConfigError",
"StorageError",
"ImageError",
"PortalError",
"NetworkError",
"RpcError",
"InternalError",
"ExecutionError",
"NotFoundError",
"AlreadyExistsError",
"InvalidStateError",
"DatabaseError",
"InvalidArgumentError",
"StoppedError",
"ResourceExhaustedError",
# Error types (Python convenience)
"ExecError",
"TimeoutError",
"ParseError",
]
)
except ImportError:
pass

# Import Python convenience wrappers (re-exported via __all__)
try:
from .codebox import CodeBox # noqa: F401
from .errors import BoxliteError, ExecError, ParseError, TimeoutError # noqa: F401
from .exec import ExecResult # noqa: F401
from .simplebox import SimpleBox # noqa: F401

__all__.extend(
[
# Python convenience wrappers
"SimpleBox",
"CodeBox",
"ExecResult",
# Error types
"BoxliteError",
"ExecError",
"TimeoutError",
"ParseError",
]
)
except ImportError:
Expand Down
128 changes: 126 additions & 2 deletions sdks/python/boxlite/errors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
"""
BoxLite error types.

Provides a hierarchy of exceptions for different failure modes.
Provides a hierarchy of exceptions matching the Rust BoxliteError variants.
"""

__all__ = ["BoxliteError", "ExecError", "TimeoutError", "ParseError"]
__all__ = [
"BoxliteError",
"EngineError",
"ConfigError",
"StorageError",
"ImageError",
"PortalError",
"NetworkError",
"RpcError",
"InternalError",
"ExecutionError",
"NotFoundError",
"AlreadyExistsError",
"InvalidStateError",
"DatabaseError",
"InvalidArgumentError",
"StoppedError",
"ResourceExhaustedError",
# Convenience aliases
"ExecError",
"TimeoutError",
"ParseError",
]


class BoxliteError(Exception):
Expand All @@ -13,6 +35,108 @@ class BoxliteError(Exception):
pass


# ── Mapped from Rust BoxliteError variants ───────────────────────────────


class EngineError(BoxliteError):
"""Raised when the VM engine reports an error."""

pass


class ConfigError(BoxliteError):
"""Raised for configuration errors (invalid options, incompatible settings)."""

pass


class StorageError(BoxliteError):
"""Raised when a filesystem or storage operation fails."""

pass


class ImageError(BoxliteError):
"""Raised when image pull, resolution, or extraction fails."""

pass


class PortalError(BoxliteError):
"""Raised when host-guest communication (gRPC portal) fails."""

pass


class NetworkError(BoxliteError):
"""Raised when a networking operation fails."""

pass


class RpcError(BoxliteError):
"""Raised when a gRPC or transport-level error occurs."""

pass


class InternalError(BoxliteError):
"""Raised for unexpected internal errors."""

pass


class ExecutionError(BoxliteError):
"""Raised when command execution fails at the runtime level."""

pass


class NotFoundError(BoxliteError):
"""Raised when a box or resource is not found."""

pass


class AlreadyExistsError(BoxliteError):
"""Raised when a box or resource already exists."""

pass


class InvalidStateError(BoxliteError):
"""Raised when a box is in the wrong state for the requested operation."""

pass


class DatabaseError(BoxliteError):
"""Raised when a database operation fails."""

pass


class InvalidArgumentError(BoxliteError):
"""Raised when an invalid argument is provided."""

pass


class StoppedError(BoxliteError):
"""Raised when operating on a stopped box or shutdown runtime."""

pass


class ResourceExhaustedError(BoxliteError):
"""Raised when a system resource limit is reached (e.g., VM address spaces exhausted)."""

pass


# ── Convenience exceptions (Python-side only) ────────────────────────────


class ExecError(BoxliteError):
"""
Raised when a command execution fails (non-zero exit code).
Expand Down
25 changes: 14 additions & 11 deletions sdks/python/src/box_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::info::PyBoxInfo;
use crate::metrics::PyBoxMetrics;
use crate::snapshot_options::{PyCloneOptions, PyExportOptions};
use crate::snapshots::PySnapshotHandle;
use crate::util::map_err;
use crate::util::map_boxlite_err;
use boxlite::{BoxCommand, CloneOptions, ExportOptions, LiteBox};
use pyo3::prelude::*;

Expand Down Expand Up @@ -78,7 +78,7 @@ impl PyBox {
cmd = cmd.working_dir(cwd);
}

let execution = handle.exec(cmd).await.map_err(map_err)?;
let execution = handle.exec(cmd).await.map_err(map_boxlite_err)?;

Ok(PyExecution {
execution: Arc::new(execution),
Expand All @@ -91,7 +91,7 @@ impl PyBox {
let handle = Arc::clone(&self.handle);

pyo3_async_runtimes::tokio::future_into_py(py, async move {
handle.start().await.map_err(map_err)?;
handle.start().await.map_err(map_boxlite_err)?;
Ok(())
})
}
Expand All @@ -101,7 +101,7 @@ impl PyBox {
let handle = Arc::clone(&self.handle);

pyo3_async_runtimes::tokio::future_into_py(py, async move {
handle.stop().await.map_err(map_err)?;
handle.stop().await.map_err(map_boxlite_err)?;
Ok(())
})
}
Expand All @@ -110,7 +110,7 @@ impl PyBox {
let handle = Arc::clone(&self.handle);

pyo3_async_runtimes::tokio::future_into_py(py, async move {
let metrics = handle.metrics().await.map_err(map_err)?;
let metrics = handle.metrics().await.map_err(map_boxlite_err)?;
Ok(PyBoxMetrics::from(metrics))
})
}
Expand All @@ -129,7 +129,7 @@ impl PyBox {
let archive = handle
.export(options, std::path::Path::new(&dest))
.await
.map_err(map_err)?;
.map_err(map_boxlite_err)?;
Ok(archive.path().to_string_lossy().to_string())
})
}
Expand All @@ -145,7 +145,10 @@ impl PyBox {
let handle = Arc::clone(&self.handle);
let options: CloneOptions = options.map(Into::into).unwrap_or_default();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let cloned = handle.clone_box(options, name).await.map_err(map_err)?;
let cloned = handle
.clone_box(options, name)
.await
.map_err(map_boxlite_err)?;
Ok(PyBox {
handle: Arc::new(cloned),
})
Expand All @@ -169,7 +172,7 @@ impl PyBox {
handle
.copy_into(std::path::Path::new(&host_path), &container_dest, opts)
.await
.map_err(map_err)?;
.map_err(map_boxlite_err)?;
Ok(())
})
}
Expand All @@ -191,7 +194,7 @@ impl PyBox {
handle
.copy_out(&container_src, std::path::Path::new(&host_dest), opts)
.await
.map_err(map_err)?;
.map_err(map_boxlite_err)?;
Ok(())
})
}
Expand All @@ -202,7 +205,7 @@ impl PyBox {

pyo3_async_runtimes::tokio::future_into_py(py, async move {
// Auto-start on context entry
handle.start().await.map_err(map_err)?;
handle.start().await.map_err(map_boxlite_err)?;
Ok(PyBox { handle })
})
}
Expand All @@ -218,7 +221,7 @@ impl PyBox {
let handle = Arc::clone(&slf.handle);

pyo3_async_runtimes::tokio::future_into_py(py, async move {
handle.stop().await.map_err(map_err)?;
handle.stop().await.map_err(map_boxlite_err)?;
Ok(())
})
}
Expand Down
Loading
Loading