-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Function default values #27
base: main
Are you sure you want to change the base?
Conversation
Fixed breaking test Optional Type excluded from option Cargo fmt Cargo clippy
The pipeline is now failing with the same clippy issues as the last commit on main. So I assume all issues on my side are fixed |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #27 +/- ##
==========================================
+ Coverage 66.55% 66.73% +0.18%
==========================================
Files 22 22
Lines 3199 3223 +24
==========================================
+ Hits 2129 2151 +22
- Misses 1070 1072 +2 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for your contributions @NiklasVousten!
This is a great addition! I was wondering how to achieve something similar with builders akin to Bon, but this PR does it more directly while still being compatible with such wrappers.
I did a quick test, but I think there are some unsupported cases:
-
The updated identifier
optional_*
containing.is_some()
check does not seem to get created/propagated in some class methods. I think getting rid of the new identifier (without the "optional_" prefix) would be ideal to resolve this issue (and directly check forp_*.is_some()
where needed. -
kwargs
seem to be tricky and sometimes result in both redundant and non-compilable code. I included a specific case related to Python dataclass below
Special case with dataclass
@dataclass
class EnvSpec:
id: str
entry_point: EnvCreator | str | None = field(default=None)
# Environment attributes
reward_threshold: float | None = field(default=None)
nondeterministic: bool = field(default=False)
# Wrappers
max_episode_steps: int | None = field(default=None)
order_enforce: bool = field(default=True)
autoreset: bool = field(default=False)
disable_env_checker: bool = field(default=False)
apply_api_compatibility: bool = field(default=False)
# Environment arguments
kwargs: dict = field(default_factory=dict)
# post-init attributes
namespace: str | None = field(init=False)
name: str = field(init=False)
version: int | None = field(init=False)
# applied wrappers
additional_wrappers: tuple[WrapperSpec, ...] = field(default_factory=tuple)
# Vectorized environment entry point
vector_entry_point: VectorEnvCreator | str | None = field(default=None)
Neither of the optional_p
is being used
pub fn new<'py>(
py: ::pyo3::marker::Python<'py>,
p_id: &str,
p_entry_point: Option<impl ::pyo3::IntoPy<::pyo3::Py<::pyo3::types::PyAny>>>,
p_reward_threshold: ::std::option::Option<f64>,
p_nondeterministic: Option<bool>,
p_max_episode_steps: ::std::option::Option<i64>,
p_order_enforce: Option<bool>,
p_autoreset: Option<bool>,
p_disable_env_checker: Option<bool>,
p_apply_api_compatibility: Option<bool>,
p_kwargs: Option<impl ::pyo3::types::IntoPyDict>,
p_additional_wrappers: Option<&[::pyo3::Bound<'py, ::pyo3::types::PyAny>]>,
p_vector_entry_point: Option<
impl ::pyo3::IntoPy<::pyo3::Py<::pyo3::types::PyAny>>,
>,
) -> ::pyo3::PyResult<::pyo3::Bound<'py, Self>> {
let optional_p_entry_point = p_entry_point.is_some();
let p_entry_point = ::pyo3::IntoPy::<::pyo3::Py<::pyo3::types::PyAny>>::into_py(
p_entry_point,
py,
);
let p_entry_point = p_entry_point.bind(py);
let optional_p_nondeterministic = p_nondeterministic.is_some();
let optional_p_order_enforce = p_order_enforce.is_some();
let optional_p_autoreset = p_autoreset.is_some();
let optional_p_disable_env_checker = p_disable_env_checker.is_some();
let optional_p_apply_api_compatibility = p_apply_api_compatibility.is_some();
let optional_p_kwargs = p_kwargs.is_some();
let p_kwargs = ::pyo3::types::IntoPyDict::into_py_dict_bound(p_kwargs, py);
let optional_p_additional_wrappers = p_additional_wrappers.is_some();
let optional_p_vector_entry_point = p_vector_entry_point.is_some();
let p_vector_entry_point =
::pyo3::IntoPy::<::pyo3::Py<::pyo3::types::PyAny>>::into_py(
p_vector_entry_point,
py,
);
let p_vector_entry_point = p_vector_entry_point.bind(py);
::pyo3::types::PyAnyMethods::extract(&::pyo3::types::PyAnyMethods::call1(
::pyo3::types::PyAnyMethods::getattr(
py.import_bound(::pyo3::intern!(py, "gymnasium.envs.registration"))?
.as_any(),
::pyo3::intern!(py, "EnvSpec"),
)?
.as_any(),
::pyo3::types::PyTuple::new_bound(
py,
[
::pyo3::ToPyObject::to_object(&p_id, py),
::pyo3::ToPyObject::to_object(&p_entry_point, py),
::pyo3::ToPyObject::to_object(&p_reward_threshold, py),
::pyo3::ToPyObject::to_object(&p_nondeterministic, py),
::pyo3::ToPyObject::to_object(&p_max_episode_steps, py),
::pyo3::ToPyObject::to_object(&p_order_enforce, py),
::pyo3::ToPyObject::to_object(&p_autoreset, py),
::pyo3::ToPyObject::to_object(&p_disable_env_checker, py),
::pyo3::ToPyObject::to_object(&p_apply_api_compatibility, py),
::pyo3::ToPyObject::to_object(&p_kwargs, py),
::pyo3::ToPyObject::to_object(&p_additional_wrappers, py),
::pyo3::ToPyObject::to_object(&p_vector_entry_point, py),
],
),
)?)
}
FYI; I am busy at the moment, so I won't be able to look much more into this for a couple of weeks.
Also, the tooling/test suite in the repository is generally lacking. Maybe you already found something better, but this helped me debug specific cases (not optimal, yet better than what is included...).
mod test;
rm ./pyo3_bindgen_engine/src/test.rs
touch ./pyo3_bindgen_engine/src/test.rs
cargo run -- -m <decently_complex_python_module> -o ./pyo3_bindgen_engine/src/test.rs
cargo fmt
cargo check |
The rust function signature did not allow default values. Instead the user had to know these values to fill them in.
Every paramter, that has a default value, will now be wrapped inside an option. A user can this then set to None.
If this option is None, it will be excluded from the kwargs dict, and thus the default value will be used.
Furthermore, the Optional Value from typing is excluded.
I am not sure if everything was done correctly and every occurence was changed.
There might be edge cases where this will break, however on the tested examples this worked fine.