Merge branch 'main' into immutable

This commit is contained in:
David Hewitt 2022-04-19 19:00:33 +01:00
commit 7118e94947
20 changed files with 248 additions and 57 deletions

View File

@ -278,7 +278,7 @@ jobs:
run: | run: |
sudo apt-get install -y mingw-w64 llvm sudo apt-get install -y mingw-w64 llvm
rustup target add x86_64-pc-windows-gnu x86_64-pc-windows-msvc rustup target add x86_64-pc-windows-gnu x86_64-pc-windows-msvc
which cargo-xwin > /dev/null || cargo install cargo-xwin python -m pip install cargo-xwin
cargo build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-gnu cargo build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-gnu
cargo xwin build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-msvc cargo xwin build --manifest-path examples/maturin-starter/Cargo.toml --features abi3 --target x86_64-pc-windows-msvc
- name: Test cross compile to Windows with maturin - name: Test cross compile to Windows with maturin

View File

@ -8,19 +8,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
## [0.16.4] - 2022-04-14
### Added ### Added
- Add `PyTzInfoAccess` trait for safe access to time zone information. [#2263](https://github.com/PyO3/pyo3/pull/2263)
- Add an experimental `generate-abi3-import-lib` feature to auto-generate `python3.dll` import libraries for Windows. [#2282](https://github.com/PyO3/pyo3/pull/2282) - Add an experimental `generate-abi3-import-lib` feature to auto-generate `python3.dll` import libraries for Windows. [#2282](https://github.com/PyO3/pyo3/pull/2282)
- Add FFI definitions for `PyDateTime_BaseTime` and `PyDateTime_BaseDateTime`. [#2294](https://github.com/PyO3/pyo3/pull/2294) - Add FFI definitions for `PyDateTime_BaseTime` and `PyDateTime_BaseDateTime`. [#2294](https://github.com/PyO3/pyo3/pull/2294)
### Changed ### Changed
- Default to "m" ABI tag when choosing `libpython` link name for CPython 3.7 on Unix. [#2288](https://github.com/PyO3/pyo3/pull/2288)
- Improved performance of failing calls to `FromPyObject::extract` which is common when functions accept multiple distinct types. [#2279](https://github.com/PyO3/pyo3/pull/2279) - Improved performance of failing calls to `FromPyObject::extract` which is common when functions accept multiple distinct types. [#2279](https://github.com/PyO3/pyo3/pull/2279)
- Default to "m" ABI tag when choosing `libpython` link name for CPython 3.7 on Unix. [#2288](https://github.com/PyO3/pyo3/pull/2288)
- Allow to compile "abi3" extensions without a working build host Python interpreter. [#2293](https://github.com/PyO3/pyo3/pull/2293)
### Fixed ### Fixed
- Crates depending on PyO3 can collect code coverage via LLVM instrumentation using stable Rust. [#2286](https://github.com/PyO3/pyo3/pull/2286)
- Fix segfault when calling FFI methods `PyDateTime_DATE_GET_TZINFO` or `PyDateTime_TIME_GET_TZINFO` on `datetime` or `time` without a tzinfo. [#2289](https://github.com/PyO3/pyo3/pull/2289) - Fix segfault when calling FFI methods `PyDateTime_DATE_GET_TZINFO` or `PyDateTime_TIME_GET_TZINFO` on `datetime` or `time` without a tzinfo. [#2289](https://github.com/PyO3/pyo3/pull/2289)
- Fix directory names starting with the letter `n` breaking serialization of the interpreter configuration on Windows since PyO3 0.16.3. [#2299](https://github.com/PyO3/pyo3/pull/2299)
## [0.16.3] - 2022-04-05 ## [0.16.3] - 2022-04-05
@ -164,6 +170,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix FFI definition of `_PyLong_NumBits` to return `size_t` instead of `c_int`. [#2161](https://github.com/PyO3/pyo3/pull/2161) - Fix FFI definition of `_PyLong_NumBits` to return `size_t` instead of `c_int`. [#2161](https://github.com/PyO3/pyo3/pull/2161)
- Fix `TypeError` thrown when argument parsing failed missing the originating causes. [2177](https://github.com/PyO3/pyo3/pull/2178) - Fix `TypeError` thrown when argument parsing failed missing the originating causes. [2177](https://github.com/PyO3/pyo3/pull/2178)
## [0.15.2] - 2022-04-14
### Packaging
- Backport of PyPy 3.9 support from PyO3 0.16. [#2262](https://github.com/PyO3/pyo3/pull/2262)
## [0.15.1] - 2021-11-19 ## [0.15.1] - 2021-11-19
### Added ### Added
@ -1145,11 +1157,13 @@ Yanked
- Initial release - Initial release
[Unreleased]: https://github.com/pyo3/pyo3/compare/v0.16.3...HEAD [Unreleased]: https://github.com/pyo3/pyo3/compare/v0.16.4...HEAD
[0.16.3]: https://github.com/pyo3/pyo3/compare/v0.16.3...v0.16.4
[0.16.3]: https://github.com/pyo3/pyo3/compare/v0.16.2...v0.16.3 [0.16.3]: https://github.com/pyo3/pyo3/compare/v0.16.2...v0.16.3
[0.16.2]: https://github.com/pyo3/pyo3/compare/v0.16.1...v0.16.2 [0.16.2]: https://github.com/pyo3/pyo3/compare/v0.16.1...v0.16.2
[0.16.1]: https://github.com/pyo3/pyo3/compare/v0.16.0...v0.16.1 [0.16.1]: https://github.com/pyo3/pyo3/compare/v0.16.0...v0.16.1
[0.16.0]: https://github.com/pyo3/pyo3/compare/v0.15.1...v0.16.0 [0.16.0]: https://github.com/pyo3/pyo3/compare/v0.15.1...v0.16.0
[0.15.2]: https://github.com/pyo3/pyo3/compare/v0.15.1...v0.15.2
[0.15.1]: https://github.com/pyo3/pyo3/compare/v0.15.0...v0.15.1 [0.15.1]: https://github.com/pyo3/pyo3/compare/v0.15.0...v0.15.1
[0.15.0]: https://github.com/pyo3/pyo3/compare/v0.14.5...v0.15.0 [0.15.0]: https://github.com/pyo3/pyo3/compare/v0.14.5...v0.15.0
[0.14.5]: https://github.com/pyo3/pyo3/compare/v0.14.4...v0.14.5 [0.14.5]: https://github.com/pyo3/pyo3/compare/v0.14.4...v0.14.5

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pyo3" name = "pyo3"
version = "0.16.3" version = "0.16.4"
description = "Bindings to Python interpreter" description = "Bindings to Python interpreter"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"] authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
readme = "README.md" readme = "README.md"
@ -19,10 +19,10 @@ libc = "0.2.62"
parking_lot = ">= 0.11, < 0.13" parking_lot = ">= 0.11, < 0.13"
# ffi bindings to the python interpreter, split into a seperate crate so they can be used independently # ffi bindings to the python interpreter, split into a seperate crate so they can be used independently
pyo3-ffi = { path = "pyo3-ffi", version = "=0.16.3" } pyo3-ffi = { path = "pyo3-ffi", version = "=0.16.4" }
# support crates for macros feature # support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.16.3", optional = true } pyo3-macros = { path = "pyo3-macros", version = "=0.16.4", optional = true }
indoc = { version = "1.0.3", optional = true } indoc = { version = "1.0.3", optional = true }
unindent = { version = "0.1.4", optional = true } unindent = { version = "0.1.4", optional = true }
@ -50,7 +50,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.61" serde_json = "1.0.61"
[build-dependencies] [build-dependencies]
pyo3-build-config = { path = "pyo3-build-config", version = "0.16.3", features = ["resolve-config"] } pyo3-build-config = { path = "pyo3-build-config", version = "0.16.4", features = ["resolve-config"] }
[features] [features]
default = ["macros", "pyproto"] default = ["macros", "pyproto"]
@ -67,7 +67,7 @@ pyproto = ["pyo3-macros/pyproto"]
# Use this feature when building an extension module. # Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved, # It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters. # so that the module can also be used with statically linked python interpreters.
extension-module = ["pyo3-ffi/extension-module"] extension-module = ["pyo3-build-config/extension-module", "pyo3-ffi/extension-module"]
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more. # Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3", "pyo3-macros/abi3"] abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3", "pyo3-macros/abi3"]

View File

@ -68,7 +68,7 @@ name = "string_sum"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
pyo3 = { version = "0.16.3", features = ["extension-module"] } pyo3 = { version = "0.16.4", features = ["extension-module"] }
``` ```
**`src/lib.rs`** **`src/lib.rs`**
@ -132,7 +132,7 @@ Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like th
```toml ```toml
[dependencies.pyo3] [dependencies.pyo3]
version = "0.16.3" version = "0.16.4"
features = ["auto-initialize"] features = ["auto-initialize"]
``` ```

View File

@ -5,7 +5,7 @@ publish = false
edition = "2018" edition = "2018"
[dev-dependencies] [dev-dependencies]
pyo3 = { version = "0.16.3", path = "..", features = ["auto-initialize", "extension-module"] } pyo3 = { version = "0.16.4", path = "..", features = ["auto-initialize", "extension-module"] }
[[example]] [[example]]
name = "decorator" name = "decorator"

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.16.3"); variable::set("PYO3_VERSION", "0.16.4");
file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml"); file::rename(".template/pyproject.toml", "pyproject.toml");
file::rename(".template/tox.ini", "tox.ini"); file::rename(".template/tox.ini", "tox.ini");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.16.3"); variable::set("PYO3_VERSION", "0.16.4");
file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml"); file::rename(".template/pyproject.toml", "pyproject.toml");
file::rename(".template/tox.ini", "tox.ini"); file::rename(".template/tox.ini", "tox.ini");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.16.3"); variable::set("PYO3_VERSION", "0.16.4");
file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/setup.cfg", "setup.cfg"); file::rename(".template/setup.cfg", "setup.cfg");
file::rename(".template/tox.ini", "tox.ini"); file::rename(".template/tox.ini", "tox.ini");

View File

@ -1,4 +1,4 @@
variable::set("PYO3_VERSION", "0.16.3"); variable::set("PYO3_VERSION", "0.16.4");
file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/tox.ini", "tox.ini"); file::rename(".template/tox.ini", "tox.ini");
file::delete(".template"); file::delete(".template");

View File

@ -157,6 +157,8 @@ PyO3 is only able to link your extension module to api3 version up to and includ
#### Building `abi3` extensions without a Python interpreter #### Building `abi3` extensions without a Python interpreter
As an advanced feature, you can build PyO3 wheel without calling Python interpreter with the environment variable `PYO3_NO_PYTHON` set. As an advanced feature, you can build PyO3 wheel without calling Python interpreter with the environment variable `PYO3_NO_PYTHON` set.
Also, if the build host Python interpreter is not found or is too old or otherwise unusable,
PyO3 will still attempt to compile `abi3` extension modules after displaying a warning message.
On Unix-like systems this works unconditionally; on Windows you must also set the `RUSTFLAGS` environment variable On Unix-like systems this works unconditionally; on Windows you must also set the `RUSTFLAGS` environment variable
to contain `-L native=/path/to/python/libs` so that the linker can find `python3.lib`. to contain `-L native=/path/to/python/libs` so that the linker can find `python3.lib`.

View File

@ -124,7 +124,7 @@ def contributors(session: nox.Session) -> None:
else: else:
break break
authors = sorted(list(authors)) authors = sorted(list(authors), key=lambda author: author.lower())
for author in authors: for author in authors:
print(f"@{author}") print(f"@{author}")

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pyo3-build-config" name = "pyo3-build-config"
version = "0.16.3" version = "0.16.4"
description = "Build configuration for the PyO3 ecosystem" description = "Build configuration for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"] authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"] keywords = ["pyo3", "python", "cpython", "ffi"]
@ -26,6 +26,10 @@ default = []
# script. If this feature isn't enabled, the build script no-ops. # script. If this feature isn't enabled, the build script no-ops.
resolve-config = [] resolve-config = []
# This feature is enabled by pyo3 when building an extension module.
extension-module = []
# These features are enabled by pyo3 when building Stable ABI extension modules.
abi3 = [] abi3 = []
abi3-py37 = ["abi3-py38"] abi3-py37 = ["abi3-py38"]
abi3-py38 = ["abi3-py39"] abi3-py38 = ["abi3-py39"]

View File

@ -233,6 +233,13 @@ print("mingw", get_platform().startswith("mingw"))
"#; "#;
let output = run_python_script(interpreter.as_ref(), SCRIPT)?; let output = run_python_script(interpreter.as_ref(), SCRIPT)?;
let map: HashMap<String, String> = parse_script_output(&output); let map: HashMap<String, String> = parse_script_output(&output);
ensure!(
!map.is_empty(),
"broken Python interpreter: {}",
interpreter.as_ref().display()
);
let shared = map["shared"].as_str() == "True"; let shared = map["shared"].as_str() == "True";
let version = PythonVersion { let version = PythonVersion {
@ -366,7 +373,7 @@ print("mingw", get_platform().startswith("mingw"))
#[doc(hidden)] #[doc(hidden)]
pub fn from_cargo_dep_env() -> Option<Result<Self>> { pub fn from_cargo_dep_env() -> Option<Result<Self>> {
cargo_env_var("DEP_PYTHON_PYO3_CONFIG") cargo_env_var("DEP_PYTHON_PYO3_CONFIG")
.map(|buf| InterpreterConfig::from_reader(buf.replace("\\n", "\n").as_bytes())) .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf)))
} }
#[doc(hidden)] #[doc(hidden)]
@ -464,11 +471,7 @@ print("mingw", get_platform().startswith("mingw"))
let mut buf = Vec::new(); let mut buf = Vec::new();
self.to_writer(&mut buf)?; self.to_writer(&mut buf)?;
// escape newlines in env var // escape newlines in env var
if let Ok(config) = str::from_utf8(&buf) { println!("cargo:PYO3_CONFIG={}", escape(&buf));
println!("cargo:PYO3_CONFIG={}", config.replace('\n', "\\n"));
} else {
bail!("unable to emit interpreter config to link env for downstream use");
}
Ok(()) Ok(())
} }
@ -682,14 +685,14 @@ pub fn is_extension_module() -> bool {
/// Checks if we need to link to `libpython` for the current build target. /// Checks if we need to link to `libpython` for the current build target.
/// ///
/// Must be called from a crate PyO3 build script. /// Must be called from a PyO3 crate build script.
pub fn is_linking_libpython() -> bool { pub fn is_linking_libpython() -> bool {
is_linking_libpython_for_target(&target_triple_from_env()) is_linking_libpython_for_target(&target_triple_from_env())
} }
/// Checks if we need to link to `libpython` for the target. /// Checks if we need to link to `libpython` for the target.
/// ///
/// Must be called from a crate PyO3 build script. /// Must be called from a PyO3 crate build script.
fn is_linking_libpython_for_target(target: &Triple) -> bool { fn is_linking_libpython_for_target(target: &Triple) -> bool {
target.operating_system == OperatingSystem::Windows target.operating_system == OperatingSystem::Windows
|| target.environment == Environment::Android || target.environment == Environment::Android
@ -697,6 +700,18 @@ fn is_linking_libpython_for_target(target: &Triple) -> bool {
|| !is_extension_module() || !is_extension_module()
} }
/// Checks if we need to discover the Python library directory
/// to link the extension module binary.
///
/// Must be called from a PyO3 crate build script.
fn require_libdir_for_target(target: &Triple) -> bool {
let is_generating_libpython = cfg!(feature = "python3-dll-a")
&& target.operating_system == OperatingSystem::Windows
&& is_abi3();
is_linking_libpython_for_target(target) && !is_generating_libpython
}
/// Configuration needed by PyO3 to cross-compile for a target platform. /// Configuration needed by PyO3 to cross-compile for a target platform.
/// ///
/// Usually this is collected from the environment (i.e. `PYO3_CROSS_*` and `CARGO_CFG_TARGET_*`) /// Usually this is collected from the environment (i.e. `PYO3_CROSS_*` and `CARGO_CFG_TARGET_*`)
@ -1646,6 +1661,18 @@ pub fn find_interpreter() -> Result<PathBuf> {
} }
} }
/// Locates and extracts the build host Python interpreter configuration.
///
/// Lowers the configured Python version to `abi3_version` if required.
fn get_host_interpreter(abi3_version: Option<PythonVersion>) -> Result<InterpreterConfig> {
let interpreter_path = find_interpreter()?;
let mut interpreter_config = InterpreterConfig::from_interpreter(interpreter_path)?;
interpreter_config.fixup_for_abi3_version(abi3_version)?;
Ok(interpreter_config)
}
/// Generates an interpreter config suitable for cross-compilation. /// Generates an interpreter config suitable for cross-compilation.
/// ///
/// This must be called from PyO3's build script, because it relies on environment variables such as /// This must be called from PyO3's build script, because it relies on environment variables such as
@ -1666,31 +1693,78 @@ pub fn make_cross_compile_config() -> Result<Option<InterpreterConfig>> {
/// Only used by `pyo3-build-config` build script. /// Only used by `pyo3-build-config` build script.
#[allow(dead_code, unused_mut)] #[allow(dead_code, unused_mut)]
pub fn make_interpreter_config() -> Result<InterpreterConfig> { pub fn make_interpreter_config() -> Result<InterpreterConfig> {
let host = Triple::host();
let abi3_version = get_abi3_version(); let abi3_version = get_abi3_version();
if have_python_interpreter() { // See if we can safely skip the Python interpreter configuration detection.
let mut interpreter_config = InterpreterConfig::from_interpreter(find_interpreter()?)?; // Unix "abi3" extension modules can usually be built without any interpreter.
interpreter_config.fixup_for_abi3_version(abi3_version)?; let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host);
Ok(interpreter_config)
} else if let Some(version) = abi3_version {
let host = Triple::host();
let mut interpreter_config = default_abi3_config(&host, version);
// Auto generate python3.dll import libraries for Windows targets. if have_python_interpreter() {
#[cfg(feature = "python3-dll-a")] match get_host_interpreter(abi3_version) {
{ Ok(interpreter_config) => return Ok(interpreter_config),
interpreter_config.lib_dir = self::abi3_import_lib::generate_abi3_import_lib(&host)?; // Bail if the interpreter configuration is required to build.
Err(e) if need_interpreter => return Err(e),
_ => {
// Fall back to the "abi3" defaults just as if `PYO3_NO_PYTHON`
// environment variable was set.
warn!("Compiling without a working Python interpreter.");
}
}
} else {
ensure!(
abi3_version.is_some(),
"An abi3-py3* feature must be specified when compiling without a Python interpreter."
);
};
let mut interpreter_config = default_abi3_config(&host, abi3_version.unwrap());
// Auto generate python3.dll import libraries for Windows targets.
#[cfg(feature = "python3-dll-a")]
{
interpreter_config.lib_dir = self::abi3_import_lib::generate_abi3_import_lib(&host)?;
}
Ok(interpreter_config)
}
fn escape(bytes: &[u8]) -> String {
let mut escaped = String::with_capacity(2 * bytes.len());
for byte in bytes {
const LUT: &[u8; 16] = b"0123456789abcdef";
escaped.push(LUT[(byte >> 4) as usize] as char);
escaped.push(LUT[(byte & 0x0F) as usize] as char);
}
escaped
}
fn unescape(escaped: &str) -> Vec<u8> {
assert!(escaped.len() % 2 == 0, "invalid hex encoding");
let mut bytes = Vec::with_capacity(escaped.len() / 2);
for chunk in escaped.as_bytes().chunks_exact(2) {
fn unhex(hex: u8) -> u8 {
match hex {
b'a'..=b'f' => hex - b'a' + 10,
b'0'..=b'9' => hex - b'0',
_ => panic!("invalid hex encoding"),
}
} }
Ok(interpreter_config) bytes.push(unhex(chunk[0]) << 4 | unhex(chunk[1]));
} else {
bail!("An abi3-py3* feature must be specified when compiling without a Python interpreter.")
} }
bytes
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{io::Cursor, iter::FromIterator}; use std::iter::FromIterator;
use target_lexicon::triple; use target_lexicon::triple;
use super::*; use super::*;
@ -1713,10 +1787,7 @@ mod tests {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap(); config.to_writer(&mut buf).unwrap();
assert_eq!( assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
config,
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
);
// And some different options, for variety // And some different options, for variety
@ -1744,17 +1815,37 @@ mod tests {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap(); config.to_writer(&mut buf).unwrap();
assert_eq!( assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
config, }
InterpreterConfig::from_reader(Cursor::new(buf)).unwrap()
); #[test]
fn test_config_file_roundtrip_with_escaping() {
let config = InterpreterConfig {
abi3: true,
build_flags: BuildFlags::default(),
pointer_width: Some(32),
executable: Some("executable".into()),
implementation: PythonImplementation::CPython,
lib_name: Some("lib_name".into()),
lib_dir: Some("lib_dir\\n".into()),
shared: true,
version: MINIMUM_SUPPORTED_VERSION,
suppress_build_script_link_lines: true,
extra_build_script_lines: vec!["cargo:test1".to_string(), "cargo:test2".to_string()],
};
let mut buf: Vec<u8> = Vec::new();
config.to_writer(&mut buf).unwrap();
let buf = unescape(&escape(&buf));
assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap());
} }
#[test] #[test]
fn test_config_file_defaults() { fn test_config_file_defaults() {
// Only version is required // Only version is required
assert_eq!( assert_eq!(
InterpreterConfig::from_reader(Cursor::new("version=3.7")).unwrap(), InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(),
InterpreterConfig { InterpreterConfig {
version: PythonVersion { major: 3, minor: 7 }, version: PythonVersion { major: 3, minor: 7 },
implementation: PythonImplementation::CPython, implementation: PythonImplementation::CPython,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pyo3-ffi" name = "pyo3-ffi"
version = "0.16.3" version = "0.16.4"
description = "Python-API bindings for the PyO3 ecosystem" description = "Python-API bindings for the PyO3 ecosystem"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"] authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"] keywords = ["pyo3", "python", "cpython", "ffi"]
@ -35,4 +35,4 @@ abi3-py310 = ["abi3", "pyo3-build-config/abi3-py310"]
[build-dependencies] [build-dependencies]
pyo3-build-config = { path = "../pyo3-build-config", version = "0.16.3", features = ["resolve-config"] } pyo3-build-config = { path = "../pyo3-build-config", version = "0.16.4", features = ["resolve-config"] }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pyo3-macros-backend" name = "pyo3-macros-backend"
version = "0.16.3" version = "0.16.4"
description = "Code generation for PyO3 package" description = "Code generation for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"] authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"] keywords = ["pyo3", "python", "cpython", "ffi"]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pyo3-macros" name = "pyo3-macros"
version = "0.16.3" version = "0.16.4"
description = "Proc macros for PyO3 package" description = "Proc macros for PyO3 package"
authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"] authors = ["PyO3 Project and Contributors <https://github.com/PyO3>"]
keywords = ["pyo3", "python", "cpython", "ffi"] keywords = ["pyo3", "python", "cpython", "ffi"]
@ -23,4 +23,4 @@ abi3 = ["pyo3-macros-backend/abi3"]
proc-macro2 = { version = "1", default-features = false } proc-macro2 = { version = "1", default-features = false }
quote = "1" quote = "1"
syn = { version = "1.0.56", features = ["full", "extra-traits"] } syn = { version = "1.0.56", features = ["full", "extra-traits"] }
pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.16.3" } pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.16.4" }

View File

@ -3,7 +3,7 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{ use pyo3::types::{
PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple,
PyTzInfo, PyTzInfo, PyTzInfoAccess,
}; };
#[pyfunction] #[pyfunction]
@ -179,6 +179,16 @@ fn datetime_from_timestamp<'p>(
PyDateTime::from_timestamp(py, ts, tz) PyDateTime::from_timestamp(py, ts, tz)
} }
#[pyfunction]
fn get_datetime_tzinfo(dt: &PyDateTime) -> Option<&PyTzInfo> {
dt.get_tzinfo()
}
#[pyfunction]
fn get_time_tzinfo(dt: &PyTime) -> Option<&PyTzInfo> {
dt.get_tzinfo()
}
#[pyclass(extends=PyTzInfo)] #[pyclass(extends=PyTzInfo)]
pub struct TzClass {} pub struct TzClass {}
@ -214,6 +224,8 @@ pub fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(make_datetime, m)?)?; m.add_function(wrap_pyfunction!(make_datetime, m)?)?;
m.add_function(wrap_pyfunction!(get_datetime_tuple, m)?)?; m.add_function(wrap_pyfunction!(get_datetime_tuple, m)?)?;
m.add_function(wrap_pyfunction!(datetime_from_timestamp, m)?)?; m.add_function(wrap_pyfunction!(datetime_from_timestamp, m)?)?;
m.add_function(wrap_pyfunction!(get_datetime_tzinfo, m)?)?;
m.add_function(wrap_pyfunction!(get_time_tzinfo, m)?)?;
// Functions not supported by PyPy // Functions not supported by PyPy
#[cfg(not(PyPy))] #[cfg(not(PyPy))]

View File

@ -114,6 +114,7 @@ def test_time(args, kwargs):
assert act == exp assert act == exp
assert act.tzinfo is exp.tzinfo assert act.tzinfo is exp.tzinfo
assert rdt.get_time_tzinfo(act) == exp.tzinfo
@given(t=st.times()) @given(t=st.times())
@ -194,6 +195,7 @@ def test_datetime(args, kwargs):
assert act == exp assert act == exp
assert act.tzinfo is exp.tzinfo assert act.tzinfo is exp.tzinfo
assert rdt.get_datetime_tzinfo(act) == exp.tzinfo
@given(dt=st.datetimes()) @given(dt=st.datetimes())

View File

@ -4,9 +4,8 @@
//! documentation](https://docs.python.org/3/library/datetime.html) //! documentation](https://docs.python.org/3/library/datetime.html)
use crate::err::PyResult; use crate::err::PyResult;
use crate::ffi;
use crate::ffi::{ use crate::ffi::{
PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp, self, PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp,
}; };
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
use crate::ffi::{PyDateTime_DATE_GET_FOLD, PyDateTime_TIME_GET_FOLD}; use crate::ffi::{PyDateTime_DATE_GET_FOLD, PyDateTime_TIME_GET_FOLD};
@ -22,6 +21,7 @@ use crate::ffi::{
PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND, PyDateTime_TIME_GET_MINUTE, PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND, PyDateTime_TIME_GET_MINUTE,
PyDateTime_TIME_GET_SECOND, PyDateTime_TIME_GET_SECOND,
}; };
use crate::instance::PyNativeType;
use crate::types::PyTuple; use crate::types::PyTuple;
use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject}; use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject};
use std::os::raw::c_int; use std::os::raw::c_int;
@ -160,6 +160,16 @@ pub trait PyTimeAccess {
fn get_fold(&self) -> bool; fn get_fold(&self) -> bool;
} }
/// Trait for accessing the components of a struct containing a tzinfo.
pub trait PyTzInfoAccess {
/// Returns the tzinfo (which may be None).
///
/// Implementations should conform to the upstream documentation:
/// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_TZINFO>
/// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_TIME_GET_TZINFO>
fn get_tzinfo(&self) -> Option<&PyTzInfo>;
}
/// Bindings around `datetime.date` /// Bindings around `datetime.date`
#[repr(transparent)] #[repr(transparent)]
pub struct PyDate(PyAny); pub struct PyDate(PyAny);
@ -354,6 +364,19 @@ impl PyTimeAccess for PyDateTime {
} }
} }
impl PyTzInfoAccess for PyDateTime {
fn get_tzinfo(&self) -> Option<&PyTzInfo> {
let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
unsafe {
if (*ptr).hastzinfo != 0 {
Some(self.py().from_borrowed_ptr((*ptr).tzinfo))
} else {
None
}
}
}
}
/// Bindings for `datetime.time` /// Bindings for `datetime.time`
#[repr(transparent)] #[repr(transparent)]
pub struct PyTime(PyAny); pub struct PyTime(PyAny);
@ -439,6 +462,19 @@ impl PyTimeAccess for PyTime {
} }
} }
impl PyTzInfoAccess for PyTime {
fn get_tzinfo(&self) -> Option<&PyTzInfo> {
let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
unsafe {
if (*ptr).hastzinfo != 0 {
Some(self.py().from_borrowed_ptr((*ptr).tzinfo))
} else {
None
}
}
}
}
/// Bindings for `datetime.tzinfo` /// Bindings for `datetime.tzinfo`
/// ///
/// This is an abstract base class and should not be constructed directly. /// This is an abstract base class and should not be constructed directly.
@ -524,4 +560,33 @@ mod tests {
assert!(b.unwrap().get_fold()); assert!(b.unwrap().get_fold());
}); });
} }
#[cfg(not(PyPy))]
#[test]
fn test_get_tzinfo() {
crate::Python::with_gil(|py| {
use crate::conversion::ToPyObject;
use crate::types::{PyDateTime, PyTime, PyTzInfoAccess};
let datetime = py.import("datetime").map_err(|e| e.print(py)).unwrap();
let timezone = datetime.getattr("timezone").unwrap();
let utc = timezone.getattr("utc").unwrap().to_object(py);
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
assert!(dt.get_tzinfo().unwrap().eq(&utc).unwrap());
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
assert!(dt.get_tzinfo().is_none());
let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
assert!(t.get_tzinfo().unwrap().eq(&utc).unwrap());
let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
assert!(t.get_tzinfo().is_none());
});
}
} }

View File

@ -11,6 +11,7 @@ pub use self::complex::PyComplex;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
pub use self::datetime::{ pub use self::datetime::{
PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTzInfo, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTzInfo,
PyTzInfoAccess,
}; };
pub use self::dict::{IntoPyDict, PyDict}; pub use self::dict::{IntoPyDict, PyDict};
pub use self::floatob::PyFloat; pub use self::floatob::PyFloat;