Skip to content

Commit 2634c56

Browse files
authored
Merge pull request #27 from LilyAcorn/refactor-pyo3-usage
Refactor pyo3 usage
2 parents 7cd790d + e0e068d commit 2634c56

File tree

2 files changed

+97
-101
lines changed

2 files changed

+97
-101
lines changed

src/lib.rs

Lines changed: 90 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -13,123 +13,112 @@ use pyo3::create_exception;
1313
create_exception!(rustfluent, ParserError, pyo3::exceptions::PyException);
1414

1515
#[pymodule]
16-
fn rustfluent(m: &Bound<'_, PyModule>) -> PyResult<()> {
17-
m.add_class::<Bundle>()?;
18-
m.add("ParserError", m.py().get_type::<ParserError>())?;
19-
Ok(())
20-
}
21-
22-
#[pyclass]
23-
struct Bundle {
24-
bundle: FluentBundle<FluentResource>,
25-
}
16+
mod rustfluent {
17+
use super::*;
2618

27-
#[pymethods]
28-
impl Bundle {
29-
#[new]
30-
#[pyo3(signature = (language, ftl_filenames, strict=false))]
31-
fn new(language: &str, ftl_filenames: &'_ Bound<'_, PyList>, strict: bool) -> PyResult<Self> {
32-
let langid: LanguageIdentifier = language.parse().expect("Parsing failed");
33-
let mut bundle = FluentBundle::new_concurrent(vec![langid]);
19+
#[pymodule_export]
20+
use super::ParserError;
3421

35-
for file_path in ftl_filenames.iter() {
36-
let path_string = file_path.to_string();
37-
let contents = fs::read_to_string(path_string)
38-
.map_err(|_| PyFileNotFoundError::new_err(file_path.to_string()))?;
22+
#[pyclass]
23+
struct Bundle {
24+
bundle: FluentBundle<FluentResource>,
25+
}
3926

40-
let resource = match FluentResource::try_new(contents) {
41-
Ok(resource) => resource,
42-
Err(_) if strict => {
43-
return Err(ParserError::new_err(format!(
44-
"Error when parsing {file_path}."
27+
#[pymethods]
28+
impl Bundle {
29+
#[new]
30+
#[pyo3(signature = (language, ftl_filenames, strict=false))]
31+
fn new(
32+
language: &str,
33+
ftl_filenames: &'_ Bound<'_, PyList>,
34+
strict: bool,
35+
) -> PyResult<Self> {
36+
let langid: LanguageIdentifier = match language.parse() {
37+
Ok(langid) => langid,
38+
Err(_) => {
39+
return Err(PyValueError::new_err(format!(
40+
"Invalid language: '{language}'"
4541
)));
4642
}
47-
Err(error) => {
48-
// The first element of the error is the parsed resource, minus any
49-
// invalid messages.
50-
error.0
51-
}
5243
};
53-
bundle.add_resource_overriding(resource);
54-
}
44+
let mut bundle = FluentBundle::new_concurrent(vec![langid]);
5545

56-
Ok(Self { bundle })
57-
}
46+
for file_path in ftl_filenames.iter() {
47+
let path_string = file_path.to_string();
48+
let contents = fs::read_to_string(path_string)
49+
.map_err(|_| PyFileNotFoundError::new_err(file_path.to_string()))?;
50+
51+
let resource = match FluentResource::try_new(contents) {
52+
Ok(resource) => resource,
53+
Err(_) if strict => {
54+
return Err(ParserError::new_err(format!(
55+
"Error when parsing {file_path}."
56+
)));
57+
}
58+
Err((resource, _errors)) => resource,
59+
};
60+
bundle.add_resource_overriding(resource);
61+
}
5862

59-
#[pyo3(signature = (identifier, variables=None, use_isolating=true))]
60-
pub fn get_translation(
61-
&mut self,
62-
identifier: &str,
63-
variables: Option<&Bound<'_, PyDict>>,
64-
use_isolating: bool,
65-
) -> PyResult<String> {
66-
self.bundle.set_use_isolating(use_isolating);
63+
Ok(Self { bundle })
64+
}
6765

68-
let msg = self
69-
.bundle
70-
.get_message(identifier)
71-
.ok_or_else(|| (PyValueError::new_err(format!("{identifier} not found"))))?;
66+
#[pyo3(signature = (identifier, variables=None, use_isolating=true))]
67+
pub fn get_translation(
68+
&mut self,
69+
identifier: &str,
70+
variables: Option<&Bound<'_, PyDict>>,
71+
use_isolating: bool,
72+
) -> PyResult<String> {
73+
self.bundle.set_use_isolating(use_isolating);
7274

73-
let mut errors = vec![];
74-
let pattern = msg.value().ok_or_else(|| {
75-
PyValueError::new_err(format!("{identifier} - Message has no value.",))
76-
})?;
75+
let msg = self
76+
.bundle
77+
.get_message(identifier)
78+
.ok_or_else(|| (PyValueError::new_err(format!("{identifier} not found"))))?;
7779

78-
let mut args = FluentArgs::new();
80+
let pattern = msg.value().ok_or_else(|| {
81+
PyValueError::new_err(format!("{identifier} - Message has no value.",))
82+
})?;
7983

80-
if let Some(variables) = variables {
81-
for variable in variables {
82-
// Make sure the variable key is a Python string,
83-
// raising a TypeError if not.
84-
let python_key = variable.0;
85-
if !python_key.is_instance_of::<PyString>() {
86-
return Err(PyTypeError::new_err(format!(
87-
"Variable key not a str, got {python_key}."
88-
)));
89-
}
90-
let key = python_key.to_string();
91-
// Set the variable value as a string or integer,
92-
// raising a TypeError if not.
93-
let python_value = variable.1;
94-
if python_value.is_instance_of::<PyString>() {
95-
args.set(key, python_value.to_string());
96-
} else if python_value.is_instance_of::<PyInt>() {
97-
match python_value.extract::<i32>() {
98-
Ok(int_value) => {
99-
args.set(key, int_value);
100-
}
101-
_ => {
102-
// The Python integer overflowed i32.
103-
// Fall back to displaying the variable key as its value.
104-
let fallback_value = key.clone();
105-
args.set(key, fallback_value);
106-
}
84+
let mut args = FluentArgs::new();
85+
86+
if let Some(variables) = variables {
87+
for (python_key, python_value) in variables {
88+
// Make sure the variable key is a Python string,
89+
// raising a TypeError if not.
90+
if !python_key.is_instance_of::<PyString>() {
91+
return Err(PyTypeError::new_err(format!(
92+
"Variable key not a str, got {python_key}."
93+
)));
10794
}
108-
} else if python_value.is_instance_of::<PyDate>() {
109-
// Display the Python date as YYYY-MM-DD.
110-
match python_value.extract::<NaiveDate>() {
111-
Ok(chrono_date) => {
112-
args.set(key, chrono_date.format("%Y-%m-%d").to_string());
113-
}
114-
_ => {
115-
// Could not convert.
116-
// Fall back to displaying the variable key as its value.
117-
let fallback_value = key.clone();
118-
args.set(key, fallback_value);
119-
}
95+
let key = python_key.to_string();
96+
// Set the variable value as a string or integer,
97+
// raising a TypeError if not.
98+
if python_value.is_instance_of::<PyString>() {
99+
args.set(key, python_value.to_string());
100+
} else if python_value.is_instance_of::<PyInt>()
101+
&& let Ok(int_value) = python_value.extract::<i32>()
102+
{
103+
args.set(key, int_value);
104+
} else if python_value.is_instance_of::<PyDate>()
105+
&& let Ok(chrono_date) = python_value.extract::<NaiveDate>()
106+
{
107+
args.set(key, chrono_date.format("%Y-%m-%d").to_string());
108+
} else {
109+
// The variable value was of an unsupported type.
110+
// Fall back to displaying the variable key as its value.
111+
let fallback_value = key.clone();
112+
args.set(key, fallback_value);
120113
}
121-
} else {
122-
// The variable value was of an unsupported type.
123-
// Fall back to displaying the variable key as its value.
124-
let fallback_value = key.clone();
125-
args.set(key, fallback_value);
126114
}
127115
}
128-
}
129116

130-
let value = self
131-
.bundle
132-
.format_pattern(pattern, Some(&args), &mut errors);
133-
Ok(value.to_string())
117+
let mut errors = vec![];
118+
let value = self
119+
.bundle
120+
.format_pattern(pattern, Some(&args), &mut errors);
121+
Ok(value.to_string())
122+
}
134123
}
135124
}

tests/test_python_interface.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ def test_variables_of_different_types(description, identifier, variables, expect
6969
assert result == expected
7070

7171

72+
def test_invalid_language():
73+
with pytest.raises(ValueError) as exc_info:
74+
fluent.Bundle("$", [])
75+
76+
assert str(exc_info.value) == "Invalid language: '$'"
77+
78+
7279
@pytest.mark.parametrize(
7380
"key",
7481
(

0 commit comments

Comments
 (0)