Skip to content

Commit 71419d5

Browse files
committed
Raise Python exceptions instead of aborting the process
Add InvalidFlatbuffer exception, raised upon invalid data into unpack Raise ValueError upon out of bounds number being passed to __init__ for enums
1 parent e128f84 commit 71419d5

5 files changed

Lines changed: 114 additions & 50 deletions

File tree

Cargo.lock

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rlbot-flatbuffers-py"
3-
version = "0.3.3"
3+
version = "0.3.4"
44
edition = "2021"
55
description = "A Python module implemented in Rust for serializing and deserializing RLBot's flatbuffers"
66
repository = "https://github.com/VirxEC/rlbot-flatbuffers-py"

build.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ impl PythonBindGenerator {
6565
let mut file_contents = vec![];
6666

6767
file_contents.push(Cow::Borrowed(match bind_type {
68-
PythonBindType::Struct | PythonBindType::Union => "use crate::{generated::rlbot::flat, FromGil, IntoGil};",
69-
PythonBindType::Enum => "use crate::generated::rlbot::flat;",
68+
PythonBindType::Struct => "use crate::{flat_err_to_py, generated::rlbot::flat, FromGil, IntoGil};",
69+
PythonBindType::Union => "use crate::{generated::rlbot::flat, FromGil, IntoGil};",
70+
PythonBindType::Enum => "use crate::{flat_err_to_py, generated::rlbot::flat};",
7071
}));
7172

7273
if bind_type != PythonBindType::Union {
@@ -82,8 +83,10 @@ impl PythonBindGenerator {
8283
}
8384

8485
file_contents.push(Cow::Borrowed(match bind_type {
85-
PythonBindType::Struct => "use pyo3::{pyclass, pymethods, types::PyBytes, Bound, Py, Python};",
86-
PythonBindType::Enum => "use pyo3::{pyclass, pymethods, types::PyBytes, Bound, Python};",
86+
PythonBindType::Struct => "use pyo3::{pyclass, pymethods, types::PyBytes, Bound, Py, PyResult, Python};",
87+
PythonBindType::Enum => {
88+
"use pyo3::{exceptions::PyValueError, pyclass, pymethods, types::PyBytes, Bound, PyResult, Python};"
89+
}
8790
PythonBindType::Union => "use pyo3::{pyclass, pymethods, Py, PyObject, Python, ToPyObject};",
8891
}));
8992

@@ -809,19 +812,20 @@ impl PythonBindGenerator {
809812
assert!(u8::try_from(self.types.len()).is_ok());
810813

811814
self.write_str(" #[pyo3(signature = (value=Default::default()))]");
812-
self.write_str(" pub fn new(value: u8) -> Self {");
815+
self.write_str(" pub fn new(value: u8) -> PyResult<Self> {");
813816
self.write_str(" match value {");
814817

815818
for variable_info in &self.types {
816819
let variable_name = &variable_info[0];
817820
let variable_value = &variable_info[1];
818821

819-
self.file_contents
820-
.push(Cow::Owned(format!(" {variable_value} => Self::{variable_name},")));
822+
self.file_contents.push(Cow::Owned(format!(
823+
" {variable_value} => Ok(Self::{variable_name}),"
824+
)));
821825
}
822826

823827
if self.types.len() != usize::from(u8::MAX) {
824-
self.write_str(" v => panic!(\"Unknown value: {v}\"),");
828+
self.write_str(" v => Err(PyValueError::new_err(format!(\"Unknown value of {v}\"))),");
825829
}
826830

827831
self.write_str(" }");
@@ -1118,14 +1122,17 @@ impl PythonBindGenerator {
11181122
self.write_str(" #[staticmethod]");
11191123

11201124
if self.bind_type == PythonBindType::Enum {
1121-
self.write_str(" fn unpack(data: &[u8]) -> Self {");
1122-
self.write_string(format!(" root::<flat::{}>(data).unwrap().into()", self.struct_name));
1125+
self.write_str(" fn unpack(data: &[u8]) -> PyResult<Self> {");
1126+
self.write_string(format!(" match root::<flat::{}>(data) {{", self.struct_name));
1127+
self.write_str(" Ok(flat_t) => Ok(flat_t.into()),");
1128+
self.write_str(" Err(e) => Err(flat_err_to_py(e)),");
1129+
self.write_str(" }");
11231130
} else {
1124-
self.write_str(" fn unpack(py: Python, data: &[u8]) -> Py<Self> {");
1125-
self.write_string(format!(
1126-
" root::<flat::{}>(data).unwrap().unpack().into_gil(py)",
1127-
self.struct_name
1128-
));
1131+
self.write_str(" fn unpack(py: Python, data: &[u8]) -> PyResult<Py<Self>> {");
1132+
self.write_string(format!(" match root::<flat::{}>(data) {{", self.struct_name));
1133+
self.write_str(" Ok(flat_t) => Ok(flat_t.unpack().into_gil(py)),");
1134+
self.write_str(" Err(e) => Err(flat_err_to_py(e)),");
1135+
self.write_str(" }");
11291136
}
11301137

11311138
self.write_str(" }");
@@ -1226,6 +1233,8 @@ fn pyi_generator(type_data: &[(String, String, Vec<Vec<String>>)]) -> io::Result
12261233
Cow::Borrowed("__doc__: str"),
12271234
Cow::Borrowed("__version__: str"),
12281235
Cow::Borrowed(""),
1236+
Cow::Borrowed("class InvalidFlatbuffer(ValueError): ..."),
1237+
Cow::Borrowed(""),
12291238
];
12301239

12311240
let primitive_map = [
@@ -1347,7 +1356,10 @@ fn pyi_generator(type_data: &[(String, String, Vec<Vec<String>>)]) -> io::Result
13471356
file_contents.push(Cow::Borrowed(""));
13481357

13491358
if is_enum {
1350-
file_contents.push(Cow::Borrowed(" def __init__(self, value: int = 0): ..."));
1359+
file_contents.push(Cow::Borrowed(" def __init__(self, value: int = 0):"));
1360+
file_contents.push(Cow::Borrowed(" \"\"\""));
1361+
file_contents.push(Cow::Borrowed(" :raises ValueError: If the `value` is not a valid enum value"));
1362+
file_contents.push(Cow::Borrowed(" \"\"\""));
13511363
} else {
13521364
file_contents.push(Cow::Borrowed(" def __init__("));
13531365
file_contents.push(Cow::Borrowed(" self,"));
@@ -1404,7 +1416,12 @@ fn pyi_generator(type_data: &[(String, String, Vec<Vec<String>>)]) -> io::Result
14041416
if !is_union {
14051417
file_contents.push(Cow::Borrowed(" def pack(self) -> bytes: ..."));
14061418
file_contents.push(Cow::Borrowed(" @staticmethod"));
1407-
file_contents.push(Cow::Owned(format!(" def unpack(data: bytes) -> {type_name}: ...")));
1419+
file_contents.push(Cow::Owned(format!(" def unpack(data: bytes) -> {type_name}:")));
1420+
file_contents.push(Cow::Borrowed(" \"\"\""));
1421+
file_contents.push(Cow::Borrowed(
1422+
" :raises InvalidFlatbuffer: If the `data` is invalid for this type",
1423+
));
1424+
file_contents.push(Cow::Borrowed(" \"\"\""));
14081425
}
14091426

14101427
file_contents.push(Cow::Borrowed(""));

pytest.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __add__(self, other):
2424
)
2525
dgs.game_info_state.world_gravity_z = Float(-650)
2626
dgs.game_info_state.end_match.val = True
27-
dgs.console_commands = [ConsoleCommand("freeze")]
27+
dgs.console_commands = [ConsoleCommand("dump_items")]
2828
dgs.ball_state = DesiredBallState()
2929

3030
print(repr(dgs))
@@ -52,20 +52,53 @@ def __add__(self, other):
5252
print(comm.content.decode("utf-8"))
5353
print()
5454

55-
num_trials = 1_000_000
55+
try:
56+
AirState(8)
57+
except ValueError as e:
58+
print(e)
59+
print()
60+
61+
invalid_data = comm.pack()
62+
63+
try:
64+
RenderMessage.unpack(invalid_data)
65+
except InvalidFlatbuffer as e:
66+
print(e)
67+
68+
print("Running quick benchmark...")
69+
70+
num_trials = 100_000
5671

5772
total_make_time = 0
5873
total_pack_time = 0
5974
total_unpack_time = 0
6075
for _ in range(num_trials):
6176
start = time_ns()
6277
desired_game_state = DesiredGameState(
63-
DesiredBallState(DesiredPhysics()),
64-
car_states=[DesiredCarState(boost_amount=100)],
78+
DesiredBallState(
79+
DesiredPhysics(
80+
Vector3Partial(0, 0, 0),
81+
RotatorPartial(0, 0, 0),
82+
Vector3Partial(0, 0, 0),
83+
Vector3Partial(0, 0, 0),
84+
)
85+
),
86+
car_states=[
87+
DesiredCarState(
88+
DesiredPhysics(
89+
Vector3Partial(0, 0, 0),
90+
RotatorPartial(0, 0, 0),
91+
Vector3Partial(0, 0, 0),
92+
Vector3Partial(0, 0, 0),
93+
),
94+
100,
95+
)
96+
for _ in range(8)
97+
],
6598
game_info_state=DesiredGameInfoState(
6699
game_speed=1, world_gravity_z=-650, end_match=True
67100
),
68-
console_commands=[ConsoleCommand("freeze")],
101+
console_commands=[ConsoleCommand("dump_items")],
69102
)
70103
total_make_time += time_ns() - start
71104

src/lib.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,18 @@ pub mod generated;
1212
#[allow(clippy::enum_variant_names)]
1313
mod python;
1414

15-
use pyo3::{prelude::*, types::PyBytes, PyClass};
15+
use pyo3::{create_exception, exceptions::PyValueError, prelude::*, types::PyBytes, PyClass};
1616
use python::*;
17+
use std::panic::Location;
18+
19+
create_exception!(rlbot_flatbuffers, InvalidFlatbuffer, PyValueError, "Invalid FlatBuffer");
20+
21+
#[track_caller]
22+
pub fn flat_err_to_py(err: flatbuffers::InvalidFlatbuffer) -> PyErr {
23+
let caller = Location::caller();
24+
let err_msg = format!("Can't make flatbuffer @ \"rlbot_flatbuffers/{}\":\n {err}", caller.file());
25+
InvalidFlatbuffer::new_err(err_msg)
26+
}
1727

1828
pub trait FromGil<T> {
1929
fn from_gil(py: Python, obj: T) -> Self;
@@ -108,13 +118,14 @@ impl FromGil<Bools> for Py<Bool> {
108118
}
109119

110120
macro_rules! pynamedmodule {
111-
(doc: $doc:literal, name: $name:tt, classes: [$($class_name:ident),*], vars: [$(($var_name:literal, $value:expr)),*]) => {
121+
(doc: $doc:literal, name: $name:tt, classes: [$($class_name:ident),*], vars: [$(($var_name:literal, $value:expr)),*], exceptions: [$($except:expr),*]) => {
112122
#[doc = $doc]
113123
#[pymodule]
114124
#[allow(redundant_semicolons)]
115-
fn $name(m: Bound<PyModule>) -> PyResult<()> {
125+
fn $name(py: Python, m: Bound<PyModule>) -> PyResult<()> {
116126
$(m.add_class::<$class_name>()?);*;
117127
$(m.add($var_name, $value)?);*;
128+
$(m.add(stringify!($except), py.get_type_bound::<$except>())?);*;
118129
Ok(())
119130
}
120131
};
@@ -213,5 +224,8 @@ pynamedmodule! {
213224
],
214225
vars: [
215226
("__version__", env!("CARGO_PKG_VERSION"))
227+
],
228+
exceptions: [
229+
InvalidFlatbuffer
216230
]
217231
}

0 commit comments

Comments
 (0)