From 44727d2beba5a622d2dec6ec77ac3f290999126a Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 17 Dec 2020 09:14:17 +0000 Subject: [PATCH] review: kngwyu --- CHANGELOG.md | 2 +- src/err/mod.rs | 2 +- src/exceptions.rs | 4 +- src/lib.rs | 2 +- src/python.rs | 123 ++++++++++++++++++++++++---------------------- 5 files changed, 68 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5832c016674..1bf72daaf39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add FFI definitions for `PyBuffer_SizeFromFormat`, `PyObject_LengthHint`, `PyObject_CallNoArgs`, `PyObject_CallOneArg`, `PyObject_CallMethodNoArgs`, `PyObject_CallMethodOneArg`, `PyObject_VectorcallDict`, and `PyObject_VectorcallMethod`. [#1287](https://github.com/PyO3/pyo3/pull/1287) - Add section about Python::check_signals to the FAQ page of the user guide. [#1301](https://github.com/PyO3/pyo3/pull/1301) - Add conversions between u128/i128 and PyLong for PyPy. [#1310](https://github.com/PyO3/pyo3/pull/1310) -- Add `Python::version()` to get the running interpreter version. [#1322](https://github.com/PyO3/pyo3/pull/1322) +- Add `Python::version()` and `Python::version_info()` to get the running interpreter version. [#1322](https://github.com/PyO3/pyo3/pull/1322) ### Changed - Change return type `PyType::name()` from `Cow` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152) diff --git a/src/err/mod.rs b/src/err/mod.rs index 79b17d23cf0..7e0241bc636 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -602,7 +602,7 @@ mod tests { .split(", "); assert_eq!(fields.next().unwrap(), "type: "); - if py.version() >= (3, 7) { + if py.version_info() >= (3, 7) { assert_eq!(fields.next().unwrap(), "value: Exception('banana')"); } else { // Python 3.6 and below formats the repr differently diff --git a/src/exceptions.rs b/src/exceptions.rs index b1bc111a7ab..ef585c78a77 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -599,7 +599,7 @@ mod test { .into_instance(py) .into_ref(py); - if py.version() >= (3, 7) { + if py.version_info() >= (3, 7) { assert_eq!(format!("{:?}", exc), "Exception('banana')"); } else { assert_eq!(format!("{:?}", exc), "Exception('banana',)"); @@ -607,7 +607,7 @@ mod test { let source = exc.source().expect("cause should exist"); - if py.version() >= (3, 7) { + if py.version_info() >= (3, 7) { assert_eq!(format!("{:?}", source), "TypeError('peach')"); } else { assert_eq!(format!("{:?}", source), "TypeError('peach',)"); diff --git a/src/lib.rs b/src/lib.rs index 40a01880d6c..82df7814e93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,7 +144,7 @@ pub use crate::instance::{Py, PyNativeType, PyObject}; pub use crate::pycell::{PyCell, PyRef, PyRefMut}; pub use crate::pyclass::PyClass; pub use crate::pyclass_init::PyClassInitializer; -pub use crate::python::{prepare_freethreaded_python, Python, PythonVersion}; +pub use crate::python::{prepare_freethreaded_python, Python, PythonVersionInfo}; pub use crate::type_object::{type_flags, PyTypeInfo}; // Since PyAny is as important as PyObject, we expose it to the top level. pub use crate::types::PyAny; diff --git a/src/python.rs b/src/python.rs index 995fae715c4..f2ccf4b99eb 100644 --- a/src/python.rs +++ b/src/python.rs @@ -18,29 +18,15 @@ pub use gil::prepare_freethreaded_python; /// /// See [Python::version]. #[derive(Debug)] -pub struct PythonVersion { - major: u8, - minor: (u8, Option<&'static str>), - patch: Option<(u8, Option<&'static str>)>, +pub struct PythonVersionInfo { + pub major: u8, + pub minor: u8, + pub patch: u8, + pub suffix: Option<&'static str>, } -impl PythonVersion { - /// Gets the major version. - pub fn major(&self) -> u8 { - self.major - } - - /// Gets the minor version. - pub fn minor(&self) -> u8 { - self.minor.0 - } - - /// Gets the patch version, if any. - pub fn patch(&self) -> Option { - self.patch.as_ref().map(|patch| patch.0) - } - - /// Parses a hard-coded Python interpreter version string. +impl PythonVersionInfo { + /// Parses a hard-coded Python interpreter version string (e.g. 3.9.0a4+). /// /// Panics if the string is ill-formatted. fn from_str(version_number_str: &'static str) -> Self { @@ -67,43 +53,42 @@ impl PythonVersion { let major = major_str .parse() .expect("Python major version not an integer"); - let minor = split_and_parse_number(minor_str); - if minor.1.is_some() { + let (minor, suffix) = split_and_parse_number(minor_str); + if suffix.is_some() { assert!(patch_str.is_none()); - return PythonVersion { + return PythonVersionInfo { major, minor, - patch: None, + patch: 0, + suffix, }; } - let patch = patch_str.map(split_and_parse_number); - PythonVersion { + let (patch, suffix) = patch_str.map(split_and_parse_number).unwrap_or_default(); + PythonVersionInfo { major, minor, patch, + suffix } } } -impl PartialEq<(u8, u8)> for PythonVersion { +impl PartialEq<(u8, u8)> for PythonVersionInfo { fn eq(&self, other: &(u8, u8)) -> bool { - self.major == other.0 && self.minor.0 == other.1 + self.major == other.0 && self.minor == other.1 } } -impl PartialEq<(u8, u8, u8)> for PythonVersion { +impl PartialEq<(u8, u8, u8)> for PythonVersionInfo { fn eq(&self, other: &(u8, u8, u8)) -> bool { self.major == other.0 - && self.minor.0 == other.1 - && match self.patch { - Some((patch, _)) => patch == other.2, - _ => other.2 == 0, - } + && self.minor == other.1 + && self.patch == other.2 } } -impl PartialOrd<(u8, u8)> for PythonVersion { +impl PartialOrd<(u8, u8)> for PythonVersionInfo { fn partial_cmp(&self, other: &(u8, u8)) -> Option { match self.major.cmp(&other.0) { Ordering::Less => return Some(Ordering::Less), @@ -111,11 +96,11 @@ impl PartialOrd<(u8, u8)> for PythonVersion { Ordering::Equal => {} }; - Some(self.minor.0.cmp(&other.1)) + Some(self.minor.cmp(&other.1)) } } -impl PartialOrd<(u8, u8, u8)> for PythonVersion { +impl PartialOrd<(u8, u8, u8)> for PythonVersionInfo { fn partial_cmp(&self, other: &(u8, u8, u8)) -> Option { match self .partial_cmp(&(other.0, other.1)) @@ -126,7 +111,7 @@ impl PartialOrd<(u8, u8, u8)> for PythonVersion { Ordering::Equal => {} }; - Some(self.patch.map_or(0, |patch| patch.0).cmp(&other.2)) + Some(self.patch.cmp(&other.2)) } } @@ -419,29 +404,47 @@ impl<'p> Python<'p> { unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_NotImplemented()) } } - /// Gets the running Python interpreter version. + /// Gets the running Python interpreter version as a string. + /// + /// This is a wrapper around the ffi call Py_GetVersion. /// /// # Example /// ```rust /// # use pyo3::Python; /// Python::with_gil(|py| { - /// // PyO3 supports Python 3.6 and up. - /// assert!(py.version() >= (3, 6)); - /// assert!(py.version() >= (3, 6, 0)); + /// // The full string could be, for example: + /// // "3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]" + /// assert!(py.version().starts_with("3.")); /// }); /// ``` - pub fn version(self) -> PythonVersion { - let version_str = unsafe { + pub fn version(self) -> &'static str { + unsafe { CStr::from_ptr(ffi::Py_GetVersion() as *const c_char) .to_str() .expect("Python version string not UTF-8") - }; + } + } + + /// Gets the running Python interpreter version as a struct similar to + /// `sys.version_info`. + /// + /// # Example + /// ```rust + /// # use pyo3::Python; + /// Python::with_gil(|py| { + /// // PyO3 supports Python 3.6 and up. + /// assert!(py.version_info() >= (3, 6)); + /// assert!(py.version_info() >= (3, 6, 0)); + /// }); + /// ``` + pub fn version_info(self) -> PythonVersionInfo { + let version_str = self.version(); // Portion of the version string returned by Py_GetVersion up to the first space is the // version number. let version_number_str = version_str.split(' ').next().unwrap_or(version_str); - PythonVersion::from_str(version_number_str) + PythonVersionInfo::from_str(version_number_str) } /// Registers the object in the release pool, and tries to downcast to specific type. @@ -762,8 +765,8 @@ mod test { } #[test] - fn test_python_version() { - let version = Python::with_gil(|py| py.version()); + fn test_python_version_info() { + let version = Python::with_gil(|py| py.version_info()); #[cfg(Py_3_6)] assert!(version >= (3, 6)); #[cfg(Py_3_6)] @@ -783,16 +786,16 @@ mod test { } #[test] - fn test_python_version_parse() { - assert!(PythonVersion::from_str("3.5.0a1") >= (3, 5, 0)); - assert!(PythonVersion::from_str("3.5+") >= (3, 5, 0)); - assert!(PythonVersion::from_str("3.5+") == (3, 5, 0)); - assert!(PythonVersion::from_str("3.5+") != (3, 5, 1)); - assert!(PythonVersion::from_str("3.5+") == (3, 5, 0)); - assert!(PythonVersion::from_str("3.5.2a1+") < (3, 5, 3)); - assert!(PythonVersion::from_str("3.5.2a1+") == (3, 5, 2)); - assert!(PythonVersion::from_str("3.5.2a1+") == (3, 5)); - assert!(PythonVersion::from_str("3.5.2a1+") < (3, 6)); - assert!(PythonVersion::from_str("3.5.2a1+") > (3, 4)); + fn test_python_version_info_parse() { + assert!(PythonVersionInfo::from_str("3.5.0a1") >= (3, 5, 0)); + assert!(PythonVersionInfo::from_str("3.5+") >= (3, 5, 0)); + assert!(PythonVersionInfo::from_str("3.5+") == (3, 5, 0)); + assert!(PythonVersionInfo::from_str("3.5+") != (3, 5, 1)); + assert!(PythonVersionInfo::from_str("3.5.2a1+") < (3, 5, 3)); + assert!(PythonVersionInfo::from_str("3.5.2a1+") == (3, 5, 2)); + assert!(PythonVersionInfo::from_str("3.5.2a1+") == (3, 5)); + assert!(PythonVersionInfo::from_str("3.5+") == (3, 5)); + assert!(PythonVersionInfo::from_str("3.5.2a1+") < (3, 6)); + assert!(PythonVersionInfo::from_str("3.5.2a1+") > (3, 4)); } }