Repository: PyO3/pyo3 Branch: main Commit: baca1031feed Files: 766 Total size: 4.6 MB Directory structure: gitextract_jnohosl3/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.md │ ├── actions/ │ │ └── fetch-merge-base/ │ │ └── action.yml │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── benches.yml │ ├── build.yml │ ├── cache-cleanup.yml │ ├── changelog.yml │ ├── ci-cache-warmup.yml │ ├── ci.yml │ ├── coverage-pr-base.yml │ ├── netlify-build.yml │ ├── netlify-deploy.yml │ └── release.yml ├── .gitignore ├── .netlify/ │ ├── internal_banner.html │ └── redirect.sh ├── .towncrier.template.md ├── Architecture.md ├── CHANGELOG.md ├── CITATION.cff ├── Cargo.toml ├── Code-of-Conduct.md ├── Contributing.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── Releasing.md ├── assets/ │ └── script.py ├── build.rs ├── codecov.yml ├── emscripten/ │ ├── .gitignore │ ├── Makefile │ └── runner.py ├── examples/ │ ├── Cargo.toml │ ├── README.md │ ├── decorator/ │ │ ├── .template/ │ │ │ ├── Cargo.toml │ │ │ ├── pre-script.rhai │ │ │ └── pyproject.toml │ │ ├── Cargo.toml │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── cargo-generate.toml │ │ ├── noxfile.py │ │ ├── pyproject.toml │ │ ├── src/ │ │ │ └── lib.rs │ │ └── tests/ │ │ ├── example.py │ │ └── test_.py │ ├── getitem/ │ │ ├── .template/ │ │ │ ├── Cargo.toml │ │ │ ├── pre-script.rhai │ │ │ └── pyproject.toml │ │ ├── Cargo.toml │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── cargo-generate.toml │ │ ├── noxfile.py │ │ ├── pyproject.toml │ │ ├── src/ │ │ │ └── lib.rs │ │ └── tests/ │ │ └── test_getitem.py │ ├── maturin-starter/ │ │ ├── .template/ │ │ │ ├── Cargo.toml │ │ │ ├── pre-script.rhai │ │ │ └── pyproject.toml │ │ ├── Cargo.toml │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── cargo-generate.toml │ │ ├── maturin_starter/ │ │ │ └── __init__.py │ │ ├── noxfile.py │ │ ├── pyproject.toml │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ └── submodule.rs │ │ └── tests/ │ │ ├── test_maturin_starter.py │ │ └── test_submodule.py │ ├── plugin/ │ │ ├── .template/ │ │ │ ├── Cargo.toml │ │ │ ├── plugin_api/ │ │ │ │ └── Cargo.toml │ │ │ └── pre-script.rhai │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── cargo-generate.toml │ │ ├── plugin_api/ │ │ │ ├── Cargo.toml │ │ │ ├── noxfile.py │ │ │ ├── pyproject.toml │ │ │ ├── src/ │ │ │ │ └── lib.rs │ │ │ └── tests/ │ │ │ ├── test_Gadget.py │ │ │ └── test_import.py │ │ ├── python_plugin/ │ │ │ ├── gadget_init_plugin.py │ │ │ └── rng.py │ │ └── src/ │ │ └── main.rs │ ├── setuptools-rust-starter/ │ │ ├── .template/ │ │ │ ├── Cargo.toml │ │ │ ├── pre-script.rhai │ │ │ └── setup.cfg │ │ ├── Cargo.toml │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── cargo-generate.toml │ │ ├── noxfile.py │ │ ├── pyproject.toml │ │ ├── requirements-dev.txt │ │ ├── setuptools_rust_starter/ │ │ │ └── __init__.py │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ └── submodule.rs │ │ └── tests/ │ │ ├── test_setuptools_rust_starter.py │ │ └── test_submodule.py │ └── word-count/ │ ├── .template/ │ │ ├── Cargo.toml │ │ ├── pre-script.rhai │ │ └── pyproject.toml │ ├── Cargo.toml │ ├── MANIFEST.in │ ├── README.md │ ├── cargo-generate.toml │ ├── noxfile.py │ ├── pyproject.toml │ ├── src/ │ │ └── lib.rs │ ├── tests/ │ │ └── test_word_count.py │ └── word_count/ │ └── __init__.py ├── guide/ │ ├── book.toml │ ├── pyclass-parameters.md │ ├── pyo3_version.py │ ├── src/ │ │ ├── SUMMARY.md │ │ ├── advanced.md │ │ ├── async-await.md │ │ ├── building-and-distribution/ │ │ │ └── multiple-python-versions.md │ │ ├── building-and-distribution.md │ │ ├── changelog.md │ │ ├── class/ │ │ │ ├── call.md │ │ │ ├── numeric.md │ │ │ ├── object.md │ │ │ ├── protocols.md │ │ │ └── thread-safety.md │ │ ├── class.md │ │ ├── contributing.md │ │ ├── conversions/ │ │ │ ├── tables.md │ │ │ └── traits.md │ │ ├── conversions.md │ │ ├── debugging.md │ │ ├── ecosystem/ │ │ │ ├── async-await.md │ │ │ ├── logging.md │ │ │ └── tracing.md │ │ ├── ecosystem.md │ │ ├── exception.md │ │ ├── faq.md │ │ ├── features.md │ │ ├── free-threading.md │ │ ├── function/ │ │ │ ├── error-handling.md │ │ │ └── signature.md │ │ ├── function-calls.md │ │ ├── function.md │ │ ├── getting-started.md │ │ ├── index.md │ │ ├── migration.md │ │ ├── module.md │ │ ├── parallelism.md │ │ ├── performance.md │ │ ├── python-from-rust/ │ │ │ ├── calling-existing-code.md │ │ │ └── function-calls.md │ │ ├── python-from-rust.md │ │ ├── python-typing-hints.md │ │ ├── rust-from-python.md │ │ ├── trait-bounds.md │ │ ├── type-stub.md │ │ └── types.md │ └── theme/ │ ├── tabs.css │ └── tabs.js ├── newsfragments/ │ ├── .gitignore │ ├── 5349.added.md │ ├── 5349.changed.md │ ├── 5668.added.md │ ├── 5668.fixed.md │ ├── 5668.removed.md │ ├── 5753.changed.md │ ├── 5770.added.md │ ├── 5782.added.md │ ├── 5797.changed.md │ ├── 5803.changed.md │ ├── 5809.packaging.md │ ├── 5824.changed.md │ ├── 5828.added.md │ ├── 5830.changed.md │ ├── 5837.fixed.md │ ├── 5839.changed.md │ ├── 5841.changed.md │ ├── 5847.fixed.md │ ├── 5849.added.md │ ├── 5857.added.md │ ├── 5865.packaging.md │ ├── 5866.changed.md │ ├── 5883.changed.md │ ├── 5887.added.md │ ├── 5891.added.md │ ├── 5893.removed.md │ ├── 5896.changed.md │ └── 5897.changed.md ├── noxfile.py ├── pyo3-benches/ │ ├── Cargo.toml │ ├── benches/ │ │ ├── bench_any.rs │ │ ├── bench_attach.rs │ │ ├── bench_bigint.rs │ │ ├── bench_call.rs │ │ ├── bench_comparisons.rs │ │ ├── bench_critical_sections.rs │ │ ├── bench_decimal.rs │ │ ├── bench_dict.rs │ │ ├── bench_err.rs │ │ ├── bench_extract.rs │ │ ├── bench_frompyobject.rs │ │ ├── bench_intern.rs │ │ ├── bench_intopyobject.rs │ │ ├── bench_list.rs │ │ ├── bench_py.rs │ │ ├── bench_pyclass.rs │ │ ├── bench_pystring_from_fmt.rs │ │ ├── bench_set.rs │ │ └── bench_tuple.rs │ └── build.rs ├── pyo3-build-config/ │ ├── Cargo.toml │ ├── build.rs │ └── src/ │ ├── errors.rs │ ├── impl_.rs │ └── lib.rs ├── pyo3-ffi/ │ ├── ACKNOWLEDGEMENTS │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── examples/ │ │ ├── README.md │ │ ├── sequential/ │ │ │ ├── .template/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── pre-script.rhai │ │ │ │ └── pyproject.toml │ │ │ ├── Cargo.toml │ │ │ ├── MANIFEST.in │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ ├── cargo-generate.toml │ │ │ ├── noxfile.py │ │ │ ├── pyproject.toml │ │ │ ├── src/ │ │ │ │ ├── id.rs │ │ │ │ ├── lib.rs │ │ │ │ └── module.rs │ │ │ └── tests/ │ │ │ ├── test.rs │ │ │ └── test_.py │ │ └── string-sum/ │ │ ├── .template/ │ │ │ ├── Cargo.toml │ │ │ ├── pre-script.rhai │ │ │ └── pyproject.toml │ │ ├── Cargo.toml │ │ ├── MANIFEST.in │ │ ├── README.md │ │ ├── build.rs │ │ ├── cargo-generate.toml │ │ ├── noxfile.py │ │ ├── pyproject.toml │ │ ├── src/ │ │ │ └── lib.rs │ │ └── tests/ │ │ └── test_.py │ └── src/ │ ├── abstract_.rs │ ├── bltinmodule.rs │ ├── boolobject.rs │ ├── bytearrayobject.rs │ ├── bytesobject.rs │ ├── ceval.rs │ ├── codecs.rs │ ├── compat/ │ │ ├── mod.rs │ │ ├── py_3_10.rs │ │ ├── py_3_13.rs │ │ ├── py_3_14.rs │ │ ├── py_3_15.rs │ │ └── py_3_9.rs │ ├── compile.rs │ ├── complexobject.rs │ ├── context.rs │ ├── cpython/ │ │ ├── abstract_.rs │ │ ├── bytesobject.rs │ │ ├── ceval.rs │ │ ├── code.rs │ │ ├── compile.rs │ │ ├── complexobject.rs │ │ ├── critical_section.rs │ │ ├── descrobject.rs │ │ ├── dictobject.rs │ │ ├── floatobject.rs │ │ ├── frameobject.rs │ │ ├── funcobject.rs │ │ ├── genobject.rs │ │ ├── import.rs │ │ ├── initconfig.rs │ │ ├── listobject.rs │ │ ├── lock.rs │ │ ├── longobject.rs │ │ ├── methodobject.rs │ │ ├── mod.rs │ │ ├── object.rs │ │ ├── objimpl.rs │ │ ├── pydebug.rs │ │ ├── pyerrors.rs │ │ ├── pyframe.rs │ │ ├── pyhash.rs │ │ ├── pylifecycle.rs │ │ ├── pymem.rs │ │ ├── pystate.rs │ │ ├── pythonrun.rs │ │ ├── tupleobject.rs │ │ ├── unicodeobject.rs │ │ └── weakrefobject.rs │ ├── datetime.rs │ ├── descrobject.rs │ ├── dictobject.rs │ ├── enumobject.rs │ ├── fileobject.rs │ ├── fileutils.rs │ ├── floatobject.rs │ ├── genericaliasobject.rs │ ├── impl_/ │ │ ├── macros.rs │ │ └── mod.rs │ ├── import.rs │ ├── intrcheck.rs │ ├── iterobject.rs │ ├── lib.rs │ ├── listobject.rs │ ├── longobject.rs │ ├── marshal.rs │ ├── memoryobject.rs │ ├── methodobject.rs │ ├── modsupport.rs │ ├── moduleobject.rs │ ├── object.rs │ ├── objimpl.rs │ ├── osmodule.rs │ ├── pyarena.rs │ ├── pybuffer.rs │ ├── pycapsule.rs │ ├── pyerrors.rs │ ├── pyframe.rs │ ├── pyhash.rs │ ├── pylifecycle.rs │ ├── pymem.rs │ ├── pyport.rs │ ├── pystate.rs │ ├── pystrtod.rs │ ├── pythonrun.rs │ ├── pytypedefs.rs │ ├── rangeobject.rs │ ├── refcount.rs │ ├── setobject.rs │ ├── sliceobject.rs │ ├── structmember.rs │ ├── structseq.rs │ ├── sysmodule.rs │ ├── traceback.rs │ ├── tupleobject.rs │ ├── typeslots.rs │ ├── unicodeobject.rs │ ├── warnings.rs │ └── weakrefobject.rs ├── pyo3-ffi-check/ │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── macro/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── src/ │ │ └── main.rs │ └── wrapper.h ├── pyo3-introspection/ │ ├── Cargo.toml │ ├── src/ │ │ ├── introspection.rs │ │ ├── lib.rs │ │ ├── model.rs │ │ └── stubs.rs │ └── tests/ │ └── test.rs ├── pyo3-macros/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── pyo3-macros-backend/ │ ├── Cargo.toml │ └── src/ │ ├── attributes.rs │ ├── combine_errors.rs │ ├── derive_attributes.rs │ ├── frompyobject.rs │ ├── intopyobject.rs │ ├── introspection.rs │ ├── konst.rs │ ├── lib.rs │ ├── method.rs │ ├── module.rs │ ├── params.rs │ ├── py_expr.rs │ ├── pyclass.rs │ ├── pyfunction/ │ │ └── signature.rs │ ├── pyfunction.rs │ ├── pyimpl.rs │ ├── pymethod.rs │ ├── quotes.rs │ └── utils.rs ├── pyo3-runtime/ │ ├── README.md │ ├── pyproject.toml │ ├── src/ │ │ └── pyo3_runtime/ │ │ └── __init__.py │ └── tests/ │ └── __init__.py ├── pyproject.toml ├── pytests/ │ ├── Cargo.toml │ ├── MANIFEST.in │ ├── MODULE_DOC.md │ ├── README.md │ ├── build.rs │ ├── conftest.py │ ├── noxfile.py │ ├── pyproject.toml │ ├── src/ │ │ ├── awaitable.rs │ │ ├── buf_and_str.rs │ │ ├── comparisons.rs │ │ ├── consts.rs │ │ ├── datetime.rs │ │ ├── dict_iter.rs │ │ ├── enums.rs │ │ ├── exception.rs │ │ ├── lib.rs │ │ ├── misc.rs │ │ ├── objstore.rs │ │ ├── othermod.rs │ │ ├── path.rs │ │ ├── pyclasses.rs │ │ ├── pyfunctions.rs │ │ ├── sequence.rs │ │ └── subclassing.rs │ ├── stubs/ │ │ ├── __init__.pyi │ │ ├── awaitable.pyi │ │ ├── buf_and_str.pyi │ │ ├── comparisons.pyi │ │ ├── consts.pyi │ │ ├── datetime.pyi │ │ ├── dict_iter.pyi │ │ ├── enums.pyi │ │ ├── exception.pyi │ │ ├── misc.pyi │ │ ├── objstore.pyi │ │ ├── othermod.pyi │ │ ├── path.pyi │ │ ├── pyclasses.pyi │ │ ├── pyfunctions.pyi │ │ ├── sequence.pyi │ │ └── subclassing.pyi │ └── tests/ │ ├── test_awaitable.py │ ├── test_buf_and_str.py │ ├── test_comparisons.py │ ├── test_datetime.py │ ├── test_dict_iter.py │ ├── test_enums.py │ ├── test_enums_match.py │ ├── test_hammer_attaching_in_thread.py │ ├── test_misc.py │ ├── test_objstore.py │ ├── test_othermod.py │ ├── test_path.py │ ├── test_pyclasses.py │ ├── test_pyfunctions.py │ ├── test_sequence.py │ └── test_subclassing.py ├── src/ │ ├── buffer.rs │ ├── byteswriter.rs │ ├── call.rs │ ├── conversion.rs │ ├── conversions/ │ │ ├── anyhow.rs │ │ ├── bigdecimal.rs │ │ ├── bytes.rs │ │ ├── chrono.rs │ │ ├── chrono_tz.rs │ │ ├── either.rs │ │ ├── eyre.rs │ │ ├── hashbrown.rs │ │ ├── indexmap.rs │ │ ├── jiff.rs │ │ ├── mod.rs │ │ ├── num_bigint.rs │ │ ├── num_complex.rs │ │ ├── num_rational.rs │ │ ├── ordered_float.rs │ │ ├── rust_decimal.rs │ │ ├── serde.rs │ │ ├── smallvec.rs │ │ ├── std/ │ │ │ ├── array.rs │ │ │ ├── cell.rs │ │ │ ├── cstring.rs │ │ │ ├── ipaddr.rs │ │ │ ├── map.rs │ │ │ ├── mod.rs │ │ │ ├── num.rs │ │ │ ├── option.rs │ │ │ ├── osstr.rs │ │ │ ├── path.rs │ │ │ ├── set.rs │ │ │ ├── slice.rs │ │ │ ├── string.rs │ │ │ ├── time.rs │ │ │ └── vec.rs │ │ ├── time.rs │ │ └── uuid.rs │ ├── coroutine/ │ │ ├── cancel.rs │ │ └── waker.rs │ ├── coroutine.rs │ ├── err/ │ │ ├── cast_error.rs │ │ ├── downcast_error.rs │ │ ├── err_state.rs │ │ ├── impls.rs │ │ └── mod.rs │ ├── exceptions.rs │ ├── ffi/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── ffi_ptr_ext.rs │ ├── fmt.rs │ ├── impl_/ │ │ ├── callback.rs │ │ ├── concat.rs │ │ ├── coroutine.rs │ │ ├── deprecated.rs │ │ ├── exceptions.rs │ │ ├── extract_argument.rs │ │ ├── freelist.rs │ │ ├── frompyobject.rs │ │ ├── introspection.rs │ │ ├── panic.rs │ │ ├── pycell.rs │ │ ├── pyclass/ │ │ │ ├── assertions.rs │ │ │ ├── doc.rs │ │ │ ├── lazy_type_object.rs │ │ │ └── probes.rs │ │ ├── pyclass.rs │ │ ├── pyclass_init.rs │ │ ├── pyfunction.rs │ │ ├── pymethods.rs │ │ ├── pymodule.rs │ │ ├── trampoline.rs │ │ ├── unindent.rs │ │ └── wrap.rs │ ├── impl_.rs │ ├── inspect.rs │ ├── instance.rs │ ├── internal/ │ │ ├── get_slot.rs │ │ └── state.rs │ ├── internal.rs │ ├── internal_tricks.rs │ ├── interpreter_lifecycle.rs │ ├── lib.rs │ ├── macros.rs │ ├── marker.rs │ ├── marshal.rs │ ├── panic.rs │ ├── prelude.rs │ ├── py_result_ext.rs │ ├── pybacked.rs │ ├── pycell/ │ │ └── impl_.rs │ ├── pycell.rs │ ├── pyclass/ │ │ ├── create_type_object.rs │ │ ├── gc.rs │ │ └── guard.rs │ ├── pyclass.rs │ ├── pyclass_init.rs │ ├── sealed.rs │ ├── sync/ │ │ ├── critical_section.rs │ │ └── once_lock.rs │ ├── sync.rs │ ├── test_utils.rs │ ├── tests/ │ │ ├── hygiene/ │ │ │ ├── misc.rs │ │ │ ├── mod.rs │ │ │ ├── pyclass.rs │ │ │ ├── pyfunction.rs │ │ │ ├── pymethods.rs │ │ │ └── pymodule.rs │ │ └── mod.rs │ ├── type_object.rs │ ├── types/ │ │ ├── any.rs │ │ ├── boolobject.rs │ │ ├── bytearray.rs │ │ ├── bytes.rs │ │ ├── capsule.rs │ │ ├── code.rs │ │ ├── complex.rs │ │ ├── datetime.rs │ │ ├── dict.rs │ │ ├── ellipsis.rs │ │ ├── float.rs │ │ ├── frame.rs │ │ ├── frozenset.rs │ │ ├── function.rs │ │ ├── genericalias.rs │ │ ├── iterator.rs │ │ ├── list.rs │ │ ├── mapping.rs │ │ ├── mappingproxy.rs │ │ ├── memoryview.rs │ │ ├── mod.rs │ │ ├── module.rs │ │ ├── mutex.rs │ │ ├── none.rs │ │ ├── notimplemented.rs │ │ ├── num.rs │ │ ├── pysuper.rs │ │ ├── range.rs │ │ ├── sequence.rs │ │ ├── set.rs │ │ ├── slice.rs │ │ ├── string.rs │ │ ├── traceback.rs │ │ ├── tuple.rs │ │ ├── typeobject.rs │ │ └── weakref/ │ │ ├── anyref.rs │ │ ├── mod.rs │ │ ├── proxy.rs │ │ └── reference.rs │ └── version.rs └── tests/ ├── test_anyhow.rs ├── test_append_to_inittab.rs ├── test_arithmetics.rs ├── test_buffer.rs ├── test_buffer_protocol.rs ├── test_bytes.rs ├── test_class_attributes.rs ├── test_class_basics.rs ├── test_class_comparisons.rs ├── test_class_conversion.rs ├── test_class_formatting.rs ├── test_class_init.rs ├── test_class_new.rs ├── test_compile_error.rs ├── test_coroutine.rs ├── test_datetime.rs ├── test_datetime_import.rs ├── test_declarative_module.rs ├── test_default_impls.rs ├── test_enum.rs ├── test_exceptions.rs ├── test_field_cfg.rs ├── test_frompy_intopy_roundtrip.rs ├── test_frompyobject.rs ├── test_gc.rs ├── test_getter_setter.rs ├── test_inheritance.rs ├── test_intopyobject.rs ├── test_macro_docs.rs ├── test_macros.rs ├── test_mapping.rs ├── test_methods.rs ├── test_module.rs ├── test_multiple_pymethods.rs ├── test_proto_methods.rs ├── test_pybuffer_drop_without_interpreter.rs ├── test_pyerr_debug_unformattable.rs ├── test_pyfunction.rs ├── test_pyself.rs ├── test_sequence.rs ├── test_serde.rs ├── test_static_slots.rs ├── test_string.rs ├── test_super.rs ├── test_text_signature.rs ├── test_utils/ │ └── mod.rs ├── test_variable_arguments.rs ├── test_various.rs └── ui/ ├── abi3_dict.rs ├── abi3_dict.stderr ├── abi3_inheritance.rs ├── abi3_inheritance.stderr ├── abi3_nativetype_inheritance.rs ├── abi3_nativetype_inheritance.stderr ├── abi3_weakref.rs ├── abi3_weakref.stderr ├── ambiguous_associated_items.rs ├── deprecated_pyfn.rs ├── deprecated_pyfn.stderr ├── duplicate_pymodule_submodule.rs ├── duplicate_pymodule_submodule.stderr ├── empty.rs ├── forbid_unsafe.rs ├── get_set_all.rs ├── get_set_all.stderr ├── immutable_type.rs ├── immutable_type.stderr ├── invalid_annotation.rs ├── invalid_annotation.stderr ├── invalid_annotation_return.rs ├── invalid_annotation_return.stderr ├── invalid_argument_attributes.rs ├── invalid_argument_attributes.stderr ├── invalid_async.rs ├── invalid_async.stderr ├── invalid_base_class.rs ├── invalid_base_class.stderr ├── invalid_cancel_handle.rs ├── invalid_cancel_handle.stderr ├── invalid_closure.rs ├── invalid_closure.stderr ├── invalid_frompy_derive.rs ├── invalid_frompy_derive.stderr ├── invalid_frozen_pyclass_borrow.rs ├── invalid_frozen_pyclass_borrow.stderr ├── invalid_intern_arg.rs ├── invalid_intern_arg.stderr ├── invalid_intopy_derive.rs ├── invalid_intopy_derive.stderr ├── invalid_intopy_with.rs ├── invalid_intopy_with.stderr ├── invalid_property_args.rs ├── invalid_property_args.stderr ├── invalid_proto_pymethods.rs ├── invalid_proto_pymethods.stderr ├── invalid_pycallargs.rs ├── invalid_pycallargs.stderr ├── invalid_pyclass_args.rs ├── invalid_pyclass_args.stderr ├── invalid_pyclass_doc.rs ├── invalid_pyclass_doc.stderr ├── invalid_pyclass_enum.rs ├── invalid_pyclass_enum.stderr ├── invalid_pyclass_generic.rs ├── invalid_pyclass_generic.stderr ├── invalid_pyclass_init.rs ├── invalid_pyclass_init.stderr ├── invalid_pyclass_item.rs ├── invalid_pyclass_item.stderr ├── invalid_pyfunction_argument.rs ├── invalid_pyfunction_argument.stderr ├── invalid_pyfunction_definition.rs ├── invalid_pyfunction_definition.stderr ├── invalid_pyfunction_signatures.rs ├── invalid_pyfunction_signatures.stderr ├── invalid_pyfunction_warn.rs ├── invalid_pyfunction_warn.stderr ├── invalid_pyfunctions.rs ├── invalid_pyfunctions.stderr ├── invalid_pymethod_enum.rs ├── invalid_pymethod_enum.stderr ├── invalid_pymethod_names.rs ├── invalid_pymethod_names.stderr ├── invalid_pymethod_receiver.rs ├── invalid_pymethod_receiver.stderr ├── invalid_pymethods.rs ├── invalid_pymethods.stderr ├── invalid_pymethods_buffer.rs ├── invalid_pymethods_buffer.stderr ├── invalid_pymethods_duplicates.rs ├── invalid_pymethods_duplicates.stderr ├── invalid_pymethods_warn.rs ├── invalid_pymethods_warn.stderr ├── invalid_pymodule_args.rs ├── invalid_pymodule_args.stderr ├── invalid_pymodule_glob.rs ├── invalid_pymodule_glob.stderr ├── invalid_pymodule_in_root.rs ├── invalid_pymodule_in_root.stderr ├── invalid_pymodule_trait.rs ├── invalid_pymodule_trait.stderr ├── invalid_pymodule_two_pymodule_init.rs ├── invalid_pymodule_two_pymodule_init.stderr ├── invalid_result_conversion.rs ├── invalid_result_conversion.stderr ├── missing_intopy.rs ├── missing_intopy.stderr ├── not_send.rs ├── not_send.stderr ├── not_send2.rs ├── not_send2.stderr ├── pyclass_generic_enum.rs ├── pyclass_generic_enum.stderr ├── pyclass_probe.rs ├── pyclass_send.rs ├── pyclass_send.stderr ├── pymodule_missing_docs.rs ├── reject_generics.rs ├── reject_generics.stderr ├── static_ref.rs ├── static_ref.stderr ├── traverse.rs ├── traverse.stderr ├── wrong_aspyref_lifetimes.rs └── wrong_aspyref_lifetimes.stderr ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: 🐛 Bug Report description: Create a bug report type: Bug body: - type: markdown attributes: value: | Thank you for taking the time to fill out this bug report! Please fill out the form below... - type: textarea id: description attributes: label: Bug Description description: Please provide a clear and concise description of what the bug is. placeholder: The bug is... validations: required: true - type: textarea id: reproduce attributes: label: Steps to Reproduce description: Provide steps to reproduce this bug. placeholder: | 1. 2. 3. validations: required: true - type: textarea id: debug attributes: label: Backtrace description: If your bug produces a backtrace, please include it here. render: shell - type: input id: os_version attributes: label: Your operating system and version validations: required: true - type: input id: py_version attributes: label: Your Python version (`python --version`) placeholder: ex. Python 3.10.0 validations: required: true - type: input id: rust_version attributes: label: Your Rust version (`rustc --version`) placeholder: ex. rustc 1.55.0 (c8dfcfe04 2021-09-06) validations: required: true - type: input id: pyo3_version attributes: label: Your PyO3 version placeholder: ex. 0.14.0 validations: required: true - type: textarea id: install_method attributes: label: How did you install python? Did you use a virtualenv? placeholder: | apt pyenv pacman brew python.org installer microsoft store etc... validations: required: true - type: textarea id: additional-info attributes: label: Additional Info description: Any additional info that you think might be useful or relevant to this bug ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: ❓ Question url: https://github.com/PyO3/pyo3/discussions about: Ask and answer questions about PyO3 on Discussions - name: 🔧 Troubleshooting url: https://github.com/PyO3/pyo3/discussions about: For troubleshooting help, see the Discussions - name: 👋 Chat url: https://discord.gg/33kcChzH7f about: Engage with PyO3's users and developers on Discord ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: 💡 Feature request about: Suggest an idea for this project type: Feature --- ================================================ FILE: .github/actions/fetch-merge-base/action.yml ================================================ name: 'Fetch Merge Base' description: 'Fetches the merge base between two branches, deepening the git checkout until complete' inputs: base_ref: description: 'The base branch reference' required: true head_ref: description: 'The head branch reference' required: true outputs: merge_base: description: 'The merge base commit SHA' value: ${{ steps.fetch_merge_base.outputs.merge_base }} runs: using: "composite" steps: - name: Fetch Merge Base id: fetch_merge_base shell: bash run: | # fetch the merge commit between the PR base and head git fetch -u --progress --depth=1 origin "+$BASE_REF:$BASE_REF" "+$HEAD_REF:$HEAD_REF" while [ -z "$(git merge-base "$BASE_REF" "$HEAD_REF")" ]; do git fetch -u -q --deepen="10" origin "$BASE_REF" "$HEAD_REF"; done MERGE_BASE=$(git merge-base "$BASE_REF" "$HEAD_REF") echo "merge_base=$MERGE_BASE" >> $GITHUB_OUTPUT env: BASE_REF: "${{ inputs.base_ref }}" HEAD_REF: "${{ inputs.head_ref }}" ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/pyo3-benches/" schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/pyo3-ffi-check/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: # dependabot default labels - "dependencies" - "github-actions" # additional labels - "CI-skip-changelog" ================================================ FILE: .github/pull_request_template.md ================================================ Thank you for contributing to PyO3! By submitting these contributions you agree for them to be dual-licensed under PyO3's [MIT OR Apache-2.0 license](https://github.com/PyO3/pyo3#license). Please consider adding the following to your pull request: - an entry for this PR in newsfragments - see [https://pyo3.rs/main/contributing.html#documenting-changes] - or start the PR title with `docs:` if this is a docs-only change to skip the check - or start the PR title with `ci:` if this is a ci-only change to skip the check - docs to all new functions and / or detail in the guide - tests for all new or changed functions PyO3's CI pipeline will check your pull request, thus make sure you have checked the `Contributing.md` guidelines. To run most of its tests locally, you can run `nox`. See `nox --list-sessions` for a list of supported actions. ================================================ FILE: .github/workflows/benches.yml ================================================ name: benches on: push: branches: - "main" pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}-benches cancel-in-progress: true env: UV_PYTHON: "3.14t" jobs: benchmarks: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6.0.2 - uses: astral-sh/setup-uv@v7 with: # codspeed action needs to be run from within the final Python environment activate-environment: true save-cache: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@stable with: components: rust-src - uses: Swatinem/rust-cache@v2 with: workspaces: | . pyo3-benches save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} cache-all-crates: "true" cache-workspace-crates: "true" - uses: taiki-e/install-action@v2 with: tool: cargo-codspeed - name: Run the benchmarks uses: CodSpeedHQ/action@v4 with: run: uvx nox -s codspeed token: ${{ secrets.CODSPEED_TOKEN }} mode: simulation ================================================ FILE: .github/workflows/build.yml ================================================ on: workflow_call: inputs: os: required: true type: string python-version: required: true type: string python-architecture: required: true type: string rust: required: true type: string rust-target: required: true type: string MSRV: required: true type: string verbose: type: boolean default: false env: NOX_DEFAULT_VENV_BACKEND: uv jobs: build: continue-on-error: ${{ endsWith(inputs.python-version, '-dev') || contains(fromJSON('["3.7", "3.8"]'), inputs.python-version) || contains(fromJSON('["beta", "nightly"]'), inputs.rust) }} runs-on: ${{ inputs.os }} if: ${{ !(startsWith(inputs.python-version, 'graalpy') && startsWith(inputs.os, 'windows')) }} steps: - uses: actions/checkout@v6.0.2 with: # For PRs, we need to run on the real PR head, not the resultant merge of the PR into the target branch. # # This is necessary for coverage reporting to make sense; we then get exactly the coverage change # between the base branch and the real PR head. # # If it were run on the merge commit the problem is that the coverage potentially does not align # with the commit diff, because the merge may affect line numbers. ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} # installs using setup-python do not work for arm macOS 3.9 and below - if: ${{ !(inputs.os == 'macos-latest' && contains(fromJSON('["3.7", "3.8", "3.9"]'), inputs.python-version) && inputs.python-architecture == 'x64') }} name: Set up Python ${{ inputs.python-version }} uses: actions/setup-python@v6 with: python-version: ${{ inputs.python-version }} architecture: ${{ inputs.python-architecture }} # PyPy can have FFI changes within Python versions, which creates pain in CI check-latest: ${{ startsWith(inputs.python-version, 'pypy') }} # workaround for the above, only available for 3.9 - if: ${{ inputs.os == 'macos-latest' && contains(fromJSON('["3.9"]'), inputs.python-version) && inputs.python-architecture == 'x64' }} name: Set up Python ${{ inputs.python-version }} uses: astral-sh/setup-uv@v7 with: python-version: cpython-${{ inputs.python-version }}-macos-x86-64 save-cache: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - name: Install nox run: python -m pip install --upgrade pip && pip install nox[uv] - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ inputs.rust }} targets: ${{ inputs.rust-target }} # rust-src needed to correctly format errors, see #1865 components: rust-src,llvm-tools-preview # On windows 32 bit, we are running on an x64 host, so we need to specifically set the target # NB we don't do this for *all* jobs because it breaks coverage of proc macros to have an # explicit target set. - name: Set Rust target for Windows 32-bit if: inputs.os == 'windows-latest' && inputs.python-architecture == 'x86' shell: bash run: | echo "CARGO_BUILD_TARGET=i686-pc-windows-msvc" >> $GITHUB_ENV # windows on arm image contains x86-64 libclang - name: Install LLVM and Clang if: inputs.os == 'windows-11-arm' uses: KyleMayes/install-llvm-action@v2 with: # to match windows-2022 images version: "18" - name: Install zoneinfo backport for Python 3.7 / 3.8 if: contains(fromJSON('["3.7", "3.8"]'), inputs.python-version) run: python -m pip install backports.zoneinfo - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - if: inputs.os == 'ubuntu-latest' name: Prepare LD_LIBRARY_PATH (Ubuntu only) run: echo LD_LIBRARY_PATH=${pythonLocation}/lib >> $GITHUB_ENV - if: inputs.rust == inputs.MSRV name: Prepare MSRV package versions run: nox -s set-msrv-package-versions - if: inputs.rust != 'stable' name: Ignore changed error messages when using trybuild run: echo "TRYBUILD=overwrite" >> "$GITHUB_ENV" - uses: dorny/paths-filter@v4 if: ${{ inputs.rust == 'stable' && !startsWith(inputs.python-version, 'graalpy') }} id: ffi-changes with: base: ${{ github.event.merge_group.base_ref }} ref: ${{ github.event.merge_group.head_ref }} filters: | changed: - 'pyo3-ffi/**' - 'pyo3-ffi-check/**' - '.github/workflows/ci.yml' - '.github/workflows/build.yml' - name: Run pyo3-ffi-check # TODO: investigate graalpy failures if: ${{ endsWith(inputs.python-version, '-dev') || (steps.ffi-changes.outputs.changed == 'true' && inputs.rust == 'stable' && !startsWith(inputs.python-version, 'graalpy')) }} run: nox -s ffi-check - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Prepare coverage environment run: | cargo llvm-cov clean --workspace --profraw-only nox -s set-coverage-env - name: Build docs run: nox -s docs - name: Run Rust tests run: nox -s test-rust - name: Test python examples and tests shell: bash run: nox -s test-py env: CARGO_TARGET_DIR: ${{ github.workspace }}/target - name: Generate coverage report # needs investigation why llvm-cov fails on windows-11-arm continue-on-error: ${{ inputs.os == 'windows-11-arm' }} run: cargo llvm-cov --package=pyo3 --package=pyo3-build-config --package=pyo3-macros-backend --package=pyo3-macros --package=pyo3-ffi report --codecov --output-path coverage.json - name: Upload coverage report uses: codecov/codecov-action@v5 # needs investigation why llvm-cov fails on windows-11-arm continue-on-error: ${{ inputs.os == 'windows-11-arm' }} with: files: coverage.json name: ${{ inputs.os }}/${{ inputs.python-version }}/${{ inputs.rust }} token: ${{ secrets.CODECOV_TOKEN }} env: CARGO_TERM_VERBOSE: ${{ inputs.verbose }} RUST_BACKTRACE: 1 RUSTFLAGS: "-D warnings" RUSTDOCFLAGS: "-D warnings" ================================================ FILE: .github/workflows/cache-cleanup.yml ================================================ name: CI Cache Cleanup on: pull_request_target: types: - closed jobs: cleanup: runs-on: ubuntu-latest permissions: actions: write steps: - name: Cleanup run: | gh extension install actions/gh-actions-cache echo "Fetching list of cache key" cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) ## Setting this to not fail the workflow while deleting cache keys. set +e echo "Deleting caches..." for cacheKey in $cacheKeysForPR do gh actions-cache delete -R $REPO -B $BRANCH --confirm -- $cacheKey done echo "Done" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge ================================================ FILE: .github/workflows/changelog.yml ================================================ name: changelog on: pull_request: types: [opened, synchronize, labeled, unlabeled, edited] jobs: check: name: Check changelog entry runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: '3.14' - run: python -m pip install --upgrade pip && pip install nox[uv] - run: nox -s check-changelog ================================================ FILE: .github/workflows/ci-cache-warmup.yml ================================================ name: CI Cache Warmup on: push: branches: - main jobs: cross-compilation-windows: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-pc-windows-gnu,x86_64-pc-windows-msvc components: rust-src - uses: actions/cache/restore@v5 with: # https://github.com/PyO3/maturin/discussions/1953 path: ~/.cache/cargo-xwin key: cargo-xwin-cache - name: Test cross compile to Windows run: | set -ex sudo apt-get install -y mingw-w64 llvm pip install nox nox -s test-cross-compilation-windows - uses: actions/cache/save@v5 with: path: ~/.cache/cargo-xwin key: cargo-xwin-cache ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: merge_group: types: [checks_requested] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }} cancel-in-progress: true env: CARGO_TERM_COLOR: always jobs: fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - run: python -m pip install --upgrade pip && pip install nox[uv] - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Check python formatting and lints (ruff) run: nox -s ruff - name: Check rust formatting (rustfmt) run: nox -s rustfmt - name: Check markdown formatting (rumdl) run: nox -s rumdl resolve: runs-on: ubuntu-latest outputs: MSRV: ${{ steps.resolve-msrv.outputs.MSRV }} verbose: ${{ runner.debug == '1' }} steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - name: resolve MSRV id: resolve-msrv run: echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["workspace"]["package"]["rust-version"])'` >> $GITHUB_OUTPUT semver-checks: if: github.event_name == 'pull_request' needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - name: Fetch merge base id: fetch_merge_base uses: ./.github/actions/fetch-merge-base with: base_ref: "refs/heads/${{ github.event.pull_request.base.ref }}" head_ref: "refs/pull/${{ github.event.pull_request.number }}/merge" - uses: obi1kenobi/cargo-semver-checks-action@v2 with: baseline-rev: ${{ steps.fetch_merge_base.outputs.merge_base }} check-msrv: needs: [fmt, resolve] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ needs.resolve.outputs.MSRV }} components: rust-src - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - run: python -m pip install --upgrade pip && pip install nox[uv] # This is a smoke test to confirm that CI will run on MSRV (including dev dependencies) - name: Check with MSRV package versions run: | nox -s set-msrv-package-versions nox -s check-all env: CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu clippy: needs: [fmt] runs-on: ubuntu-24.04-arm strategy: # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: rust: [stable] target: [ "aarch64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "powerpc64le-unknown-linux-gnu", "s390x-unknown-linux-gnu", "wasm32-wasip1", "x86_64-pc-windows-msvc", "i686-pc-windows-msvc", "aarch64-pc-windows-msvc", ] include: # Run beta clippy as a way to detect any incoming lints which may affect downstream users - rust: beta target: "x86_64-unknown-linux-gnu" name: clippy/${{ matrix.target }}/${{ matrix.rust }} continue-on-error: ${{ matrix.rust != 'stable' }} steps: - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} targets: ${{ matrix.target }} components: clippy,rust-src - uses: astral-sh/setup-uv@v7 with: save-cache: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - run: uvx nox -s clippy-all env: CARGO_BUILD_TARGET: ${{ matrix.target }} build-pr: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }} name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }} needs: [fmt, resolve] uses: ./.github/workflows/build.yml with: os: ${{ matrix.platform.os }} python-version: ${{ matrix.python-version }} python-architecture: ${{ matrix.platform.python-architecture }} rust: ${{ matrix.rust }} rust-target: ${{ matrix.platform.rust-target }} MSRV: ${{ needs.resolve.outputs.MSRV }} verbose: ${{ needs.resolve.outputs.verbose == 'true' }} secrets: inherit strategy: # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: rust: [stable] python-version: ["3.14"] platform: [ { os: "macos-latest", python-architecture: "arm64", rust-target: "aarch64-apple-darwin", }, { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", }, { os: "ubuntu-24.04-arm", python-architecture: "arm64", rust-target: "aarch64-unknown-linux-gnu", }, { os: "windows-latest", python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc", }, { os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc", }, { os: "windows-11-arm", python-architecture: "arm64", rust-target: "aarch64-pc-windows-msvc", }, ] include: # Test nightly Rust on PRs so that PR authors have a chance to fix nightly # failures, as nightly does not block merge. - rust: nightly python-version: "3.14" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } # Also test free-threaded Python just for latest Python version, on ubuntu # (run for all OSes on build-full) - rust: stable python-version: "3.14t" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } build-full: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} rust-${{ matrix.rust }} needs: [fmt, resolve] uses: ./.github/workflows/build.yml with: os: ${{ matrix.platform.os }} python-version: ${{ matrix.python-version }} python-architecture: ${{ matrix.platform.python-architecture }} rust: ${{ matrix.rust }} rust-target: ${{ matrix.platform.rust-target }} MSRV: ${{ needs.resolve.outputs.MSRV }} verbose: ${{ needs.resolve.outputs.verbose == 'true' }} secrets: inherit strategy: # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: rust: [stable] python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15-dev", "3.15t-dev", "pypy3.11", "graalpy25.0", ] platform: [ { os: "macos-latest", python-architecture: "arm64", rust-target: "aarch64-apple-darwin", }, { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", }, { os: "windows-latest", python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc", }, ] include: # Test minimal supported Rust version - rust: ${{ needs.resolve.outputs.MSRV }} python-version: "3.14" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } # Test the `nightly` feature - rust: nightly python-version: "3.14" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } # Run rust beta to help catch toolchain regressions - rust: beta python-version: "3.14" platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } # Test 32-bit Windows and x64 macOS only with the latest Python version - rust: stable python-version: "3.14" platform: { os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc", } - rust: stable python-version: "3.14" platform: { os: "macos-latest", python-architecture: "x64", rust-target: "x86_64-apple-darwin", } # ubuntu-latest (24.04) no longer supports 3.7, so run on 22.04 - rust: stable python-version: "3.7" platform: { os: "ubuntu-22.04", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", } # arm64 macOS Python not available on GitHub Actions until 3.10, test older versions on x64 - rust: stable python-version: "3.7" platform: { os: "macos-15-intel", python-architecture: "x64", rust-target: "x86_64-apple-darwin", } - rust: stable python-version: "3.8" platform: { os: "macos-15-intel", python-architecture: "x64", rust-target: "x86_64-apple-darwin", } - rust: stable python-version: "3.9" platform: { os: "macos-15-intel", python-architecture: "x64", rust-target: "x86_64-apple-darwin", } # test latest Python on arm64 linux & windows runners - rust: stable python-version: "3.14" platform: { os: "ubuntu-24.04-arm", python-architecture: "arm64", rust-target: "aarch64-unknown-linux-gnu", } - rust: stable python-version: "3.14" platform: { os: "windows-11-arm", python-architecture: "arm64", rust-target: "aarch64-pc-windows-msvc", } exclude: # ubuntu-latest (24.04) no longer supports 3.7 - python-version: "3.7" platform: { os: "ubuntu-latest" } # arm64 macOS Python not available on GitHub Actions until 3.10 - rust: stable python-version: "3.7" platform: { os: "macos-latest", python-architecture: "arm64", rust-target: "aarch64-apple-darwin", } - rust: stable python-version: "3.8" platform: { os: "macos-latest", python-architecture: "arm64", rust-target: "aarch64-apple-darwin", } - rust: stable python-version: "3.9" platform: { os: "macos-latest", python-architecture: "arm64", rust-target: "aarch64-apple-darwin", } valgrind: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@valgrind - run: python -m pip install --upgrade pip && pip install nox[uv] - run: nox -s test-rust -- release skip-full env: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: valgrind --leak-check=no --error-exitcode=1 RUST_BACKTRACE: 1 TRYBUILD: overwrite careful: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@nightly with: components: rust-src - uses: taiki-e/install-action@cargo-careful - run: python -m pip install --upgrade pip && pip install nox[uv] - run: nox -s test-rust -- careful skip-full env: RUST_BACKTRACE: 1 TRYBUILD: overwrite docsrs: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@nightly with: components: rust-src - run: cargo rustdoc --lib --no-default-features --features full,jiff-02 -Zunstable-options --config "build.rustdocflags=[\"--cfg\", \"docsrs\"]" emscripten: name: emscripten if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: 3.14 id: setup-python - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-emscripten components: rust-src - uses: actions/setup-node@v6 with: node-version: 24 - run: python -m pip install --upgrade pip && pip install nox[uv] - uses: actions/cache/restore@v5 id: cache with: path: | .nox/emscripten key: emscripten-${{ hashFiles('emscripten/*') }}-${{ hashFiles('noxfile.py') }}-${{ steps.setup-python.outputs.python-path }} - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - name: Build if: steps.cache.outputs.cache-hit != 'true' run: nox -s build-emscripten - name: Test run: nox -s test-emscripten - uses: actions/cache/save@v5 if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} with: path: | .nox/emscripten key: emscripten-${{ hashFiles('emscripten/*') }}-${{ hashFiles('noxfile.py') }}-${{ steps.setup-python.outputs.python-path }} wasm32-wasip1: name: wasm32-wasip1 if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} needs: [fmt] runs-on: ubuntu-latest env: WASI_SDK_PATH: "/opt/wasi-sdk" CPYTHON_PATH: "${{ github.workspace }}/wasi/cpython" steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: 3.14 id: setup-python - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: wasm32-wasip1 components: rust-src - name: "Install wasmtime" uses: bytecodealliance/actions/wasmtime/setup@v1 - name: "Install WASI SDK" run: | mkdir ${{ env.WASI_SDK_PATH }} && \ curl -s -S --location https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-linux.tar.gz | \ tar --strip-components 1 --directory ${{ env.WASI_SDK_PATH }} --extract --gunzip $WASI_SDK_PATH/bin/clang --version - uses: actions/cache/restore@v5 id: cache-wasip1-python with: path: ${{ env.CPYTHON_PATH }}/cross-build/ key: wasm32-wasip1-python - uses: actions/checkout@v6.0.2 with: repository: python/cpython ref: 3.14 path: ${{ env.CPYTHON_PATH }} fetch-depth: 1 - name: Build run: | cd ${{ env.CPYTHON_PATH }} cat >> Tools/wasm/wasi/config.site-wasm32-wasi <<'EOF' # Force-disable POSIX dynamic loading for WASI ac_cv_func_dlopen=no ac_cv_lib_dl_dlopen=no EOF python Tools/wasm/wasi build --quiet -- --config-cache cp cross-build/wasm32-wasip1/libpython3.14.a \ cross-build/wasm32-wasip1/Modules/_hacl/libHacl_HMAC.a \ cross-build/wasm32-wasip1/Modules/_decimal/libmpdec/libmpdec.a \ cross-build/wasm32-wasip1/Modules/expat/libexpat.a \ cross-build/wasm32-wasip1/build/lib.wasi-wasm32-3.14/ - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - name: Test env: PYO3_CROSS_LIB_DIR: ${{ env.CPYTHON_PATH }}/cross-build/wasm32-wasip1/build/lib.wasi-wasm32-3.14/ CARGO_BUILD_TARGET: wasm32-wasip1 CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime run --dir ${{ env.CPYTHON_PATH }}::/ --env PYTHONPATH=/lib RUSTFLAGS: > -C link-arg=-L${{ env.WASI_SDK_PATH }}/share/wasi-sysroot/lib/wasm32-wasi -C link-arg=-lwasi-emulated-signal -C link-arg=-lwasi-emulated-process-clocks -C link-arg=-lwasi-emulated-getpid -C link-arg=-lmpdec -C link-arg=-lHacl_HMAC -C link-arg=-lexpat run: RUSTDOCFLAGS=$RUSTFLAGS cargo test - uses: actions/cache/save@v5 if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} with: path: ${{ env.CPYTHON_PATH }}/cross-build/ key: ${{ steps.cache-wasip1-python.outputs.cache-primary-key }} test-debug: if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@stable with: components: rust-src - name: Install python3 standalone debug build with nox run: | PBS_RELEASE="20241219" PBS_PYTHON_VERSION="3.13.1" PBS_ARCHIVE="cpython-${PBS_PYTHON_VERSION}+${PBS_RELEASE}-x86_64-unknown-linux-gnu-debug-full.tar.zst" wget "https://github.com/indygreg/python-build-standalone/releases/download/${PBS_RELEASE}/${PBS_ARCHIVE}" tar -I zstd -xf "${PBS_ARCHIVE}" ls -l $(pwd)/python/install/bin ls -l $(pwd)/python/install/lib echo PATH=$(pwd)/python/install/bin:$PATH >> $GITHUB_ENV echo LD_LIBRARY_PATH=$(pwd)/python/install/lib:$LD_LIBRARY_PATH >> $GITHUB_ENV echo PYTHONHOME=$(pwd)/python/install >> $GITHUB_ENV echo PYO3_PYTHON=$(pwd)/python/install/bin/python3 >> $GITHUB_ENV - run: python3 -m sysconfig - run: python3 -m pip install --upgrade pip && pip install nox[uv] - run: | PYO3_CONFIG_FILE=$(mktemp) cat > $PYO3_CONFIG_FILE << EOF implementation=CPython version=3.13 shared=true abi3=false lib_name=python3.13d lib_dir=${{ github.workspace }}/python/install/lib executable=${{ github.workspace }}/python/install/bin/python3 pointer_width=64 build_flags=Py_DEBUG,Py_REF_DEBUG suppress_build_script_link_lines=false EOF echo PYO3_CONFIG_FILE=$PYO3_CONFIG_FILE >> $GITHUB_ENV - run: python3 -m nox -s test test-version-limits: needs: [fmt] if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@stable - run: python3 -m pip install --upgrade pip && pip install nox[uv] - run: python3 -m nox -s test-version-limits check-feature-powerset: needs: [fmt, resolve] if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} runs-on: ubuntu-latest name: check-feature-powerset ${{ matrix.rust }} strategy: # run on stable and MSRV to check that all combinations of features are expected to build fine on our supported # range of compilers matrix: rust: ["stable"] include: - rust: ${{ needs.resolve.outputs.MSRV }} steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - uses: dtolnay/rust-toolchain@master with: toolchain: stable - uses: taiki-e/install-action@v2 with: tool: cargo-hack,cargo-minimal-versions - run: python3 -m pip install --upgrade pip && pip install nox[uv] - run: python3 -m nox -s check-feature-powerset -- ${{ matrix.rust != 'stable' && 'minimal-versions' || '' }} test-cross-compilation: needs: [fmt] if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} runs-on: ${{ matrix.os }} name: test-cross-compilation ${{ matrix.os }} -> ${{ matrix.target }} strategy: # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: include: # ubuntu "cross compile" to itself - os: "ubuntu-latest" target: "x86_64-unknown-linux-gnu" flags: "-i python3.13" manylinux: auto # ubuntu x86_64 -> aarch64 - os: "ubuntu-latest" target: "aarch64-unknown-linux-gnu" flags: "-i python3.13" manylinux: auto # ubuntu x86_64 -> windows x86_64 - os: "ubuntu-latest" target: "x86_64-pc-windows-gnu" # TODO: remove pyo3/generate-import-lib feature when maturin supports cross compiling to Windows without it flags: "-i python3.13 --features pyo3/generate-import-lib" # windows x86_64 -> aarch64 - os: "windows-latest" target: "aarch64-pc-windows-msvc" flags: "-i python3.13 --features pyo3/generate-import-lib" steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: Swatinem/rust-cache@v2 with: workspaces: examples/maturin-starter save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} key: ${{ matrix.target }} - name: Setup cross-compiler if: ${{ matrix.target == 'x86_64-pc-windows-gnu' }} run: sudo apt-get install -y mingw-w64 llvm - name: Compile version-specific library uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux }} args: --release -m examples/maturin-starter/Cargo.toml ${{ matrix.flags }} - name: Compile abi3 library uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: ${{ matrix.manylinux }} args: --release -m examples/maturin-starter/Cargo.toml --features abi3 ${{ matrix.flags }} test-cross-compilation-windows: needs: [fmt] if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: dtolnay/rust-toolchain@stable with: targets: x86_64-pc-windows-gnu,x86_64-pc-windows-msvc components: rust-src # load cache (prepared in ci-cache-warmup.yml) - uses: actions/cache/restore@v5 with: path: ~/.cache/cargo-xwin key: cargo-xwin-cache - name: Test cross compile to Windows run: | set -ex sudo apt-get install -y mingw-w64 llvm pip install nox nox -s test-cross-compilation-windows test-introspection: needs: [fmt] if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-test-introspection') || contains(github.event.pull_request.labels.*.name, 'CI-build-full') || github.event_name != 'pull_request' }} strategy: matrix: platform: [ { os: "macos-latest", python-architecture: "arm64", rust-target: "aarch64-apple-darwin", }, { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu", }, { os: "windows-latest", python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc", }, { os: "windows-latest", python-architecture: "x86", rust-target: "i686-pc-windows-msvc", }, ] runs-on: ${{ matrix.platform.os }} steps: - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.platform.rust-target }} - uses: actions/setup-python@v6 with: python-version: "3.14" architecture: ${{ matrix.platform.python-architecture }} - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} - run: python -m pip install --upgrade pip && pip install nox[uv] - run: nox -s test-introspection env: CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }} test-introspection-pr: needs: [fmt] if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-build-full') && github.event_name == 'pull_request' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@stable - uses: actions/setup-python@v6 with: python-version: "3.14" - run: python -m pip install --upgrade pip && pip install nox[uv] - run: nox -s test-introspection mypy-pytests: needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@stable with: components: rust-src - uses: actions/setup-python@v6 with: python-version: "3.14" - run: python -m pip install --upgrade pip && pip install nox[uv] - run: nox -s mypy working-directory: pytests conclusion: needs: - fmt - check-msrv - clippy - build-pr - build-full - valgrind - careful - docsrs - emscripten - test-debug - test-version-limits - check-feature-powerset - test-cross-compilation - test-cross-compilation-windows - test-introspection if: always() runs-on: ubuntu-latest steps: - name: Result run: | jq -C <<< "${needs}" # Check if all needs were successful or skipped. "$(jq -r 'all(.result as $result | (["success", "skipped"] | contains([$result])))' <<< "${needs}")" env: needs: ${{ toJson(needs) }} ================================================ FILE: .github/workflows/coverage-pr-base.yml ================================================ # This runs as a separate job because it needs to run on the `pull_request_target` event # in order to access the CODECOV_TOKEN secret. # # This is safe because this doesn't run arbitrary code from PRs. name: Set Codecov PR base on: # See safety note / doc at the top of this file. pull_request_target: jobs: coverage-pr-base: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: '3.14' - name: Fetch merge base id: fetch_merge_base uses: ./.github/actions/fetch-merge-base with: base_ref: "refs/heads/${{ github.event.pull_request.base.ref }}" head_ref: "refs/pull/${{ github.event.pull_request.number }}/head" - name: Set PR base on codecov run: | pip install codecov-cli codecovcli pr-base-picking \ --base-sha ${{ steps.fetch_merge_base.outputs.merge_base }} \ --pr ${{ github.event.number }} \ --slug PyO3/pyo3 \ --token ${{ secrets.CODECOV_TOKEN }} \ --service github ================================================ FILE: .github/workflows/netlify-build.yml ================================================ name: netlify-build on: push: branches: - main pull_request: release: types: [published] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true env: CARGO_TERM_COLOR: always jobs: guide-build: runs-on: ubuntu-latest outputs: tag_name: ${{ steps.prepare_tag.outputs.tag_name }} steps: - uses: actions/checkout@v6.0.2 - uses: actions/setup-python@v6 with: python-version: "3.14" - uses: dtolnay/rust-toolchain@nightly - name: Setup mdBook uses: taiki-e/install-action@v2 with: tool: mdbook@0.5, mdbook-tabs@0.3, lychee - name: Prepare tag id: prepare_tag run: | TAG_NAME="${GITHUB_REF##*/}" echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT - name: Restore lychee cache id: restore-cache uses: actions/cache/restore@v5 with: path: .lycheecache key: lychee # This builds the book in target/guide/. - name: Build the guide run: | python -m pip install --upgrade pip && pip install nox[uv] nox -s ${{ github.event_name == 'release' && 'build-guide' || 'check-guide' }} env: PYO3_VERSION_TAG: ${{ steps.prepare_tag.outputs.tag_name }} # allows lychee to get better rate limits from github GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Save lychee cache uses: actions/cache/save@v5 if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }} with: path: .lycheecache key: ${{ steps.restore-cache.outputs.cache-primary-key }} # We store the versioned guides on GitHub's gh-pages branch for convenience # (the full gh-pages branch is pulled in the build-netlify-site step) - name: Deploy the guide if: ${{ github.event_name == 'release' }} uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/guide/ destination_dir: ${{ steps.prepare_tag.outputs.tag_name }} full_commit_message: "Upload documentation for ${{ steps.prepare_tag.outputs.tag_name }}" - name: Get current PyO3 version run: | PYO3_VERSION=$(cargo search pyo3 --limit 1 | head -1 | tr -s ' ' | cut -d ' ' -f 3 | tr -d '"') echo "PYO3_VERSION=${PYO3_VERSION}" >> $GITHUB_ENV - name: Build the site run: | python -m pip install --upgrade pip && pip install nox[uv] towncrier requests nox -s build-netlify-site -- ${{ (github.ref != 'refs/heads/main' && '--preview') || '' }} # Upload the built site as an artifact for deploy workflow to consume - name: Upload Build Artifact uses: actions/upload-artifact@v7 with: name: site path: ./netlify_build ================================================ FILE: .github/workflows/netlify-deploy.yml ================================================ # This runs as a separate job because it needs to run on the `workflow_run` event # in order to access the netlify secrets. # # This is safe because this doesn't run arbitrary code from PRs. name: netlify-deploy on: workflow_run: workflows: ["netlify-build"] types: - completed env: CARGO_TERM_COLOR: always jobs: deploy: runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' environment: netlify steps: - name: Download Build Artifact uses: actions/download-artifact@v8 with: name: site github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - name: Install netlify-cli run: | npm install -g netlify-cli - name: Deploy to Netlify run: | ls -la DEBUG=* netlify deploy \ --site ${{ secrets.NETLIFY_SITE_ID }} \ --auth ${{ secrets.NETLIFY_TOKEN }} \ ${{ ((github.event.workflow_run.head_repository.full_name == 'PyO3/pyo3') && (github.event.workflow_run.head_branch == 'main') && '--prod') || '' }} \ --json | tee deploy_output.json # credit: https://www.raulmelo.me/en/blog/deploying-netlify-github-actions-guide - name: Generate URL Preview id: url_preview if: ${{ github.event.workflow_run.event == 'pull_request' }} run: | NETLIFY_PREVIEW_URL=$(jq -r '.deploy_url' deploy_output.json) echo "NETLIFY_PREVIEW_URL=$NETLIFY_PREVIEW_URL" >> "$GITHUB_OUTPUT" - name: Post Netlify Preview Status to PR if: ${{ github.event.workflow_run.event == 'pull_request' }} uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const previewUrl = '${{ steps.url_preview.outputs.NETLIFY_PREVIEW_URL }}'; const commitSha = '${{ github.event.workflow_run.head_sha }}'; await github.rest.repos.createCommitStatus({ owner: context.repo.owner, repo: context.repo.repo, sha: commitSha, state: 'success', target_url: previewUrl, description: 'click to view Netlify preview deploy', context: 'netlify-deploy / preview' }); ================================================ FILE: .github/workflows/release.yml ================================================ name: Release Rust Crate on: push: tags: - "v*" workflow_dispatch: inputs: version: description: The version to build jobs: release: permissions: id-token: write runs-on: ubuntu-latest environment: release steps: - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v5.0.1 with: # The tag to build or the tag received by the tag event ref: ${{ github.event.inputs.version || github.ref }} persist-credentials: false - uses: astral-sh/setup-uv@v7 with: save-cache: false - uses: rust-lang/crates-io-auth-action@v1 id: auth - name: Publish to crates.io run: uvx nox -s publish env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} ================================================ FILE: .gitignore ================================================ target Cargo.lock /doc /gh-pages build/ *.py[co] __pycache__/ .cache .pytest_cache/ dist/ .tox/ .mypy_cache/ .hypothesis/ .eggs/ venv* guide/book/ guide/src/LICENSE-APACHE guide/src/LICENSE-MIT *.so *.out *.egg-info extensions/stamps/ pip-wheel-metadata valgrind-python.supp *.pyd lcov.info coverage.json netlify_build/ .nox/ .vscode/ .lycheecache ================================================ FILE: .netlify/internal_banner.html ================================================
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here
================================================ FILE: .netlify/redirect.sh ================================================ # Add redirect for each documented version set +x # these loops get very spammy and fill the deploy log for d in netlify_build/v*; do version="${d/netlify_build\/v/}" echo "/v$version/doc/* https://docs.rs/pyo3/$version/:splat" >> netlify_build/_redirects if [ $version != $PYO3_VERSION ]; then # for old versions, mark the files in the latest version as the canonical URL for file in $(find $d -type f); do file_path="${file/$d\//}" # remove index.html and/or .html suffix to match the page URL on the # final netlfiy site url_path="$file_path" if [[ $file_path == index.html ]]; then url_path="" elif [[ $file_path == *.html ]]; then url_path="${file_path%.html}" fi echo "/v$version/$url_path" >> netlify_build/_headers if test -f "netlify_build/v$PYO3_VERSION/$file_path"; then echo " Link: ; rel=\"canonical\"" >> netlify_build/_headers else # this file doesn't exist in the latest guide, don't index it echo " X-Robots-Tag: noindex" >> netlify_build/_headers fi done fi done # Add latest redirect echo "/latest/* /v${PYO3_VERSION}/:splat 302" >> netlify_build/_redirects # some backwards compatbiility redirects echo "/latest/building_and_distribution/* /latest/building-and-distribution/:splat 302" >> netlify_build/_redirects echo "/latest/building-and-distribution/multiple_python_versions/* /latest/building-and-distribution/multiple-python-versions:splat 302" >> netlify_build/_redirects echo "/latest/function/error_handling/* /latest/function/error-handling/:splat 302" >> netlify_build/_redirects echo "/latest/getting_started/* /latest/getting-started/:splat 302" >> netlify_build/_redirects echo "/latest/python_from_rust/* /latest/python-from-rust/:splat 302" >> netlify_build/_redirects echo "/latest/python_typing_hints/* /latest/python-typing-hints/:splat 302" >> netlify_build/_redirects echo "/latest/trait_bounds/* /latest/trait-bounds/:splat 302" >> netlify_build/_redirects ## Add landing page redirect if [ "${CONTEXT}" == "deploy-preview" ]; then echo "/ /main/" >> netlify_build/_redirects else echo "/ /v${PYO3_VERSION}/ 302" >> netlify_build/_redirects fi set -x ================================================ FILE: .towncrier.template.md ================================================ {% for section_text, section in sections.items() %}{%- if section %}{{section_text}}{% endif -%} {% if section %} {% for category in ['packaging', 'added', 'changed', 'removed', 'fixed' ] if category in section %} ### {{ definitions[category]['name'] }} {% if definitions[category]['showcontent'] %} {% for text, pull_requests in section[category].items() %} - {{ text }} {{ pull_requests|join(', ') }} {% endfor %} {% else %} - {{ section[category]['']|join(', ') }} {% endif %} {% endfor %} {% else %} No significant changes. {% endif %} {% endfor %} ================================================ FILE: Architecture.md ================================================ # PyO3: Architecture This document roughly describes the high-level architecture of PyO3. If you want to become familiar with the codebase you are in the right place! ## Overview PyO3 provides a bridge between Rust and Python, based on the [Python/C API]. Thus, PyO3 has low-level bindings of these API as its core. On top of that, we have higher-level bindings to operate Python objects safely. Also, to define Python classes and functions in Rust code, we have `trait PyClass` and a set of protocol traits (e.g., `PyIterProtocol`) for supporting object protocols (i.e., `__dunder__` methods). Since implementing `PyClass` requires lots of boilerplate, we have a proc-macro `#[pyclass]`. To summarize, there are six main parts to the PyO3 codebase. 1. [Low-level bindings of Python/C API.](#1-low-level-bindings-of-python-capi) - [`pyo3-ffi`] and [`src/ffi`] 2. [Bindings to Python objects.](#2-bindings-to-python-objects) - [`src/instance.rs`] and [`src/types`] 3. [`PyClass` and related functionalities.](#3-pyclass-and-related-functionalities) - [`src/pycell.rs`], [`src/pyclass.rs`], and more 4. [Procedural macros to simplify usage for users.](#4-procedural-macros-to-simplify-usage-for-users) - [`src/impl_`], [`pyo3-macros`] and [`pyo3-macros-backend`] 5. [`build.rs` and `pyo3-build-config`](#5-buildrs-and-pyo3-build-config) - [`build.rs`](https://github.com/PyO3/pyo3/tree/main/build.rs) - [`pyo3-build-config`] ## 1. Low-level bindings of Python/C API [`pyo3-ffi`] contains wrappers of the [Python/C API]. This is currently done by hand rather than automated tooling because: - it gives us best control about how to adapt C conventions to Rust, and - there are many Python interpreter versions we support in a single set of files. We aim to provide straight-forward Rust wrappers resembling the file structure of [`cpython/Include`](https://github.com/python/cpython/tree/main/Include). We are continuously updating the module to match the latest CPython version which PyO3 supports (i.e. as of time of writing Python 3.13). The tracking issue is [#1289](https://github.com/PyO3/pyo3/issues/1289), and contribution is welcome. In the [`pyo3-ffi`] crate, there is lots of conditional compilation such as `#[cfg(Py_LIMITED_API)]`, `#[cfg(Py_3_7)]`, and `#[cfg(PyPy)]`. `Py_LIMITED_API` corresponds to `#define Py_LIMITED_API` macro in Python/C API. With `Py_LIMITED_API`, we can build a Python-version-agnostic binary called an [abi3 wheel](https://pyo3.rs/latest/building-and-distribution.html#py_limited_apiabi3). `Py_3_7` means that the API is available from Python >= 3.7. There are also `Py_3_8`, `Py_3_9`, and so on. `PyPy` means that the API definition is for PyPy. Those flags are set in [`build.rs`](#6-buildrs-and-pyo3-build-config). ## 2. Bindings to Python objects [`src/types`] contains bindings to [built-in types](https://docs.python.org/3/library/stdtypes.html) of Python, such as `dict` and `list`. For historical reasons, Python's `object` is called `PyAny` in PyO3 and located in [`src/types/any.rs`]. Currently, `PyAny` is a straightforward wrapper of `ffi::PyObject`, defined as: ```rust #[repr(transparent)] pub struct PyAny(UnsafeCell); ``` Concrete Python objects are implemented by wrapping `PyAny`, e.g.,: ```rust #[repr(transparent)] pub struct PyDict(PyAny); ``` These types are not intended to be accessed directly, and instead are used through the `Py` and `Bound` smart pointers. We have some macros in [`src/types/mod.rs`] which make it easier to implement APIs for concrete Python types. ## 3. `PyClass` and related functionalities [`src/pycell.rs`], [`src/pyclass.rs`], and [`src/type_object.rs`] contain types and traits to make `#[pyclass]` work. Also, [`src/pyclass_init.rs`] and [`src/impl_/pyclass.rs`] have related functionalities. To realize object-oriented programming in C, all Python objects have `ob_base: PyObject` as their first field in their structure definition. Thanks to this guarantee, casting `*mut A` to `*mut PyObject` is valid if `A` is a Python object. To ensure this guarantee, we have a wrapper struct `PyClassObject` in [`src/pycell/impl_.rs`] which is roughly: ```rust #[repr(C)] pub struct PyClassObject { ob_base: crate::ffi::PyObject, inner: T, } ``` Thus, when copying a Rust struct to a Python object, we first allocate `PyClassObject` on the Python heap and then move `T` into it. The primary way to interact with Python objects implemented in Rust is through the `Bound<'py, T>` smart pointer. By having the `'py` lifetime of the `Python<'py>` token, this ties the lifetime of the `Bound<'py, T>` smart pointer to the lifetime for which the thread is attached to the Python interpreter and allows PyO3 to call Python APIs at maximum efficiency. `Bound<'py, T>` requires that `T` implements `PyClass`. This trait is somewhat complex and derives many traits, but the most important one is `PyTypeInfo` in [`src/type_object.rs`]. `PyTypeInfo` is also implemented for built-in types. In Python, all objects have their types, and types are also objects of `type`. For example, you can see `type({})` shows `dict` and `type(type({}))` shows `type` in Python REPL. `T: PyTypeInfo` implies that `T` has a corresponding type object. ### Protocol methods Python has some built-in special methods called dunder methods, such as `__iter__`. They are called "slots" in the [abstract objects layer](https://docs.python.org/3/c-api/abstract.html) in Python/C API. We provide a way to implement those protocols similarly, by recognizing special names in `#[pymethods]`, with a few new ones for slots that can not be implemented in Python, such as GC support. ## 4. Procedural macros to simplify usage for users. [`pyo3-macros`] provides five proc-macro APIs: `pymodule`, `pyfunction`, `pyclass`, `pymethods`, and `#[derive(FromPyObject)]`. [`pyo3-macros-backend`] has the actual implementations of these APIs. [`src/impl_`] contains `#[doc(hidden)]` functionality used in code generated by these proc-macros, such as parsing function arguments. ## 5. `build.rs` and `pyo3-build-config` PyO3 supports a wide range of OSes, interpreters and use cases. The correct environment must be detected at build time in order to set up relevant conditional compilation correctly. This logic is captured in the [`pyo3-build-config`] crate, which is a `build-dependency` of `pyo3` and `pyo3-macros`, and can also be used by downstream users in the same way. In [`pyo3-build-config`]'s `build.rs` the build environment is detected and inlined into the crate as a "config file". This works in all cases except for cross-compiling, where it is necessary to capture this from the `pyo3` `build.rs` to get some extra environment variables that Cargo doesn't set for build dependencies. The `pyo3` `build.rs` also runs some safety checks such as ensuring the Python version detected is actually supported. Some of the functionality of `pyo3-build-config`: - Find the interpreter for build and detect the Python version. - We have to set some version flags like `#[cfg(Py_3_7)]`. - If the interpreter is PyPy, we set `#[cfg(PyPy)`. - If the `PYO3_CONFIG_FILE` environment variable is set then that file's contents will be used instead of any detected configuration. - If the `PYO3_NO_PYTHON` environment variable is set then the interpreter detection is bypassed entirely and only abi3 extensions can be built. - Check if we are building a Python extension. - If we are building an extension (e.g., Python library installable by `pip`), we don't link `libpython` on most platforms (to allow for statically-linked Python interpreters). The `PYO3_BUILD_EXTENSION_MODULE` environment variable suppresses linking. - Cross-compiling configuration - If `TARGET` architecture and `HOST` architecture differ, we can find cross compile information from environment variables (`PYO3_CROSS_LIB_DIR`, `PYO3_CROSS_PYTHON_VERSION` and `PYO3_CROSS_PYTHON_IMPLEMENTATION`) or system files. When cross compiling extension modules it is often possible to make it work without any additional user input. - On Windows, `pyo3-ffi` uses Rust's `raw-dylib` linking feature to link against the Python DLL directly without needing import libraries (`.lib` files). The build script emits a `pyo3_dll` cfg with the target DLL name, and the `extern_libpython!` macro expands to the appropriate `#[link(name = "...", kind = "raw-dylib")]` attribute. This enables cross compiling Python extensions for Windows without having to install any Windows Python libraries. [python/c api]: https://docs.python.org/3/c-api/ [`pyo3-macros`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros [`pyo3-macros-backend`]: https://github.com/PyO3/pyo3/tree/main/pyo3-macros-backend [`pyo3-build-config`]: https://github.com/PyO3/pyo3/tree/main/pyo3-build-config [`pyo3-ffi`]: https://github.com/PyO3/pyo3/tree/main/pyo3-ffi [`src/class`]: https://github.com/PyO3/pyo3/tree/main/src/class [`src/ffi`]: https://github.com/PyO3/pyo3/tree/main/src/ffi [`src/types`]: https://github.com/PyO3/pyo3/tree/main/src/types [`src/impl_`]: https://github.com/PyO3/pyo3/blob/main/src/impl_ [`src/instance.rs`]: https://github.com/PyO3/pyo3/tree/main/src/instance.rs [`src/pycell.rs`]: https://github.com/PyO3/pyo3/tree/main/src/pycell.rs [`src/pyclass.rs`]: https://github.com/PyO3/pyo3/tree/main/src/pyclass.rs [`src/pyclass_init.rs`]: https://github.com/PyO3/pyo3/tree/main/src/pyclass_init.rs [`src/pyclass_slot.rs`]: https://github.com/PyO3/pyo3/tree/main/src/pyclass_slot.rs [`src/type_object.rs`]: https://github.com/PyO3/pyo3/tree/main/src/type_object.rs [`src/class/methods.rs`]: https://github.com/PyO3/pyo3/tree/main/src/class/methods.rs [`src/class/impl_.rs`]: https://github.com/PyO3/pyo3/tree/main/src/class/impl_.rs [`src/types/any.rs`]: https://github.com/PyO3/pyo3/tree/main/src/types/any.rs [`src/types/mod.rs`]: https://github.com/PyO3/pyo3/tree/main/src/types/mod.rs ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. For help with updating to new PyO3 versions, please see the [migration guide](https://pyo3.rs/latest/migration.html). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). To see unreleased changes, please see the [CHANGELOG on the main branch guide](https://pyo3.rs/main/changelog.html). ## [0.28.2] - 2026-02-18 ### Fixed - Fix complex enum `__qualname__` not using python name [#5815](https://github.com/PyO3/pyo3/pull/5815) - Fix FFI definition `PyType_GetTypeDataSize` (was incorrectly named `PyObject_GetTypeDataSize`). [#5819](https://github.com/PyO3/pyo3/pull/5819) - Fix memory corruption when subclassing native types with `abi3` feature on Python 3.12+ (newly enabled in PyO3 0.28.0). [#5823](https://github.com/PyO3/pyo3/pull/5823) ## [0.28.1] - 2026-02-14 ### Fixed - Fix `*args` / `**kwargs` support in` experimental-async` feature (regressed in 0.28.0). [#5771](https://github.com/PyO3/pyo3/pull/5771) - Fix `clippy::declare_interior_mutable_const` warning inside `#[pyclass]` generated code on enums. [#5772](https://github.com/PyO3/pyo3/pull/5772) - Fix `ambiguous_associated_items` compilation error when deriving `FromPyObject` or using `#[pyclass(from_py_object)]` macro on enums with `Error` variant. [#5784](https://github.com/PyO3/pyo3/pull/5784) - Fix `__qualname__` for complex `#[pyclass]` enum variants to include the enum name. [#5796](https://github.com/PyO3/pyo3/pull/5796) - Fix missing `std::sync::atomic::Ordering` import for targets without atomic64. [#5808](https://github.com/PyO3/pyo3/pull/5808) ## [0.28.0] - 2026-02-01 ### Packaging - Bump MSRV to Rust 1.83. [#5531](https://github.com/PyO3/pyo3/pull/5531) - Bump minimum supported `quote` version to 1.0.37. [#5531](https://github.com/PyO3/pyo3/pull/5531) - Bump supported GraalPy version to 25.0. [#5542](https://github.com/PyO3/pyo3/pull/5542) - Drop `memoffset` dependency. [#5545](https://github.com/PyO3/pyo3/pull/5545) - Support for free-threaded Python is now opt-out rather than opt-in. [#5564](https://github.com/PyO3/pyo3/pull/5564) - Bump `target-lexicon` dependency to 0.13.3. [#5571](https://github.com/PyO3/pyo3/pull/5571) - Drop `indoc` and `unindent` dependencies. [#5608](https://github.com/PyO3/pyo3/pull/5608) ### Added - Add `__init__` support in `#[pymethods]`. [#4951](https://github.com/PyO3/pyo3/pull/4951) - Expose `PySuper` on PyPy, GraalPy and ABI3 [#4951](https://github.com/PyO3/pyo3/pull/4951) - Add `PyString::from_fmt` and `py_format!` macro. [#5199](https://github.com/PyO3/pyo3/pull/5199) - Add `#[pyclass(new = "from_fields")]` option. [#5421](https://github.com/PyO3/pyo3/pull/5421) - Add `pyo3::buffer::PyUntypedBuffer`, a type-erased form of `PyBuffer`. [#5458](https://github.com/PyO3/pyo3/pull/5458) - Add `PyBytes::new_with_writer` [#5517](https://github.com/PyO3/pyo3/pull/5517) - Add `PyClass::NAME`. [#5579](https://github.com/PyO3/pyo3/pull/5579) - Add `pyo3_build_config::add_libpython_rpath_link_args`. [#5624](https://github.com/PyO3/pyo3/pull/5624) - Add `PyBackedStr::clone_ref` and `PyBackedBytes::clone_ref` methods. [#5654](https://github.com/PyO3/pyo3/pull/5654) - Add `PyCapsule::new_with_pointer` and `PyCapsule::new_with_pointer_and_destructor` for creating capsules with raw pointers. [#5689](https://github.com/PyO3/pyo3/pull/5689) - Add `#[deleter]` attribute to implement property deleters in `#[methods]`. [#5699](https://github.com/PyO3/pyo3/pull/5699) - Add `IntoPyObject` and `FromPyObject` implementations for `uuid::NonNilUuid`. [#5707](https://github.com/PyO3/pyo3/pull/5707) - Add `PyBackedStr::as_str` and `PyBackedStr::as_py_str` methods. [#5723](https://github.com/PyO3/pyo3/pull/5723) - Add support for subclassing native types (`PyDict`, exceptions, ...) when building for abi3 on Python 3.12+. [#5733](https://github.com/PyO3/pyo3/pull/5733) - Add support for subclassing `PyList` when building for Python 3.12+. [#5734](https://github.com/PyO3/pyo3/pull/5734) - FFI definitions: - Add FFI definitions `PyEval_GetFrameBuiltins`, `PyEval_GetFrameGlobals` and `PyEval_GetFrameLocals` on Python 3.13 and up. [#5590](https://github.com/PyO3/pyo3/pull/5590) - Add FFI definitions `PyObject_New`, `PyObject_NewVar`, `PyObject_GC_Resize`, `PyObject_GC_New`, and `PyObject_GC_NewVar`. [#5591](https://github.com/PyO3/pyo3/pull/5591) - Added FFI definitions and an unsafe Rust API wrapping `Py_BEGIN_CRITICAL_SECTION_MUTEX` and `Py_BEGIN_CRITICAL_SECTION_MUTEX2`. [#5642](https://github.com/PyO3/pyo3/pull/5642) - Add FFI definition `PyDict_GetItemStringRef` on Python 3.13 and up. [#5659](https://github.com/PyO3/pyo3/pull/5659) - Add FFI definition `PyIter_NextItem` on Python 3.14 and up, and `compat::PyIter_NextItem` for older versions. [#5661](https://github.com/PyO3/pyo3/pull/5661) - Add FFI definitions `PyThreadState_GetInterpreter` and `PyThreadState_GetID` on Python 3.9+, `PyThreadState_EnterTracing` and `PyThreadState_LeaveTracing` on Python 3.11+, `PyThreadState_GetUnchecked` on Python 3.13+, and `compat::PyThreadState_GetUnchecked`. [#5711](https://github.com/PyO3/pyo3/pull/5711) - Add FFI definitions `PyImport_ImportModuleAttr` and `PyImport_ImportModuleAttrString` on Python 3.14+. [#5737](https://github.com/PyO3/pyo3/pull/5737) - Add FFI definitions for the `PyABIInfo` and `PyModExport` APIs available in Python 3.15. [#5746](https://github.com/PyO3/pyo3/pull/5746) - `experimental-inspect`: - Emit base classes. [#5331](https://github.com/PyO3/pyo3/pull/5331) - Emit `@typing.final` on final classes. [#5552](https://github.com/PyO3/pyo3/pull/5552) - Generate nested classes for complex enums. [#5708](https://github.com/PyO3/pyo3/pull/5708) - Emit `async` keyword for async functions. [#5731](https://github.com/PyO3/pyo3/pull/5731) ### Changed - Call `sys.unraisablehook` instead of `PyErr_Print` if panicking on null FFI pointer in `Bound`, `Borrowed` and `Py` constructors. [#5496](https://github.com/PyO3/pyo3/pull/5496) - Use PEP-489 multi-phase initialization for `#[pymodule]`. [#5525](https://github.com/PyO3/pyo3/pull/5525) - Deprecate implicit by-value implementation of `FromPyObject` for `#[pyclass]`. [#5550](https://github.com/PyO3/pyo3/pull/5550) - Deprecate `PyTypeInfo::NAME` and `PyTypeInfo::MODULE`. [#5579](https://github.com/PyO3/pyo3/pull/5579) - Deprecate `Py::from_{owned,borrowed}[or_{err,opt}]` constructors from raw pointer. [#5585](https://github.com/PyO3/pyo3/pull/5585) - Deprecate FFI definitions `PyEval_AcquireLock` and `PyEval_ReleaseLock`. [#5590](https://github.com/PyO3/pyo3/pull/5590) - Relax `'py: 'a` bound in `Py::extract`. [#5594](https://github.com/PyO3/pyo3/pull/5594) - Add a `T: PyTypeCheck` bound to the `IntoPyObject` implementations on `Bound`, `Borrowed` and `Py`. [#5640](https://github.com/PyO3/pyo3/pull/5640) - The `with_critical_section` and `with_critical_section2` functions are moved to `pyo3::sync::critical_section`. [#5642](https://github.com/PyO3/pyo3/pull/5642) - Use `PyIter_NextItem` in `PyIterator::next` implementation. [#5661](https://github.com/PyO3/pyo3/pull/5661) - `IntoPyObject` for simple enums now uses a singleton value, allowing identity (python `is`) comparisons. [#5665](https://github.com/PyO3/pyo3/pull/5665) - Allow any `Sequence[int]` in `FromPyObject` on `Cow<[u8]>` and change the error type to `PyErr`. [#5667](https://github.com/PyO3/pyo3/pull/5667) - `async` pymethods now borrow `self` only for the duration of awaiting the future, not the entire method call. [#5684](https://github.com/PyO3/pyo3/pull/5684) - Change `CastError` formatted message to directly describe the "is not an instance of" failure condition. [#5693](https://github.com/PyO3/pyo3/pull/5693) - Add `#[inline]` hints to many methods on `PyBackedStr`. [#5723](https://github.com/PyO3/pyo3/pull/5723) - Remove redundant internal counters from `BoundSetIterator` and `BoundFrozenSetIterator`. [#5725](https://github.com/PyO3/pyo3/pull/5725) - Implement `PyIterator::size_hint` on abi3 builds (previously was only on unlimited API builds). [#5727](https://github.com/PyO3/pyo3/pull/5727) - Deprecate FFI definition `PyImport_ImportModuleNoBlock` (deprecated in Python 3.13). [#5737](https://github.com/PyO3/pyo3/pull/5737) - `#[new]` can now return arbitrary Python objects. [#5739](https://github.com/PyO3/pyo3/pull/5739) - `experimental-inspect`: - Introduce `TypeHint` and make use of it to encode type hint annotations. [#5438](https://github.com/PyO3/pyo3/pull/5438) - Rename `PyType{Info,Check}::TYPE_INFO` into `PyType{Info,Check}::TYPE_HINT`. [#5438](https://github.com/PyO3/pyo3/pull/5438) [#5619](https://github.com/PyO3/pyo3/pull/5619) [#5641](https://github.com/PyO3/pyo3/pull/5641) - Fill annotations on function arguments and return values for all types supported natively by PyO3. [#5634](https://github.com/PyO3/pyo3/pull/5634) [#5637](https://github.com/PyO3/pyo3/pull/5637) [#5639](https://github.com/PyO3/pyo3/pull/5639) - Use `_typeshed.Incomplete` instead of `typing.Any` as default type hint, to make it easier to spot incomplete trait implementations. [#5744](https://github.com/PyO3/pyo3/pull/5744) - Use general Python expression syntax for type hints. [#5671](https://github.com/PyO3/pyo3/pull/5671) ### Removed - Remove all functionality deprecated in PyO3 0.25 and 0.26. [#5740](https://github.com/PyO3/pyo3/pull/5740) - FFI definitions: - Remove FFI definition `PyEval_GetCallStats` (removed from CPython in Python 3.7). [#5590](https://github.com/PyO3/pyo3/pull/5590) - Remove FFI definitions `PyEval_AcquireLock` and `PyEval_ReleaseLock` on Python 3.13 and up. [#5590](https://github.com/PyO3/pyo3/pull/5590) - Remove private FFI definitions `_PyObject_New`, `_PyObject_NewVar`, `_PyObject_GC_Resize`, `_PyObject_GC_New`, and `_PyObject_GC_NewVar`. [#5591](https://github.com/PyO3/pyo3/pull/5591) - Remove private FFI definitions `_PyDict_SetItem_KnownHash`, `_PyDict_Next`, `_PyDict_NewPresized`, `_PyDict_Contains_KnownHash`, and `_PyDict_Contains`. [#5659](https://github.com/PyO3/pyo3/pull/5659) - Remove private FFI definitions `_PyFrameEvalFunction`, `_PyInterpreterState_GetEvalFrameFunc` and `_PyInterpreterState_SetEvalFrameFunc`. [#5711](https://github.com/PyO3/pyo3/pull/5711) - Remove private FFI definitions `_PyImport_IsInitialized`, `_PyImport_SetModule`, `_PyImport_SetModuleString`, `_PyImport_AcquireLock`, `_PyImport_ReleaseLock`, `_PyImport_FindBuiltin`, `_PyImport_FindExtensionObject`, `_PyImport_FixupBuiltin`, and `_PyImport_FixupExtensionObject`. [#5737](https://github.com/PyO3/pyo3/pull/5737) ### Fixed - Fix `PyModuleMethods::add_submodule()` to use the last segment of the submodule name as the attribute name on the parent module instead of using the full name. [#5375](https://github.com/PyO3/pyo3/pull/5375) - Link with libpython for Cygwin extension modules. [#5571](https://github.com/PyO3/pyo3/pull/5571) - Link against the limited API DLL for Cygwin when abi3 is used. [#5574](https://github.com/PyO3/pyo3/pull/5574) - Handle errors in `PyIterator` when calling `size_hint` [#5604](https://github.com/PyO3/pyo3/pull/5604) - Link with libpython for iOS extension modules. [#5605](https://github.com/PyO3/pyo3/pull/5605) - Correct `IntoPyObject` output type of `PyBackedStr` to be `PyString`, not `PyAny`. [#5655](https://github.com/PyO3/pyo3/pull/5655) - Fix `async` functions to return `None` rather than empty tuple `()`. [#5685](https://github.com/PyO3/pyo3/pull/5685) - Fix compile error when using references to `#[pyclass]` types (e.g. `&MyClass`) as arguments to async `#[pyfunction]`s. [#5725](https://github.com/PyO3/pyo3/pull/5725) - FFI definitions: - Fix FFI definition `PyMemberDescrObject.d_member` to use `PyMemberDef` for Python 3.11+ (was incorrectly `PyGetSetDef`). [#5647](https://github.com/PyO3/pyo3/pull/5647) - Mark FFI definition `PyThreadState_GetFrame` available with abi3 in 3.10+. [#5711](https://github.com/PyO3/pyo3/pull/5711) - Fix FFI definition `PyImport_GetModule` on PyPy. [#5737](https://github.com/PyO3/pyo3/pull/5737) - `experimental-inspect`: - fix `__new__` return type to be the built object type and not `None`. [#5555](https://github.com/PyO3/pyo3/pull/5555) - fix imports of decorators. [#5618](https://github.com/PyO3/pyo3/pull/5618) - fix the return type annotation of `PyResult<()>` (must be `None` and not `tuple`) [#5674](https://github.com/PyO3/pyo3/pull/5674) ## [0.27.2] - 2025-11-30 ### Changed - Disable subclassing `PyDict` on GraalPy (unsupported for now, may crash at runtime). [#5653](https://github.com/PyO3/pyo3/pull/5653) ### Fixed - Fix crash when compiling on Rust 1.92+ with both debug assertions and optimizations enabled. [#5638](https://github.com/PyO3/pyo3/pull/5638) - Fix FFI definition of `PyDictObject` on PyPy. [#5653](https://github.com/PyO3/pyo3/pull/5653) ## [0.27.1] - 2025-10-21 ### Fixed - Fix `clippy:declare_interior_mutable_const` warning from `#[pyfunction]`. [#5538](https://github.com/PyO3/pyo3/pull/5538) - Expose `pyo3::types::PySendResult` in public API. [#5539](https://github.com/PyO3/pyo3/pull/5539) ## [0.27.0] - 2025-10-19 ### Packaging - Extend range of supported versions of `hashbrown` optional dependency to include version 0.16. [#5428](https://github.com/PyO3/pyo3/pull/5428) - Bump optional `num-bigint` dependency minimum version to 0.4.4. [#5471](https://github.com/PyO3/pyo3/pull/5471) - Test against Python 3.14 final release. [#5499](https://github.com/PyO3/pyo3/pull/5499) - Drop support for PyPy 3.9 and 3.10. [#5516](https://github.com/PyO3/pyo3/pull/5516) - Provide a better error message when building an outdated PyO3 for a too-new Python version. [#5519](https://github.com/PyO3/pyo3/pull/5519) ### Added - Add `FromPyObjectOwned` as convenient trait bound for `FromPyObject` when the data is not borrowed from Python. [#4390](https://github.com/PyO3/pyo3/pull/4390) - Add `Borrowed::extract`, same as `PyAnyMethods::extract`, but does not restrict the lifetime by deref. [#4390](https://github.com/PyO3/pyo3/pull/4390) - `experimental-inspect`: basic support for `#[derive(IntoPyObject)]` (no struct fields support yet). [#5365](https://github.com/PyO3/pyo3/pull/5365) - `experimental-inspect`: support `#[pyo3(get, set)]` and `#[pyclass(get_all, set_all)]`. [#5370](https://github.com/PyO3/pyo3/pull/5370) - Add `PyTypeCheck::classinfo_object` that returns an object that can be used as parameter in `isinstance` or `issubclass`. [#5387](https://github.com/PyO3/pyo3/pull/5387) - Implement `PyTypeInfo` on `datetime.*` types even when the limited API is enabled. [#5388](https://github.com/PyO3/pyo3/pull/5388) - Implement `PyTypeInfo` on `PyIterator`, `PyMapping` and `PySequence`. [#5402](https://github.com/PyO3/pyo3/pull/5402) - Implement `PyTypeInfo` on `PyCode` when using the stable ABI. [#5403](https://github.com/PyO3/pyo3/pull/5403) - Implement `PyTypeInfo` on `PyWeakrefReference` when using the stable ABI. [#5404](https://github.com/PyO3/pyo3/pull/5404) - Add `pyo3::sync::RwLockExt` trait, analogous to `pyo3::sync::MutexExt` for readwrite locks. [#5435](https://github.com/PyO3/pyo3/pull/5435) - Add `PyString::from_bytes`. [#5437](https://github.com/PyO3/pyo3/pull/5437) - Implement `AsRef<[u8]>` for `PyBytes`. [#5445](https://github.com/PyO3/pyo3/pull/5445) - Add `CastError` and `CastIntoError`. [#5468](https://github.com/PyO3/pyo3/pull/5468) - Add `PyCapsuleMethods::pointer_checked` and `PyCapsuleMethods::is_valid_checked`. [#5474](https://github.com/PyO3/pyo3/pull/5474) - Add `Borrowed::cast`, `Borrowed::cast_exact` and `Borrowed::cast_unchecked`. [#5475](https://github.com/PyO3/pyo3/pull/5475) - Add conversions for `jiff::civil::ISOWeekDate`. [#5478](https://github.com/PyO3/pyo3/pull/5478) - Add conversions for `&Cstr`, `Cstring` and `Cow`. [#5482](https://github.com/PyO3/pyo3/pull/5482) - add `#[pyclass(skip_from_py_object)]` option, to opt-out of the `FromPyObject: PyClass + Clone` blanket impl. [#5488](https://github.com/PyO3/pyo3/pull/5488) - Add `PyErr::add_note`. [#5489](https://github.com/PyO3/pyo3/pull/5489) - Add `FromPyObject` impl for `Cow` & `Cow`. [#5497](https://github.com/PyO3/pyo3/pull/5497) - Add `#[pyclass(from_py_object)]` pyclass option, to opt-in to the extraction of pyclasses by value (requires `Clone`). [#5506](https://github.com/PyO3/pyo3/pull/5506) ### Changed - Rework `FromPyObject` trait for flexibility and performance: [#4390](https://github.com/PyO3/pyo3/pull/4390) - Add a second lifetime to `FromPyObject`, to allow borrowing data from Python objects (e.g. `&str` from Python `str`). - Replace `extract_bound` with `extract`, which takes `Borrowed<'a, 'py, PyAny>`. - Optimize `FromPyObject` implementations for `Vec` and `[u8; N]` from `bytes` and `bytearray`. [#5244](https://github.com/PyO3/pyo3/pull/5244) - Deprecate `#[pyfn]` attribute. [#5384](https://github.com/PyO3/pyo3/pull/5384) - Fetch type name dynamically on cast errors instead of using `PyTypeCheck::NAME`. [#5387](https://github.com/PyO3/pyo3/pull/5387) - Deprecate `PyTypeCheck::NAME` in favour of `PyTypeCheck::classinfo_object` which provides the type information at runtime. [#5387](https://github.com/PyO3/pyo3/pull/5387) - `PyClassGuard(Mut)` and `PyRef(Mut)` extraction now returns an opaque Rust error [#5413](https://github.com/PyO3/pyo3/pull/5413) - Fetch type name dynamically when exporting types implementing `PyTypeInfo` with `#[pymodule_use]`. [#5414](https://github.com/PyO3/pyo3/pull/5414) - Improve `Debug` representation of `PyBuffer`. [#5442](https://github.com/PyO3/pyo3/pull/5442) - `experimental-inspect`: change the way introspection data is emitted in the binaries to avoid a pointer indirection and simplify parsing. [#5450](https://github.com/PyO3/pyo3/pull/5450) - Optimize `Py::drop` for the case when attached to the Python interpreter. [#5454](https://github.com/PyO3/pyo3/pull/5454) - Replace `DowncastError` and `DowncastIntoError` with `CastError` and `CastIntoError`. [#5468](https://github.com/PyO3/pyo3/pull/5468) - Enable fast-path for 128-bit integer conversions on `GraalPy`. [#5471](https://github.com/PyO3/pyo3/pull/5471) - Deprecate `PyAnyMethods::downcast` functions in favour of `Bound::cast` functions. [#5472](https://github.com/PyO3/pyo3/pull/5472) - Make `PyTypeCheck` an `unsafe trait`. [#5473](https://github.com/PyO3/pyo3/pull/5473) - Deprecate unchecked `PyCapsuleMethods`: `pointer()`, `reference()`, and `is_valid()`. [#5474](https://github.com/PyO3/pyo3/pull/5474) - Reduce lifetime of return value in `PyCapsuleMethods::reference`. [#5474](https://github.com/PyO3/pyo3/pull/5474) - `PyCapsuleMethods::name` now returns `CapsuleName` wrapper instead of `&CStr`. [#5474](https://github.com/PyO3/pyo3/pull/5474) - Deprecate `import_exception_bound` in favour of `import_exception`. [#5480](https://github.com/PyO3/pyo3/pull/5480) - `PyList::get_item_unchecked`, `PyTuple::get_item_unchecked`, and `PyTuple::get_borrowed_item_unchecked` no longer check for null values at the provided index. [#5494](https://github.com/PyO3/pyo3/pull/5494) - Allow converting naive datetime into chrono `DateTime`. [#5507](https://github.com/PyO3/pyo3/pull/5507) ### Removed - Removed `FromPyObjectBound` trait. [#4390](https://github.com/PyO3/pyo3/pull/4390) ### Fixed - Fix compilation failure on `wasm32-wasip2`. [#5368](https://github.com/PyO3/pyo3/pull/5368) - Fix `OsStr` conversion for non-utf8 strings on Windows. [#5444](https://github.com/PyO3/pyo3/pull/5444) - Fix issue with `cargo vendor` caused by gitignored build artifact `emscripten/pybuilddir.txt`. [#5456](https://github.com/PyO3/pyo3/pull/5456) - Stop leaking `PyMethodDef` instances inside `#[pyfunction]` macro generated code. [#5459](https://github.com/PyO3/pyo3/pull/5459) - Don't export definition of FFI struct `PyObjectObFlagsAndRefcnt` on 32-bit Python 3.14 (doesn't exist). [#5499](https://github.com/PyO3/pyo3/pull/5499) - Fix failure to build for `abi3` interpreters on Windows using maturin's built-in sysconfig in combination with the `generate-import-lib` feature. [#5503](https://github.com/PyO3/pyo3/pull/5503) - Fix FFI definitions `PyModule_ExecDef` and `PyModule_FromDefAndSpec2` on PyPy. [#5529](https://github.com/PyO3/pyo3/pull/5529) ## [0.26.0] - 2025-08-29 ### Packaging - Bump hashbrown dependency to 0.15. [#5152](https://github.com/PyO3/pyo3/pull/5152) - Update MSRV to 1.74. [#5171](https://github.com/PyO3/pyo3/pull/5171) - Set the same maximum supported version for alternative interpreters as for CPython. [#5192](https://github.com/PyO3/pyo3/pull/5192) - Add optional `bytes` dependency to add conversions for `bytes::Bytes`. [#5252](https://github.com/PyO3/pyo3/pull/5252) - Publish new crate `pyo3-introspection` to pair with the `experimental-inspect` feature. [#5300](https://github.com/PyO3/pyo3/pull/5300) - The `PYO3_BUILD_EXTENSION_MODULE` now causes the same effect as the `extension-module` feature. Eventually we expect maturin and setuptools-rust to set this environment variable automatically. Users with their own build systems will need to do the same. [#5343](https://github.com/PyO3/pyo3/pull/5343) ### Added - Add `#[pyo3(warn(message = "...", category = ...))]` attribute for automatic warnings generation for `#[pyfunction]` and `#[pymethods]`. [#4364](https://github.com/PyO3/pyo3/pull/4364) - Add `PyMutex`, available on Python 3.13 and newer. [#4523](https://github.com/PyO3/pyo3/pull/4523) - Add FFI definition `PyMutex_IsLocked`, available on Python 3.14 and newer. [#4523](https://github.com/PyO3/pyo3/pull/4523) - Add `PyString::from_encoded_object`. [#5017](https://github.com/PyO3/pyo3/pull/5017) - `experimental-inspect`: add basic input type annotations. [#5089](https://github.com/PyO3/pyo3/pull/5089) - Add FFI function definitions for `PyFrameObject` from CPython 3.13. [#5154](https://github.com/PyO3/pyo3/pull/5154) - `experimental-inspect`: tag modules created using `#[pymodule]` or `#[pymodule_init]` functions as incomplete. [#5207](https://github.com/PyO3/pyo3/pull/5207) - `experimental-inspect`: add basic return type support. [#5208](https://github.com/PyO3/pyo3/pull/5208) - Add `PyCode::compile` and `PyCodeMethods::run` to create and execute code objects. [#5217](https://github.com/PyO3/pyo3/pull/5217) - Add `PyOnceLock` type for thread-safe single-initialization. [#5223](https://github.com/PyO3/pyo3/pull/5223) - Add `PyClassGuard(Mut)` pyclass holders. In the future they will replace `PyRef(Mut)`. [#5233](https://github.com/PyO3/pyo3/pull/5233) - `experimental-inspect`: allow annotations in `#[pyo3(signature)]` signature attribute. [#5241](https://github.com/PyO3/pyo3/pull/5241) - Implement `MutexExt` for parking_lot's/lock_api `ReentrantMutex`. [#5258](https://github.com/PyO3/pyo3/pull/5258) - `experimental-inspect`: support class associated constants. [#5272](https://github.com/PyO3/pyo3/pull/5272) - Add `Bound::cast` family of functions superseding the `PyAnyMethods::downcast` family. [#5289](https://github.com/PyO3/pyo3/pull/5289) - Add FFI definitions `Py_Version` and `Py_IsFinalizing`. [#5317](https://github.com/PyO3/pyo3/pull/5317) - `experimental-inspect`: add output type annotation for `#[pyclass]`. [#5320](https://github.com/PyO3/pyo3/pull/5320) - `experimental-inspect`: support `#[pyclass(eq, eq_int, ord, hash, str)]`. [#5338](https://github.com/PyO3/pyo3/pull/5338) - `experimental-inspect`: add basic support for `#[derive(FromPyObject)]` (no struct fields support yet). [#5339](https://github.com/PyO3/pyo3/pull/5339) - Add `Python::try_attach`. [#5342](https://github.com/PyO3/pyo3/pull/5342) ### Changed - Use `Py_TPFLAGS_DISALLOW_INSTANTIATION` instead of a `__new__` which always fails for a `#[pyclass]` without a `#[new]` on Python 3.10 and up. [#4568](https://github.com/PyO3/pyo3/pull/4568) - `PyModule::from_code` now defaults `file_name` to `` if empty. [#4777](https://github.com/PyO3/pyo3/pull/4777) - Deprecate `PyString::from_object` in favour of `PyString::from_encoded_object`. [#5017](https://github.com/PyO3/pyo3/pull/5017) - When building with `abi3` for a Python version newer than pyo3 supports, automatically fall back to an abi3 build for the latest supported version. [#5144](https://github.com/PyO3/pyo3/pull/5144) - Change `is_instance_of` trait bound from `PyTypeInfo` to `PyTypeCheck`. [#5146](https://github.com/PyO3/pyo3/pull/5146) - Many PyO3 proc macros now report multiple errors instead of only the first one. [#5159](https://github.com/PyO3/pyo3/pull/5159) - Change `MutexExt` return type to be an associated type. [#5201](https://github.com/PyO3/pyo3/pull/5201) - Use `PyCallArgs` for `Py::call` and friends so they're equivalent to their `Bound` counterpart. [#5206](https://github.com/PyO3/pyo3/pull/5206) - Rename `Python::with_gil` to `Python::attach`. [#5209](https://github.com/PyO3/pyo3/pull/5209) - Rename `Python::allow_threads` to `Python::detach` [#5221](https://github.com/PyO3/pyo3/pull/5221) - Deprecate `GILOnceCell` type in favour of `PyOnceLock`. [#5223](https://github.com/PyO3/pyo3/pull/5223) - Rename `pyo3::prepare_freethreaded_python` to `Python::initialize`. [#5247](https://github.com/PyO3/pyo3/pull/5247) - Convert `PyMemoryError` into/from `io::ErrorKind::OutOfMemory`. [#5256](https://github.com/PyO3/pyo3/pull/5256) - Deprecate `GILProtected`. [#5285](https://github.com/PyO3/pyo3/pull/5285) - Move `#[pyclass]` docstring formatting from import time to compile time. [#5286](https://github.com/PyO3/pyo3/pull/5286) - `Python::attach` will now panic if the Python interpreter is in the process of shutting down. [#5317](https://github.com/PyO3/pyo3/pull/5317) - Add fast-path to `PyTypeInfo::type_object` for `#[pyclass]` types. [#5324](https://github.com/PyO3/pyo3/pull/5324) - Deprecate `PyObject` type alias for `Py`. [#5325](https://github.com/PyO3/pyo3/pull/5325) - Rename `Python::with_gil_unchecked` to `Python::attach_unchecked`. [#5340](https://github.com/PyO3/pyo3/pull/5340) - Rename `Python::assume_gil_acquired` to `Python::assume_attached`. [#5354](https://github.com/PyO3/pyo3/pull/5354) ### Removed - Remove FFI definition of internals of `PyFrameObject`. [#5154](https://github.com/PyO3/pyo3/pull/5154) - Remove `Eq` and `PartialEq` implementations on `PyGetSetDef` FFI definition. [#5196](https://github.com/PyO3/pyo3/pull/5196) - Remove private FFI definitions `_Py_IsCoreInitialized` and `_Py_InitializeMain`. [#5317](https://github.com/PyO3/pyo3/pull/5317) ### Fixed - Use critical section in `PyByteArray::to_vec` on freethreaded build to replicate GIL-enabled "soundness". [#4742](https://github.com/PyO3/pyo3/pull/4742) - Fix precision loss when converting `bigdecimal` into Python. [#5198](https://github.com/PyO3/pyo3/pull/5198) - Don't treat win7 target as a cross-compilation. [#5210](https://github.com/PyO3/pyo3/pull/5210) - WASM targets no longer require exception handling support for Python < 3.14. [#5239](https://github.com/PyO3/pyo3/pull/5239) - Fix segfault when dropping `PyBuffer` after the Python interpreter has been finalized. [#5242](https://github.com/PyO3/pyo3/pull/5242) - `experimental-inspect`: better automated imports generation. [#5251](https://github.com/PyO3/pyo3/pull/5251) - `experimental-inspect`: fix introspection of `__richcmp__`, `__concat__`, `__repeat__`, `__inplace_concat__` and `__inplace_repeat__`. [#5273](https://github.com/PyO3/pyo3/pull/5273) - fixed a leaked borrow, when converting a mutable sub class into a frozen base class using `PyRef::into_super` [#5281](https://github.com/PyO3/pyo3/pull/5281) - Fix FFI definition `Py_Exit` (never returns, was `()` return value, now `!`). [#5317](https://github.com/PyO3/pyo3/pull/5317) - `experimental-inspect`: fix handling of module members gated behind `#[cfg(...)]` attributes. [#5318](https://github.com/PyO3/pyo3/pull/5318) ## [0.25.1] - 2025-06-12 ### Packaging - Add support for Windows on ARM64. [#5145](https://github.com/PyO3/pyo3/pull/5145) - Add `chrono-local` feature for optional conversions for chrono's `Local` timezone & `DateTime` instances. [#5174](https://github.com/PyO3/pyo3/pull/5174) ### Added - Add FFI definition `PyBytes_AS_STRING`. [#5121](https://github.com/PyO3/pyo3/pull/5121) - Add support for module associated consts introspection. [#5150](https://github.com/PyO3/pyo3/pull/5150) ### Changed - Enable "vectorcall" FFI definitions on GraalPy. [#5121](https://github.com/PyO3/pyo3/pull/5121) - Use `Py_Is` function on GraalPy [#5121](https://github.com/PyO3/pyo3/pull/5121) ### Fixed - Report a better compile error for `async` declarations when not using `experimental-async` feature. [#5156](https://github.com/PyO3/pyo3/pull/5156) - Fix implementation of `FromPyObject` for `uuid::Uuid` on big-endian architectures. [#5161](https://github.com/PyO3/pyo3/pull/5161) - Fix segmentation faults on 32-bit x86 with Python 3.14. [#5180](https://github.com/PyO3/pyo3/pull/5180) ## [0.25.0] - 2025-05-14 ### Packaging - Support Python 3.14.0b1. [#4811](https://github.com/PyO3/pyo3/pull/4811) - Bump supported GraalPy version to 24.2. [#5116](https://github.com/PyO3/pyo3/pull/5116) - Add optional `bigdecimal` dependency to add conversions for `bigdecimal::BigDecimal`. [#5011](https://github.com/PyO3/pyo3/pull/5011) - Add optional `time` dependency to add conversions for `time` types. [#5057](https://github.com/PyO3/pyo3/pull/5057) - Remove `cfg-if` dependency. [#5110](https://github.com/PyO3/pyo3/pull/5110) - Add optional `ordered_float` dependency to add conversions for `ordered_float::NotNan` and `ordered_float::OrderedFloat`. [#5114](https://github.com/PyO3/pyo3/pull/5114) ### Added - Add initial type stub generation to the `experimental-inspect` feature. [#3977](https://github.com/PyO3/pyo3/pull/3977) - Add `#[pyclass(generic)]` option to support runtime generic typing. [#4926](https://github.com/PyO3/pyo3/pull/4926) - Implement `OnceExt` & `MutexExt` for `parking_lot` & `lock_api`. Use the new extension traits by enabling the `arc_lock`, `lock_api`, or `parking_lot` cargo features. [#5044](https://github.com/PyO3/pyo3/pull/5044) - Implement `From`/`Into` for `Borrowed` -> `Py`. [#5054](https://github.com/PyO3/pyo3/pull/5054) - Add `PyTzInfo` constructors. [#5055](https://github.com/PyO3/pyo3/pull/5055) - Add FFI definition `PY_INVALID_STACK_EFFECT`. [#5064](https://github.com/PyO3/pyo3/pull/5064) - Implement `AsRef>` for `Py`, `Bound` and `Borrowed`. [#5071](https://github.com/PyO3/pyo3/pull/5071) - Add FFI definition `PyModule_Add` and `compat::PyModule_Add`. [#5085](https://github.com/PyO3/pyo3/pull/5085) - Add FFI definitions `Py_HashBuffer`, `Py_HashPointer`, and `PyObject_GenericHash`. [#5086](https://github.com/PyO3/pyo3/pull/5086) - Support `#[pymodule_export]` on `const` items in declarative modules. [#5096](https://github.com/PyO3/pyo3/pull/5096) - Add `#[pyclass(immutable_type)]` option (on Python 3.14+ with `abi3`, or 3.10+ otherwise) for immutable type objects. [#5101](https://github.com/PyO3/pyo3/pull/5101) - Support `#[pyo3(rename_all)]` support on `#[derive(IntoPyObject)]`. [#5112](https://github.com/PyO3/pyo3/pull/5112) - Add `PyRange` wrapper. [#5117](https://github.com/PyO3/pyo3/pull/5117) ### Changed - Enable use of `datetime` types with `abi3` feature enabled. [#4970](https://github.com/PyO3/pyo3/pull/4970) - Deprecate `timezone_utc` in favor of `PyTzInfo::utc`. [#5055](https://github.com/PyO3/pyo3/pull/5055) - Reduce visibility of some CPython implementation details: [#5064](https://github.com/PyO3/pyo3/pull/5064) - The FFI definition `PyCodeObject` is now an opaque struct on all Python versions. - The FFI definition `PyFutureFeatures` is now only defined up until Python 3.10 (it was present in CPython headers but unused in 3.11 and 3.12). - Change `PyAnyMethods::is` to take `other: &Bound`. [#5071](https://github.com/PyO3/pyo3/pull/5071) - Change `Py::is` to take `other: &Py`. [#5071](https://github.com/PyO3/pyo3/pull/5071) - Change `PyVisit::call` to take `T: Into>>`. [#5071](https://github.com/PyO3/pyo3/pull/5071) - Expose `PyDateTime_DATE_GET_TZINFO` and `PyDateTime_TIME_GET_TZINFO` on PyPy 3.10 and later. [#5079](https://github.com/PyO3/pyo3/pull/5079) - Add `#[track_caller]` to `with_gil` and `with_gil_unchecked`. [#5109](https://github.com/PyO3/pyo3/pull/5109) - Use `std::thread::park()` instead of `libc::pause()` or `sleep(9999999)`. [#5115](https://github.com/PyO3/pyo3/pull/5115) ### Removed - Remove all functionality deprecated in PyO3 0.23. [#4982](https://github.com/PyO3/pyo3/pull/4982) - Remove deprecated `IntoPy` and `ToPyObject` traits. [#5010](https://github.com/PyO3/pyo3/pull/5010) - Remove private types from `pyo3-ffi` (i.e. starting with `_Py`) which are not referenced by public APIs: `_PyLocalMonitors`, `_Py_GlobalMonitors`, `_PyCoCached`, `_PyCoLineInstrumentationData`, `_PyCoMonitoringData`, `_PyCompilerSrcLocation`, `_PyErr_StackItem`. [#5064](https://github.com/PyO3/pyo3/pull/5064) - Remove FFI definition `PyCode_GetNumFree` (PyO3 cannot support it due to knowledge of the code object). [#5064](https://github.com/PyO3/pyo3/pull/5064) - Remove `AsPyPointer` trait. [#5071](https://github.com/PyO3/pyo3/pull/5071) - Remove support for the deprecated string form of `from_py_with`. [#5097](https://github.com/PyO3/pyo3/pull/5097) - Remove FFI definitions of private static variables: `_PyMethodWrapper_Type`, `_PyCoroWrapper_Type`, `_PyImport_FrozenBootstrap`, `_PyImport_FrozenStdlib`, `_PyImport_FrozenTest`, `_PyManagedBuffer_Type`, `_PySet_Dummy`, `_PyWeakref_ProxyType`, and `_PyWeakref_CallableProxyType`. [#5105](https://github.com/PyO3/pyo3/pull/5105) - Remove FFI definitions `PyASCIIObjectState`, `PyUnicode_IS_ASCII`, `PyUnicode_IS_COMPACT`, and `PyUnicode_IS_COMPACT_ASCII` on Python 3.14 and newer. [#5133](https://github.com/PyO3/pyo3/pull/5133) ### Fixed - Correctly pick up the shared state for conda-based Python installation when reading information from sysconfigdata. [#5037](https://github.com/PyO3/pyo3/pull/5037) - Fix compile failure with `#[derive(IntoPyObject, FromPyObject)]` when using `#[pyo3()]` options recognised by only one of the two derives. [#5070](https://github.com/PyO3/pyo3/pull/5070) - Fix various compile errors from missing FFI definitions using certain feature combinations on PyPy and GraalPy. [#5091](https://github.com/PyO3/pyo3/pull/5091) - Fallback on `backports.zoneinfo` for python <3.9 when converting timezones into python. [#5120](https://github.com/PyO3/pyo3/pull/5120) ## [0.24.2] - 2025-04-21 ### Fixed - Fix `unused_imports` lint of `#[pyfunction]` and `#[pymethods]` expanded in `macro_rules` context. [#5030](https://github.com/PyO3/pyo3/pull/5030) - Fix size of `PyCodeObject::_co_instrumentation_version` ffi struct member on Python 3.13 for systems where `uintptr_t` is not 64 bits. [#5048](https://github.com/PyO3/pyo3/pull/5048) - Fix struct-type complex enum variant fields incorrectly exposing raw identifiers as `r#ident` in Python bindings. [#5050](https://github.com/PyO3/pyo3/pull/5050) ## [0.24.1] - 2025-03-31 ### Added - Add `abi3-py313` feature. [#4969](https://github.com/PyO3/pyo3/pull/4969) - Add `PyAnyMethods::getattr_opt`. [#4978](https://github.com/PyO3/pyo3/pull/4978) - Add `PyInt::new` constructor for all supported number types (i32, u32, i64, u64, isize, usize). [#4984](https://github.com/PyO3/pyo3/pull/4984) - Add `pyo3::sync::with_critical_section2`. [#4992](https://github.com/PyO3/pyo3/pull/4992) - Implement `PyCallArgs` for `Borrowed<'_, 'py, PyTuple>`, `&Bound<'py, PyTuple>`, and `&Py`. [#5013](https://github.com/PyO3/pyo3/pull/5013) ### Fixed - Fix `is_type_of` for native types not using same specialized check as `is_type_of_bound`. [#4981](https://github.com/PyO3/pyo3/pull/4981) - Fix `Probe` class naming issue with `#[pymethods]`. [#4988](https://github.com/PyO3/pyo3/pull/4988) - Fix compile failure with required `#[pyfunction]` arguments taking `Option<&str>` and `Option<&T>` (for `#[pyclass]` types). [#5002](https://github.com/PyO3/pyo3/pull/5002) - Fix `PyString::from_object` causing of bounds reads with `encoding` and `errors` parameters which are not nul-terminated. [#5008](https://github.com/PyO3/pyo3/pull/5008) - Fix compile error when additional options follow after `crate` for `#[pyfunction]`. [#5015](https://github.com/PyO3/pyo3/pull/5015) ## [0.24.0] - 2025-03-09 ### Packaging - Add supported CPython/PyPy versions to cargo package metadata. [#4756](https://github.com/PyO3/pyo3/pull/4756) - Bump `target-lexicon` dependency to 0.13. [#4822](https://github.com/PyO3/pyo3/pull/4822) - Add optional `jiff` dependency to add conversions for `jiff` datetime types. [#4823](https://github.com/PyO3/pyo3/pull/4823) - Add optional `uuid` dependency to add conversions for `uuid::Uuid`. [#4864](https://github.com/PyO3/pyo3/pull/4864) - Bump minimum supported `inventory` version to 0.3.5. [#4954](https://github.com/PyO3/pyo3/pull/4954) ### Added - Add `PyIterator::send` method to allow sending values into a python generator. [#4746](https://github.com/PyO3/pyo3/pull/4746) - Add `PyCallArgs` trait for passing arguments into the Python calling protocol. This enabled using a faster calling convention for certain types, improving performance. [#4768](https://github.com/PyO3/pyo3/pull/4768) - Add `#[pyo3(default = ...']` option for `#[derive(FromPyObject)]` to set a default value for extracted fields of named structs. [#4829](https://github.com/PyO3/pyo3/pull/4829) - Add `#[pyo3(into_py_with = ...)]` option for `#[derive(IntoPyObject, IntoPyObjectRef)]`. [#4850](https://github.com/PyO3/pyo3/pull/4850) - Add FFI definitions `PyThreadState_GetFrame` and `PyFrame_GetBack`. [#4866](https://github.com/PyO3/pyo3/pull/4866) - Optimize `last` for `BoundListIterator`, `BoundTupleIterator` and `BorrowedTupleIterator`. [#4878](https://github.com/PyO3/pyo3/pull/4878) - Optimize `Iterator::count()` for `PyDict`, `PyList`, `PyTuple` & `PySet`. [#4878](https://github.com/PyO3/pyo3/pull/4878) - Optimize `nth`, `nth_back`, `advance_by` and `advance_back_by` for `BoundTupleIterator` [#4897](https://github.com/PyO3/pyo3/pull/4897) - Add support for `types.GenericAlias` as `pyo3::types::PyGenericAlias`. [#4917](https://github.com/PyO3/pyo3/pull/4917) - Add `MutextExt` trait to help avoid deadlocks with the GIL while locking a `std::sync::Mutex`. [#4934](https://github.com/PyO3/pyo3/pull/4934) - Add `#[pyo3(rename_all = "...")]` option for `#[derive(FromPyObject)]`. [#4941](https://github.com/PyO3/pyo3/pull/4941) ### Changed - Optimize `nth`, `nth_back`, `advance_by` and `advance_back_by` for `BoundListIterator`. [#4810](https://github.com/PyO3/pyo3/pull/4810) - Use `DerefToPyAny` in blanket implementations of `From>` and `From>` for `PyObject`. [#4593](https://github.com/PyO3/pyo3/pull/4593) - Map `io::ErrorKind::IsADirectory`/`NotADirectory` to the corresponding Python exception on Rust 1.83+. [#4747](https://github.com/PyO3/pyo3/pull/4747) - `PyAnyMethods::call` and friends now require `PyCallArgs` for their positional arguments. [#4768](https://github.com/PyO3/pyo3/pull/4768) - Expose FFI definitions for `PyObject_Vectorcall(Method)` on the stable abi on 3.12+. [#4853](https://github.com/PyO3/pyo3/pull/4853) - `#[pyo3(from_py_with = ...)]` now take a path rather than a string literal [#4860](https://github.com/PyO3/pyo3/pull/4860) - Format Python traceback in impl Debug for PyErr. [#4900](https://github.com/PyO3/pyo3/pull/4900) - Convert `PathBuf` & `Path` into Python `pathlib.Path` instead of `PyString`. [#4925](https://github.com/PyO3/pyo3/pull/4925) - Relax parsing of exotic Python versions. [#4949](https://github.com/PyO3/pyo3/pull/4949) - PyO3 threads now hang instead of `pthread_exit` trying to acquire the GIL when the interpreter is shutting down. This mimics the [Python 3.14](https://github.com/python/cpython/issues/87135) behavior and avoids undefined behavior and crashes. [#4874](https://github.com/PyO3/pyo3/pull/4874) ### Removed - Remove implementations of `Deref` for `PyAny` and other "native" types. [#4593](https://github.com/PyO3/pyo3/pull/4593) - Remove implicit default of trailing optional arguments (see #2935) [#4729](https://github.com/PyO3/pyo3/pull/4729) - Remove the deprecated implicit eq fallback for simple enums. [#4730](https://github.com/PyO3/pyo3/pull/4730) ### Fixed - Correct FFI definition of `PyIter_Send` to return a `PySendResult`. [#4746](https://github.com/PyO3/pyo3/pull/4746) - Fix a thread safety issue in the runtime borrow checker used by mutable pyclass instances on the free-threaded build. [#4948](https://github.com/PyO3/pyo3/pull/4948) ## [0.23.5] - 2025-02-22 ### Packaging - Add support for PyPy3.11 [#4760](https://github.com/PyO3/pyo3/pull/4760) ### Fixed - Fix thread-unsafe implementation of freelist pyclasses on the free-threaded build. [#4902](https://github.com/PyO3/pyo3/pull/4902) - Re-enable a workaround for situations where CPython incorrectly does not add `__builtins__` to `__globals__` in code executed by `Python::py_run` (was removed in PyO3 0.23.0). [#4921](https://github.com/PyO3/pyo3/pull/4921) ## [0.23.4] - 2025-01-10 ### Added - Add `PyList::locked_for_each`, which uses a critical section to lock the list on the free-threaded build. [#4789](https://github.com/PyO3/pyo3/pull/4789) - Add `pyo3_build_config::add_python_framework_link_args` build script API to set rpath when using macOS system Python. [#4833](https://github.com/PyO3/pyo3/pull/4833) ### Changed - Use `datetime.fold` to distinguish ambiguous datetimes when converting to and from `chrono::DateTime` (rather than erroring). [#4791](https://github.com/PyO3/pyo3/pull/4791) - Optimize PyList iteration on the free-threaded build. [#4789](https://github.com/PyO3/pyo3/pull/4789) ### Fixed - Fix unnecessary internal `py.allow_threads` GIL-switch when attempting to access contents of a `PyErr` which originated from Python (could lead to unintended deadlocks). [#4766](https://github.com/PyO3/pyo3/pull/4766) - Fix thread-unsafe access of dict internals in `BoundDictIterator` on the free-threaded build. [#4788](https://github.com/PyO3/pyo3/pull/4788) * Fix unnecessary critical sections in `BoundDictIterator` on the free-threaded build. [#4788](https://github.com/PyO3/pyo3/pull/4788) - Fix time-of-check to time-of-use issues with list iteration on the free-threaded build. [#4789](https://github.com/PyO3/pyo3/pull/4789) - Fix `chrono::DateTime` to-Python conversion when `Tz` is `chrono_tz::Tz`. [#4790](https://github.com/PyO3/pyo3/pull/4790) - Fix `#[pyclass]` not being able to be named `Probe`. [#4794](https://github.com/PyO3/pyo3/pull/4794) - Fix not treating cross-compilation from x64 to aarch64 on Windows as a cross-compile. [#4800](https://github.com/PyO3/pyo3/pull/4800) - Fix missing struct fields on GraalPy when subclassing builtin classes. [#4802](https://github.com/PyO3/pyo3/pull/4802) - Fix generating import lib for PyPy when `abi3` feature is enabled. [#4806](https://github.com/PyO3/pyo3/pull/4806) - Fix generating import lib for python3.13t when `abi3` feature is enabled. [#4808](https://github.com/PyO3/pyo3/pull/4808) - Fix compile failure for raw identifiers like `r#box` in `derive(FromPyObject)`. [#4814](https://github.com/PyO3/pyo3/pull/4814) - Fix compile failure for `#[pyclass]` enum variants with more than 12 fields. [#4832](https://github.com/PyO3/pyo3/pull/4832) ## [0.23.3] - 2024-12-03 ### Packaging - Bump optional `python3-dll-a` dependency to 0.2.11. [#4749](https://github.com/PyO3/pyo3/pull/4749) ### Fixed - Fix unresolved symbol link failures on Windows when compiling for Python 3.13t with `abi3` features enabled. [#4733](https://github.com/PyO3/pyo3/pull/4733) - Fix unresolved symbol link failures on Windows when compiling for Python 3.13t using the `generate-import-lib` feature. [#4749](https://github.com/PyO3/pyo3/pull/4749) - Fix compile-time regression in PyO3 0.23.0 where changing `PYO3_CONFIG_FILE` would not reconfigure PyO3 for the new interpreter. [#4758](https://github.com/PyO3/pyo3/pull/4758) ## [0.23.2] - 2024-11-25 ### Added - Add `IntoPyObjectExt` trait. [#4708](https://github.com/PyO3/pyo3/pull/4708) ### Fixed - Fix compile failures when building for free-threaded Python when the `abi3` or `abi3-pyxx` features are enabled. [#4719](https://github.com/PyO3/pyo3/pull/4719) - Fix `ambiguous_associated_items` lint error in `#[pyclass]` and `#[derive(IntoPyObject)]` macros. [#4725](https://github.com/PyO3/pyo3/pull/4725) ## [0.23.1] - 2024-11-16 Re-release of 0.23.0 with fixes to docs.rs build. ## [0.23.0] - 2024-11-15 ### Packaging - Drop support for PyPy 3.7 and 3.8. [#4582](https://github.com/PyO3/pyo3/pull/4582) - Extend range of supported versions of `hashbrown` optional dependency to include version 0.15. [#4604](https://github.com/PyO3/pyo3/pull/4604) - Bump minimum version of `eyre` optional dependency to 0.6.8. [#4617](https://github.com/PyO3/pyo3/pull/4617) - Bump minimum version of `hashbrown` optional dependency to 0.14.5. [#4617](https://github.com/PyO3/pyo3/pull/4617) - Bump minimum version of `indexmap` optional dependency to 2.5.0. [#4617](https://github.com/PyO3/pyo3/pull/4617) - Bump minimum version of `num-complex` optional dependency to 0.4.6. [#4617](https://github.com/PyO3/pyo3/pull/4617) - Bump minimum version of `chrono-tz` optional dependency to 0.10. [#4617](https://github.com/PyO3/pyo3/pull/4617) - Support free-threaded Python 3.13t. [#4588](https://github.com/PyO3/pyo3/pull/4588) ### Added - Add `IntoPyObject` (fallible) conversion trait to convert from Rust to Python values. [#4060](https://github.com/PyO3/pyo3/pull/4060) - Add `#[pyclass(str="")]` option to generate `__str__` based on a `Display` implementation or format string. [#4233](https://github.com/PyO3/pyo3/pull/4233) - Implement `PartialEq` for `Bound<'py, PyInt>` with `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128` and `isize`. [#4317](https://github.com/PyO3/pyo3/pull/4317) - Implement `PartialEq` and `PartialEq` for `Bound<'py, PyFloat>`. [#4348](https://github.com/PyO3/pyo3/pull/4348) - Add `as_super` and `into_super` methods for `Bound`. [#4351](https://github.com/PyO3/pyo3/pull/4351) - Add FFI definitions `PyCFunctionFast` and `PyCFunctionFastWithKeywords` [#4415](https://github.com/PyO3/pyo3/pull/4415) - Add FFI definitions for `PyMutex` on Python 3.13 and newer. [#4421](https://github.com/PyO3/pyo3/pull/4421) - Add `PyDict::locked_for_each` to iterate efficiently on freethreaded Python. [#4439](https://github.com/PyO3/pyo3/pull/4439) - Add FFI definitions `PyObject_GetOptionalAttr`, `PyObject_GetOptionalAttrString`, `PyObject_HasAttrWithError`, `PyObject_HasAttrStringWithError`, `Py_CONSTANT_*` constants, `Py_GetConstant`, `Py_GetConstantBorrowed`, and `PyType_GetModuleByDef` on Python 3.13 and newer. [#4447](https://github.com/PyO3/pyo3/pull/4447) - Add FFI definitions for the Python critical section API available on Python 3.13 and newer. [#4477](https://github.com/PyO3/pyo3/pull/4477) - Add derive macro for `IntoPyObject`. [#4495](https://github.com/PyO3/pyo3/pull/4495) - Add `Borrowed::as_ptr`. [#4520](https://github.com/PyO3/pyo3/pull/4520) - Add FFI definition for `PyImport_AddModuleRef`. [#4529](https://github.com/PyO3/pyo3/pull/4529) - Add `PyAnyMethods::try_iter`. [#4553](https://github.com/PyO3/pyo3/pull/4553) - Add `pyo3::sync::with_critical_section`, a wrapper around the Python Critical Section API added in Python 3.13. [#4587](https://github.com/PyO3/pyo3/pull/4587) - Add `#[pymodule(gil_used = false)]` option to declare that a module supports the free-threaded build. [#4588](https://github.com/PyO3/pyo3/pull/4588) - Add `PyModule::gil_used` method to declare that a module supports the free-threaded build. [#4588](https://github.com/PyO3/pyo3/pull/4588) - Add FFI definition `PyDateTime_CAPSULE_NAME`. [#4634](https://github.com/PyO3/pyo3/pull/4634) - Add `PyMappingProxy` type to represent the `mappingproxy` Python class. [#4644](https://github.com/PyO3/pyo3/pull/4644) - Add FFI definitions `PyList_Extend` and `PyList_Clear`. [#4667](https://github.com/PyO3/pyo3/pull/4667) - Add derive macro for `IntoPyObjectRef`. [#4674](https://github.com/PyO3/pyo3/pull/4674) - Add `pyo3::sync::OnceExt` and `pyo3::sync::OnceLockExt` traits. [#4676](https://github.com/PyO3/pyo3/pull/4676) ### Changed - Prefer `IntoPyObject` over `IntoPy>>` for `#[pyfunction]` and `#[pymethods]` return types. [#4060](https://github.com/PyO3/pyo3/pull/4060) - Report multiple errors from `#[pyclass]` and `#[pyo3(..)]` attributes. [#4243](https://github.com/PyO3/pyo3/pull/4243) - Nested declarative `#[pymodule]` are automatically treated as submodules (no `PyInit_` entrypoint is created). [#4308](https://github.com/PyO3/pyo3/pull/4308) - Deprecate `PyAnyMethods::is_ellipsis` (`Py::is_ellipsis` was deprecated in PyO3 0.20). [#4322](https://github.com/PyO3/pyo3/pull/4322) - Deprecate `PyLong` in favor of `PyInt`. [#4347](https://github.com/PyO3/pyo3/pull/4347) - Rename `IntoPyDict::into_py_dict_bound` to `IntoPyDict::into_py_dict`. [#4388](https://github.com/PyO3/pyo3/pull/4388) - `PyModule::from_code` now expects `&CStr` as arguments instead of `&str`. [#4404](https://github.com/PyO3/pyo3/pull/4404) - Use "fastcall" Python calling convention for `#[pyfunction]`s when compiling on abi3 for Python 3.10 and up. [#4415](https://github.com/PyO3/pyo3/pull/4415) - Remove `Copy` and `Clone` from `PyObject` struct FFI definition. [#4434](https://github.com/PyO3/pyo3/pull/4434) - `Python::eval` and `Python::run` now take a `&CStr` instead of `&str`. [#4435](https://github.com/PyO3/pyo3/pull/4435) - Deprecate `IPowModulo`, `PyClassAttributeDef`, `PyGetterDef`, `PyMethodDef`, `PyMethodDefType`, and `PySetterDef` from PyO3's public API. [#4441](https://github.com/PyO3/pyo3/pull/4441) - `IntoPyObject` impls for `Vec`, `&[u8]`, `[u8; N]`, `Cow<[u8]>` and `SmallVec<[u8; N]>` now convert into Python `bytes` rather than a `list` of integers. [#4442](https://github.com/PyO3/pyo3/pull/4442) - Emit a compile-time error when attempting to subclass a class that doesn't allow subclassing. [#4453](https://github.com/PyO3/pyo3/pull/4453) - `IntoPyDict::into_py_dict` is now fallible due to `IntoPyObject` migration. [#4493](https://github.com/PyO3/pyo3/pull/4493) - The `abi3` feature will now override config files provided via `PYO3_BUILD_CONFIG`. [#4497](https://github.com/PyO3/pyo3/pull/4497) - Disable the `GILProtected` struct on free-threaded Python. [#4504](https://github.com/PyO3/pyo3/pull/4504) - Updated FFI definitions for functions and struct fields that have been deprecated or removed from CPython. [#4534](https://github.com/PyO3/pyo3/pull/4534) - Disable `PyListMethods::get_item_unchecked` on free-threaded Python. [#4539](https://github.com/PyO3/pyo3/pull/4539) - Add `GILOnceCell::import`. [#4542](https://github.com/PyO3/pyo3/pull/4542) - Deprecate `PyAnyMethods::iter` in favour of `PyAnyMethods::try_iter`. [#4553](https://github.com/PyO3/pyo3/pull/4553) - The `#[pyclass]` macro now requires a types to be `Sync`. (Except for `#[pyclass(unsendable)]` types). [#4566](https://github.com/PyO3/pyo3/pull/4566) - `PyList::new` and `PyTuple::new` are now fallible due to `IntoPyObject` migration. [#4580](https://github.com/PyO3/pyo3/pull/4580) - `PyErr::matches` is now fallible due to `IntoPyObject` migration. [#4595](https://github.com/PyO3/pyo3/pull/4595) - Deprecate `ToPyObject` in favour of `IntoPyObject` [#4595](https://github.com/PyO3/pyo3/pull/4595) - Deprecate `PyWeakrefMethods::get_option`. [#4597](https://github.com/PyO3/pyo3/pull/4597) - Seal `PyWeakrefMethods` trait. [#4598](https://github.com/PyO3/pyo3/pull/4598) - Remove `PyNativeTypeInitializer` and `PyObjectInit` from the PyO3 public API. [#4611](https://github.com/PyO3/pyo3/pull/4611) - Deprecate `IntoPy` in favor of `IntoPyObject` [#4618](https://github.com/PyO3/pyo3/pull/4618) - Eagerly normalize exceptions in `PyErr::take()` and `PyErr::fetch()` on Python 3.11 and older. [#4655](https://github.com/PyO3/pyo3/pull/4655) - Move `IntoPy::type_output` to `IntoPyObject::type_output`. [#4657](https://github.com/PyO3/pyo3/pull/4657) - Change return type of `PyMapping::keys`, `PyMapping::values` and `PyMapping::items` to `Bound<'py, PyList>` instead of `Bound<'py, PySequence>`. [#4661](https://github.com/PyO3/pyo3/pull/4661) - Complex enums now allow field types that either implement `IntoPyObject` by reference or by value together with `Clone`. This makes `Py` available as field type. [#4694](https://github.com/PyO3/pyo3/pull/4694) ### Removed - Remove all functionality deprecated in PyO3 0.20. [#4322](https://github.com/PyO3/pyo3/pull/4322) - Remove all functionality deprecated in PyO3 0.21. [#4323](https://github.com/PyO3/pyo3/pull/4323) - Deprecate `PyUnicode` in favour of `PyString`. [#4370](https://github.com/PyO3/pyo3/pull/4370) - Remove deprecated `gil-refs` feature. [#4378](https://github.com/PyO3/pyo3/pull/4378) - Remove private FFI definitions `_Py_IMMORTAL_REFCNT`, `_Py_IsImmortal`, `_Py_TPFLAGS_STATIC_BUILTIN`, `_Py_Dealloc`, `_Py_IncRef`, `_Py_DecRef`. [#4447](https://github.com/PyO3/pyo3/pull/4447) - Remove private FFI definitions `_Py_c_sum`, `_Py_c_diff`, `_Py_c_neg`, `_Py_c_prod`, `_Py_c_quot`, `_Py_c_pow`, `_Py_c_abs`. [#4521](https://github.com/PyO3/pyo3/pull/4521) - Remove `_borrowed` methods of `PyWeakRef` and `PyWeakRefProxy`. [#4528](https://github.com/PyO3/pyo3/pull/4528) - Removed private FFI definition `_PyErr_ChainExceptions`. [#4534](https://github.com/PyO3/pyo3/pull/4534) ### Fixed - Fix invalid library search path `lib_dir` when cross-compiling. [#4389](https://github.com/PyO3/pyo3/pull/4389) - Fix FFI definition `Py_Is` for PyPy on 3.10 to call the function defined by PyPy. [#4447](https://github.com/PyO3/pyo3/pull/4447) - Fix compile failure when using `#[cfg]` attributes for simple enum variants. [#4509](https://github.com/PyO3/pyo3/pull/4509) - Fix compiler warning for `non_snake_case` method names inside `#[pymethods]` generated code. [#4567](https://github.com/PyO3/pyo3/pull/4567) - Fix compile error with `#[derive(FromPyObject)]` generic struct with trait bounds. [#4645](https://github.com/PyO3/pyo3/pull/4645) - Fix compile error for `#[classmethod]` and `#[staticmethod]` on magic methods. [#4654](https://github.com/PyO3/pyo3/pull/4654) - Fix compile warning for `unsafe_op_in_unsafe_fn` in generated macro code. [#4674](https://github.com/PyO3/pyo3/pull/4674) - Fix incorrect deprecation warning for `#[pyclass] enum`s with custom `__eq__` implementation. [#4692](https://github.com/PyO3/pyo3/pull/4692) - Fix `non_upper_case_globals` lint firing for generated `__match_args__` on complex enums. [#4705](https://github.com/PyO3/pyo3/pull/4705) ## [0.22.5] - 2024-10-15 ### Fixed - Fix regression in 0.22.4 of naming collision in `__clear__` slot and `clear` method generated code. [#4619](https://github.com/PyO3/pyo3/pull/4619) ## [0.22.4] - 2024-10-12 ### Added - Add FFI definition `PyWeakref_GetRef` and `compat::PyWeakref_GetRef`. [#4528](https://github.com/PyO3/pyo3/pull/4528) ### Changed - Deprecate `_borrowed` methods on `PyWeakRef` and `PyWeakrefProxy` (just use the owning forms). [#4590](https://github.com/PyO3/pyo3/pull/4590) ### Fixed - Revert removal of private FFI function `_PyLong_NumBits` on Python 3.13 and later. [#4450](https://github.com/PyO3/pyo3/pull/4450) - Fix `__traverse__` functions for base classes not being called by subclasses created with `#[pyclass(extends = ...)]`. [#4563](https://github.com/PyO3/pyo3/pull/4563) - Fix regression in 0.22.3 failing compiles under `#![forbid(unsafe_code)]`. [#4574](https://github.com/PyO3/pyo3/pull/4574) - Fix `create_exception` macro triggering lint and compile errors due to interaction with `gil-refs` feature. [#4589](https://github.com/PyO3/pyo3/pull/4589) - Workaround possible use-after-free in `_borrowed` methods on `PyWeakRef` and `PyWeakrefProxy` by leaking their contents. [#4590](https://github.com/PyO3/pyo3/pull/4590) - Fix crash calling `PyType_GetSlot` on static types before Python 3.10. [#4599](https://github.com/PyO3/pyo3/pull/4599) ## [0.22.3] - 2024-09-15 ### Added - Add `pyo3::ffi::compat` namespace with compatibility shims for C API functions added in recent versions of Python. - Add FFI definition `PyDict_GetItemRef` on Python 3.13 and newer, and `compat::PyDict_GetItemRef` for all versions. [#4355](https://github.com/PyO3/pyo3/pull/4355) - Add FFI definition `PyList_GetItemRef` on Python 3.13 and newer, and `pyo3_ffi::compat::PyList_GetItemRef` for all versions. [#4410](https://github.com/PyO3/pyo3/pull/4410) - Add FFI definitions `compat::Py_NewRef` and `compat::Py_XNewRef`. [#4445](https://github.com/PyO3/pyo3/pull/4445) - Add FFI definitions `compat::PyObject_CallNoArgs` and `compat::PyObject_CallMethodNoArgs`. [#4461](https://github.com/PyO3/pyo3/pull/4461) - Add `GilOnceCell>::clone_ref`. [#4511](https://github.com/PyO3/pyo3/pull/4511) ### Changed - Improve error messages for `#[pyfunction]` defined inside `#[pymethods]`. [#4349](https://github.com/PyO3/pyo3/pull/4349) - Improve performance of calls to Python by using the vectorcall calling convention where possible. [#4456](https://github.com/PyO3/pyo3/pull/4456) - Mention the type name in the exception message when trying to instantiate a class with no constructor defined. [#4481](https://github.com/PyO3/pyo3/pull/4481) ### Removed - Remove private FFI definition `_Py_PackageContext`. [#4420](https://github.com/PyO3/pyo3/pull/4420) ### Fixed - Fix compile failure in declarative `#[pymodule]` under presence of `#![no_implicit_prelude]`. [#4328](https://github.com/PyO3/pyo3/pull/4328) - Fix use of borrowed reference in `PyDict::get_item` (unsafe in free-threaded Python). [#4355](https://github.com/PyO3/pyo3/pull/4355) - Fix `#[pyclass(eq)]` macro hygiene issues for structs and enums. [#4359](https://github.com/PyO3/pyo3/pull/4359) - Fix hygiene/span issues of `#[pyfunction]` and `#[pymethods]` generated code which affected expansion in `macro_rules` context. [#4382](https://github.com/PyO3/pyo3/pull/4382) - Fix `unsafe_code` lint error in `#[pyclass]` generated code. [#4396](https://github.com/PyO3/pyo3/pull/4396) - Fix async functions returning a tuple only returning the first element to Python. [#4407](https://github.com/PyO3/pyo3/pull/4407) - Fix use of borrowed reference in `PyList::get_item` (unsafe in free-threaded Python). [#4410](https://github.com/PyO3/pyo3/pull/4410) - Correct FFI definition `PyArg_ParseTupleAndKeywords` to take `*const *const c_char` instead of `*mut *mut c_char` on Python 3.13 and up. [#4420](https://github.com/PyO3/pyo3/pull/4420) - Fix a soundness bug with `PyClassInitializer`: panic if adding subclass to existing instance via `PyClassInitializer::from(Py).add_subclass(SubClass)`. [#4454](https://github.com/PyO3/pyo3/pull/4454) - Fix illegal reference counting op inside implementation of `__traverse__` handlers. [#4479](https://github.com/PyO3/pyo3/pull/4479) ## [0.22.2] - 2024-07-17 ### Packaging - Require opt-in to freethreaded Python using the `UNSAFE_PYO3_BUILD_FREE_THREADED=1` environment variable (it is not yet supported by PyO3). [#4327](https://github.com/PyO3/pyo3/pull/4327) ### Changed - Use FFI function calls for reference counting on all abi3 versions. [#4324](https://github.com/PyO3/pyo3/pull/4324) - `#[pymodule(...)]` now directly accepts all relevant `#[pyo3(...)]` options. [#4330](https://github.com/PyO3/pyo3/pull/4330) ### Fixed - Fix compile failure in declarative `#[pymodule]` under presence of `#![no_implicit_prelude]`. [#4328](https://github.com/PyO3/pyo3/pull/4328) - Fix compile failure due to c-string literals on Rust < 1.79. [#4353](https://github.com/PyO3/pyo3/pull/4353) ## [0.22.1] - 2024-07-06 ### Added - Add `#[pyo3(submodule)]` option for declarative `#[pymodule]`s. [#4301](https://github.com/PyO3/pyo3/pull/4301) - Implement `PartialEq` for `Bound<'py, PyBool>`. [#4305](https://github.com/PyO3/pyo3/pull/4305) ### Fixed - Return `NotImplemented` instead of raising `TypeError` from generated equality method when comparing different types. [#4287](https://github.com/PyO3/pyo3/pull/4287) - Handle full-path `#[pyo3::prelude::pymodule]` and similar for `#[pyclass]` and `#[pyfunction]` in declarative modules. [#4288](https://github.com/PyO3/pyo3/pull/4288) - Fix 128-bit int regression on big-endian platforms with Python <3.13. [#4291](https://github.com/PyO3/pyo3/pull/4291) - Stop generating code that will never be covered with declarative modules. [#4297](https://github.com/PyO3/pyo3/pull/4297) - Fix invalid deprecation warning for trailing optional on `#[setter]` function. [#4304](https://github.com/PyO3/pyo3/pull/4304) ## [0.22.0] - 2024-06-24 ### Packaging - Update `heck` dependency to 0.5. [#3966](https://github.com/PyO3/pyo3/pull/3966) - Extend range of supported versions of `chrono-tz` optional dependency to include version 0.10. [#4061](https://github.com/PyO3/pyo3/pull/4061) - Update MSRV to 1.63. [#4129](https://github.com/PyO3/pyo3/pull/4129) - Add optional `num-rational` feature to add conversions with Python's `fractions.Fraction`. [#4148](https://github.com/PyO3/pyo3/pull/4148) - Support Python 3.13. [#4184](https://github.com/PyO3/pyo3/pull/4184) ### Added - Add `PyWeakref`, `PyWeakrefReference` and `PyWeakrefProxy`. [#3835](https://github.com/PyO3/pyo3/pull/3835) - Support `#[pyclass]` on enums that have tuple variants. [#4072](https://github.com/PyO3/pyo3/pull/4072) - Add support for scientific notation in `Decimal` conversion. [#4079](https://github.com/PyO3/pyo3/pull/4079) - Add `pyo3_disable_reference_pool` conditional compilation flag to avoid the overhead of the global reference pool at the cost of known limitations as explained in the performance section of the guide. [#4095](https://github.com/PyO3/pyo3/pull/4095) - Add `#[pyo3(constructor = (...))]` to customize the generated constructors for complex enum variants. [#4158](https://github.com/PyO3/pyo3/pull/4158) - Add `PyType::module`, which always matches Python `__module__`. [#4196](https://github.com/PyO3/pyo3/pull/4196) - Add `PyType::fully_qualified_name` which matches the "fully qualified name" defined in [PEP 737](https://peps.python.org/pep-0737). [#4196](https://github.com/PyO3/pyo3/pull/4196) - Add `PyTypeMethods::mro` and `PyTypeMethods::bases`. [#4197](https://github.com/PyO3/pyo3/pull/4197) - Add `#[pyclass(ord)]` to implement ordering based on `PartialOrd`. [#4202](https://github.com/PyO3/pyo3/pull/4202) - Implement `ToPyObject` and `IntoPy` for `PyBackedStr` and `PyBackedBytes`. [#4205](https://github.com/PyO3/pyo3/pull/4205) - Add `#[pyclass(hash)]` option to implement `__hash__` in terms of the `Hash` implementation [#4206](https://github.com/PyO3/pyo3/pull/4206) - Add `#[pyclass(eq)]` option to generate `__eq__` based on `PartialEq`, and `#[pyclass(eq_int)]` for simple enums to implement equality based on their discriminants. [#4210](https://github.com/PyO3/pyo3/pull/4210) - Implement `From>` for `PyClassInitializer`. [#4214](https://github.com/PyO3/pyo3/pull/4214) - Add `as_super` methods to `PyRef` and `PyRefMut` for accessing the base class by reference. [#4219](https://github.com/PyO3/pyo3/pull/4219) - Implement `PartialEq` for `Bound<'py, PyString>`. [#4245](https://github.com/PyO3/pyo3/pull/4245) - Implement `PyModuleMethods::filename` on PyPy. [#4249](https://github.com/PyO3/pyo3/pull/4249) - Implement `PartialEq<[u8]>` for `Bound<'py, PyBytes>`. [#4250](https://github.com/PyO3/pyo3/pull/4250) - Add `pyo3_ffi::c_str` macro to create `&'static CStr` on Rust versions which don't have 1.77's `c""` literals. [#4255](https://github.com/PyO3/pyo3/pull/4255) - Support `bool` conversion with `numpy` 2.0's `numpy.bool` type [#4258](https://github.com/PyO3/pyo3/pull/4258) - Add `PyAnyMethods::{bitnot, matmul, floor_div, rem, divmod}`. [#4264](https://github.com/PyO3/pyo3/pull/4264) ### Changed - Change the type of `PySliceIndices::slicelength` and the `length` parameter of `PySlice::indices()`. [#3761](https://github.com/PyO3/pyo3/pull/3761) - Deprecate implicit default for trailing optional arguments [#4078](https://github.com/PyO3/pyo3/pull/4078) - `Clone`ing pointers into the Python heap has been moved behind the `py-clone` feature, as it must panic without the GIL being held as a soundness fix. [#4095](https://github.com/PyO3/pyo3/pull/4095) - Add `#[track_caller]` to all `Py`, `Bound<'py, T>` and `Borrowed<'a, 'py, T>` methods which can panic. [#4098](https://github.com/PyO3/pyo3/pull/4098) - Change `PyAnyMethods::dir` to be fallible and return `PyResult>` (and similar for `PyAny::dir`). [#4100](https://github.com/PyO3/pyo3/pull/4100) - The global reference pool (to track pending reference count decrements) is now initialized lazily to avoid the overhead of taking a mutex upon function entry when the functionality is not actually used. [#4178](https://github.com/PyO3/pyo3/pull/4178) - Emit error messages when using `weakref` or `dict` when compiling for `abi3` for Python older than 3.9. [#4194](https://github.com/PyO3/pyo3/pull/4194) - Change `PyType::name` to always match Python `__name__`. [#4196](https://github.com/PyO3/pyo3/pull/4196) - Remove CPython internal ffi call for complex number including: add, sub, mul, div, neg, abs, pow. Added PyAnyMethods::{abs, pos, neg} [#4201](https://github.com/PyO3/pyo3/pull/4201) - Deprecate implicit integer comparison for simple enums in favor of `#[pyclass(eq_int)]`. [#4210](https://github.com/PyO3/pyo3/pull/4210) - Set the `module=` attribute of declarative modules' child `#[pymodule]`s and `#[pyclass]`es. [#4213](https://github.com/PyO3/pyo3/pull/4213) - Set the `module` option for complex enum variants from the value set on the complex enum `module`. [#4228](https://github.com/PyO3/pyo3/pull/4228) - Respect the Python "limited API" when building for the `abi3` feature on PyPy or GraalPy. [#4237](https://github.com/PyO3/pyo3/pull/4237) - Optimize code generated by `#[pyo3(get)]` on `#[pyclass]` fields. [#4254](https://github.com/PyO3/pyo3/pull/4254) - `PyCFunction::new`, `PyCFunction::new_with_keywords` and `PyCFunction::new_closure` now take `&'static CStr` name and doc arguments (previously was `&'static str`). [#4255](https://github.com/PyO3/pyo3/pull/4255) - The `experimental-declarative-modules` feature is now stabilized and available by default. [#4257](https://github.com/PyO3/pyo3/pull/4257) ### Fixed - Fix panic when `PYO3_CROSS_LIB_DIR` is set to a missing path. [#4043](https://github.com/PyO3/pyo3/pull/4043) - Fix a compile error when exporting an exception created with `create_exception!` living in a different Rust module using the `declarative-module` feature. [#4086](https://github.com/PyO3/pyo3/pull/4086) - Fix FFI definitions of `PY_VECTORCALL_ARGUMENTS_OFFSET` and `PyVectorcall_NARGS` to fix a false-positive assertion. [#4104](https://github.com/PyO3/pyo3/pull/4104) - Disable `PyUnicode_DATA` on PyPy: not exposed by PyPy. [#4116](https://github.com/PyO3/pyo3/pull/4116) - Correctly handle `#[pyo3(from_py_with = ...)]` attribute on dunder (`__magic__`) method arguments instead of silently ignoring it. [#4117](https://github.com/PyO3/pyo3/pull/4117) - Fix a compile error when declaring a standalone function or class method with a Python name that is a Rust keyword. [#4226](https://github.com/PyO3/pyo3/pull/4226) - Fix declarative modules discarding doc comments on the `mod` node. [#4236](https://github.com/PyO3/pyo3/pull/4236) - Fix `__dict__` attribute missing for `#[pyclass(dict)]` instances when building for `abi3` on Python 3.9. [#4251](https://github.com/PyO3/pyo3/pull/4251) ## [0.21.2] - 2024-04-16 ### Changed - Deprecate the `PySet::empty()` gil-ref constructor. [#4082](https://github.com/PyO3/pyo3/pull/4082) ### Fixed - Fix compile error for `async fn` in `#[pymethods]` with a `&self` receiver and more than one additional argument. [#4035](https://github.com/PyO3/pyo3/pull/4035) - Improve error message for wrong receiver type in `__traverse__`. [#4045](https://github.com/PyO3/pyo3/pull/4045) - Fix compile error when exporting a `#[pyclass]` living in a different Rust module using the `experimental-declarative-modules` feature. [#4054](https://github.com/PyO3/pyo3/pull/4054) - Fix `missing_docs` lint triggering on documented `#[pymodule]` functions. [#4067](https://github.com/PyO3/pyo3/pull/4067) - Fix undefined symbol errors for extension modules on AIX (by linking `libpython`). [#4073](https://github.com/PyO3/pyo3/pull/4073) ## [0.21.1] - 2024-04-01 ### Added - Implement `Send` and `Sync` for `PyBackedStr` and `PyBackedBytes`. [#4007](https://github.com/PyO3/pyo3/pull/4007) - Implement `Clone`, `Debug`, `PartialEq`, `Eq`, `PartialOrd`, `Ord` and `Hash` implementation for `PyBackedBytes` and `PyBackedStr`, and `Display` for `PyBackedStr`. [#4020](https://github.com/PyO3/pyo3/pull/4020) - Add `import_exception_bound!` macro to import exception types without generating GIL Ref functionality for them. [#4027](https://github.com/PyO3/pyo3/pull/4027) ### Changed - Emit deprecation warning for uses of GIL Refs as `#[setter]` function arguments. [#3998](https://github.com/PyO3/pyo3/pull/3998) - Add `#[inline]` hints on many `Bound` and `Borrowed` methods. [#4024](https://github.com/PyO3/pyo3/pull/4024) ### Fixed - Handle `#[pyo3(from_py_with = "")]` in `#[setter]` methods [#3995](https://github.com/PyO3/pyo3/pull/3995) - Allow extraction of `&Bound` in `#[setter]` methods. [#3998](https://github.com/PyO3/pyo3/pull/3998) - Fix some uncovered code blocks emitted by `#[pymodule]`, `#[pyfunction]` and `#[pyclass]` macros. [#4009](https://github.com/PyO3/pyo3/pull/4009) - Fix typo in the panic message when a class referenced in `pyo3::import_exception!` does not exist. [#4012](https://github.com/PyO3/pyo3/pull/4012) - Fix compile error when using an async `#[pymethod]` with a receiver and additional arguments. [#4015](https://github.com/PyO3/pyo3/pull/4015) ## [0.21.0] - 2024-03-25 ### Added - Add support for GraalPy (24.0 and up). [#3247](https://github.com/PyO3/pyo3/pull/3247) - Add `PyMemoryView` type. [#3514](https://github.com/PyO3/pyo3/pull/3514) - Allow `async fn` in for `#[pyfunction]` and `#[pymethods]`, with the `experimental-async` feature. [#3540](https://github.com/PyO3/pyo3/pull/3540) [#3588](https://github.com/PyO3/pyo3/pull/3588) [#3599](https://github.com/PyO3/pyo3/pull/3599) [#3931](https://github.com/PyO3/pyo3/pull/3931) - Implement `PyTypeInfo` for `PyEllipsis`, `PyNone` and `PyNotImplemented`. [#3577](https://github.com/PyO3/pyo3/pull/3577) - Support `#[pyclass]` on enums that have non-unit variants. [#3582](https://github.com/PyO3/pyo3/pull/3582) - Support `chrono` feature with `abi3` feature. [#3664](https://github.com/PyO3/pyo3/pull/3664) - `FromPyObject`, `IntoPy` and `ToPyObject` are implemented on `std::duration::Duration` [#3670](https://github.com/PyO3/pyo3/pull/3670) - Add `PyString::to_cow`. Add `Py::to_str`, `Py::to_cow`, and `Py::to_string_lossy`, as ways to access Python string data safely beyond the GIL lifetime. [#3677](https://github.com/PyO3/pyo3/pull/3677) - Add `Bound` and `Borrowed` smart pointers as a new API for accessing Python objects. [#3686](https://github.com/PyO3/pyo3/pull/3686) - Add `PyNativeType::as_borrowed` to convert "GIL refs" to the new `Bound` smart pointer. [#3692](https://github.com/PyO3/pyo3/pull/3692) - Add `FromPyObject::extract_bound` method, to migrate `FromPyObject` implementations to the Bound API. [#3706](https://github.com/PyO3/pyo3/pull/3706) - Add `gil-refs` feature to allow continued use of the deprecated GIL Refs APIs. [#3707](https://github.com/PyO3/pyo3/pull/3707) - Add methods to `PyAnyMethods` for binary operators (`add`, `sub`, etc.) [#3712](https://github.com/PyO3/pyo3/pull/3712) - Add `chrono-tz` feature allowing conversion between `chrono_tz::Tz` and `zoneinfo.ZoneInfo` [#3730](https://github.com/PyO3/pyo3/pull/3730) - Add FFI definition `PyType_GetModuleByDef`. [#3734](https://github.com/PyO3/pyo3/pull/3734) - Conversion between `std::time::SystemTime` and `datetime.datetime` [#3736](https://github.com/PyO3/pyo3/pull/3736) - Add `Py::as_any` and `Py::into_any`. [#3785](https://github.com/PyO3/pyo3/pull/3785) - Add `PyStringMethods::encode_utf8`. [#3801](https://github.com/PyO3/pyo3/pull/3801) - Add `PyBackedStr` and `PyBackedBytes`, as alternatives to `&str` and `&bytes` where a Python object owns the data. [#3802](https://github.com/PyO3/pyo3/pull/3802) [#3991](https://github.com/PyO3/pyo3/pull/3991) - Allow `#[pymodule]` macro on Rust `mod` blocks, with the `experimental-declarative-modules` feature. [#3815](https://github.com/PyO3/pyo3/pull/3815) - Implement `ExactSizeIterator` for `set` and `frozenset` iterators on `abi3` feature. [#3849](https://github.com/PyO3/pyo3/pull/3849) - Add `Py::drop_ref` to explicitly drop a `Py`` and immediately decrease the Python reference count if the GIL is already held. [#3871](https://github.com/PyO3/pyo3/pull/3871) - Allow `#[pymodule]` macro on single argument functions that take `&Bound<'_, PyModule>`. [#3905](https://github.com/PyO3/pyo3/pull/3905) - Implement `FromPyObject` for `Cow`. [#3928](https://github.com/PyO3/pyo3/pull/3928) - Implement `Default` for `GILOnceCell`. [#3971](https://github.com/PyO3/pyo3/pull/3971) - Add `PyDictMethods::into_mapping`, `PyListMethods::into_sequence` and `PyTupleMethods::into_sequence`. [#3982](https://github.com/PyO3/pyo3/pull/3982) ### Changed - `PyDict::from_sequence` now takes a single argument of type `&PyAny` (previously took two arguments `Python` and `PyObject`). [#3532](https://github.com/PyO3/pyo3/pull/3532) - Deprecate `Py::is_ellipsis` and `PyAny::is_ellipsis` in favour of `any.is(py.Ellipsis())`. [#3577](https://github.com/PyO3/pyo3/pull/3577) - Split some `PyTypeInfo` functionality into new traits `HasPyGilRef` and `PyTypeCheck`. [#3600](https://github.com/PyO3/pyo3/pull/3600) - Deprecate `PyTryFrom` and `PyTryInto` traits in favor of `any.downcast()` via the `PyTypeCheck` and `PyTypeInfo` traits. [#3601](https://github.com/PyO3/pyo3/pull/3601) - Allow async methods to accept `&self`/`&mut self` [#3609](https://github.com/PyO3/pyo3/pull/3609) - `FromPyObject` for set types now also accept `frozenset` objects as input. [#3632](https://github.com/PyO3/pyo3/pull/3632) - `FromPyObject` for `bool` now also accepts NumPy's `bool_` as input. [#3638](https://github.com/PyO3/pyo3/pull/3638) - Add `AsRefSource` associated type to `PyNativeType`. [#3653](https://github.com/PyO3/pyo3/pull/3653) - Rename `.is_true` to `.is_truthy` on `PyAny` and `Py` to clarify that the test is not based on identity with or equality to the True singleton. [#3657](https://github.com/PyO3/pyo3/pull/3657) - `PyType::name` is now `PyType::qualname` whereas `PyType::name` efficiently accesses the full name which includes the module name. [#3660](https://github.com/PyO3/pyo3/pull/3660) - The `Iter(A)NextOutput` types are now deprecated and `__(a)next__` can directly return anything which can be converted into Python objects, i.e. awaitables do not need to be wrapped into `IterANextOutput` or `Option` any more. `Option` can still be used as well and returning `None` will trigger the fast path for `__next__`, stopping iteration without having to raise a `StopIteration` exception. [#3661](https://github.com/PyO3/pyo3/pull/3661) - Implement `FromPyObject` on `chrono::DateTime` for all `Tz`, not just `FixedOffset` and `Utc`. [#3663](https://github.com/PyO3/pyo3/pull/3663) - Add lifetime parameter to `PyTzInfoAccess` trait. For the deprecated gil-ref API, the trait is now implemented for `&'py PyTime` and `&'py PyDateTime` instead of `PyTime` and `PyDate`. [#3679](https://github.com/PyO3/pyo3/pull/3679) - Calls to `__traverse__` become no-ops for unsendable pyclasses if on the wrong thread, thereby avoiding hard aborts at the cost of potential leakage. [#3689](https://github.com/PyO3/pyo3/pull/3689) - Include `PyNativeType` in `pyo3::prelude`. [#3692](https://github.com/PyO3/pyo3/pull/3692) - Improve performance of `extract::` (and other integer types) by avoiding call to `__index__()` converting the value to an integer for 3.10+. Gives performance improvement of around 30% for successful extraction. [#3742](https://github.com/PyO3/pyo3/pull/3742) - Relax bound of `FromPyObject` for `Py` to just `T: PyTypeCheck`. [#3776](https://github.com/PyO3/pyo3/pull/3776) - `PySet` and `PyFrozenSet` iterators now always iterate the equivalent of `iter(set)`. (A "fast path" with no noticeable performance benefit was removed.) [#3849](https://github.com/PyO3/pyo3/pull/3849) - Move implementations of `FromPyObject` for `&str`, `Cow`, `&[u8]` and `Cow<[u8]>` onto a temporary trait `FromPyObjectBound` when `gil-refs` feature is deactivated. [#3928](https://github.com/PyO3/pyo3/pull/3928) - Deprecate `GILPool`, `Python::with_pool`, and `Python::new_pool`. [#3947](https://github.com/PyO3/pyo3/pull/3947) ### Removed - Remove all functionality deprecated in PyO3 0.19. [#3603](https://github.com/PyO3/pyo3/pull/3603) ### Fixed - Match PyPy 7.3.14 in removing PyPy-only symbol `Py_MAX_NDIMS` in favour of `PyBUF_MAX_NDIM`. [#3757](https://github.com/PyO3/pyo3/pull/3757) - Fix segmentation fault using `datetime` types when an invalid `datetime` module is on sys.path. [#3818](https://github.com/PyO3/pyo3/pull/3818) - Fix `non_local_definitions` lint warning triggered by many PyO3 macros. [#3901](https://github.com/PyO3/pyo3/pull/3901) - Disable `PyCode` and `PyCode_Type` on PyPy: `PyCode_Type` is not exposed by PyPy. [#3934](https://github.com/PyO3/pyo3/pull/3934) ## [0.21.0-beta.0] - 2024-03-10 Prerelease of PyO3 0.21. See [the GitHub diff](https://github.com/pyo3/pyo3/compare/v0.21.0-beta.0...v0.21.0) for what changed between 0.21.0-beta.0 and the final release. ## [0.20.3] - 2024-02-23 ### Packaging - Add `portable-atomic` dependency. [#3619](https://github.com/PyO3/pyo3/pull/3619) - Check maximum version of Python at build time and for versions not yet supported require opt-in to the `abi3` stable ABI by the environment variable `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`. [#3821](https://github.com/PyO3/pyo3/pull/3821) ### Fixed - Use `portable-atomic` to support platforms without 64-bit atomics. [#3619](https://github.com/PyO3/pyo3/pull/3619) - Fix compilation failure with `either` feature enabled without `experimental-inspect` enabled. [#3834](https://github.com/PyO3/pyo3/pull/3834) ## [0.20.2] - 2024-01-04 ### Packaging - Pin `pyo3` and `pyo3-ffi` dependencies on `pyo3-build-config` to require the same patch version, i.e. `pyo3` 0.20.2 requires _exactly_ `pyo3-build-config` 0.20.2. [#3721](https://github.com/PyO3/pyo3/pull/3721) ### Fixed - Fix compile failure when building `pyo3` 0.20.0 with latest `pyo3-build-config` 0.20.X. [#3724](https://github.com/PyO3/pyo3/pull/3724) - Fix docs.rs build. [#3722](https://github.com/PyO3/pyo3/pull/3722) ## [0.20.1] - 2023-12-30 ### Added - Add optional `either` feature to add conversions for `either::Either` sum type. [#3456](https://github.com/PyO3/pyo3/pull/3456) - Add optional `smallvec` feature to add conversions for `smallvec::SmallVec`. [#3507](https://github.com/PyO3/pyo3/pull/3507) - Add `take` and `into_inner` methods to `GILOnceCell` [#3556](https://github.com/PyO3/pyo3/pull/3556) - `#[classmethod]` methods can now also receive `Py` as their first argument. [#3587](https://github.com/PyO3/pyo3/pull/3587) - `#[pyfunction(pass_module)]` can now also receive `Py` as their first argument. [#3587](https://github.com/PyO3/pyo3/pull/3587) - Add `traverse` method to `GILProtected`. [#3616](https://github.com/PyO3/pyo3/pull/3616) - Added `abi3-py312` feature [#3687](https://github.com/PyO3/pyo3/pull/3687) ### Fixed - Fix minimum version specification for optional `chrono` dependency. [#3512](https://github.com/PyO3/pyo3/pull/3512) - Silenced new `clippy::unnecessary_fallible_conversions` warning when using a `Py` `self` receiver. [#3564](https://github.com/PyO3/pyo3/pull/3564) ## [0.20.0] - 2023-10-11 ### Packaging - Dual-license PyO3 under either the Apache 2.0 OR the MIT license. This makes the project GPLv2 compatible. [#3108](https://github.com/PyO3/pyo3/pull/3108) - Update MSRV to Rust 1.56. [#3208](https://github.com/PyO3/pyo3/pull/3208) - Bump `indoc` dependency to 2.0 and `unindent` dependency to 0.2. [#3237](https://github.com/PyO3/pyo3/pull/3237) - Bump `syn` dependency to 2.0. [#3239](https://github.com/PyO3/pyo3/pull/3239) - Drop support for debug builds of Python 3.7. [#3387](https://github.com/PyO3/pyo3/pull/3387) - Bump `chrono` optional dependency to require 0.4.25 or newer. [#3427](https://github.com/PyO3/pyo3/pull/3427) - Support Python 3.12. [#3488](https://github.com/PyO3/pyo3/pull/3488) ### Added - Support `__lt__`, `__le__`, `__eq__`, `__ne__`, `__gt__` and `__ge__` in `#[pymethods]`. [#3203](https://github.com/PyO3/pyo3/pull/3203) - Add FFI definition `Py_GETENV`. [#3336](https://github.com/PyO3/pyo3/pull/3336) - Add `as_ptr` and `into_ptr` inherent methods for `Py`, `PyAny`, `PyRef`, and `PyRefMut`. [#3359](https://github.com/PyO3/pyo3/pull/3359) - Implement `DoubleEndedIterator` for `PyTupleIterator` and `PyListIterator`. [#3366](https://github.com/PyO3/pyo3/pull/3366) - Add `#[pyclass(rename_all = "...")]` option: this allows renaming all getters and setters of a struct, or all variants of an enum. Available renaming rules are: `"camelCase"`, `"kebab-case"`, `"lowercase"`, `"PascalCase"`, `"SCREAMING-KEBAB-CASE"`, `"SCREAMING_SNAKE_CASE"`, `"snake_case"`, `"UPPERCASE"`. [#3384](https://github.com/PyO3/pyo3/pull/3384) - Add FFI definitions `PyObject_GC_IsTracked` and `PyObject_GC_IsFinalized` on Python 3.9 and up (PyPy 3.10 and up). [#3403](https://github.com/PyO3/pyo3/pull/3403) - Add types for `None`, `Ellipsis`, and `NotImplemented`. [#3408](https://github.com/PyO3/pyo3/pull/3408) - Add FFI definitions for the `Py_mod_multiple_interpreters` constant and its possible values. [#3494](https://github.com/PyO3/pyo3/pull/3494) - Add FFI definitions for `PyInterpreterConfig` struct, its constants and `Py_NewInterpreterFromConfig`. [#3502](https://github.com/PyO3/pyo3/pull/3502) ### Changed - Change `PySet::discard` to return `PyResult` (previously returned nothing). [#3281](https://github.com/PyO3/pyo3/pull/3281) - Optimize implementation of `IntoPy` for Rust tuples to Python tuples. [#3321](https://github.com/PyO3/pyo3/pull/3321) - Change `PyDict::get_item` to no longer suppress arbitrary exceptions (the return type is now `PyResult>` instead of `Option<&PyAny>`), and deprecate `PyDict::get_item_with_error`. [#3330](https://github.com/PyO3/pyo3/pull/3330) - Deprecate FFI definitions which are deprecated in Python 3.12. [#3336](https://github.com/PyO3/pyo3/pull/3336) - `AsPyPointer` is now an `unsafe trait`. [#3358](https://github.com/PyO3/pyo3/pull/3358) - Accept all `os.PathLike` values in implementation of `FromPyObject` for `PathBuf`. [#3374](https://github.com/PyO3/pyo3/pull/3374) - Add `__builtins__` to globals in `py.run()` and `py.eval()` if they're missing. [#3378](https://github.com/PyO3/pyo3/pull/3378) - Optimize implementation of `FromPyObject` for `BigInt` and `BigUint`. [#3379](https://github.com/PyO3/pyo3/pull/3379) - `PyIterator::from_object` and `PyByteArray::from` now take a single argument of type `&PyAny` (previously took two arguments `Python` and `AsPyPointer`). [#3389](https://github.com/PyO3/pyo3/pull/3389) - Replace `AsPyPointer` with `AsRef` as a bound in the blanket implementation of `From<&T> for PyObject`. [#3391](https://github.com/PyO3/pyo3/pull/3391) - Replace blanket `impl IntoPy for &T where T: AsPyPointer` with implementations of `impl IntoPy` for `&PyAny`, `&T where T: AsRef`, and `&Py`. [#3393](https://github.com/PyO3/pyo3/pull/3393) - Preserve `std::io::Error` kind in implementation of `From` for `PyErr` [#3396](https://github.com/PyO3/pyo3/pull/3396) - Try to select a relevant `ErrorKind` in implementation of `From` for `OSError` subclass. [#3397](https://github.com/PyO3/pyo3/pull/3397) - Retrieve the original `PyErr` in implementation of `From` for `PyErr` if the `std::io::Error` has been built using a Python exception (previously would create a new exception wrapping the `std::io::Error`). [#3402](https://github.com/PyO3/pyo3/pull/3402) - `#[pymodule]` will now return the same module object on repeated import by the same Python interpreter, on Python 3.9 and up. [#3446](https://github.com/PyO3/pyo3/pull/3446) - Truncate leap-seconds and warn when converting `chrono` types to Python `datetime` types (`datetime` cannot represent leap-seconds). [#3458](https://github.com/PyO3/pyo3/pull/3458) - `Err` returned from `#[pyfunction]` will now have a non-None `__context__` if called from inside a `catch` block. [#3455](https://github.com/PyO3/pyo3/pull/3455) - Deprecate undocumented `#[__new__]` form of `#[new]` attribute. [#3505](https://github.com/PyO3/pyo3/pull/3505) ### Removed - Remove all functionality deprecated in PyO3 0.18, including `#[args]` attribute for `#[pymethods]`. [#3232](https://github.com/PyO3/pyo3/pull/3232) - Remove `IntoPyPointer` trait in favour of `into_ptr` inherent methods. [#3385](https://github.com/PyO3/pyo3/pull/3385) ### Fixed - Handle exceptions properly in `PySet::discard`. [#3281](https://github.com/PyO3/pyo3/pull/3281) - The `PyTupleIterator` type returned by `PyTuple::iter` is now public and hence can be named by downstream crates. [#3366](https://github.com/PyO3/pyo3/pull/3366) - Linking of `PyOS_FSPath` on PyPy. [#3374](https://github.com/PyO3/pyo3/pull/3374) - Fix memory leak in `PyTypeBuilder::build`. [#3401](https://github.com/PyO3/pyo3/pull/3401) - Disable removed FFI definitions `_Py_GetAllocatedBlocks`, `_PyObject_GC_Malloc`, and `_PyObject_GC_Calloc` on Python 3.11 and up. [#3403](https://github.com/PyO3/pyo3/pull/3403) - Fix `ResourceWarning` and crashes related to GC when running with debug builds of CPython. [#3404](https://github.com/PyO3/pyo3/pull/3404) - Some-wrapping of `Option` default arguments will no longer re-wrap `Some(T)` or expressions evaluating to `None`. [#3461](https://github.com/PyO3/pyo3/pull/3461) - Fix `IterNextOutput::Return` not returning a value on PyPy. [#3471](https://github.com/PyO3/pyo3/pull/3471) - Emit compile errors instead of ignoring macro invocations inside `#[pymethods]` blocks. [#3491](https://github.com/PyO3/pyo3/pull/3491) - Emit error on invalid arguments to `#[new]`, `#[classmethod]`, `#[staticmethod]`, and `#[classattr]`. [#3484](https://github.com/PyO3/pyo3/pull/3484) - Disable `PyMarshal_WriteObjectToString` from `PyMarshal_ReadObjectFromString` with the `abi3` feature. [#3490](https://github.com/PyO3/pyo3/pull/3490) - Fix FFI definitions for `_PyFrameEvalFunction` on Python 3.11 and up (it now receives a `_PyInterpreterFrame` opaque struct). [#3500](https://github.com/PyO3/pyo3/pull/3500) ## [0.19.2] - 2023-08-01 ### Added - Add FFI definitions `PyState_AddModule`, `PyState_RemoveModule` and `PyState_FindModule` for PyPy 3.9 and up. [#3295](https://github.com/PyO3/pyo3/pull/3295) - Add FFI definitions `_PyObject_CallFunction_SizeT` and `_PyObject_CallMethod_SizeT`. [#3297](https://github.com/PyO3/pyo3/pull/3297) - Add a "performance" section to the guide collecting performance-related tricks and problems. [#3304](https://github.com/PyO3/pyo3/pull/3304) - Add `PyErr::Display` for all Python versions, and FFI symbol `PyErr_DisplayException` for Python 3.12. [#3334](https://github.com/PyO3/pyo3/pull/3334) - Add FFI definition `PyType_GetDict()` for Python 3.12. [#3339](https://github.com/PyO3/pyo3/pull/3339) - Add `PyAny::downcast_exact`. [#3346](https://github.com/PyO3/pyo3/pull/3346) - Add `PySlice::full()` to construct a full slice (`::`). [#3353](https://github.com/PyO3/pyo3/pull/3353) ### Changed - Update `PyErr` for 3.12 betas to avoid deprecated ffi methods. [#3306](https://github.com/PyO3/pyo3/pull/3306) - Update FFI definitions of `object.h` for Python 3.12.0b4. [#3335](https://github.com/PyO3/pyo3/pull/3335) - Update `pyo3::ffi` struct definitions to be compatible with 3.12.0b4. [#3342](https://github.com/PyO3/pyo3/pull/3342) - Optimize conversion of `float` to `f64` (and `PyFloat::value`) on non-abi3 builds. [#3345](https://github.com/PyO3/pyo3/pull/3345) ### Fixed - Fix timezone conversion bug for FixedOffset datetimes that were being incorrectly converted to and from UTC. [#3269](https://github.com/PyO3/pyo3/pull/3269) - Fix `SystemError` raised in `PyUnicodeDecodeError_Create` on PyPy 3.10. [#3297](https://github.com/PyO3/pyo3/pull/3297) - Correct FFI definition `Py_EnterRecursiveCall` to return `c_int` (was incorrectly returning `()`). [#3300](https://github.com/PyO3/pyo3/pull/3300) - Fix case where `PyErr::matches` and `PyErr::is_instance` returned results inconsistent with `PyErr::get_type`. [#3313](https://github.com/PyO3/pyo3/pull/3313) - Fix loss of panic message in `PanicException` when unwinding after the exception was "normalized". [#3326](https://github.com/PyO3/pyo3/pull/3326) - Fix `PyErr::from_value` and `PyErr::into_value` losing traceback on conversion. [#3328](https://github.com/PyO3/pyo3/pull/3328) - Fix reference counting of immortal objects on Python 3.12.0b4. [#3335](https://github.com/PyO3/pyo3/pull/3335) ## [0.19.1] - 2023-07-03 ### Packaging - Extend range of supported versions of `hashbrown` optional dependency to include version 0.14 [#3258](https://github.com/PyO3/pyo3/pull/3258) - Extend range of supported versions of `indexmap` optional dependency to include version 2. [#3277](https://github.com/PyO3/pyo3/pull/3277) - Support PyPy 3.10. [#3289](https://github.com/PyO3/pyo3/pull/3289) ### Added - Add `pyo3::types::PyFrozenSetBuilder` to allow building a `PyFrozenSet` item by item. [#3156](https://github.com/PyO3/pyo3/pull/3156) - Add support for converting to and from Python's `ipaddress.IPv4Address`/`ipaddress.IPv6Address` and `std::net::IpAddr`. [#3197](https://github.com/PyO3/pyo3/pull/3197) - Add support for `num-bigint` feature in combination with `abi3`. [#3198](https://github.com/PyO3/pyo3/pull/3198) - Add `PyErr_GetRaisedException()`, `PyErr_SetRaisedException()` to FFI definitions for Python 3.12 and later. [#3248](https://github.com/PyO3/pyo3/pull/3248) - Add `Python::with_pool` which is a safer but more limited alternative to `Python::new_pool`. [#3263](https://github.com/PyO3/pyo3/pull/3263) - Add `PyDict::get_item_with_error` on PyPy. [#3270](https://github.com/PyO3/pyo3/pull/3270) - Allow `#[new]` methods may to return `Py` in order to return existing instances. [#3287](https://github.com/PyO3/pyo3/pull/3287) ### Fixed - Fix conversion of classes implementing `__complex__` to `Complex` when using `abi3` or PyPy. [#3185](https://github.com/PyO3/pyo3/pull/3185) - Stop suppressing unrelated exceptions in `PyAny::hasattr`. [#3271](https://github.com/PyO3/pyo3/pull/3271) - Fix memory leak when creating `PySet` or `PyFrozenSet` or returning types converted into these internally, e.g. `HashSet` or `BTreeSet`. [#3286](https://github.com/PyO3/pyo3/pull/3286) ## [0.19.0] - 2023-05-31 ### Packaging - Correct dependency on syn to version 1.0.85 instead of the incorrect version 1.0.56. [#3152](https://github.com/PyO3/pyo3/pull/3152) ### Added - Accept `text_signature` option (and automatically generate signature) for `#[new]` in `#[pymethods]`. [#2980](https://github.com/PyO3/pyo3/pull/2980) - Add support for converting to and from Python's `decimal.Decimal` and `rust_decimal::Decimal`. [#3016](https://github.com/PyO3/pyo3/pull/3016) - Add `#[pyo3(from_item_all)]` when deriving `FromPyObject` to specify `get_item` as getter for all fields. [#3120](https://github.com/PyO3/pyo3/pull/3120) - Add `pyo3::exceptions::PyBaseExceptionGroup` for Python 3.11, and corresponding FFI definition `PyExc_BaseExceptionGroup`. [#3141](https://github.com/PyO3/pyo3/pull/3141) - Accept `#[new]` with `#[classmethod]` to create a constructor which receives a (subtype's) class/`PyType` as its first argument. [#3157](https://github.com/PyO3/pyo3/pull/3157) - Add `PyClass::get` and `Py::get` for GIL-independent access to classes with `#[pyclass(frozen)]`. [#3158](https://github.com/PyO3/pyo3/pull/3158) - Add `PyAny::is_exact_instance` and `PyAny::is_exact_instance_of`. [#3161](https://github.com/PyO3/pyo3/pull/3161) ### Changed - `PyAny::is_instance_of::(obj)` is now equivalent to `T::is_type_of(obj)`, and now returns `bool` instead of `PyResult`. [#2881](https://github.com/PyO3/pyo3/pull/2881) - Deprecate `text_signature` option on `#[pyclass]` structs. [#2980](https://github.com/PyO3/pyo3/pull/2980) - No longer wrap `anyhow::Error`/`eyre::Report` containing a basic `PyErr` without a chain in a `PyRuntimeError`. [#3004](https://github.com/PyO3/pyo3/pull/3004) - - Change `#[getter]` and `#[setter]` to use a common call "trampoline" to slightly reduce generated code size and compile times. [#3029](https://github.com/PyO3/pyo3/pull/3029) - Improve default values for str, numbers and bool in automatically-generated `text_signature`. [#3050](https://github.com/PyO3/pyo3/pull/3050) - Improve default value for `None` in automatically-generated `text_signature`. [#3066](https://github.com/PyO3/pyo3/pull/3066) - Rename `PySequence::list` and `PySequence::tuple` to `PySequence::to_list` and `PySequence::to_tuple`. (The old names continue to exist as deprecated forms.) [#3111](https://github.com/PyO3/pyo3/pull/3111) - Extend the lifetime of the GIL token returned by `PyRef::py` and `PyRefMut::py` to match the underlying borrow. [#3131](https://github.com/PyO3/pyo3/pull/3131) - Safe access to the GIL, for example via `Python::with_gil`, is now locked inside of implementations of the `__traverse__` slot. [#3168](https://github.com/PyO3/pyo3/pull/3168) ### Removed - Remove all functionality deprecated in PyO3 0.17, most prominently `Python::acquire_gil` is replaced by `Python::with_gil`. [#2981](https://github.com/PyO3/pyo3/pull/2981) ### Fixed - Correct FFI definitions `PyGetSetDef`, `PyMemberDef`, `PyStructSequence_Field` and `PyStructSequence_Desc` to have `*const c_char` members for `name` and `doc` (not `*mut c_char`). [#3036](https://github.com/PyO3/pyo3/pull/3036) - Fix panic on `fmt::Display`, instead return `""` string and report error via `sys.unraisablehook()` [#3062](https://github.com/PyO3/pyo3/pull/3062) - Fix a compile error of "temporary value dropped while borrowed" when `#[pyfunction]`s take references into `#[pyclass]`es [#3142](https://github.com/PyO3/pyo3/pull/3142) - Fix crashes caused by PyO3 applying deferred reference count updates when entering a `__traverse__` implementation. [#3168](https://github.com/PyO3/pyo3/pull/3168) - Forbid running the `Drop` implementations of unsendable classes on other threads. [#3176](https://github.com/PyO3/pyo3/pull/3176) - Fix a compile error when `#[pymethods]` items come from somewhere else (for example, as a macro argument) and a custom receiver like `Py` is used. [#3178](https://github.com/PyO3/pyo3/pull/3178) ## [0.18.3] - 2023-04-13 ### Added - Add `GILProtected` to mediate concurrent access to a value using Python's global interpreter lock (GIL). [#2975](https://github.com/PyO3/pyo3/pull/2975) - Support `PyASCIIObject` / `PyUnicode` and associated methods on big-endian architectures. [#3015](https://github.com/PyO3/pyo3/pull/3015) - Add FFI definition `_PyDict_Contains_KnownHash()` for CPython 3.10 and up. [#3088](https://github.com/PyO3/pyo3/pull/3088) ### Fixed - Fix compile error for `#[pymethods]` and `#[pyfunction]` called "output". [#3022](https://github.com/PyO3/pyo3/pull/3022) - Fix compile error in generated code for magic methods implemented as a `#[staticmethod]`. [#3055](https://github.com/PyO3/pyo3/pull/3055) - Fix `is_instance` for `PyDateTime` (would incorrectly check for a `PyDate`). [#3071](https://github.com/PyO3/pyo3/pull/3071) - Fix upstream deprecation of `PyUnicode_InternImmortal` since Python 3.10. [#3071](https://github.com/PyO3/pyo3/pull/3087) ## [0.18.2] - 2023-03-24 ### Packaging - Disable default features of `chrono` to avoid depending on `time` v0.1.x. [#2939](https://github.com/PyO3/pyo3/pull/2939) ### Added - Implement `IntoPy`, `ToPyObject` and `FromPyObject` for `Cow<[u8]>` to efficiently handle both `bytes` and `bytearray` objects. [#2899](https://github.com/PyO3/pyo3/pull/2899) - Implement `IntoPy`, `ToPyObject` and `FromPyObject` for `Cell`. [#3014](https://github.com/PyO3/pyo3/pull/3014) - Add `PyList::to_tuple()`, as a convenient and efficient conversion from lists to tuples. [#3042](https://github.com/PyO3/pyo3/pull/3042) - Add `PyTuple::to_list()`, as a convenient and efficient conversion from tuples to lists. [#3044](https://github.com/PyO3/pyo3/pull/3044) ### Changed - Optimize `PySequence` conversion for `list` and `tuple` inputs. [#2944](https://github.com/PyO3/pyo3/pull/2944) - Improve exception raised when creating `#[pyclass]` type object fails during module import. [#2947](https://github.com/PyO3/pyo3/pull/2947) - Optimize `PyMapping` conversion for `dict` inputs. [#2954](https://github.com/PyO3/pyo3/pull/2954) - Allow `create_exception!` to take a `dotted.module` to place the exception in a submodule. [#2979](https://github.com/PyO3/pyo3/pull/2979) ### Fixed - Fix a reference counting race condition affecting `PyObject`s cloned in `allow_threads` blocks. [#2952](https://github.com/PyO3/pyo3/pull/2952) - Fix `clippy::redundant_closure` lint on default arguments in `#[pyo3(signature = (...))]` annotations. [#2990](https://github.com/PyO3/pyo3/pull/2990) - Fix `non_snake_case` lint on generated code in `#[pyfunction]` macro. [#2993](https://github.com/PyO3/pyo3/pull/2993) - Fix some FFI definitions for the upcoming PyPy 3.10 release. [#3031](https://github.com/PyO3/pyo3/pull/3031) ## [0.18.1] - 2023-02-07 ### Added - Add `PyErr::write_unraisable()`. [#2889](https://github.com/PyO3/pyo3/pull/2889) - Add `Python::Ellipsis()` and `PyAny::is_ellipsis()` methods. [#2911](https://github.com/PyO3/pyo3/pull/2911) - Add `PyDict::update()` and `PyDict::update_if_missing()` methods. [#2912](https://github.com/PyO3/pyo3/pull/2912) ### Changed - FFI definition `PyIter_Check` on CPython 3.7 is now implemented as `hasattr(type(obj), "__next__")`, which works correctly on all platforms and adds support for `abi3`. [#2914](https://github.com/PyO3/pyo3/pull/2914) - Warn about unknown config keys in `PYO3_CONFIG_FILE` instead of denying. [#2926](https://github.com/PyO3/pyo3/pull/2926) ### Fixed - Send errors returned by `__releasebuffer__` to `sys.unraisablehook` rather than causing `SystemError`. [#2886](https://github.com/PyO3/pyo3/pull/2886) - Fix downcast to `PyIterator` succeeding for Python classes which did not implement `__next__`. [#2914](https://github.com/PyO3/pyo3/pull/2914) - Fix segfault in `__traverse__` when visiting `None` fields of `Option`. [#2921](https://github.com/PyO3/pyo3/pull/2921) - Fix `#[pymethods(crate = "...")]` option being ignored. [#2923](https://github.com/PyO3/pyo3/pull/2923) - Link against `pythonXY_d.dll` for debug Python builds on Windows. [#2937](https://github.com/PyO3/pyo3/pull/2937) ## [0.18.0] - 2023-01-17 ### Packaging - Relax `indexmap` optional depecency to allow `>= 1.6, < 2`. [#2849](https://github.com/PyO3/pyo3/pull/2849) - Relax `hashbrown` optional dependency to allow `>= 0.9, < 0.14`. [#2875](https://github.com/PyO3/pyo3/pull/2875) - Update `memoffset` dependency to 0.8. [#2875](https://github.com/PyO3/pyo3/pull/2875) ### Added - Add `GILOnceCell::get_or_try_init` for fallible `GILOnceCell` initialization. [#2398](https://github.com/PyO3/pyo3/pull/2398) - Add experimental feature `experimental-inspect` with `type_input()` and `type_output()` helpers to get the Python type of any Python-compatible object. [#2490](https://github.com/PyO3/pyo3/pull/2490) [#2882](https://github.com/PyO3/pyo3/pull/2882) - The `#[pyclass]` macro can now take `get_all` and `set_all` to create getters and setters for every field. [#2692](https://github.com/PyO3/pyo3/pull/2692) - Add `#[pyo3(signature = (...))]` option for `#[pyfunction]` and `#[pymethods]`. [#2702](https://github.com/PyO3/pyo3/pull/2702) - `pyo3-build-config`: rebuild when `PYO3_ENVIRONMENT_SIGNATURE` environment variable value changes. [#2727](https://github.com/PyO3/pyo3/pull/2727) - Add conversions between non-zero int types in `std::num` and Python `int`. [#2730](https://github.com/PyO3/pyo3/pull/2730) - Add `Py::downcast()` as a companion to `PyAny::downcast()`, as well as `downcast_unchecked()` for both types. [#2734](https://github.com/PyO3/pyo3/pull/2734) - Add types for all built-in `Warning` classes as well as `PyErr::warn_explicit`. [#2742](https://github.com/PyO3/pyo3/pull/2742) - Add `abi3-py311` feature. [#2776](https://github.com/PyO3/pyo3/pull/2776) - Add FFI definition `_PyErr_ChainExceptions()` for CPython. [#2788](https://github.com/PyO3/pyo3/pull/2788) - Add FFI definitions `PyVectorcall_NARGS` and `PY_VECTORCALL_ARGUMENTS_OFFSET` for PyPy 3.8 and up. [#2811](https://github.com/PyO3/pyo3/pull/2811) - Add `PyList::get_item_unchecked` for PyPy. [#2827](https://github.com/PyO3/pyo3/pull/2827) ### Changed - PyO3's macros now emit a much nicer error message if function return values don't implement the required trait(s). [#2664](https://github.com/PyO3/pyo3/pull/2664) - Use a TypeError, rather than a ValueError, when refusing to treat a str as a Vec. [#2685](https://github.com/PyO3/pyo3/pull/2685) - Change `PyCFunction::new_closure` to take `name` and `doc` arguments. [#2686](https://github.com/PyO3/pyo3/pull/2686) - `PyType::is_subclass`, `PyErr::is_instance` and `PyAny::is_instance` now take `&PyAny` instead of `&PyType` arguments, so that they work with objects that pretend to be types using `__subclasscheck__` and `__instancecheck__`. [#2695](https://github.com/PyO3/pyo3/pull/2695) - Deprecate `#[args]` attribute and passing "args" specification directly to `#[pyfunction]` in favor of the new `#[pyo3(signature = (...))]` option. [#2702](https://github.com/PyO3/pyo3/pull/2702) - Deprecate required arguments after `Option` arguments to `#[pyfunction]` and `#[pymethods]` without also using `#[pyo3(signature)]` to specify whether the arguments should be required or have defaults. [#2703](https://github.com/PyO3/pyo3/pull/2703) - Change `#[pyfunction]` and `#[pymethods]` to use a common call "trampoline" to slightly reduce generated code size and compile times. [#2705](https://github.com/PyO3/pyo3/pull/2705) - `PyAny::cast_as()` and `Py::cast_as()` are now deprecated in favor of `PyAny::downcast()` and the new `Py::downcast()`. [#2734](https://github.com/PyO3/pyo3/pull/2734) - Relax lifetime bounds on `PyAny::downcast()`. [#2734](https://github.com/PyO3/pyo3/pull/2734) - Automatically generate `__text_signature__` for all Python functions created using `#[pyfunction]` and `#[pymethods]`. [#2784](https://github.com/PyO3/pyo3/pull/2784) - Accept any iterator in `PySet::new` and `PyFrozenSet::new`. [#2795](https://github.com/PyO3/pyo3/pull/2795) - Mixing `#[cfg(...)]` and `#[pyo3(...)]` attributes on `#[pyclass]` struct fields will now work. [#2796](https://github.com/PyO3/pyo3/pull/2796) - Re-enable `PyFunction` on when building for abi3 or PyPy. [#2838](https://github.com/PyO3/pyo3/pull/2838) - Improve `derive(FromPyObject)` to use `intern!` when applicable for `#[pyo3(item)]`. [#2879](https://github.com/PyO3/pyo3/pull/2879) ### Removed - Remove the deprecated `pyproto` feature, `#[pyproto]` macro, and all accompanying APIs. [#2587](https://github.com/PyO3/pyo3/pull/2587) - Remove all functionality deprecated in PyO3 0.16. [#2843](https://github.com/PyO3/pyo3/pull/2843) ### Fixed - Disable `PyModule::filename` on PyPy. [#2715](https://github.com/PyO3/pyo3/pull/2715) - `PyCodeObject` is now once again defined with fields on Python 3.7. [#2726](https://github.com/PyO3/pyo3/pull/2726) - Raise a `TypeError` if `#[new]` pymethods with no arguments receive arguments when called from Python. [#2749](https://github.com/PyO3/pyo3/pull/2749) - Use the `NOARGS` argument calling convention for methods that have a single `py: Python` argument (as a performance optimization). [#2760](https://github.com/PyO3/pyo3/pull/2760) - Fix truncation of `isize` values to `c_long` in `PySlice::new`. [#2769](https://github.com/PyO3/pyo3/pull/2769) - Fix soundness issue with FFI definition `PyUnicodeDecodeError_Create` on PyPy leading to indeterminate behavior (typically a `TypeError`). [#2772](https://github.com/PyO3/pyo3/pull/2772) - Allow functions taking `**kwargs` to accept keyword arguments which share a name with a positional-only argument (as permitted by PEP 570). [#2800](https://github.com/PyO3/pyo3/pull/2800) - Fix unresolved symbol for `PyObject_Vectorcall` on PyPy 3.9 and up. [#2811](https://github.com/PyO3/pyo3/pull/2811) - Fix memory leak in `PyCFunction::new_closure`. [#2842](https://github.com/PyO3/pyo3/pull/2842) ## [0.17.3] - 2022-11-01 ### Packaging - Support Python 3.11. (Previous versions of PyO3 0.17 have been tested against Python 3.11 release candidates and are expected to be compatible, this is the first version tested against Python 3.11.0.) [#2708](https://github.com/PyO3/pyo3/pull/2708) ### Added - Implemented `ExactSizeIterator` for `PyListIterator`, `PyDictIterator`, `PySetIterator` and `PyFrozenSetIterator`. [#2676](https://github.com/PyO3/pyo3/pull/2676) ### Fixed - Fix regression of `impl FromPyObject for [T; N]` no longer accepting types passing `PySequence_Check`, e.g. NumPy arrays, since version 0.17.0. This the same fix that was applied `impl FromPyObject for Vec` in version 0.17.1 extended to fixed-size arrays. [#2675](https://github.com/PyO3/pyo3/pull/2675) - Fix UB in `FunctionDescription::extract_arguments_fastcall` due to creating slices from a null pointer. [#2687](https://github.com/PyO3/pyo3/pull/2687) ## [0.17.2] - 2022-10-04 ### Packaging - Added optional `chrono` feature to convert `chrono` types into types in the `datetime` module. [#2612](https://github.com/PyO3/pyo3/pull/2612) ### Added - Add support for `num-bigint` feature on `PyPy`. [#2626](https://github.com/PyO3/pyo3/pull/2626) ### Fixed - Correctly implement `__richcmp__` for enums, fixing `__ne__` returning always returning `True`. [#2622](https://github.com/PyO3/pyo3/pull/2622) - Fix compile error since 0.17.0 with `Option<&SomePyClass>` argument with a default. [#2630](https://github.com/PyO3/pyo3/pull/2630) - Fix regression of `impl FromPyObject for Vec` no longer accepting types passing `PySequence_Check`, e.g. NumPy arrays, since 0.17.0. [#2631](https://github.com/PyO3/pyo3/pull/2631) ## [0.17.1] - 2022-08-28 ### Fixed - Fix visibility of `PyDictItems`, `PyDictKeys`, and `PyDictValues` types added in PyO3 0.17.0. - Fix compile failure when using `#[pyo3(from_py_with = "...")]` attribute on an argument of type `Option`. [#2592](https://github.com/PyO3/pyo3/pull/2592) - Fix clippy `redundant-closure` lint on `**kwargs` arguments for `#[pyfunction]` and `#[pymethods]`. [#2595](https://github.com/PyO3/pyo3/pull/2595) ## [0.17.0] - 2022-08-23 ### Packaging - Update inventory dependency to `0.3` (the `multiple-pymethods` feature now requires Rust 1.62 for correctness). [#2492](https://github.com/PyO3/pyo3/pull/2492) ### Added - Add `timezone_utc`. [#1588](https://github.com/PyO3/pyo3/pull/1588) - Implement `ToPyObject` for `[T; N]`. [#2313](https://github.com/PyO3/pyo3/pull/2313) - Add `PyDictKeys`, `PyDictValues` and `PyDictItems` Rust types. [#2358](https://github.com/PyO3/pyo3/pull/2358) - Add `append_to_inittab`. [#2377](https://github.com/PyO3/pyo3/pull/2377) - Add FFI definition `PyFrame_GetCode`. [#2406](https://github.com/PyO3/pyo3/pull/2406) - Add `PyCode` and `PyFrame` high level objects. [#2408](https://github.com/PyO3/pyo3/pull/2408) - Add FFI definitions `Py_fstring_input`, `sendfunc`, and `_PyErr_StackItem`. [#2423](https://github.com/PyO3/pyo3/pull/2423) - Add `PyDateTime::new_with_fold`, `PyTime::new_with_fold`, `PyTime::get_fold`, and `PyDateTime::get_fold` for PyPy. [#2428](https://github.com/PyO3/pyo3/pull/2428) - Add `#[pyclass(frozen)]`. [#2448](https://github.com/PyO3/pyo3/pull/2448) - Accept `#[pyo3(name)]` on enum variants. [#2457](https://github.com/PyO3/pyo3/pull/2457) - Add `CompareOp::matches` to implement `__richcmp__` as the result of a Rust `std::cmp::Ordering` comparison. [#2460](https://github.com/PyO3/pyo3/pull/2460) - Add `PySuper` type. [#2486](https://github.com/PyO3/pyo3/pull/2486) - Support PyPy on Windows with the `generate-import-lib` feature. [#2506](https://github.com/PyO3/pyo3/pull/2506) - Add FFI definitions `Py_EnterRecursiveCall` and `Py_LeaveRecursiveCall`. [#2511](https://github.com/PyO3/pyo3/pull/2511) - Add `PyDict::get_item_with_error`. [#2536](https://github.com/PyO3/pyo3/pull/2536) - Add `#[pyclass(sequence)]` option. [#2567](https://github.com/PyO3/pyo3/pull/2567) ### Changed - Change datetime constructors taking a `tzinfo` to take `Option<&PyTzInfo>` instead of `Option<&PyObject>`: `PyDateTime::new`, `PyDateTime::new_with_fold`, `PyTime::new`, and `PyTime::new_with_fold`. [#1588](https://github.com/PyO3/pyo3/pull/1588) - Move `PyTypeObject::type_object` method to the `PyTypeInfo` trait, and deprecate the `PyTypeObject` trait. [#2287](https://github.com/PyO3/pyo3/pull/2287) - Methods of `Py` and `PyAny` now accept `impl IntoPy>` rather than just `&str` to allow use of the `intern!` macro. [#2312](https://github.com/PyO3/pyo3/pull/2312) - Change the deprecated `pyproto` feature to be opt-in instead of opt-out. [#2322](https://github.com/PyO3/pyo3/pull/2322) - Emit better error messages when `#[pyfunction]` return types do not implement `IntoPy`. [#2326](https://github.com/PyO3/pyo3/pull/2326) - Require `T: IntoPy` for `impl IntoPy for [T; N]` instead of `T: ToPyObject`. [#2326](https://github.com/PyO3/pyo3/pull/2326) - Deprecate the `ToBorrowedObject` trait. [#2333](https://github.com/PyO3/pyo3/pull/2333) - Iterators over `PySet` and `PyDict` will now panic if the underlying collection is mutated during the iteration. [#2380](https://github.com/PyO3/pyo3/pull/2380) - Iterators over `PySet` and `PyDict` will now panic if the underlying collection is mutated during the iteration. [#2380](https://github.com/PyO3/pyo3/pull/2380) - Allow `#[classattr]` methods to be fallible. [#2385](https://github.com/PyO3/pyo3/pull/2385) - Prevent multiple `#[pymethods]` with the same name for a single `#[pyclass]`. [#2399](https://github.com/PyO3/pyo3/pull/2399) - Fixup `lib_name` when using `PYO3_CONFIG_FILE`. [#2404](https://github.com/PyO3/pyo3/pull/2404) - Add a message to the `ValueError` raised by the `#[derive(FromPyObject)]` implementation for a tuple struct. [#2414](https://github.com/PyO3/pyo3/pull/2414) - Allow `#[classattr]` methods to take `Python` argument. [#2456](https://github.com/PyO3/pyo3/pull/2456) - Rework `PyCapsule` type to resolve soundness issues: [#2485](https://github.com/PyO3/pyo3/pull/2485) - `PyCapsule::new` and `PyCapsule::new_with_destructor` now take `name: Option` instead of `&CStr`. - The destructor `F` in `PyCapsule::new_with_destructor` must now be `Send`. - `PyCapsule::get_context` deprecated in favor of `PyCapsule::context` which doesn't take a `py: Python<'_>` argument. - `PyCapsule::set_context` no longer takes a `py: Python<'_>` argument. - `PyCapsule::name` now returns `PyResult>` instead of `&CStr`. - `FromPyObject::extract` for `Vec` no longer accepts Python `str` inputs. [#2500](https://github.com/PyO3/pyo3/pull/2500) - Ensure each `#[pymodule]` is only initialized once. [#2523](https://github.com/PyO3/pyo3/pull/2523) - `pyo3_build_config::add_extension_module_link_args` now also emits linker arguments for `wasm32-unknown-emscripten`. [#2538](https://github.com/PyO3/pyo3/pull/2538) - Type checks for `PySequence` and `PyMapping` now require inputs to inherit from (or register with) `collections.abc.Sequence` and `collections.abc.Mapping` respectively. [#2477](https://github.com/PyO3/pyo3/pull/2477) - Disable `PyFunction` on when building for abi3 or PyPy. [#2542](https://github.com/PyO3/pyo3/pull/2542) - Deprecate `Python::acquire_gil`. [#2549](https://github.com/PyO3/pyo3/pull/2549) ### Removed - Remove all functionality deprecated in PyO3 0.15. [#2283](https://github.com/PyO3/pyo3/pull/2283) - Make the `Dict`, `WeakRef` and `BaseNativeType` members of the `PyClass` private implementation details. [#2572](https://github.com/PyO3/pyo3/pull/2572) ### Fixed - Enable incorrectly disabled FFI definition `PyThreadState_DeleteCurrent`. [#2357](https://github.com/PyO3/pyo3/pull/2357) - Fix `wrap_pymodule` interactions with name resolution rules: it no longer "sees through" glob imports of `use submodule::*` when `submodule::submodule` is a `#[pymodule]`. [#2363](https://github.com/PyO3/pyo3/pull/2363) - Correct FFI definition `PyEval_EvalCodeEx` to take `*const *mut PyObject` array arguments instead of `*mut *mut PyObject`. [#2368](https://github.com/PyO3/pyo3/pull/2368) - Fix "raw-ident" structs (e.g. `#[pyclass] struct r#RawName`) incorrectly having `r#` at the start of the class name created in Python. [#2395](https://github.com/PyO3/pyo3/pull/2395) - Correct FFI definition `Py_tracefunc` to be `unsafe extern "C" fn` (was previously safe). [#2407](https://github.com/PyO3/pyo3/pull/2407) - Fix compile failure with `#[pyo3(from_py_with = "...")]` annotations on a field in a `#[derive(FromPyObject)]` struct. [#2414](https://github.com/PyO3/pyo3/pull/2414) - Fix FFI definitions `_PyDateTime_BaseTime` and `_PyDateTime_BaseDateTime` lacking leading underscores in their names. [#2421](https://github.com/PyO3/pyo3/pull/2421) - Remove FFI definition `PyArena` on Python 3.10 and up. [#2421](https://github.com/PyO3/pyo3/pull/2421) - Fix FFI definition `PyCompilerFlags` missing member `cf_feature_version` on Python 3.8 and up. [#2423](https://github.com/PyO3/pyo3/pull/2423) - Fix FFI definition `PyAsyncMethods` missing member `am_send` on Python 3.10 and up. [#2423](https://github.com/PyO3/pyo3/pull/2423) - Fix FFI definition `PyGenObject` having multiple incorrect members on various Python versions. [#2423](https://github.com/PyO3/pyo3/pull/2423) - Fix FFI definition `PySyntaxErrorObject` missing members `end_lineno` and `end_offset` on Python 3.10 and up. [#2423](https://github.com/PyO3/pyo3/pull/2423) - Fix FFI definition `PyHeapTypeObject` missing member `ht_module` on Python 3.9 and up. [#2423](https://github.com/PyO3/pyo3/pull/2423) - Fix FFI definition `PyFrameObject` having multiple incorrect members on various Python versions. [#2424](https://github.com/PyO3/pyo3/pull/2424) [#2434](https://github.com/PyO3/pyo3/pull/2434) - Fix FFI definition `PyTypeObject` missing deprecated field `tp_print` on Python 3.8. [#2428](https://github.com/PyO3/pyo3/pull/2428) - Fix FFI definitions `PyDateTime_CAPI`. `PyDateTime_Date`, `PyASCIIObject`, `PyBaseExceptionObject`, `PyListObject`, and `PyTypeObject` on PyPy. [#2428](https://github.com/PyO3/pyo3/pull/2428) - Fix FFI definition `_inittab` field `initfunc` typo'd as `initfun`. [#2431](https://github.com/PyO3/pyo3/pull/2431) - Fix FFI definitions `_PyDateTime_BaseTime` and `_PyDateTime_BaseDateTime` incorrectly having `fold` member. [#2432](https://github.com/PyO3/pyo3/pull/2432) - Fix FFI definitions `PyTypeObject`. `PyHeapTypeObject`, and `PyCFunctionObject` having incorrect members on PyPy 3.9. [#2433](https://github.com/PyO3/pyo3/pull/2433) - Fix FFI definition `PyGetSetDef` to have `*const c_char` for `doc` member (not `*mut c_char`). [#2439](https://github.com/PyO3/pyo3/pull/2439) - Fix `#[pyo3(from_py_with = "...")]` being ignored for 1-element tuple structs and transparent structs. [#2440](https://github.com/PyO3/pyo3/pull/2440) - Use `memoffset` to avoid UB when computing `PyCell` layout. [#2450](https://github.com/PyO3/pyo3/pull/2450) - Fix incorrect enum names being returned by the generated `repr` for enums renamed by `#[pyclass(name = "...")]` [#2457](https://github.com/PyO3/pyo3/pull/2457) - Fix `PyObject_CallNoArgs` incorrectly being available when building for abi3 on Python 3.9. [#2476](https://github.com/PyO3/pyo3/pull/2476) - Fix several clippy warnings generated by `#[pyfunction]` arguments. [#2503](https://github.com/PyO3/pyo3/pull/2503) ## [0.16.6] - 2022-08-23 ### Changed - Fix soundness issues with `PyCapsule` type with select workarounds. Users are encourage to upgrade to PyO3 0.17 at their earliest convenience which contains API breakages which fix the issues in a long-term fashion. [#2522](https://github.com/PyO3/pyo3/pull/2522) - `PyCapsule::new` and `PyCapsule::new_with_destructor` now take ownership of a copy of the `name` to resolve a possible use-after-free. - `PyCapsule::name` now returns an empty `CStr` instead of dereferencing a null pointer if the capsule has no name. - The destructor `F` in `PyCapsule::new_with_destructor` will never be called if the capsule is deleted from a thread other than the one which the capsule was created in (a warning will be emitted). - Panics during drop of panic payload caught by PyO3 will now abort. [#2544](https://github.com/PyO3/pyo3/pull/2544) ## [0.16.5] - 2022-05-15 ### Added - Add an experimental `generate-import-lib` feature to support auto-generating non-abi3 python import libraries for Windows targets. [#2364](https://github.com/PyO3/pyo3/pull/2364) - Add FFI definition `Py_ExitStatusException`. [#2374](https://github.com/PyO3/pyo3/pull/2374) ### Changed - Deprecate experimental `generate-abi3-import-lib` feature in favor of the new `generate-import-lib` feature. [#2364](https://github.com/PyO3/pyo3/pull/2364) ### Fixed - Added missing `warn_default_encoding` field to `PyConfig` on 3.10+. The previously missing field could result in incorrect behavior or crashes. [#2370](https://github.com/PyO3/pyo3/pull/2370) - Fixed order of `pathconfig_warnings` and `program_name` fields of `PyConfig` on 3.10+. Previously, the order of the fields was swapped and this could lead to incorrect behavior or crashes. [#2370](https://github.com/PyO3/pyo3/pull/2370) ## [0.16.4] - 2022-04-14 ### 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 FFI definitions for `PyDateTime_BaseTime` and `PyDateTime_BaseDateTime`. [#2294](https://github.com/PyO3/pyo3/pull/2294) ### Changed - 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 - 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 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 ### Packaging - Extend `parking_lot` dependency supported versions to include 0.12. [#2239](https://github.com/PyO3/pyo3/pull/2239) ### Added - Add methods to `pyo3_build_config::InterpreterConfig` to run Python scripts using the configured executable. [#2092](https://github.com/PyO3/pyo3/pull/2092) - Add `as_bytes` method to `Py`. [#2235](https://github.com/PyO3/pyo3/pull/2235) - Add FFI definitions for `PyType_FromModuleAndSpec`, `PyType_GetModule`, `PyType_GetModuleState` and `PyModule_AddType`. [#2250](https://github.com/PyO3/pyo3/pull/2250) - Add `pyo3_build_config::cross_compiling_from_to` as a helper to detect when PyO3 is cross-compiling. [#2253](https://github.com/PyO3/pyo3/pull/2253) - Add `#[pyclass(mapping)]` option to leave sequence slots empty in container implementations. [#2265](https://github.com/PyO3/pyo3/pull/2265) - Add `PyString::intern` to enable usage of the Python's built-in string interning. [#2268](https://github.com/PyO3/pyo3/pull/2268) - Add `intern!` macro which can be used to amortize the cost of creating Python strings by storing them inside a `GILOnceCell`. [#2269](https://github.com/PyO3/pyo3/pull/2269) - Add `PYO3_CROSS_PYTHON_IMPLEMENTATION` environment variable for selecting the default cross Python implementation. [#2272](https://github.com/PyO3/pyo3/pull/2272) ### Changed - Allow `#[pyo3(crate = "...", text_signature = "...")]` options to be used directly in `#[pyclass(crate = "...", text_signature = "...")]`. [#2234](https://github.com/PyO3/pyo3/pull/2234) - Make `PYO3_CROSS_LIB_DIR` environment variable optional when cross compiling. [#2241](https://github.com/PyO3/pyo3/pull/2241) - Mark `METH_FASTCALL` calling convention as limited API on Python 3.10. [#2250](https://github.com/PyO3/pyo3/pull/2250) - Deprecate `pyo3_build_config::cross_compiling` in favor of `pyo3_build_config::cross_compiling_from_to`. [#2253](https://github.com/PyO3/pyo3/pull/2253) ### Fixed - Fix `abi3-py310` feature: use Python 3.10 ABI when available instead of silently falling back to the 3.9 ABI. [#2242](https://github.com/PyO3/pyo3/pull/2242) - Use shared linking mode when cross compiling against a [Framework bundle](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html) for macOS. [#2233](https://github.com/PyO3/pyo3/pull/2233) - Fix panic during compilation when `PYO3_CROSS_LIB_DIR` is set for some host/target combinations. [#2232](https://github.com/PyO3/pyo3/pull/2232) - Correct dependency version for `syn` to require minimal patch version 1.0.56. [#2240](https://github.com/PyO3/pyo3/pull/2240) ## [0.16.2] - 2022-03-15 ### Packaging - Warn when modules are imported on PyPy 3.7 versions older than PyPy 7.3.8, as they are known to have binary compatibility issues. [#2217](https://github.com/PyO3/pyo3/pull/2217) - Ensure build script of `pyo3-ffi` runs before that of `pyo3` to fix cross compilation. [#2224](https://github.com/PyO3/pyo3/pull/2224) ## [0.16.1] - 2022-03-05 ### Packaging - Extend `hashbrown` optional dependency supported versions to include 0.12. [#2197](https://github.com/PyO3/pyo3/pull/2197) ### Fixed - Fix incorrect platform detection for Windows in `pyo3-build-config`. [#2198](https://github.com/PyO3/pyo3/pull/2198) - Fix regression from 0.16 preventing cross compiling to aarch64 macOS. [#2201](https://github.com/PyO3/pyo3/pull/2201) ## [0.16.0] - 2022-02-27 ### Packaging - Update MSRV to Rust 1.48. [#2004](https://github.com/PyO3/pyo3/pull/2004) - Update `indoc` optional dependency to 1.0. [#2004](https://github.com/PyO3/pyo3/pull/2004) - Drop support for Python 3.6, remove `abi3-py36` feature. [#2006](https://github.com/PyO3/pyo3/pull/2006) - `pyo3-build-config` no longer enables the `resolve-config` feature by default. [#2008](https://github.com/PyO3/pyo3/pull/2008) - Update `inventory` optional dependency to 0.2. [#2019](https://github.com/PyO3/pyo3/pull/2019) - Drop `paste` dependency. [#2081](https://github.com/PyO3/pyo3/pull/2081) - The bindings found in `pyo3::ffi` are now a re-export of a separate `pyo3-ffi` crate. [#2126](https://github.com/PyO3/pyo3/pull/2126) - Support PyPy 3.9. [#2143](https://github.com/PyO3/pyo3/pull/2143) ### Added - Add `PyCapsule` type exposing the [Capsule API](https://docs.python.org/3/c-api/capsule.html#capsules). [#1980](https://github.com/PyO3/pyo3/pull/1980) - Add `pyo3_build_config::Sysconfigdata` and supporting APIs. [#1996](https://github.com/PyO3/pyo3/pull/1996) - Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009) - Add `#[pyo3(crate = "some::path")]` option to all attribute macros (except the deprecated `#[pyproto]`). [#2022](https://github.com/PyO3/pyo3/pull/2022) - Enable `create_exception!` macro to take an optional docstring. [#2027](https://github.com/PyO3/pyo3/pull/2027) - Enable `#[pyclass]` for fieldless (aka C-like) enums. [#2034](https://github.com/PyO3/pyo3/pull/2034) - Add buffer magic methods `__getbuffer__` and `__releasebuffer__` to `#[pymethods]`. [#2067](https://github.com/PyO3/pyo3/pull/2067) - Add support for paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081) - Enable `wrap_pyfunction!` to wrap a `#[pyfunction]` implemented in a different Rust module or crate. [#2091](https://github.com/PyO3/pyo3/pull/2091) - Add `PyAny::contains` method (`in` operator for `PyAny`). [#2115](https://github.com/PyO3/pyo3/pull/2115) - Add `PyMapping::contains` method (`in` operator for `PyMapping`). [#2133](https://github.com/PyO3/pyo3/pull/2133) - Add garbage collection magic magic methods `__traverse__` and `__clear__` to `#[pymethods]`. [#2159](https://github.com/PyO3/pyo3/pull/2159) - Add support for `from_py_with` on struct tuples and enums to override the default from-Python conversion. [#2181](https://github.com/PyO3/pyo3/pull/2181) - Add `eq`, `ne`, `lt`, `le`, `gt`, `ge` methods to `PyAny` that wrap `rich_compare`. [#2175](https://github.com/PyO3/pyo3/pull/2175) - Add `Py::is` and `PyAny::is` methods to check for object identity. [#2183](https://github.com/PyO3/pyo3/pull/2183) - Add support for the `__getattribute__` magic method. [#2187](https://github.com/PyO3/pyo3/pull/2187) ### Changed - `PyType::is_subclass`, `PyErr::is_instance` and `PyAny::is_instance` now operate run-time type object instead of a type known at compile-time. The old behavior is still available as `PyType::is_subclass_of`, `PyErr::is_instance_of` and `PyAny::is_instance_of`. [#1985](https://github.com/PyO3/pyo3/pull/1985) - Rename some methods on `PyErr` (the old names are just marked deprecated for now): [#2026](https://github.com/PyO3/pyo3/pull/2026) - `pytype` -> `get_type` - `pvalue` -> `value` (and deprecate equivalent `instance`) - `ptraceback` -> `traceback` - `from_instance` -> `from_value` - `into_instance` -> `into_value` - `PyErr::new_type` now takes an optional docstring and now returns `PyResult>` rather than a `ffi::PyTypeObject` pointer. [#2027](https://github.com/PyO3/pyo3/pull/2027) - Deprecate `PyType::is_instance`; it is inconsistent with other `is_instance` methods in PyO3. Instead of `typ.is_instance(obj)`, use `obj.is_instance(typ)`. [#2031](https://github.com/PyO3/pyo3/pull/2031) - `__getitem__`, `__setitem__` and `__delitem__` in `#[pymethods]` now implement both a Python mapping and sequence by default. [#2065](https://github.com/PyO3/pyo3/pull/2065) - Improve performance and error messages for `#[derive(FromPyObject)]` for enums. [#2068](https://github.com/PyO3/pyo3/pull/2068) - Reduce generated LLVM code size (to improve compile times) for: - internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074) [#2158](https://github.com/PyO3/pyo3/pull/2158) - `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075) [#2085](https://github.com/PyO3/pyo3/pull/2085) - `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081) [#2157](https://github.com/PyO3/pyo3/pull/2157) - Respect Rust privacy rules for items wrapped with `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081) - Add modulo argument to `__ipow__` magic method. [#2083](https://github.com/PyO3/pyo3/pull/2083) - Fix FFI definition for `_PyCFunctionFast`. [#2126](https://github.com/PyO3/pyo3/pull/2126) - `PyDateTimeAPI` and `PyDateTime_TimeZone_UTC` are now unsafe functions instead of statics. [#2126](https://github.com/PyO3/pyo3/pull/2126) - `PyDateTimeAPI` does not implicitly call `PyDateTime_IMPORT` anymore to reflect the original Python API more closely. Before the first call to `PyDateTime_IMPORT` a null pointer is returned. Therefore before calling any of the following FFI functions `PyDateTime_IMPORT` must be called to avoid undefined behavior: [#2126](https://github.com/PyO3/pyo3/pull/2126) - `PyDateTime_TimeZone_UTC` - `PyDate_Check` - `PyDate_CheckExact` - `PyDateTime_Check` - `PyDateTime_CheckExact` - `PyTime_Check` - `PyTime_CheckExact` - `PyDelta_Check` - `PyDelta_CheckExact` - `PyTZInfo_Check` - `PyTZInfo_CheckExact` - `PyDateTime_FromTimestamp` - `PyDate_FromTimestamp` - Deprecate the `gc` option for `pyclass` (e.g. `#[pyclass(gc)]`). Just implement a `__traverse__` `#[pymethod]`. [#2159](https://github.com/PyO3/pyo3/pull/2159) - The `ml_meth` field of `PyMethodDef` is now represented by the `PyMethodDefPointer` union. [2166](https://github.com/PyO3/pyo3/pull/2166) - Deprecate the `#[pyproto]` traits. [#2173](https://github.com/PyO3/pyo3/pull/2173) ### Removed - Remove all functionality deprecated in PyO3 0.14. [#2007](https://github.com/PyO3/pyo3/pull/2007) - Remove `Default` impl for `PyMethodDef`. [#2166](https://github.com/PyO3/pyo3/pull/2166) - Remove `PartialEq` impl for `Py` and `PyAny` (use the new `is` instead). [#2183](https://github.com/PyO3/pyo3/pull/2183) ### Fixed - Fix undefined symbol for `PyObject_HasAttr` on PyPy. [#2025](https://github.com/PyO3/pyo3/pull/2025) - Fix memory leak in `PyErr::into_value`. [#2026](https://github.com/PyO3/pyo3/pull/2026) - Fix clippy warning `needless-option-as-deref` in code generated by `#[pyfunction]` and `#[pymethods]`. [#2040](https://github.com/PyO3/pyo3/pull/2040) - Fix undefined behavior in `PySlice::indices`. [#2061](https://github.com/PyO3/pyo3/pull/2061) - Fix the `wrap_pymodule!` macro using the wrong name for a `#[pymodule]` with a `#[pyo3(name = "..")]` attribute. [#2081](https://github.com/PyO3/pyo3/pull/2081) - Fix magic methods in `#[pymethods]` accepting implementations with the wrong number of arguments. [#2083](https://github.com/PyO3/pyo3/pull/2083) - Fix panic in `#[pyfunction]` generated code when a required argument following an `Option` was not provided. [#2093](https://github.com/PyO3/pyo3/pull/2093) - Fixed undefined behavior caused by incorrect `ExactSizeIterator` implementations. [#2124](https://github.com/PyO3/pyo3/pull/2124) - Fix missing FFI definition `PyCMethod_New` on Python 3.9 and up. [#2143](https://github.com/PyO3/pyo3/pull/2143) - Add missing FFI definitions `_PyLong_NumBits` and `_PyLong_AsByteArray` on PyPy. [#2146](https://github.com/PyO3/pyo3/pull/2146) - Fix memory leak in implementation of `AsPyPointer` for `Option`. [#2160](https://github.com/PyO3/pyo3/pull/2160) - 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) ## [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 ### Added - Add implementations for `Py::as_ref` and `Py::into_ref` for `Py`, `Py` and `Py`. [#1682](https://github.com/PyO3/pyo3/pull/1682) - Add `PyTraceback` type to represent and format Python tracebacks. [#1977](https://github.com/PyO3/pyo3/pull/1977) ### Changed - `#[classattr]` constants with a known magic method name (which is lowercase) no longer trigger lint warnings expecting constants to be uppercase. [#1969](https://github.com/PyO3/pyo3/pull/1969) ### Fixed - Fix creating `#[classattr]` by functions with the name of a known magic method. [#1969](https://github.com/PyO3/pyo3/pull/1969) - Fix use of `catch_unwind` in `allow_threads` which can cause fatal crashes. [#1989](https://github.com/PyO3/pyo3/pull/1989) - Fix build failure on PyPy when abi3 features are activated. [#1991](https://github.com/PyO3/pyo3/pull/1991) - Fix mingw platform detection. [#1993](https://github.com/PyO3/pyo3/pull/1993) - Fix panic in `__get__` implementation when accessing descriptor on type object. [#1997](https://github.com/PyO3/pyo3/pull/1997) ## [0.15.0] - 2021-11-03 ### Packaging - `pyo3`'s `Cargo.toml` now advertises `links = "python"` to inform Cargo that it links against *libpython*. [#1819](https://github.com/PyO3/pyo3/pull/1819) - Added optional `anyhow` feature to convert `anyhow::Error` into `PyErr`. [#1822](https://github.com/PyO3/pyo3/pull/1822) - Support Python 3.10. [#1889](https://github.com/PyO3/pyo3/pull/1889) - Added optional `eyre` feature to convert `eyre::Report` into `PyErr`. [#1893](https://github.com/PyO3/pyo3/pull/1893) - Support PyPy 3.8. [#1948](https://github.com/PyO3/pyo3/pull/1948) ### Added - Add `PyList::get_item_unchecked` and `PyTuple::get_item_unchecked` to get items without bounds checks. [#1733](https://github.com/PyO3/pyo3/pull/1733) - Support `#[doc = include_str!(...)]` attributes on Rust 1.54 and up. [#1746](https://github.com/PyO3/pyo3/issues/1746) - Add `PyAny::py` as a convenience for `PyNativeType::py`. [#1751](https://github.com/PyO3/pyo3/pull/1751) - Add implementation of `std::ops::Index` for `PyList`, `PyTuple` and `PySequence`. [#1825](https://github.com/PyO3/pyo3/pull/1825) - Add range indexing implementations of `std::ops::Index` for `PyList`, `PyTuple` and `PySequence`. [#1829](https://github.com/PyO3/pyo3/pull/1829) - Add `PyMapping` type to represent the Python mapping protocol. [#1844](https://github.com/PyO3/pyo3/pull/1844) - Add commonly-used sequence methods to `PyList` and `PyTuple`. [#1849](https://github.com/PyO3/pyo3/pull/1849) - Add `as_sequence` methods to `PyList` and `PyTuple`. [#1860](https://github.com/PyO3/pyo3/pull/1860) - Add support for magic methods in `#[pymethods]`, intended as a replacement for `#[pyproto]`. [#1864](https://github.com/PyO3/pyo3/pull/1864) - Add `abi3-py310` feature. [#1889](https://github.com/PyO3/pyo3/pull/1889) - Add `PyCFunction::new_closure` to create a Python function from a Rust closure. [#1901](https://github.com/PyO3/pyo3/pull/1901) - Add support for positional-only arguments in `#[pyfunction]`. [#1925](https://github.com/PyO3/pyo3/pull/1925) - Add `PyErr::take` to attempt to fetch a Python exception if present. [#1957](https://github.com/PyO3/pyo3/pull/1957) ### Changed - `PyList`, `PyTuple` and `PySequence`'s APIs now accepts only `usize` indices instead of `isize`. [#1733](https://github.com/PyO3/pyo3/pull/1733), [#1802](https://github.com/PyO3/pyo3/pull/1802), [#1803](https://github.com/PyO3/pyo3/pull/1803) - `PyList::get_item` and `PyTuple::get_item` now return `PyResult<&PyAny>` instead of panicking. [#1733](https://github.com/PyO3/pyo3/pull/1733) - `PySequence::in_place_repeat` and `PySequence::in_place_concat` now return `PyResult<&PySequence>` instead of `PyResult<()>`, which is needed in case of immutable sequences such as tuples. [#1803](https://github.com/PyO3/pyo3/pull/1803) - `PySequence::get_slice` now returns `PyResult<&PySequence>` instead of `PyResult<&PyAny>`. [#1829](https://github.com/PyO3/pyo3/pull/1829) - Deprecate `PyTuple::split_from`. [#1804](https://github.com/PyO3/pyo3/pull/1804) - Deprecate `PyTuple::slice`, new method `PyTuple::get_slice` added with `usize` indices. [#1828](https://github.com/PyO3/pyo3/pull/1828) - Deprecate FFI definitions `PyParser_SimpleParseStringFlags`, `PyParser_SimpleParseStringFlagsFilename`, `PyParser_SimpleParseFileFlags` when building for Python 3.9. [#1830](https://github.com/PyO3/pyo3/pull/1830) - Mark FFI definitions removed in Python 3.10 `PyParser_ASTFromString`, `PyParser_ASTFromStringObject`, `PyParser_ASTFromFile`, `PyParser_ASTFromFileObject`, `PyParser_SimpleParseStringFlags`, `PyParser_SimpleParseStringFlagsFilename`, `PyParser_SimpleParseFileFlags`, `PyParser_SimpleParseString`, `PyParser_SimpleParseFile`, `Py_SymtableString`, and `Py_SymtableStringObject`. [#1830](https://github.com/PyO3/pyo3/pull/1830) - `#[pymethods]` now handles magic methods similarly to `#[pyproto]`. In the future, `#[pyproto]` may be deprecated. [#1864](https://github.com/PyO3/pyo3/pull/1864) - Deprecate FFI definitions `PySys_AddWarnOption`, `PySys_AddWarnOptionUnicode` and `PySys_HasWarnOptions`. [#1887](https://github.com/PyO3/pyo3/pull/1887) - Deprecate `#[call]` attribute in favor of using `fn __call__`. [#1929](https://github.com/PyO3/pyo3/pull/1929) - Fix missing FFI definition `_PyImport_FindExtensionObject` on Python 3.10. [#1942](https://github.com/PyO3/pyo3/pull/1942) - Change `PyErr::fetch` to panic in debug mode if no exception is present. [#1957](https://github.com/PyO3/pyo3/pull/1957) ### Fixed - Fix building with a conda environment on Windows. [#1873](https://github.com/PyO3/pyo3/pull/1873) - Fix panic on Python 3.6 when calling `Python::with_gil` with Python initialized but threading not initialized. [#1874](https://github.com/PyO3/pyo3/pull/1874) - Fix incorrect linking to version-specific DLL instead of `python3.dll` when cross-compiling to Windows with `abi3`. [#1880](https://github.com/PyO3/pyo3/pull/1880) - Fix FFI definition for `PyTuple_ClearFreeList` incorrectly being present for Python 3.9 and up. [#1887](https://github.com/PyO3/pyo3/pull/1887) - Fix panic in generated `#[derive(FromPyObject)]` for enums. [#1888](https://github.com/PyO3/pyo3/pull/1888) - Fix cross-compiling to Python 3.7 builds with the "m" abi flag. [#1908](https://github.com/PyO3/pyo3/pull/1908) - Fix `__mod__` magic method fallback to `__rmod__`. [#1934](https://github.com/PyO3/pyo3/pull/1934). - Fix missing FFI definition `_PyImport_FindExtensionObject` on Python 3.10. [#1942](https://github.com/PyO3/pyo3/pull/1942) ## [0.14.5] - 2021-09-05 ### Added - Make `pyo3_build_config::InterpreterConfig` and subfields public. [#1848](https://github.com/PyO3/pyo3/pull/1848) - Add `resolve-config` feature to the `pyo3-build-config` to control whether its build script does anything. [#1856](https://github.com/PyO3/pyo3/pull/1856) ### Fixed - Fix 0.14.4 compile regression on `s390x-unknown-linux-gnu` target. [#1850](https://github.com/PyO3/pyo3/pull/1850) ## [0.14.4] - 2021-08-29 ### Changed - Mark `PyString::data` as `unsafe` and disable it and some supporting PyUnicode FFI APIs (which depend on a C bitfield) on big-endian targets. [#1834](https://github.com/PyO3/pyo3/pull/1834) ## [0.14.3] - 2021-08-22 ### Added - Add `PyString::data` to access the raw bytes stored in a Python string. [#1794](https://github.com/PyO3/pyo3/pull/1794) ### Fixed - Raise `AttributeError` to avoid panic when calling `del` on a `#[setter]` defined class property. [#1779](https://github.com/PyO3/pyo3/pull/1779) - Restrict FFI definitions `PyGILState_Check` and `Py_tracefunc` to the unlimited API. [#1787](https://github.com/PyO3/pyo3/pull/1787) - Add missing `_type` field to `PyStatus` struct definition. [#1791](https://github.com/PyO3/pyo3/pull/1791) - Reduce lower bound `num-complex` optional dependency to support interop with `rust-numpy` and `ndarray` when building with the MSRV of 1.41 [#1799](https://github.com/PyO3/pyo3/pull/1799) - Fix memory leak in `Python::run_code`. [#1806](https://github.com/PyO3/pyo3/pull/1806) - Fix memory leak in `PyModule::from_code`. [#1810](https://github.com/PyO3/pyo3/pull/1810) - Remove use of `pyo3::` in `pyo3::types::datetime` which broke builds using `-Z avoid-dev-deps` [#1811](https://github.com/PyO3/pyo3/pull/1811) ## [0.14.2] - 2021-08-09 ### Added - Add `indexmap` feature to add `ToPyObject`, `IntoPy` and `FromPyObject` implementations for `indexmap::IndexMap`. [#1728](https://github.com/PyO3/pyo3/pull/1728) - Add `pyo3_build_config::add_extension_module_link_args` to use in build scripts to set linker arguments (for macOS). [#1755](https://github.com/PyO3/pyo3/pull/1755) - Add `Python::with_gil_unchecked` unsafe variation of `Python::with_gil` to allow obtaining a `Python` in scenarios where `Python::with_gil` would fail. [#1769](https://github.com/PyO3/pyo3/pull/1769) ### Changed - `PyErr::new` no longer acquires the Python GIL internally. [#1724](https://github.com/PyO3/pyo3/pull/1724) - Reverted PyO3 0.14.0's use of `cargo:rustc-cdylib-link-arg` in its build script, as Cargo unintentionally allowed crates to pass linker args to downstream crates in this way. Projects supporting macOS may need to restore `.cargo/config.toml` files. [#1755](https://github.com/PyO3/pyo3/pull/1755) ### Fixed - Fix regression in 0.14.0 rejecting usage of `#[doc(hidden)]` on structs and functions annotated with PyO3 macros. [#1722](https://github.com/PyO3/pyo3/pull/1722) - Fix regression in 0.14.0 leading to incorrect code coverage being computed for `#[pyfunction]`s. [#1726](https://github.com/PyO3/pyo3/pull/1726) - Fix incorrect FFI definition of `Py_Buffer` on PyPy. [#1737](https://github.com/PyO3/pyo3/pull/1737) - Fix incorrect calculation of `dictoffset` on 32-bit Windows. [#1475](https://github.com/PyO3/pyo3/pull/1475) - Fix regression in 0.13.2 leading to linking to incorrect Python library on Windows "gnu" targets. [#1759](https://github.com/PyO3/pyo3/pull/1759) - Fix compiler warning: deny trailing semicolons in expression macro. [#1762](https://github.com/PyO3/pyo3/pull/1762) - Fix incorrect FFI definition of `Py_DecodeLocale`. The 2nd argument is now `*mut Py_ssize_t` instead of `Py_ssize_t`. [#1766](https://github.com/PyO3/pyo3/pull/1766) ## [0.14.1] - 2021-07-04 ### Added - Implement `IntoPy` for `&PathBuf` and `&OsString`. [#1712](https://github.com/PyO3/pyo3/pull/1712) ### Fixed - Fix crashes on PyPy due to incorrect definitions of `PyList_SET_ITEM`. [#1713](https://github.com/PyO3/pyo3/pull/1713) ## [0.14.0] - 2021-07-03 ### Packaging - Update `num-bigint` optional dependency to 0.4. [#1481](https://github.com/PyO3/pyo3/pull/1481) - Update `num-complex` optional dependency to 0.4. [#1482](https://github.com/PyO3/pyo3/pull/1482) - Extend `hashbrown` optional dependency supported versions to include 0.11. [#1496](https://github.com/PyO3/pyo3/pull/1496) - Support PyPy 3.7. [#1538](https://github.com/PyO3/pyo3/pull/1538) ### Added - Extend conversions for `[T; N]` to all `N` using const generics (on Rust 1.51 and up). [#1128](https://github.com/PyO3/pyo3/pull/1128) - Add conversions between `OsStr`/ `OsString` and Python strings. [#1379](https://github.com/PyO3/pyo3/pull/1379) - Add conversions between `Path`/ `PathBuf` and Python strings (and `pathlib.Path` objects). [#1379](https://github.com/PyO3/pyo3/pull/1379) [#1654](https://github.com/PyO3/pyo3/pull/1654) - Add a new set of `#[pyo3(...)]` attributes to control various PyO3 macro functionality: - `#[pyo3(from_py_with = "...")]` function arguments and struct fields to override the default from-Python conversion. [#1411](https://github.com/PyO3/pyo3/pull/1411) - `#[pyo3(name = "...")]` for setting Python names. [#1567](https://github.com/PyO3/pyo3/pull/1567) - `#[pyo3(text_signature = "...")]` for setting text signature. [#1658](https://github.com/PyO3/pyo3/pull/1658) - Add FFI definition `PyCFunction_CheckExact` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425) - Add FFI definition `Py_IS_TYPE`. [#1429](https://github.com/PyO3/pyo3/pull/1429) - Add FFI definition `_Py_InitializeMain`. [#1473](https://github.com/PyO3/pyo3/pull/1473) - Add FFI definitions from `cpython/import.h`.[#1475](https://github.com/PyO3/pyo3/pull/1475) - Add tuple and unit struct support for `#[pyclass]` macro. [#1504](https://github.com/PyO3/pyo3/pull/1504) - Add FFI definition `PyDateTime_TimeZone_UTC`. [#1572](https://github.com/PyO3/pyo3/pull/1572) - Add support for `#[pyclass(extends=Exception)]`. [#1591](https://github.com/PyO3/pyo3/pull/1591) - Add `PyErr::cause` and `PyErr::set_cause`. [#1679](https://github.com/PyO3/pyo3/pull/1679) - Add FFI definitions from `cpython/pystate.h`. [#1687](https://github.com/PyO3/pyo3/pull/1687/) - Add `wrap_pyfunction!` macro to `pyo3::prelude`. [#1695](https://github.com/PyO3/pyo3/pull/1695) ### Changed - Allow only one `#[pymethods]` block per `#[pyclass]` by default, to remove the dependency on `inventory`. Add a `multiple-pymethods` feature to opt-in the original behavior and dependency on `inventory`. [#1457](https://github.com/PyO3/pyo3/pull/1457) - Change `PyTimeAccess::get_fold` to return a `bool` instead of a `u8`. [#1397](https://github.com/PyO3/pyo3/pull/1397) - Deprecate FFI definition `PyCFunction_Call` for Python 3.9 and up. [#1425](https://github.com/PyO3/pyo3/pull/1425) - Deprecate FFI definition `PyModule_GetFilename`. [#1425](https://github.com/PyO3/pyo3/pull/1425) - The `auto-initialize` feature is no longer enabled by default. [#1443](https://github.com/PyO3/pyo3/pull/1443) - Change `PyCFunction::new` and `PyCFunction::new_with_keywords` to take `&'static str` arguments rather than implicitly copying (and leaking) them. [#1450](https://github.com/PyO3/pyo3/pull/1450) - Deprecate `PyModule::call`, `PyModule::call0`, `PyModule::call1` and `PyModule::get`. [#1492](https://github.com/PyO3/pyo3/pull/1492) - Add length information to `PyBufferError`s raised from `PyBuffer::copy_to_slice` and `PyBuffer::copy_from_slice`. [#1534](https://github.com/PyO3/pyo3/pull/1534) - Automatically set `-undefined` and `dynamic_lookup` linker arguments on macOS with the `extension-module` feature. [#1539](https://github.com/PyO3/pyo3/pull/1539) - Deprecate `#[pyproto]` methods which are easier to implement as `#[pymethods]`: [#1560](https://github.com/PyO3/pyo3/pull/1560) - `PyBasicProtocol::__bytes__` and `PyBasicProtocol::__format__` - `PyContextProtocol::__enter__` and `PyContextProtocol::__exit__` - `PyDescrProtocol::__delete__` and `PyDescrProtocol::__set_name__` - `PyMappingProtocol::__reversed__` - `PyNumberProtocol::__complex__` and `PyNumberProtocol::__round__` - `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__` - Deprecate several attributes in favor of the new `#[pyo3(...)]` options: - `#[name = "..."]`, replaced by `#[pyo3(name = "...")]` [#1567](https://github.com/PyO3/pyo3/pull/1567) - `#[pyfn(m, "name")]`, replaced by `#[pyfn(m)] #[pyo3(name = "...")]`. [#1610](https://github.com/PyO3/pyo3/pull/1610) - `#[pymodule(name)]`, replaced by `#[pymodule] #[pyo3(name = "...")]` [#1650](https://github.com/PyO3/pyo3/pull/1650) - `#[text_signature = "..."]`, replaced by `#[pyo3(text_signature = "...")]`. [#1658](https://github.com/PyO3/pyo3/pull/1658) - Reduce LLVM line counts to improve compilation times. [#1604](https://github.com/PyO3/pyo3/pull/1604) - No longer call `PyEval_InitThreads` in `#[pymodule]` init code. [#1630](https://github.com/PyO3/pyo3/pull/1630) - Use `METH_FASTCALL` argument passing convention, when possible, to improve `#[pyfunction]` and method performance. [#1619](https://github.com/PyO3/pyo3/pull/1619), [#1660](https://github.com/PyO3/pyo3/pull/1660) - Filter sysconfigdata candidates by architecture when cross-compiling. [#1626](https://github.com/PyO3/pyo3/pull/1626) ### Removed - Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426) - Remove deprecated methods `Python::is_instance`, `Python::is_subclass`, `Python::release`, `Python::xdecref`, and `Py::from_owned_ptr_or_panic`. [#1426](https://github.com/PyO3/pyo3/pull/1426) - Remove many FFI definitions which never existed in the Python C-API: - (previously deprecated) `PyGetSetDef_INIT`, `PyGetSetDef_DICT`, `PyCoro_Check`, `PyCoroWrapper_Check`, and `PyAsyncGen_Check` [#1426](https://github.com/PyO3/pyo3/pull/1426) - `PyMethodDef_INIT` [#1426](https://github.com/PyO3/pyo3/pull/1426) - `PyTypeObject_INIT` [#1429](https://github.com/PyO3/pyo3/pull/1429) - `PyObject_Check`, `PySuper_Check`, and `FreeFunc` [#1438](https://github.com/PyO3/pyo3/pull/1438) - `PyModuleDef_INIT` [#1630](https://github.com/PyO3/pyo3/pull/1630) - Remove pyclass implementation details from `PyTypeInfo`: - `Type`, `DESCRIPTION`, and `FLAGS` [#1456](https://github.com/PyO3/pyo3/pull/1456) - `BaseType`, `BaseLayout`, `Layout`, `Initializer` [#1596](https://github.com/PyO3/pyo3/pull/1596) - Remove `PYO3_CROSS_INCLUDE_DIR` environment variable and the associated C header parsing functionality. [#1521](https://github.com/PyO3/pyo3/pull/1521) - Remove `raw_pycfunction!` macro. [#1619](https://github.com/PyO3/pyo3/pull/1619) - Remove `PyClassAlloc` trait. [#1657](https://github.com/PyO3/pyo3/pull/1657) - Remove `PyList::get_parked_item`. [#1664](https://github.com/PyO3/pyo3/pull/1664) ### Fixed - Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425) - `PYO3_CROSS_LIB_DIR` environment variable no long required when compiling for x86-64 Python from macOS arm64 and reverse. [#1428](https://github.com/PyO3/pyo3/pull/1428) - Fix FFI definition `_PyEval_RequestCodeExtraIndex`, which took an argument of the wrong type. [#1429](https://github.com/PyO3/pyo3/pull/1429) - Fix FFI definition `PyIndex_Check` missing with the `abi3` feature. [#1436](https://github.com/PyO3/pyo3/pull/1436) - Fix incorrect `TypeError` raised when keyword-only argument passed along with a positional argument in `*args`. [#1440](https://github.com/PyO3/pyo3/pull/1440) - Fix inability to use a named lifetime for `&PyTuple` of `*args` in `#[pyfunction]`. [#1440](https://github.com/PyO3/pyo3/pull/1440) - Fix use of Python argument for `#[pymethods]` inside macro expansions. [#1505](https://github.com/PyO3/pyo3/pull/1505) - No longer include `__doc__` in `__all__` generated for `#[pymodule]`. [#1509](https://github.com/PyO3/pyo3/pull/1509) - Always use cross-compiling configuration if any of the `PYO3_CROSS` family of environment variables are set. [#1514](https://github.com/PyO3/pyo3/pull/1514) - Support `EnvironmentError`, `IOError`, and `WindowsError` on PyPy. [#1533](https://github.com/PyO3/pyo3/pull/1533) - Fix unnecessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557) - Fix segfault when dereferencing `ffi::PyDateTimeAPI` without the GIL. [#1563](https://github.com/PyO3/pyo3/pull/1563) - Fix memory leak in `FromPyObject` implementations for `u128` and `i128`. [#1638](https://github.com/PyO3/pyo3/pull/1638) - Fix `#[pyclass(extends=PyDict)]` leaking the dict contents on drop. [#1657](https://github.com/PyO3/pyo3/pull/1657) - Fix segfault when calling `PyList::get_item` with negative indices. [#1668](https://github.com/PyO3/pyo3/pull/1668) - Fix FFI definitions of `PyEval_SetProfile`/`PyEval_SetTrace` to take `Option` parameters. [#1692](https://github.com/PyO3/pyo3/pull/1692) - Fix `ToPyObject` impl for `HashSet` to accept non-default hashers. [#1702](https://github.com/PyO3/pyo3/pull/1702) ## [0.13.2] - 2021-02-12 ### Packaging - Lower minimum supported Rust version to 1.41. [#1421](https://github.com/PyO3/pyo3/pull/1421) ### Added - Add unsafe API `with_embedded_python_interpreter` to initialize a Python interpreter, execute a closure, and finalize the interpreter. [#1355](https://github.com/PyO3/pyo3/pull/1355) - Add `serde` feature which provides implementations of `Serialize` and `Deserialize` for `Py`. [#1366](https://github.com/PyO3/pyo3/pull/1366) - Add FFI definition `_PyCFunctionFastWithKeywords` on Python 3.7 and up. [#1384](https://github.com/PyO3/pyo3/pull/1384) - Add `PyDateTime::new_with_fold` method. [#1398](https://github.com/PyO3/pyo3/pull/1398) - Add `size_hint` impls for `{PyDict,PyList,PySet,PyTuple}Iterator`s. [#1699](https://github.com/PyO3/pyo3/pull/1699) ### Changed - `prepare_freethreaded_python` will no longer register an `atexit` handler to call `Py_Finalize`. This resolves a number of issues with incompatible C extensions causing crashes at finalization. [#1355](https://github.com/PyO3/pyo3/pull/1355) - Mark `PyLayout::py_init`, `PyClassDict::clear_dict`, and `opt_to_pyobj` safe, as they do not perform any unsafe operations. [#1404](https://github.com/PyO3/pyo3/pull/1404) ### Fixed - Fix support for using `r#raw_idents` as argument names in pyfunctions. [#1383](https://github.com/PyO3/pyo3/pull/1383) - Fix typo in FFI definition for `PyFunction_GetCode` (was incorrectly `PyFunction_Code`). [#1387](https://github.com/PyO3/pyo3/pull/1387) - Fix FFI definitions `PyMarshal_WriteObjectToString` and `PyMarshal_ReadObjectFromString` as available in limited API. [#1387](https://github.com/PyO3/pyo3/pull/1387) - Fix FFI definitions `PyListObject` and those from `funcobject.h` as requiring non-limited API. [#1387](https://github.com/PyO3/pyo3/pull/1387) - Fix unqualified `Result` usage in `pyobject_native_type_base`. [#1402](https://github.com/PyO3/pyo3/pull/1402) - Fix build on systems where the default Python encoding is not UTF-8. [#1405](https://github.com/PyO3/pyo3/pull/1405) - Fix build on mingw / MSYS2. [#1423](https://github.com/PyO3/pyo3/pull/1423) ## [0.13.1] - 2021-01-10 ### Added - Add support for `#[pyclass(dict)]` and `#[pyclass(weakref)]` with the `abi3` feature on Python 3.9 and up. [#1342](https://github.com/PyO3/pyo3/pull/1342) - Add FFI definitions `PyOS_BeforeFork`, `PyOS_AfterFork_Parent`, `PyOS_AfterFork_Child` for Python 3.7 and up. [#1348](https://github.com/PyO3/pyo3/pull/1348) - Add an `auto-initialize` feature to control whether PyO3 should automatically initialize an embedded Python interpreter. For compatibility this feature is enabled by default in PyO3 0.13.1, but is planned to become opt-in from PyO3 0.14.0. [#1347](https://github.com/PyO3/pyo3/pull/1347) - Add support for cross-compiling to Windows without needing `PYO3_CROSS_INCLUDE_DIR`. [#1350](https://github.com/PyO3/pyo3/pull/1350) ### Deprecated - Deprecate FFI definitions `PyEval_CallObjectWithKeywords`, `PyEval_CallObject`, `PyEval_CallFunction`, `PyEval_CallMethod` when building for Python 3.9. [#1338](https://github.com/PyO3/pyo3/pull/1338) - Deprecate FFI definitions `PyGetSetDef_DICT` and `PyGetSetDef_INIT` which have never been in the Python API. [#1341](https://github.com/PyO3/pyo3/pull/1341) - Deprecate FFI definitions `PyGen_NeedsFinalizing`, `PyImport_Cleanup` (removed in 3.9), and `PyOS_InitInterrupts` (3.10). [#1348](https://github.com/PyO3/pyo3/pull/1348) - Deprecate FFI definition `PyOS_AfterFork` for Python 3.7 and up. [#1348](https://github.com/PyO3/pyo3/pull/1348) - Deprecate FFI definitions `PyCoro_Check`, `PyAsyncGen_Check`, and `PyCoroWrapper_Check`, which have never been in the Python API (for the first two, it is possible to use `PyCoro_CheckExact` and `PyAsyncGen_CheckExact` instead; these are the actual functions provided by the Python API). [#1348](https://github.com/PyO3/pyo3/pull/1348) - Deprecate FFI definitions for `PyUnicode_FromUnicode`, `PyUnicode_AsUnicode` and `PyUnicode_AsUnicodeAndSize`, which will be removed from 3.12 and up due to [PEP 623](https://www.python.org/dev/peps/pep-0623/). [#1370](https://github.com/PyO3/pyo3/pull/1370) ### Removed - Remove FFI definition `PyFrame_ClearFreeList` when building for Python 3.9. [#1341](https://github.com/PyO3/pyo3/pull/1341) - Remove FFI definition `_PyDict_Contains` when building for Python 3.10. [#1341](https://github.com/PyO3/pyo3/pull/1341) - Remove FFI definitions `PyGen_NeedsFinalizing` and `PyImport_Cleanup` (for 3.9 and up), and `PyOS_InitInterrupts` (3.10). [#1348](https://github.com/PyO3/pyo3/pull/1348) ### Fixed - Stop including `Py_TRACE_REFS` config setting automatically if `Py_DEBUG` is set on Python 3.8 and up. [#1334](https://github.com/PyO3/pyo3/pull/1334) - Remove `#[deny(warnings)]` attribute (and instead refuse warnings only in CI). [#1340](https://github.com/PyO3/pyo3/pull/1340) - Fix deprecation warning for missing `__module__` with `#[pyclass]`. [#1343](https://github.com/PyO3/pyo3/pull/1343) - Correct return type of `PyFrozenSet::empty` to `&PyFrozenSet` (was incorrectly `&PySet`). [#1351](https://github.com/PyO3/pyo3/pull/1351) - Fix missing `Py_INCREF` on heap type objects on Python versions before 3.8. [#1365](https://github.com/PyO3/pyo3/pull/1365) ## [0.13.0] - 2020-12-22 ### Packaging - Drop support for Python 3.5 (as it is now end-of-life). [#1250](https://github.com/PyO3/pyo3/pull/1250) - Bump minimum supported Rust version to 1.45. [#1272](https://github.com/PyO3/pyo3/pull/1272) - Bump indoc dependency to 1.0. [#1272](https://github.com/PyO3/pyo3/pull/1272) - Bump paste dependency to 1.0. [#1272](https://github.com/PyO3/pyo3/pull/1272) - Rename internal crates `pyo3cls` and `pyo3-derive-backend` to `pyo3-macros` and `pyo3-macros-backend` respectively. [#1317](https://github.com/PyO3/pyo3/pull/1317) ### Added - Add support for building for CPython limited API. Opting-in to the limited API enables a single extension wheel built with PyO3 to be installable on multiple Python versions. This required a few minor changes to runtime behavior of of PyO3 `#[pyclass]` types. See the migration guide for full details. [#1152](https://github.com/PyO3/pyo3/pull/1152) - Add feature flags `abi3-py36`, `abi3-py37`, `abi3-py38` etc. to set the minimum Python version when using the limited API. [#1263](https://github.com/PyO3/pyo3/pull/1263) - Add argument names to `TypeError` messages generated by pymethod wrappers. [#1212](https://github.com/PyO3/pyo3/pull/1212) - Add FFI definitions for PEP 587 "Python Initialization Configuration". [#1247](https://github.com/PyO3/pyo3/pull/1247) - Add FFI definitions for `PyEval_SetProfile` and `PyEval_SetTrace`. [#1255](https://github.com/PyO3/pyo3/pull/1255) - Add FFI definitions for context.h functions (`PyContext_New`, etc). [#1259](https://github.com/PyO3/pyo3/pull/1259) - Add `PyAny::is_instance` method. [#1276](https://github.com/PyO3/pyo3/pull/1276) - Add support for conversion between `char` and `PyString`. [#1282](https://github.com/PyO3/pyo3/pull/1282) - 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 conversions between `u128`/`i128` and `PyLong` for PyPy. [#1310](https://github.com/PyO3/pyo3/pull/1310) - Add `Python::version` and `Python::version_info` to get the running interpreter version. [#1322](https://github.com/PyO3/pyo3/pull/1322) - Add conversions for tuples of length 10, 11, and 12. [#1454](https://github.com/PyO3/pyo3/pull/1454) ### Changed - Change return type of `PyType::name` from `Cow` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152) - `#[pyclass(subclass)]` is now required for subclassing from Rust (was previously just required for subclassing from Python). [#1152](https://github.com/PyO3/pyo3/pull/1152) - Change `PyIterator` to be consistent with other native types: it is now used as `&PyIterator` instead of `PyIterator<'a>`. [#1176](https://github.com/PyO3/pyo3/pull/1176) - Change formatting of `PyDowncastError` messages to be closer to Python's builtin error messages. [#1212](https://github.com/PyO3/pyo3/pull/1212) - Change `Debug` and `Display` impls for `PyException` to be consistent with `PyAny`. [#1275](https://github.com/PyO3/pyo3/pull/1275) - Change `Debug` impl of `PyErr` to output more helpful information (acquiring the GIL if necessary). [#1275](https://github.com/PyO3/pyo3/pull/1275) - Rename `PyTypeInfo::is_instance` and `PyTypeInfo::is_exact_instance` to `PyTypeInfo::is_type_of` and `PyTypeInfo::is_exact_type_of`. [#1278](https://github.com/PyO3/pyo3/pull/1278) - Optimize `PyAny::call0`, `Py::call0` and `PyAny::call_method0` and `Py::call_method0` on Python 3.9 and up. [#1287](https://github.com/PyO3/pyo3/pull/1285) - Require double-quotes for pyclass name argument e.g `#[pyclass(name = "MyClass")]`. [#1303](https://github.com/PyO3/pyo3/pull/1303) ### Deprecated - Deprecate `Python::is_instance`, `Python::is_subclass`, `Python::release`, and `Python::xdecref`. [#1292](https://github.com/PyO3/pyo3/pull/1292) ### Removed - Remove deprecated ffi definitions `PyUnicode_AsUnicodeCopy`, `PyUnicode_GetMax`, `_Py_CheckRecursionLimit`, `PyObject_AsCharBuffer`, `PyObject_AsReadBuffer`, `PyObject_CheckReadBuffer` and `PyObject_AsWriteBuffer`, which will be removed in Python 3.10. [#1217](https://github.com/PyO3/pyo3/pull/1217) - Remove unused `python3` feature. [#1235](https://github.com/PyO3/pyo3/pull/1235) ### Fixed - Fix missing field in `PyCodeObject` struct (`co_posonlyargcount`) - caused invalid access to other fields in Python >3.7. [#1260](https://github.com/PyO3/pyo3/pull/1260) - Fix building for `x86_64-unknown-linux-musl` target from `x86_64-unknown-linux-gnu` host. [#1267](https://github.com/PyO3/pyo3/pull/1267) - Fix `#[text_signature]` interacting badly with rust `r#raw_identifiers`. [#1286](https://github.com/PyO3/pyo3/pull/1286) - Fix FFI definitions for `PyObject_Vectorcall` and `PyVectorcall_Call`. [#1287](https://github.com/PyO3/pyo3/pull/1285) - Fix building with Anaconda python inside a virtualenv. [#1290](https://github.com/PyO3/pyo3/pull/1290) - Fix definition of opaque FFI types. [#1312](https://github.com/PyO3/pyo3/pull/1312) - Fix using custom error type in pyclass `#[new]` methods. [#1319](https://github.com/PyO3/pyo3/pull/1319) ## [0.12.4] - 2020-11-28 ### Fixed - Fix reference count bug in implementation of `From>` for `PyObject`, a regression introduced in PyO3 0.12. [#1297](https://github.com/PyO3/pyo3/pull/1297) ## [0.12.3] - 2020-10-12 ### Fixed - Fix support for Rust versions 1.39 to 1.44, broken by an incorrect internal update to paste 1.0 which was done in PyO3 0.12.2. [#1234](https://github.com/PyO3/pyo3/pull/1234) ## [0.12.2] - 2020-10-12 ### Added - Add support for keyword-only arguments without default values in `#[pyfunction]`. [#1209](https://github.com/PyO3/pyo3/pull/1209) - Add `Python::check_signals` as a safe a wrapper for `PyErr_CheckSignals`. [#1214](https://github.com/PyO3/pyo3/pull/1214) ### Fixed - Fix invalid document for protocol methods. [#1169](https://github.com/PyO3/pyo3/pull/1169) - Hide docs of PyO3 private implementation details in `pyo3::class::methods`. [#1169](https://github.com/PyO3/pyo3/pull/1169) - Fix unnecessary rebuild on PATH changes when the python interpreter is provided by PYO3_PYTHON. [#1231](https://github.com/PyO3/pyo3/pull/1231) ## [0.12.1] - 2020-09-16 ### Fixed - Fix building for a 32-bit Python on 64-bit Windows with a 64-bit Rust toolchain. [#1179](https://github.com/PyO3/pyo3/pull/1179) - Fix building on platforms where `c_char` is `u8`. [#1182](https://github.com/PyO3/pyo3/pull/1182) ## [0.12.0] - 2020-09-12 ### Added - Add FFI definitions `Py_FinalizeEx`, `PyOS_getsig`, and `PyOS_setsig`. [#1021](https://github.com/PyO3/pyo3/pull/1021) - Add `PyString::to_str` for accessing `PyString` as `&str`. [#1023](https://github.com/PyO3/pyo3/pull/1023) - Add `Python::with_gil` for executing a closure with the Python GIL. [#1037](https://github.com/PyO3/pyo3/pull/1037) - Add type information to failures in `PyAny::downcast`. [#1050](https://github.com/PyO3/pyo3/pull/1050) - Implement `Debug` for `PyIterator`. [#1051](https://github.com/PyO3/pyo3/pull/1051) - Add `PyBytes::new_with` and `PyByteArray::new_with` for initialising `bytes` and `bytearray` objects using a closure. [#1074](https://github.com/PyO3/pyo3/pull/1074) - Add `#[derive(FromPyObject)]` macro for enums and structs. [#1065](https://github.com/PyO3/pyo3/pull/1065) - Add `Py::as_ref` and `Py::into_ref` for converting `Py` to `&T`. [#1098](https://github.com/PyO3/pyo3/pull/1098) - Add ability to return `Result` types other than `PyResult` from `#[pyfunction]`, `#[pymethod]` and `#[pyproto]` functions. [#1106](https://github.com/PyO3/pyo3/pull/1118). - Implement `ToPyObject`, `IntoPy`, and `FromPyObject` for [hashbrown](https://crates.io/crates/hashbrown)'s `HashMap` and `HashSet` types (requires the `hashbrown` feature). [#1114](https://github.com/PyO3/pyo3/pull/1114) - Add `#[pyfunction(pass_module)]` and `#[pyfn(pass_module)]` to pass the module object as the first function argument. [#1143](https://github.com/PyO3/pyo3/pull/1143) - Add `PyModule::add_function` and `PyModule::add_submodule` as typed alternatives to `PyModule::add_wrapped`. [#1143](https://github.com/PyO3/pyo3/pull/1143) - Add native `PyCFunction` and `PyFunction` types. [#1163](https://github.com/PyO3/pyo3/pull/1163) ### Changed - Rework exception types: [#1024](https://github.com/PyO3/pyo3/pull/1024) [#1115](https://github.com/PyO3/pyo3/pull/1115) - Rename exception types from e.g. `RuntimeError` to `PyRuntimeError`. The old names continue to exist but are deprecated. - Exception objects are now accessible as `&T` or `Py`, just like other Python-native types. - Rename `PyException::py_err` to `PyException::new_err`. - Rename `PyUnicodeDecodeErr::new_err` to `PyUnicodeDecodeErr::new`. - Remove `PyStopIteration::stop_iteration`. - Require `T: Send` for the return value `T` of `Python::allow_threads`. [#1036](https://github.com/PyO3/pyo3/pull/1036) - Rename `PYTHON_SYS_EXECUTABLE` to `PYO3_PYTHON`. The old name will continue to work (undocumented) but will be removed in a future release. [#1039](https://github.com/PyO3/pyo3/pull/1039) - Remove `unsafe` from signature of `PyType::as_type_ptr`. [#1047](https://github.com/PyO3/pyo3/pull/1047) - Change return type of `PyIterator::from_object` to `PyResult` (was `Result`). [#1051](https://github.com/PyO3/pyo3/pull/1051) - `IntoPy` is no longer implied by `FromPy`. [#1063](https://github.com/PyO3/pyo3/pull/1063) - Change `PyObject` to be a type alias for `Py`. [#1063](https://github.com/PyO3/pyo3/pull/1063) - Rework `PyErr` to be compatible with the `std::error::Error` trait: [#1067](https://github.com/PyO3/pyo3/pull/1067) [#1115](https://github.com/PyO3/pyo3/pull/1115) - Implement `Display`, `Error`, `Send` and `Sync` for `PyErr` and `PyErrArguments`. - Add `PyErr::instance` for accessing `PyErr` as `&PyBaseException`. - `PyErr`'s fields are now an implementation detail. The equivalent values can be accessed with `PyErr::ptype`, `PyErr::pvalue` and `PyErr::ptraceback`. - Change receiver of `PyErr::print` and `PyErr::print_and_set_sys_last_vars` to `&self` (was `self`). - Remove `PyErrValue`, `PyErr::from_value`, `PyErr::into_normalized`, and `PyErr::normalize`. - Remove `PyException::into`. - Remove `Into>` for `PyErr` and `PyException`. - Change methods generated by `#[pyproto]` to return `NotImplemented` if Python should try a reversed operation. #[1072](https://github.com/PyO3/pyo3/pull/1072) - Change argument to `PyModule::add` to `impl IntoPy` (was `impl ToPyObject`). #[1124](https://github.com/PyO3/pyo3/pull/1124) ### Removed - Remove many exception and `PyErr` APIs; see the "changed" section above. [#1024](https://github.com/PyO3/pyo3/pull/1024) [#1067](https://github.com/PyO3/pyo3/pull/1067) [#1115](https://github.com/PyO3/pyo3/pull/1115) - Remove `PyString::to_string` (use new `PyString::to_str`). [#1023](https://github.com/PyO3/pyo3/pull/1023) - Remove `PyString::as_bytes`. [#1023](https://github.com/PyO3/pyo3/pull/1023) - Remove `Python::register_any`. [#1023](https://github.com/PyO3/pyo3/pull/1023) - Remove `GILGuard::acquire` from the public API. Use `Python::acquire_gil` or `Python::with_gil`. [#1036](https://github.com/PyO3/pyo3/pull/1036) - Remove the `FromPy` trait. [#1063](https://github.com/PyO3/pyo3/pull/1063) - Remove the `AsPyRef` trait. [#1098](https://github.com/PyO3/pyo3/pull/1098) ### Fixed - Correct FFI definitions `Py_SetProgramName` and `Py_SetPythonHome` to take `*const` arguments (was `*mut`). [#1021](https://github.com/PyO3/pyo3/pull/1021) - Fix `FromPyObject` for `num_bigint::BigInt` for Python objects with an `__index__` method. [#1027](https://github.com/PyO3/pyo3/pull/1027) - Correct FFI definition `_PyLong_AsByteArray` to take `*mut c_uchar` argument (was `*const c_uchar`). [#1029](https://github.com/PyO3/pyo3/pull/1029) - Fix segfault with `#[pyclass(dict, unsendable)]`. [#1058](https://github.com/PyO3/pyo3/pull/1058) [#1059](https://github.com/PyO3/pyo3/pull/1059) - Fix using `&Self` as an argument type for functions in a `#[pymethods]` block. [#1071](https://github.com/PyO3/pyo3/pull/1071) - Fix best-effort build against PyPy 3.6. [#1092](https://github.com/PyO3/pyo3/pull/1092) - Fix many cases of lifetime elision in `#[pyproto]` implementations. [#1093](https://github.com/PyO3/pyo3/pull/1093) - Fix detection of Python build configuration when cross-compiling. [#1095](https://github.com/PyO3/pyo3/pull/1095) - Always link against libpython on android with the `extension-module` feature. [#1095](https://github.com/PyO3/pyo3/pull/1095) - Fix the `+` operator not trying `__radd__` when both `__add__` and `__radd__` are defined in `PyNumberProtocol` (and similar for all other reversible operators). [#1107](https://github.com/PyO3/pyo3/pull/1107) - Fix building with Anaconda python. [#1175](https://github.com/PyO3/pyo3/pull/1175) ## [0.11.1] - 2020-06-30 ### Added - `#[pyclass(unsendable)]`. [#1009](https://github.com/PyO3/pyo3/pull/1009) ### Changed - Update `parking_lot` dependency to `0.11`. [#1010](https://github.com/PyO3/pyo3/pull/1010) ## [0.11.0] - 2020-06-28 ### Added - Support stable versions of Rust (>=1.39). [#969](https://github.com/PyO3/pyo3/pull/969) - Add FFI definition `PyObject_AsFileDescriptor`. [#938](https://github.com/PyO3/pyo3/pull/938) - Add `PyByteArray::data`, `PyByteArray::as_bytes`, and `PyByteArray::as_bytes_mut`. [#967](https://github.com/PyO3/pyo3/pull/967) - Add `GILOnceCell` to use in situations where `lazy_static` or `once_cell` can deadlock. [#975](https://github.com/PyO3/pyo3/pull/975) - Add `Py::borrow`, `Py::borrow_mut`, `Py::try_borrow`, and `Py::try_borrow_mut` for accessing `#[pyclass]` values. [#976](https://github.com/PyO3/pyo3/pull/976) - Add `IterNextOutput` and `IterANextOutput` for returning from `__next__` / `__anext__`. [#997](https://github.com/PyO3/pyo3/pull/997) ### Changed - Simplify internals of `#[pyo3(get)]` attribute. (Remove the hidden API `GetPropertyValue`.) [#934](https://github.com/PyO3/pyo3/pull/934) - Call `Py_Finalize` at exit to flush buffers, etc. [#943](https://github.com/PyO3/pyo3/pull/943) - Add type parameter to PyBuffer. #[951](https://github.com/PyO3/pyo3/pull/951) - Require `Send` bound for `#[pyclass]`. [#966](https://github.com/PyO3/pyo3/pull/966) - Add `Python` argument to most methods on `PyObject` and `Py` to ensure GIL safety. [#970](https://github.com/PyO3/pyo3/pull/970) - Change signature of `PyTypeObject::type_object` - now takes `Python` argument and returns `&PyType`. [#970](https://github.com/PyO3/pyo3/pull/970) - Change return type of `PyTuple::slice` and `PyTuple::split_from` from `Py` to `&PyTuple`. [#970](https://github.com/PyO3/pyo3/pull/970) - Change return type of `PyTuple::as_slice` to `&[&PyAny]`. [#971](https://github.com/PyO3/pyo3/pull/971) - Rename `PyTypeInfo::type_object` to `type_object_raw`, and add `Python` argument. [#975](https://github.com/PyO3/pyo3/pull/975) - Update `num-complex` optional dependency from `0.2` to `0.3`. [#977](https://github.com/PyO3/pyo3/pull/977) - Update `num-bigint` optional dependency from `0.2` to `0.3`. [#978](https://github.com/PyO3/pyo3/pull/978) - `#[pyproto]` is re-implemented without specialization. [#961](https://github.com/PyO3/pyo3/pull/961) - `PyClassAlloc::alloc` is renamed to `PyClassAlloc::new`. [#990](https://github.com/PyO3/pyo3/pull/990) - `#[pyproto]` methods can now have return value `T` or `PyResult` (previously only `PyResult` was supported). [#996](https://github.com/PyO3/pyo3/pull/996) - `#[pyproto]` methods can now skip annotating the return type if it is `()`. [#998](https://github.com/PyO3/pyo3/pull/998) ### Removed - Remove `ManagedPyRef` (unused, and needs specialization) [#930](https://github.com/PyO3/pyo3/pull/930) ### Fixed - Fix passing explicit `None` to `Option` argument `#[pyfunction]` with a default value. [#936](https://github.com/PyO3/pyo3/pull/936) - Fix `PyClass.__new__`'s not respecting subclasses when inherited by a Python class. [#990](https://github.com/PyO3/pyo3/pull/990) - Fix returning `Option` from `#[pyproto]` methods. [#996](https://github.com/PyO3/pyo3/pull/996) - Fix accepting `PyRef` and `PyRefMut` to `#[getter]` and `#[setter]` methods. [#999](https://github.com/PyO3/pyo3/pull/999) ## [0.10.1] - 2020-05-14 ### Fixed - Fix deadlock in `Python::acquire_gil` after dropping a `PyObject` or `Py`. [#924](https://github.com/PyO3/pyo3/pull/924) ## [0.10.0] - 2020-05-13 ### Added - Add FFI definition `_PyDict_NewPresized`. [#849](https://github.com/PyO3/pyo3/pull/849) - Implement `IntoPy` for `HashSet` and `BTreeSet`. [#864](https://github.com/PyO3/pyo3/pull/864) - Add `PyAny::dir` method. [#886](https://github.com/PyO3/pyo3/pull/886) - Gate macros behind a `macros` feature (enabled by default). [#897](https://github.com/PyO3/pyo3/pull/897) - Add ability to define class attributes using `#[classattr]` on functions in `#[pymethods]`. [#905](https://github.com/PyO3/pyo3/pull/905) - Implement `Clone` for `PyObject` and `Py`. [#908](https://github.com/PyO3/pyo3/pull/908) - Implement `Deref` for all builtin types. (`PyList`, `PyTuple`, `PyDict` etc.) [#911](https://github.com/PyO3/pyo3/pull/911) - Implement `Deref` for `PyCell`. [#911](https://github.com/PyO3/pyo3/pull/911) - Add `#[classattr]` support for associated constants in `#[pymethods]`. [#914](https://github.com/PyO3/pyo3/pull/914) ### Changed - Panics will now be raised as a Python `PanicException`. [#797](https://github.com/PyO3/pyo3/pull/797) - Change `PyObject` and `Py` reference counts to decrement immediately upon drop when the GIL is held. [#851](https://github.com/PyO3/pyo3/pull/851) - Allow `PyIterProtocol` methods to use either `PyRef` or `PyRefMut` as the receiver type. [#856](https://github.com/PyO3/pyo3/pull/856) - Change the implementation of `FromPyObject` for `Py` to apply to a wider range of `T`, including all `T: PyClass`. [#880](https://github.com/PyO3/pyo3/pull/880) - Move all methods from the `ObjectProtocol` trait to the `PyAny` struct. [#911](https://github.com/PyO3/pyo3/pull/911) - Remove need for `#![feature(specialization)]` in crates depending on PyO3. [#917](https://github.com/PyO3/pyo3/pull/917) ### Removed - Remove `PyMethodsProtocol` trait. [#889](https://github.com/PyO3/pyo3/pull/889) - Remove `num-traits` dependency. [#895](https://github.com/PyO3/pyo3/pull/895) - Remove `ObjectProtocol` trait. [#911](https://github.com/PyO3/pyo3/pull/911) - Remove `PyAny::None`. Users should use `Python::None` instead. [#911](https://github.com/PyO3/pyo3/pull/911) - Remove all `*ProtocolImpl` traits. [#917](https://github.com/PyO3/pyo3/pull/917) ### Fixed - Fix support for `__radd__` and other `__r*__` methods as implementations for Python mathematical operators. [#839](https://github.com/PyO3/pyo3/pull/839) - Fix panics during garbage collection when traversing objects that were already mutably borrowed. [#855](https://github.com/PyO3/pyo3/pull/855) - Prevent `&'static` references to Python objects as arguments to `#[pyfunction]` and `#[pymethods]`. [#869](https://github.com/PyO3/pyo3/pull/869) - Fix lifetime safety bug with `AsPyRef::as_ref`. [#876](https://github.com/PyO3/pyo3/pull/876) - Fix `#[pyo3(get)]` attribute on `Py` fields. [#880](https://github.com/PyO3/pyo3/pull/880) - Fix segmentation faults caused by functions such as `PyList::get_item` returning borrowed objects when it was not safe to do so. [#890](https://github.com/PyO3/pyo3/pull/890) - Fix segmentation faults caused by nested `Python::acquire_gil` calls creating dangling references. [#893](https://github.com/PyO3/pyo3/pull/893) - Fix segmentatation faults when a panic occurs during a call to `Python::allow_threads`. [#912](https://github.com/PyO3/pyo3/pull/912) ## [0.9.2] - 2020-04-09 ### Added - `FromPyObject` implementations for `HashSet` and `BTreeSet`. [#842](https://github.com/PyO3/pyo3/pull/842) ### Fixed - Correctly detect 32bit architecture. [#830](https://github.com/PyO3/pyo3/pull/830) ## [0.9.1] - 2020-03-23 ### Fixed - Error messages for `#[pyclass]`. [#826](https://github.com/PyO3/pyo3/pull/826) - `FromPyObject` implementation for `PySequence`. [#827](https://github.com/PyO3/pyo3/pull/827) ## [0.9.0] - 2020-03-19 ### Added - `PyCell`, which has RefCell-like features. [#770](https://github.com/PyO3/pyo3/pull/770) - `PyClass`, `PyLayout`, `PyClassInitializer`. [#683](https://github.com/PyO3/pyo3/pull/683) - Implemented `IntoIterator` for `PySet` and `PyFrozenSet`. [#716](https://github.com/PyO3/pyo3/pull/716) - `FromPyObject` is now automatically implemented for `T: Clone` pyclasses. [#730](https://github.com/PyO3/pyo3/pull/730) - `#[pyo3(get)]` and `#[pyo3(set)]` will now use the Rust doc-comment from the field for the Python property. [#755](https://github.com/PyO3/pyo3/pull/755) - `#[setter]` functions may now take an argument of `Pyo3::Python`. [#760](https://github.com/PyO3/pyo3/pull/760) - `PyTypeInfo::BaseLayout` and `PyClass::BaseNativeType`. [#770](https://github.com/PyO3/pyo3/pull/770) - `PyDowncastImpl`. [#770](https://github.com/PyO3/pyo3/pull/770) - Implement `FromPyObject` and `IntoPy` traits for arrays (up to 32). [#778](https://github.com/PyO3/pyo3/pull/778) - `migration.md` and `types.md` in the guide. [#795](https://github.com/PyO3/pyo3/pull/795), #[802](https://github.com/PyO3/pyo3/pull/802) - `ffi::{_PyBytes_Resize, _PyDict_Next, _PyDict_Contains, _PyDict_GetDictPtr}`. #[820](https://github.com/PyO3/pyo3/pull/820) ### Changed - `#[new]` does not take `PyRawObject` and can return `Self`. [#683](https://github.com/PyO3/pyo3/pull/683) - The blanket implementations for `FromPyObject` for `&T` and `&mut T` are no longer specializable. Implement `PyTryFrom` for your type to control the behavior of `FromPyObject::extract` for your types. [#713](https://github.com/PyO3/pyo3/pull/713) - The implementation for `IntoPy for T` where `U: FromPy` is no longer specializable. Control the behavior of this via the implementation of `FromPy`. [#713](https://github.com/PyO3/pyo3/pull/713) - Use `parking_lot::Mutex` instead of `spin::Mutex`. [#734](https://github.com/PyO3/pyo3/pull/734) - Bumped minimum Rust version to `1.42.0-nightly 2020-01-21`. [#761](https://github.com/PyO3/pyo3/pull/761) - `PyRef` and `PyRefMut` are renewed for `PyCell`. [#770](https://github.com/PyO3/pyo3/pull/770) - Some new FFI functions for Python 3.8. [#784](https://github.com/PyO3/pyo3/pull/784) - `PyAny` is now on the top level module and prelude. [#816](https://github.com/PyO3/pyo3/pull/816) ### Removed - `PyRawObject`. [#683](https://github.com/PyO3/pyo3/pull/683) - `PyNoArgsFunction`. [#741](https://github.com/PyO3/pyo3/pull/741) - `initialize_type`. To set the module name for a `#[pyclass]`, use the `module` argument to the macro. #[751](https://github.com/PyO3/pyo3/pull/751) - `AsPyRef::as_mut/with/with_mut/into_py/into_mut_py`. [#770](https://github.com/PyO3/pyo3/pull/770) - `PyTryFrom::try_from_mut/try_from_mut_exact/try_from_mut_unchecked`. [#770](https://github.com/PyO3/pyo3/pull/770) - `Python::mut_from_owned_ptr/mut_from_borrowed_ptr`. [#770](https://github.com/PyO3/pyo3/pull/770) - `ObjectProtocol::get_base/get_mut_base`. [#770](https://github.com/PyO3/pyo3/pull/770) ### Fixed - Fixed unsoundness of subclassing. [#683](https://github.com/PyO3/pyo3/pull/683). - Clear error indicator when the exception is handled on the Rust side. [#719](https://github.com/PyO3/pyo3/pull/719) - Usage of raw identifiers with `#[pyo3(set)]`. [#745](https://github.com/PyO3/pyo3/pull/745) - Usage of `PyObject` with `#[pyo3(get)]`. [#760](https://github.com/PyO3/pyo3/pull/760) - `#[pymethods]` used in conjunction with `#[cfg]`. #[769](https://github.com/PyO3/pyo3/pull/769) - `"*"` in a `#[pyfunction()]` argument list incorrectly accepting any number of positional arguments (use `args = "*"` when this behavior is desired). #[792](https://github.com/PyO3/pyo3/pull/792) - `PyModule::dict`. #[809](https://github.com/PyO3/pyo3/pull/809) - Fix the case where `DESCRIPTION` is not null-terminated. #[822](https://github.com/PyO3/pyo3/pull/822) ## [0.8.5] - 2020-01-05 ### Added - Implemented `FromPyObject` for `HashMap` and `BTreeMap` - Support for `#[name = "foo"]` attribute for `#[pyfunction]` and in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692) ## [0.8.4] - 2019-12-14 ### Added - Support for `#[text_signature]` attribute. [#675](https://github.com/PyO3/pyo3/pull/675) ## [0.8.3] - 2019-11-23 ### Removed - `#[init]` is removed. [#658](https://github.com/PyO3/pyo3/pull/658) ### Fixed - Now all `&Py~` types have `!Send` bound. [#655](https://github.com/PyO3/pyo3/pull/655) - Fix a compile error raised by the stabilization of `!` type. [#672](https://github.com/PyO3/pyo3/issues/672). ## [0.8.2] - 2019-10-27 ### Added - FFI compatibility for PEP 590 Vectorcall. [#641](https://github.com/PyO3/pyo3/pull/641) ### Fixed - Fix PySequenceProtocol::set_item. [#624](https://github.com/PyO3/pyo3/pull/624) - Fix a corner case of BigInt::FromPyObject. [#630](https://github.com/PyO3/pyo3/pull/630) - Fix index errors in parameter conversion. [#631](https://github.com/PyO3/pyo3/pull/631) - Fix handling of invalid utf-8 sequences in `PyString::as_bytes`. [#639](https://github.com/PyO3/pyo3/pull/639) and `PyString::to_string_lossy` [#642](https://github.com/PyO3/pyo3/pull/642). - Remove `__contains__` and `__iter__` from PyMappingProtocol. [#644](https://github.com/PyO3/pyo3/pull/644) - Fix proc-macro definition of PySetAttrProtocol. [#645](https://github.com/PyO3/pyo3/pull/645) ## [0.8.1] - 2019-10-08 ### Added - Conversion between [num-bigint](https://github.com/rust-num/num-bigint) and Python int. [#608](https://github.com/PyO3/pyo3/pull/608) ### Fixed - Make sure the right Python interpreter is used in OSX builds. [#604](https://github.com/PyO3/pyo3/pull/604) - Patch specialization being broken by Rust 1.40. [#614](https://github.com/PyO3/pyo3/issues/614) - Fix a segfault around PyErr. [#597](https://github.com/PyO3/pyo3/pull/597) ## [0.8.0] - 2019-09-16 ### Added - `module` argument to `pyclass` macro. [#499](https://github.com/PyO3/pyo3/pull/499) - `py_run!` macro [#512](https://github.com/PyO3/pyo3/pull/512) - Use existing fields and methods before calling custom **getattr**. [#505](https://github.com/PyO3/pyo3/pull/505) - `PyBytes` can now be indexed just like `Vec` - Implement `IntoPy` for `PyRef` and `PyRefMut`. ### Changed - Implementing the Using the `gc` parameter for `pyclass` (e.g. `#[pyclass(gc)]`) without implementing the `class::PyGCProtocol` trait is now a compile-time error. Failing to implement this trait could lead to segfaults. [#532](https://github.com/PyO3/pyo3/pull/532) - `PyByteArray::data` has been replaced with `PyDataArray::to_vec` because returning a `&[u8]` is unsound. (See [this comment](https://github.com/PyO3/pyo3/issues/373#issuecomment-512332696) for a great write-up for why that was unsound) - Replace `mashup` with `paste`. - `GILPool` gained a `Python` marker to prevent it from being misused to release Python objects without the GIL held. ### Removed - `IntoPyObject` was replaced with `IntoPy` - `#[pyclass(subclass)]` is hidden a `unsound-subclass` feature because it's causing segmentation faults. ### Fixed - More readable error message for generics in pyclass [#503](https://github.com/PyO3/pyo3/pull/503) ## [0.7.0] - 2019-05-26 ### Added - PyPy support by omerbenamram in [#393](https://github.com/PyO3/pyo3/pull/393) - Have `PyModule` generate an index of its members (`__all__` list). - Allow `slf: PyRef` for pyclass(#419) - Allow to use lifetime specifiers in `pymethods` - Add `marshal` module. [#460](https://github.com/PyO3/pyo3/pull/460) ### Changed - `Python::run` returns `PyResult<()>` instead of `PyResult<&PyAny>`. - Methods decorated with `#[getter]` and `#[setter]` can now omit wrapping the result type in `PyResult` if they don't raise exceptions. ### Fixed - `type_object::PyTypeObject` has been marked unsafe because breaking the contract `type_object::PyTypeObject::init_type` can lead to UB. - Fixed automatic derive of `PySequenceProtocol` implementation in [#423](https://github.com/PyO3/pyo3/pull/423). - Capitalization & better wording to README.md. - Docstrings of properties is now properly set using the doc of the `#[getter]` method. - Fixed issues with `pymethods` crashing on doc comments containing double quotes. - `PySet::new` and `PyFrozenSet::new` now return `PyResult<&Py[Frozen]Set>`; exceptions are raised if the items are not hashable. - Fixed building using `venv` on Windows. - `PyTuple::new` now returns `&PyTuple` instead of `Py`. - Fixed several issues with argument parsing; notable, the `*args` and `**kwargs` tuple/dict now doesn't contain arguments that are otherwise assigned to parameters. ## [0.6.0] - 2019-03-28 ### Regressions - Currently, [#341](https://github.com/PyO3/pyo3/issues/341) causes `cargo test` to fail with weird linking errors when the `extension-module` feature is activated. For now you can work around this by making the `extension-module` feature optional and running the tests with `cargo test --no-default-features`: ```toml [dependencies.pyo3] version = "0.6.0" [features] extension-module = ["pyo3/extension-module"] default = ["extension-module"] ``` ### Added - Added a `wrap_pymodule!` macro similar to the existing `wrap_pyfunction!` macro. Only available on python 3 - Added support for cross compiling (e.g. to arm v7) by mtp401 in [#327](https://github.com/PyO3/pyo3/pull/327). See the "Cross Compiling" section in the "Building and Distribution" chapter of the guide for more details. - The `PyRef` and `PyRefMut` types, which allow to differentiate between an instance of a rust struct on the rust heap and an instance that is embedded inside a python object. By kngwyu in [#335](https://github.com/PyO3/pyo3/pull/335) - Added `FromPy` and `IntoPy` which are equivalent to `From` and `Into` except that they require a gil token. - Added `ManagedPyRef`, which should eventually replace `ToBorrowedObject`. ### Changed - Renamed `PyObjectRef` to `PyAny` in #388 - Renamed `add_function` to `add_wrapped` as it now also supports modules. - Renamed `#[pymodinit]` to `#[pymodule]` - `py.init(|| value)` becomes `Py::new(value)` - `py.init_ref(|| value)` becomes `PyRef::new(value)` - `py.init_mut(|| value)` becomes `PyRefMut::new(value)`. - `PyRawObject::init` is now infallible, e.g. it returns `()` instead of `PyResult<()>`. - Renamed `py_exception!` to `create_exception!` and refactored the error macros. - Renamed `wrap_function!` to `wrap_pyfunction!` - Renamed `#[prop(get, set)]` to `#[pyo3(get, set)]` - `#[pyfunction]` now supports the same arguments as `#[pyfn()]` - Some macros now emit proper spanned errors instead of panics. - Migrated to the 2018 edition - `crate::types::exceptions` moved to `crate::exceptions` - Replace `IntoPyTuple` with `IntoPy>`. - `IntoPyPointer` and `ToPyPointer` moved into the crate root. - `class::CompareOp` moved into `class::basic::CompareOp` - PyTypeObject is now a direct subtrait PyTypeCreate, removing the old cyclical implementation in [#350](https://github.com/PyO3/pyo3/pull/350) - Add `PyList::{sort, reverse}` by chr1sj0nes in [#357](https://github.com/PyO3/pyo3/pull/357) and [#358](https://github.com/PyO3/pyo3/pull/358) - Renamed the `typeob` module to `type_object` ### Removed - `PyToken` was removed due to unsoundness (See [#94](https://github.com/PyO3/pyo3/issues/94)). - Removed the unnecessary type parameter from `PyObjectAlloc` - `NoArgs`. Just use an empty tuple - `PyObjectWithGIL`. `PyNativeType` is sufficient now that PyToken is removed. ### Fixed - A soudness hole where every instances of a `#[pyclass]` struct was considered to be part of a python object, even though you can create instances that are not part of the python heap. This was fixed through `PyRef` and `PyRefMut`. - Fix kwargs support in [#328](https://github.com/PyO3/pyo3/pull/328). - Add full support for `__dict__` in [#403](https://github.com/PyO3/pyo3/pull/403). ## [0.5.3] - 2019-01-04 ### Fixed - Fix memory leak in ArrayList by kngwyu [#316](https://github.com/PyO3/pyo3/pull/316) ## [0.5.2] - 2018-11-25 ### Fixed - Fix indeterministic segfaults when creating many objects by kngwyu in [#281](https://github.com/PyO3/pyo3/pull/281) ## [0.5.1] - 2018-11-24 Yanked ## [0.5.0] - 2018-11-11 ### Added - `#[pyclass]` objects can now be returned from rust functions - `PyComplex` by kngwyu in [#226](https://github.com/PyO3/pyo3/pull/226) - `PyDict::from_sequence`, equivalent to `dict([(key, val), ...])` - Bindings for the `datetime` standard library types: `PyDate`, `PyTime`, `PyDateTime`, `PyTzInfo`, `PyDelta` with associated `ffi` types, by pganssle [#200](https://github.com/PyO3/pyo3/pull/200). - `PyString`, `PyUnicode`, and `PyBytes` now have an `as_bytes` method that returns `&[u8]`. - `PyObjectProtocol::get_type_ptr` by ijl in [#242](https://github.com/PyO3/pyo3/pull/242) ### Changed - Removes the types from the root module and the prelude. They now live in `pyo3::types` instead. - All exceptions are constructed with `py_err` instead of `new`, as they return `PyErr` and not `Self`. - `as_mut` and friends take and `&mut self` instead of `&self` - `ObjectProtocol::call` now takes an `Option<&PyDict>` for the kwargs instead of an `IntoPyDictPointer`. - `IntoPyDictPointer` was replace by `IntoPyDict` which doesn't convert `PyDict` itself anymore and returns a `PyDict` instead of `*mut PyObject`. - `PyTuple::new` now takes an `IntoIterator` instead of a slice - Updated to syn 0.15 - Split `PyTypeObject` into `PyTypeObject` without the create method and `PyTypeCreate` with requires `PyObjectAlloc + PyTypeInfo + Sized`. - Ran `cargo edition --fix` which prefixed path with `crate::` for rust 2018 - Renamed `async` to `pyasync` as async will be a keyword in the 2018 edition. - Starting to use `NonNull<*mut PyObject>` for Py and PyObject by ijl [#260](https://github.com/PyO3/pyo3/pull/260) ### Removed - Removed most entries from the prelude. The new prelude is small and clear. - Slowly removing specialization uses - `PyString`, `PyUnicode`, and `PyBytes` no longer have a `data` method (replaced by `as_bytes`) and `PyStringData` has been removed. - The pyobject_extract macro ### Fixed - Added an explanation that the GIL can temporarily be released even while holding a GILGuard. - Lots of clippy errors - Fix segfault on calling an unknown method on a PyObject - Work around a [bug](https://github.com/rust-lang/rust/issues/55380) in the rust compiler by kngwyu [#252](https://github.com/PyO3/pyo3/pull/252) - Fixed a segfault with subclassing pyo3 create classes and using `__class__` by kngwyu [#263](https://github.com/PyO3/pyo3/pull/263) ## [0.4.1] - 2018-08-20 ### Changed - PyTryFrom's error is always to `PyDowncastError` ### Fixed - Fixed compilation on nightly since `use_extern_macros` was stabilized ### Removed - The pyobject_downcast macro ## [0.4.0] - 2018-07-30 ### Changed - Merged both examples into one - Rustfmt all the things :heavy_check_mark: - Switched to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Removed - Conversions from tuples to PyDict due to [rust-lang/rust#52050](https://github.com/rust-lang/rust/issues/52050) ## [0.3.2] - 2018-07-22 ### Changed - Replaced `concat_idents` with mashup ## [0.3.1] - 2018-07-18 ### Fixed - Fixed scoping bug in pyobject_native_type that would break rust-numpy ## [0.3.0] - 2018-07-18 ### Added - A few internal macros became part of the public api ([#155](https://github.com/PyO3/pyo3/pull/155), [#186](https://github.com/PyO3/pyo3/pull/186)) - Always clone in getters. This allows using the get-annotation on all Clone-Types ### Changed - Upgraded to syn 0.14 which means much better error messages :tada: - 128 bit integer support by [kngwyu](https://github.com/kngwyu) ([#137](https://github.com/PyO3/pyo3/pull/173)) - `proc_macro` has been stabilized on nightly ([rust-lang/rust#52081](https://github.com/rust-lang/rust/pull/52081)). This means that we can remove the `proc_macro` feature, but now we need the `use_extern_macros` from the 2018 edition instead. - All proc macro are now prefixed with `py` and live in the prelude. This means you can use `#[pyclass]`, `#[pymethods]`, `#[pyproto]`, `#[pyfunction]` and `#[pymodinit]` directly, at least after a `use pyo3::prelude::*`. They were also moved into a module called `proc_macro`. You shouldn't use `#[pyo3::proc_macro::pyclass]` or other longer paths in attributes because `proc_macro_path_invoc` isn't going to be stabilized soon. - Renamed the `base` option in the `pyclass` macro to `extends`. - `#[pymodinit]` uses the function name as module name, unless the name is overridden with `#[pymodinit(name)]` - The guide is now properly versioned. ## [0.2.7] - 2018-05-18 ### Fixed - Fix nightly breakage with proc_macro_path ## [0.2.6] - 2018-04-03 ### Fixed - Fix compatibility with TryFrom trait #137 ## [0.2.5] - 2018-02-21 ### Added - CPython 3.7 support ### Fixed - Embedded CPython 3.7b1 crashes on initialization #110 - Generated extension functions are weakly typed #108 - call_method\* crashes when the method does not exist #113 - Allow importing exceptions from nested modules #116 ## [0.2.4] - 2018-01-19 ### Added - Allow to get mutable ref from PyObject #106 - Drop `RefFromPyObject` trait - Add Python::register_any method ### Fixed - Fix impl `FromPyObject` for `Py` - Mark method that work with raw pointers as unsafe #95 ## [0.2.3] - 11-27-2017 ### Changed - Rustup to 1.23.0-nightly 2017-11-07 ### Fixed - Proper `c_char` usage #93 ### Removed - Remove use of now unneeded 'AsciiExt' trait ## [0.2.2] - 09-26-2017 ### Changed - Rustup to 1.22.0-nightly 2017-09-30 ## [0.2.1] - 09-26-2017 ### Fixed - Fix rustc const_fn nightly breakage ## [0.2.0] - 08-12-2017 ### Added - Added inheritance support #15 - Added weakref support #56 - Added subclass support #64 - Added `self.__dict__` support #68 - Added `pyo3::prelude` module #70 - Better `Iterator` support for PyTuple, PyList, PyDict #75 - Introduce IntoPyDictPointer similar to IntoPyTuple #69 ### Changed - Allow to add gc support without implementing PyGCProtocol #57 - Refactor `PyErr` implementation. Drop `py` parameter from constructor. ## [0.1.0] - 07-23-2017 ### Added - Initial release [Unreleased]: https://github.com/pyo3/pyo3/compare/v0.28.2...HEAD [0.28.2]: https://github.com/pyo3/pyo3/compare/v0.28.1...v0.28.2 [0.28.1]: https://github.com/pyo3/pyo3/compare/v0.28.0...v0.28.1 [0.28.0]: https://github.com/pyo3/pyo3/compare/v0.27.2...v0.28.0 [0.27.2]: https://github.com/pyo3/pyo3/compare/v0.27.1...v0.27.2 [0.27.1]: https://github.com/pyo3/pyo3/compare/v0.27.0...v0.27.1 [0.27.0]: https://github.com/pyo3/pyo3/compare/v0.26.0...v0.27.0 [0.26.0]: https://github.com/pyo3/pyo3/compare/v0.25.1...v0.26.0 [0.25.1]: https://github.com/pyo3/pyo3/compare/v0.25.0...v0.25.1 [0.25.0]: https://github.com/pyo3/pyo3/compare/v0.24.2...v0.25.0 [0.24.2]: https://github.com/pyo3/pyo3/compare/v0.24.1...v0.24.2 [0.24.1]: https://github.com/pyo3/pyo3/compare/v0.24.0...v0.24.1 [0.24.0]: https://github.com/pyo3/pyo3/compare/v0.23.5...v0.24.0 [0.23.5]: https://github.com/pyo3/pyo3/compare/v0.23.4...v0.23.5 [0.23.4]: https://github.com/pyo3/pyo3/compare/v0.23.3...v0.23.4 [0.23.3]: https://github.com/pyo3/pyo3/compare/v0.23.2...v0.23.3 [0.23.2]: https://github.com/pyo3/pyo3/compare/v0.23.1...v0.23.2 [0.23.1]: https://github.com/pyo3/pyo3/compare/v0.23.0...v0.23.1 [0.23.0]: https://github.com/pyo3/pyo3/compare/v0.22.5...v0.23.0 [0.22.5]: https://github.com/pyo3/pyo3/compare/v0.22.4...v0.22.5 [0.22.4]: https://github.com/pyo3/pyo3/compare/v0.22.3...v0.22.4 [0.22.3]: https://github.com/pyo3/pyo3/compare/v0.22.2...v0.22.3 [0.22.2]: https://github.com/pyo3/pyo3/compare/v0.22.1...v0.22.2 [0.22.1]: https://github.com/pyo3/pyo3/compare/v0.22.0...v0.22.1 [0.22.0]: https://github.com/pyo3/pyo3/compare/v0.21.2...v0.22.0 [0.21.2]: https://github.com/pyo3/pyo3/compare/v0.21.1...v0.21.2 [0.21.1]: https://github.com/pyo3/pyo3/compare/v0.21.0...v0.21.1 [0.21.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0 [0.21.0-beta.0]: https://github.com/pyo3/pyo3/compare/v0.20.3...v0.21.0-beta.0 [0.20.3]: https://github.com/pyo3/pyo3/compare/v0.20.2...v0.20.3 [0.20.2]: https://github.com/pyo3/pyo3/compare/v0.20.1...v0.20.2 [0.20.1]: https://github.com/pyo3/pyo3/compare/v0.20.0...v0.20.1 [0.20.0]: https://github.com/pyo3/pyo3/compare/v0.19.2...v0.20.0 [0.19.2]: https://github.com/pyo3/pyo3/compare/v0.19.1...v0.19.2 [0.19.1]: https://github.com/pyo3/pyo3/compare/v0.19.0...v0.19.1 [0.19.0]: https://github.com/pyo3/pyo3/compare/v0.18.3...v0.19.0 [0.18.3]: https://github.com/pyo3/pyo3/compare/v0.18.2...v0.18.3 [0.18.2]: https://github.com/pyo3/pyo3/compare/v0.18.1...v0.18.2 [0.18.1]: https://github.com/pyo3/pyo3/compare/v0.18.0...v0.18.1 [0.18.0]: https://github.com/pyo3/pyo3/compare/v0.17.3...v0.18.0 [0.17.3]: https://github.com/pyo3/pyo3/compare/v0.17.2...v0.17.3 [0.17.2]: https://github.com/pyo3/pyo3/compare/v0.17.1...v0.17.2 [0.17.1]: https://github.com/pyo3/pyo3/compare/v0.17.0...v0.17.1 [0.17.0]: https://github.com/pyo3/pyo3/compare/v0.16.6...v0.17.0 [0.16.6]: https://github.com/pyo3/pyo3/compare/v0.16.5...v0.16.6 [0.16.5]: https://github.com/pyo3/pyo3/compare/v0.16.4...v0.16.5 [0.16.4]: 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.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.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.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.4]: https://github.com/pyo3/pyo3/compare/v0.14.3...v0.14.4 [0.14.3]: https://github.com/pyo3/pyo3/compare/v0.14.2...v0.14.3 [0.14.2]: https://github.com/pyo3/pyo3/compare/v0.14.1...v0.14.2 [0.14.1]: https://github.com/pyo3/pyo3/compare/v0.14.0...v0.14.1 [0.14.0]: https://github.com/pyo3/pyo3/compare/v0.13.2...v0.14.0 [0.13.2]: https://github.com/pyo3/pyo3/compare/v0.13.1...v0.13.2 [0.13.1]: https://github.com/pyo3/pyo3/compare/v0.13.0...v0.13.1 [0.13.0]: https://github.com/pyo3/pyo3/compare/v0.12.4...v0.13.0 [0.12.4]: https://github.com/pyo3/pyo3/compare/v0.12.3...v0.12.4 [0.12.3]: https://github.com/pyo3/pyo3/compare/v0.12.2...v0.12.3 [0.12.2]: https://github.com/pyo3/pyo3/compare/v0.12.1...v0.12.2 [0.12.1]: https://github.com/pyo3/pyo3/compare/v0.12.0...v0.12.1 [0.12.0]: https://github.com/pyo3/pyo3/compare/v0.11.1...v0.12.0 [0.11.1]: https://github.com/pyo3/pyo3/compare/v0.11.0...v0.11.1 [0.11.0]: https://github.com/pyo3/pyo3/compare/v0.10.1...v0.11.0 [0.10.1]: https://github.com/pyo3/pyo3/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/pyo3/pyo3/compare/v0.9.2...v0.10.0 [0.9.2]: https://github.com/pyo3/pyo3/compare/v0.9.1...v0.9.2 [0.9.1]: https://github.com/pyo3/pyo3/compare/v0.9.0...v0.9.1 [0.9.0]: https://github.com/pyo3/pyo3/compare/v0.8.5...v0.9.0 [0.8.5]: https://github.com/pyo3/pyo3/compare/v0.8.4...v0.8.5 [0.8.4]: https://github.com/pyo3/pyo3/compare/v0.8.3...v0.8.4 [0.8.3]: https://github.com/pyo3/pyo3/compare/v0.8.2...v0.8.3 [0.8.2]: https://github.com/pyo3/pyo3/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/pyo3/pyo3/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/pyo3/pyo3/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/pyo3/pyo3/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/pyo3/pyo3/compare/v0.5.3...v0.6.0 [0.5.3]: https://github.com/pyo3/pyo3/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/pyo3/pyo3/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/pyo3/pyo3/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/pyo3/pyo3/compare/v0.4.1...v0.5.0 [0.4.1]: https://github.com/pyo3/pyo3/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/pyo3/pyo3/compare/v0.3.2...v0.4.0 [0.3.2]: https://github.com/pyo3/pyo3/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/pyo3/pyo3/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/pyo3/pyo3/compare/v0.2.7...v0.3.0 [0.2.7]: https://github.com/pyo3/pyo3/compare/v0.2.6...v0.2.7 [0.2.6]: https://github.com/pyo3/pyo3/compare/v0.2.5...v0.2.6 [0.2.5]: https://github.com/pyo3/pyo3/compare/v0.2.4...v0.2.5 [0.2.4]: https://github.com/pyo3/pyo3/compare/v0.2.3...v0.2.4 [0.2.3]: https://github.com/pyo3/pyo3/compare/v0.2.2...v0.2.3 [0.2.2]: https://github.com/pyo3/pyo3/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/pyo3/pyo3/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/pyo3/pyo3/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/PyO3/pyo3/tree/0.1.0 ================================================ FILE: CITATION.cff ================================================ cff-version: 1.2.0 title: PyO3 message: >- If you use this software as part of a publication and wish to cite it, please use the metadata from this file. type: software authors: - name: PyO3 Project and Contributors website: https://github.com/PyO3 license: - Apache-2.0 - MIT ================================================ FILE: Cargo.toml ================================================ [package] name = "pyo3" version = "0.28.2" description = "Bindings to Python interpreter" authors = ["PyO3 Project and Contributors "] readme = "README.md" keywords = ["pyo3", "python", "cpython", "ffi"] homepage = "https://github.com/pyo3/pyo3" repository = "https://github.com/pyo3/pyo3" documentation = "https://docs.rs/crate/pyo3/" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" exclude = [ "/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py", "/.github", "/tests/test_compile_error.rs", "/tests/ui", ] edition = "2021" rust-version.workspace = true [dependencies] libc = "0.2.62" once_cell = "1.21" # ffi bindings to the python interpreter, split into a separate crate so they can be used independently pyo3-ffi = { path = "pyo3-ffi", version = "=0.28.2" } # support crate for macros feature pyo3-macros = { path = "pyo3-macros", version = "=0.28.2", optional = true } # support crate for multiple-pymethods feature inventory = { version = "0.3.5", optional = true } # crate integrations that can be added using the eponymous features anyhow = { version = "1.0.1", optional = true } bigdecimal = { version = "0.4.7", optional = true } bytes = { version = "1.10", optional = true } chrono = { version = "0.4.25", default-features = false, optional = true } chrono-tz = { version = ">= 0.10, < 0.11", default-features = false, optional = true } either = { version = "1.9", optional = true } eyre = { version = ">= 0.6.8, < 0.7", optional = true } hashbrown = { version = ">= 0.15.0, < 0.17", optional = true, default-features = false } indexmap = { version = ">= 2.5.0, < 3", optional = true } jiff-02 = { package = "jiff", version = "0.2", optional = true } num-bigint = { version = "0.4.4", optional = true } num-complex = { version = ">= 0.4.6, < 0.5", optional = true } num-rational = { version = "0.4.1", optional = true } num-traits = { version = "0.2.16", optional = true } ordered-float = { version = "5.0.0", default-features = false, optional = true } rust_decimal = { version = "1.15", default-features = false, optional = true } time = { version = "0.3.38", default-features = false, optional = true } serde = { version = "1.0", optional = true } smallvec = { version = "1.0", optional = true } uuid = { version = "1.12.0", optional = true } lock_api = { version = "0.4", optional = true } parking_lot = { version = "0.12", optional = true } iana-time-zone = { version = "0.1", optional = true, features = ["fallback"]} [target.'cfg(not(target_has_atomic = "64"))'.dependencies] portable-atomic = "1.0" [dev-dependencies] assert_approx_eq = "1.1.0" chrono = "0.4.25" chrono-tz = ">= 0.10, < 0.11" trybuild = ">=1.0.115" proptest = { version = "1.0", default-features = false, features = ["std"] } send_wrapper = "0.6" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.61" rayon = "1.6.1" futures = "0.3.28" tempfile = "3.12.0" static_assertions = "1.1.0" uuid = { version = "1.10.0", features = ["v4"] } parking_lot = { version = "0.12.3", features = ["arc_lock"] } [build-dependencies] pyo3-build-config = { path = "pyo3-build-config", version = "=0.28.2", features = ["resolve-config"] } [features] default = ["macros"] # Enables support for `async fn` for `#[pyfunction]` and `#[pymethods]`. experimental-async = ["macros", "pyo3-macros/experimental-async"] # Enables pyo3::inspect module and additional type information on FromPyObject # and IntoPy traits experimental-inspect = ["pyo3-macros/experimental-inspect"] # Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc. macros = ["pyo3-macros"] # Enables multiple #[pymethods] per #[pyclass] multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"] # Deprecated: use the `PYO3_BUILD_EXTENSION_MODULE` environment variable when # building a Python extension module (set automatically by `setuptools-rust` and # `maturin`). extension-module = ["pyo3-ffi/extension-module"] # Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more. abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"] # With abi3, we can manually set the minimum Python version. abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"] abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38", "pyo3-ffi/abi3-py38"] abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39", "pyo3-ffi/abi3-py39"] abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310"] abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311", "pyo3-ffi/abi3-py311"] abi3-py312 = ["abi3-py313", "pyo3-build-config/abi3-py312", "pyo3-ffi/abi3-py312"] abi3-py313 = ["abi3-py314", "pyo3-build-config/abi3-py313", "pyo3-ffi/abi3-py313"] abi3-py314 = ["abi3", "pyo3-build-config/abi3-py314", "pyo3-ffi/abi3-py314"] # deprecated: no longer needed, raw-dylib is used instead generate-import-lib = ["pyo3-ffi/generate-import-lib"] # Changes `Python::attach` to automatically initialize the Python interpreter if needed. auto-initialize = [] # Enables `Clone`ing references to Python objects `Py` which panics if the # thread is not attached to the Python interpreter. py-clone = [] # Adds `OnceExt` and `MutexExt` implementations to the `parking_lot` types parking_lot = ["dep:parking_lot", "lock_api"] arc_lock = ["lock_api", "lock_api/arc_lock", "parking_lot?/arc_lock"] num-bigint = ["dep:num-bigint", "dep:num-traits"] bigdecimal = ["dep:bigdecimal", "num-bigint"] chrono-local = ["chrono/clock", "dep:iana-time-zone"] # Optimizes PyObject to Vec conversion and so on. nightly = [] # Activates all additional features # This is mostly intended for testing purposes - activating *all* of these isn't particularly useful. full = [ "macros", # "multiple-pymethods", # Not supported by wasm "anyhow", "arc_lock", "bigdecimal", "bytes", "chrono", "chrono-local", "chrono-tz", "either", "experimental-async", "experimental-inspect", "eyre", "hashbrown", "indexmap", "jiff-02", "lock_api", "num-bigint", "num-complex", "num-rational", "ordered-float", "parking_lot", "py-clone", "rust_decimal", "serde", "smallvec", "time", "uuid", ] [workspace] members = [ "pyo3-ffi", "pyo3-build-config", "pyo3-macros", "pyo3-macros-backend", "pyo3-introspection", "pytests", "examples", ] package.rust-version = "1.83" [package.metadata.docs.rs] no-default-features = true features = ["full"] rustdoc-args = ["--cfg", "docsrs"] [workspace.lints.clippy] checked_conversions = "warn" dbg_macro = "warn" explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" filter_map_next = "warn" flat_map_option = "warn" let_unit_value = "warn" manual_assert = "warn" manual_ok_or = "warn" todo = "warn" # TODO: make this "warn" # https://github.com/PyO3/pyo3/issues/5487 undocumented_unsafe_blocks = "allow" unnecessary_wraps = "warn" useless_transmute = "warn" used_underscore_binding = "warn" [workspace.lints.rust] elided_lifetimes_in_paths = "warn" invalid_doc_attributes = "warn" rust_2018_idioms = { level = "warn", priority = -1 } rust_2021_prelude_collisions = "warn" unused_lifetimes = "warn" unsafe_op_in_unsafe_fn = "warn" [workspace.lints.rustdoc] broken_intra_doc_links = "warn" bare_urls = "warn" [lints] workspace = true ================================================ FILE: Code-of-Conduct.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: Contributing.md ================================================ # Contributing Thank you for your interest in contributing to PyO3! All are welcome - please consider reading our [Code of Conduct](https://github.com/PyO3/pyo3/blob/main/Code-of-Conduct.md) to keep our community positive and inclusive. If you are searching for ideas how to contribute, proceed to the ["Getting started contributing"](#getting-started-contributing) section. If you have found a specific issue to contribute to and need information about the development process, you may find the section ["Writing pull requests"](#writing-pull-requests) helpful. If you want to become familiar with the codebase, see [Architecture.md](https://github.com/PyO3/pyo3/blob/main/Architecture.md). ## Getting started contributing Please join in with any part of PyO3 which interests you. We use GitHub issues to record all bugs and ideas. Feel free to request an issue to be assigned to you if you want to work on it. You can browse the API of the non-public parts of PyO3 [here](https://pyo3.netlify.app/internal/doc/pyo3/index.html). The following sections also contain specific ideas on where to start contributing to PyO3. ## Setting up a development environment To work and develop PyO3, you need Python & Rust installed on your system. * We encourage the use of [rustup](https://rustup.rs/) to be able to select and choose specific toolchains based on the project. * [Pyenv](https://github.com/pyenv/pyenv) is also highly recommended for being able to choose a specific Python version. * [virtualenv](https://virtualenv.pypa.io/en/latest/) can also be used with or without Pyenv to use specific installed Python versions. * [`nox`][nox] is used to automate many of our CI tasks. ### Testing, linting, etc. with nox [`Nox`][nox] is used to automate many of our CI tasks and can be used locally to handle verification tasks as you code. We recommend running these actions via nox to make use of our preferred configuration options. You can install nox into your global python with pip: `pip install nox` or (recommended) with [`pipx`][pipx] `pip install pipx`, `pipx install nox` The main nox commands we have implemented are: * `nox -s test` will run the full suite of recommended rust and python tests (>10 minutes) * `nox -s test-rust -- skip-full` will run a short suite of rust tests (2-3 minutes) * `nox -s ruff` will check python linting and apply standard formatting rules * `nox -s rustfmt` will check basic rust linting and apply standard formatting rules * `nox -s rumdl` will check the markdown in the guide * `nox -s clippy` will run clippy to make recommendations on rust style * `nox -s bench` will benchmark your rust code * `nox -s codspeed` will run our suite of rust and python performance tests * `nox -s coverage` will analyse test coverage and output `coverage.json` (alternatively: `nox -s coverage lcov` outputs `lcov.info`) * `nox -s check-guide` will use [`lychee`][lychee] to check all the links in the guide and doc comments. Use `nox -l` to list the full set of subcommands you can run. #### UI Tests PyO3 uses [`trybuild`][trybuild] to develop UI tests to capture error messages from the Rust compiler for some of the macro functionality. The Rust compiler's error output differs depending on whether the `rust-src` component is installed. PyO3's CI has `rust-src` installed, so you need it locally for your UI test output to match: ```bash rustup component add rust-src ``` Because there are several feature combinations for these UI tests, when updating them all (e.g. for a new Rust compiler version) it may be helpful to use the `update-ui-tests` nox session: ```bash nox -s update-ui-tests ``` ## Ways to help ### Help users identify bugs The [PyO3 Discord server](https://discord.gg/33kcChzH7f) is very active with users who are new to PyO3, and often completely new to Rust. Helping them debug is a great way to get experience with the PyO3 codebase. Helping others often reveals bugs, documentation weaknesses, and missing APIs. It's a good idea to open GitHub issues for these immediately so the resolution can be designed and implemented! ### Implement issues ready for development Issues where the solution is clear and work is not in progress use the [needs-implementer](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-implementer) label. Don't be afraid if the solution is not clear to you! The core PyO3 contributors will be happy to mentor you through any questions you have to help you write the solution. ### Help write great docs PyO3 has a user guide (using mdbook) as well as the usual Rust API docs. The aim is for both of these to be detailed, easy to understand, and up-to-date. Pull requests are always welcome to fix typos, change wording, add examples, etc. There are some specific areas of focus where help is currently needed for the documentation: - Issues requesting documentation improvements are tracked with the [documentation](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Adocumentation) label. - Not all APIs had docs or examples when they were made. The goal is to have documentation on all PyO3 APIs ([#306](https://github.com/PyO3/pyo3/issues/306)). If you see an API lacking a doc, please write one and open a PR! To build the docs (including all features), install [`nox`][nox] and then run ```shell nox -s docs -- open ``` #### Doctests We use lots of code blocks in our docs. Run `cargo test --doc` when making changes to check that the doctests still work, or `cargo test` to run all the Rust tests including doctests. See https://doc.rust-lang.org/rustdoc/documentation-tests.html for a guide on doctests. #### Building the guide You can preview the user guide by building it locally with `mdbook`. First, install [`mdbook`][mdbook], the [`mdbook-tabs`][mdbook-tabs] plugin and [`nox`][nox]. Then, run ```shell nox -s build-guide -- --open ``` To check all links in the guide are valid, also install [`lychee`][lychee] and use the `check-guide` session instead: ```shell nox -s check-guide ``` ### Help design the next PyO3 Issues which don't yet have a clear solution use the [needs-design](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-design) label. If any of these issues interest you, please join in with the conversation on the issue! All opinions are valued, and if you're interested in going further with e.g. draft PRs to experiment with API designs, even better! ### Review pull requests Everybody is welcome to submit comments on open PRs. Please help ensure new PyO3 APIs are safe, performant, tidy, and easy to use! ## Writing pull requests Here are a few things to note when you are writing PRs. ### Testing and Continuous Integration The PyO3 repo uses GitHub Actions. PRs are blocked from merging if CI is not successful. Formatting, linting and tests are checked for all Rust and Python code (the pipeline will abort early if formatting fails to save resources). In addition, all warnings in Rust code are disallowed (using `RUSTFLAGS="-D warnings"`). Tests run with all supported Python versions with the latest stable Rust compiler, as well as for Python 3.9 with the minimum supported Rust version. If you are adding a new feature, you should add it to the `full` feature in our *Cargo.toml** so that it is tested in CI. You can run the CI pipeline components yourself with `nox`, see [the testing section above](#testing-linting-etc-with-nox). ### Documenting changes We use [towncrier](https://towncrier.readthedocs.io/en/stable/index.html) to generate a CHANGELOG for each release. To include your changes in the release notes, you should create one (or more) news items in the `newsfragments` directory. Valid news items should be saved as `..md` where `` is the pull request number and `` is one of the following: - `packaging` - for dependency changes and Python / Rust version compatibility changes - `added` - for new features - `changed` - for features which already existed but have been altered or deprecated - `removed` - for features which have been removed - `fixed` - for "changed" features which were classed as a bugfix Docs-only PRs do not need news items; start your PR title with `docs:` to skip the check. ### Style guide #### Generic code PyO3 has a lot of generic APIs to increase usability. These can come at the cost of generic code bloat. Where reasonable, try to implement a concrete sub-portion of generic functions. There are two forms of this: - If the concrete sub-portion doesn't benefit from re-use by other functions, name it `inner` and keep it as a local to the function. - If the concrete sub-portion is re-used by other functions, preferably name it `_foo` and place it directly below `foo` in the source code (where `foo` is the original generic function). #### FFI calls PyO3 makes a lot of FFI calls to Python's C API using raw pointers. Where possible try to avoid using pointers-to-temporaries in expressions: ```rust // dangerous pyo3::ffi::Something(name.to_object(py).as_ptr()); // because the following refactoring is a use-after-free error: let name = name.to_object(py).as_ptr(); pyo3::ffi::Something(name) ``` Instead, prefer to bind the safe owned `PyObject` wrapper before passing to ffi functions: ```rust let name: PyObject = name.to_object(py); pyo3::ffi::Something(name.as_ptr()) // name will automatically be freed when it falls out of scope ``` ## Python and Rust version support policy PyO3 aims to keep sufficient compatibility to make packaging Python extensions built with PyO3 feasible on most common package managers. To keep package maintainers' lives simpler, PyO3 will commit, wherever possible, to only adjust minimum supported Rust and Python versions at the same time. This bump will only come in an `0.x` release, roughly once per year, after the oldest supported Python version reaches its end-of-life. (Check https://endoflife.date/python for a clear timetable on these.) Below are guidelines on what compatibility all PRs are expected to deliver for each language. ### Python PyO3 supports all officially supported Python versions, as well as the latest PyPy3 release. All of these versions are tested in CI. #### Adding support for new CPython versions If you plan to add support for a pre-release version of CPython, here's a (non-exhaustive) checklist: - [ ] Wait until the last alpha release (usually alpha7), since ABI is not guaranteed until the first beta release - [ ] Add prerelease_ver-dev (e.g. `3.14-dev`) to `.github/workflows/ci.yml`, and bump version in `noxfile.py`, `pyo3-ffi/Cargo.toml` under `max-version` within `[package.metadata.cpython]`, and `max` within `pyo3-ffi/build.rs` - [ ] Add a new abi3-prerelease feature for the version (e.g. `abi3-py314`) - In `pyo3-build-config/Cargo.toml`, set abi3-most_current_stable to ["abi3-prerelease"] and abi3-prerelease to ["abi3"] - In `pyo3-ffi/Cargo.toml`, set abi3-most_current_stable to ["abi3-prerelease", "pyo3-build-config/abi3-most_current_stable"] and abi3-prerelease to ["abi3", "pyo3-build-config/abi3-prerelease"] - In `Cargo.toml`, set abi3-most_current_stable to ["abi3-prerelease", "pyo3-ffi/abi3-most_current_stable"] and abi3-prerelease to ["abi3", "pyo3-ffi/abi3-prerelease"] - [ ] Use `#[cfg(Py_prerelease])` (e.g. `#[cfg(Py_3_14)]`) and `#[cfg(not(Py_prerelease]))` to indicate changes between the stable branches of CPython and the pre-release - [ ] Do not add a Rust binding to any function, struct, or global variable prefixed with `_` in CPython's headers - [ ] Ping @ngoldbaum and @davidhewitt for assistance ### Rust PyO3 aims to make use of up-to-date Rust language features to keep the implementation as efficient as possible. The minimum Rust version supported will be decided when the release which bumps Python and Rust versions is made. At the time, the minimum Rust version will be set no higher than the lowest Rust version shipped in the current Debian, RHEL and Alpine Linux distributions. CI tests both the most recent stable Rust version and the minimum supported Rust version. Because of Rust's stability guarantees this is sufficient to confirm support for all Rust versions in between. ## Benchmarking PyO3 has two sets of benchmarks for evaluating some aspects of its performance. The benchmark suite is currently very small - please open PRs with new benchmarks if you're interested in helping to expand it! First, there are Rust-based benchmarks located in the `pyo3-benches` subdirectory. You can run these benchmarks with: nox -s bench Second, there is a Python-based benchmark contained in the `pytests` subdirectory. You can read more about it [here](https://github.com/PyO3/pyo3/tree/main/pytests). ## Code coverage You can view what code is and isn't covered by PyO3's tests. We aim to have 100% coverage - please check coverage and add tests if you notice a lack of coverage! - First, ensure the llvm-cov cargo plugin is installed. You may need to run the plugin through cargo once before using it with `nox`. ```shell cargo install cargo-llvm-cov cargo llvm-cov ``` - Then, generate an `lcov.info` file with ```shell nox -s coverage -- lcov ``` You can install an IDE plugin to view the coverage. For example, if you use VSCode: - Add the [coverage-gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) plugin. - Add these settings to VSCode's `settings.json`: ```json { "coverage-gutters.coverageFileNames": [ "lcov.info", "cov.xml", "coverage.xml", ], "coverage-gutters.showLineCoverage": true } ``` - You should now be able to see green highlights for code that is tested, and red highlights for code that is not tested. ## Sponsor this project At the moment there is no official organisation that accepts sponsorship on PyO3's behalf. If you're seeking to provide significant funding to the PyO3 ecosystem, please reach out to us on [GitHub](https://github.com/PyO3/pyo3/issues/new) or [Discord](https://discord.gg/33kcChzH7f) and we can discuss. In the meanwhile, some of our maintainers have personal GitHub sponsorship pages and would be grateful for your support: - [davidhewitt](https://github.com/sponsors/davidhewitt) - [messense](https://github.com/sponsors/messense) [mdbook]: https://rust-lang.github.io/mdBook/cli/index.html [mdbook-tabs]: https://mdbook-plugins.rustforweb.org/tabs.html [lychee]: https://github.com/lycheeverse/lychee [nox]: https://github.com/theacodes/nox [pipx]: https://pipx.pypa.io/stable/ [trybuild]: https://github.com/dtolnay/trybuild ================================================ FILE: LICENSE-APACHE ================================================ Copyright (c) 2017-present PyO3 Project and Contributors. https://github.com/PyO3 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2023-present PyO3 Project and Contributors. https://github.com/PyO3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # PyO3 [![actions status](https://img.shields.io/github/actions/workflow/status/PyO3/pyo3/ci.yml?branch=main&logo=github&style=)](https://github.com/PyO3/pyo3/actions) [![benchmark](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/PyO3/pyo3) [![codecov](https://img.shields.io/codecov/c/gh/PyO3/pyo3?logo=codecov)](https://codecov.io/gh/PyO3/pyo3) [![crates.io](https://img.shields.io/crates/v/pyo3?logo=rust)](https://crates.io/crates/pyo3) [![minimum rustc 1.83](https://img.shields.io/badge/rustc-1.83+-blue?logo=rust)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) [![discord server](https://img.shields.io/discord/1209263839632424990?logo=discord)](https://discord.gg/33kcChzH7f) [![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green?logo=github)](https://github.com/PyO3/pyo3/blob/main/Contributing.md) [Rust](https://www.rust-lang.org/) bindings for [Python](https://www.python.org/), including tools for creating native Python extension modules. Running and interacting with Python code from a Rust binary is also supported. - User Guide: [stable](https://pyo3.rs) | [main](https://pyo3.rs/main) - API Documentation: [stable](https://docs.rs/pyo3/) | [main](https://pyo3.rs/main/doc) ## Usage Requires Rust 1.83 or greater. PyO3 supports the following Python distributions: - CPython 3.7 or greater - PyPy 7.3 (Python 3.11+) - GraalPy 25.0 or greater (Python 3.12+) You can use PyO3 to write a native Python module in Rust, or to embed Python in a Rust binary. The following sections explain each of these in turn. ### Using Rust from Python PyO3 can be used to generate a native Python module. The easiest way to try this out for the first time is to use [`maturin`](https://github.com/PyO3/maturin). `maturin` is a tool for building and publishing Rust-based Python packages with minimal configuration. The following steps install `maturin`, use it to generate and build a new Python package, and then launch Python to import and execute a function from the package. First, follow the commands below to create a new directory containing a new Python `virtualenv`, and install `maturin` into the virtualenv using Python's package manager, `pip`: ```bash # (replace string_sum with the desired package name) $ mkdir string_sum $ cd string_sum $ python -m venv .env $ source .env/bin/activate $ pip install maturin ``` Still inside this `string_sum` directory, now run `maturin init`. This will generate the new package source. When given the choice of bindings to use, select pyo3 bindings: ```bash $ maturin init ✔ 🤷 What kind of bindings to use? · pyo3 ✨ Done! New project created string_sum ``` The most important files generated by this command are `Cargo.toml` and `lib.rs`, which will look roughly like the following: **`Cargo.toml`** ```toml [package] name = "string_sum" version = "0.1.0" edition = "2021" [lib] # The name of the native library. This is the name which will be used in Python to import the # library (i.e. `import string_sum`). If you change this, you must also change the name of the # `#[pymodule]` in `src/lib.rs`. name = "string_sum" # "cdylib" is necessary to produce a shared library for Python to import from. # # Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able # to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.: # crate-type = ["cdylib", "rlib"] crate-type = ["cdylib"] [dependencies] pyo3 = "0.28.2" ``` **`src/lib.rs`** ```rust /// A Python module implemented in Rust. The name of this module must match /// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to /// import the module. #[pyo3::pymodule] mod string_sum { use pyo3::prelude::*; /// Formats the sum of two numbers as string. #[pyfunction] fn sum_as_string(a: usize, b: usize) -> PyResult { Ok((a + b).to_string()) } } ``` Finally, run `maturin develop`. This will build the package and install it into the Python virtualenv previously created and activated. The package is then ready to be used from `python`: ```bash $ maturin develop # lots of progress output as maturin runs the compilation... $ python >>> import string_sum >>> string_sum.sum_as_string(5, 20) '25' ``` To make changes to the package, just edit the Rust source code and then re-run `maturin develop` to recompile. To run this all as a single copy-and-paste, use the bash script below (replace `string_sum` in the first command with the desired package name): ```bash mkdir string_sum && cd "$_" python -m venv .env source .env/bin/activate pip install maturin maturin init --bindings pyo3 maturin develop ``` If you want to be able to run `cargo test` or use this project in a Cargo workspace and are running into linker issues, there are some workarounds in [the FAQ](https://pyo3.rs/latest/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror). As well as with `maturin`, it is possible to build using [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](https://pyo3.rs/latest/building-and-distribution.html#manual-builds). Both offer more flexibility than `maturin` but require more configuration to get started. ### Using Python from Rust To embed Python into a Rust binary, you need to ensure that your Python installation contains a shared library. The following steps demonstrate how to ensure this (for Ubuntu), and then give some example code which runs an embedded Python interpreter. To install the Python shared library on Ubuntu: ```bash sudo apt install python3-dev ``` To install the Python shared library on RPM based distributions (e.g. Fedora, Red Hat, SuSE), install the `python3-devel` package. Start a new project with `cargo new` and add `pyo3` to the `Cargo.toml` like this: ```toml [dependencies.pyo3] version = "0.28.2" # Enabling this cargo feature will cause PyO3 to start a Python interpreter on first call to `Python::attach` features = ["auto-initialize"] ``` Example program displaying the value of `sys.version` and the current user name: ```rust use pyo3::prelude::*; use pyo3::types::IntoPyDict; fn main() -> PyResult<()> { Python::attach(|py| { let sys = py.import("sys")?; let version: String = sys.getattr("version")?.extract()?; let locals = [("os", py.import("os")?)].into_py_dict(py)?; let code = c"os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'"; let user: String = py.eval(code, None, Some(&locals))?.extract()?; println!("Hello {}, I'm Python {}", user, version); Ok(()) }) } ``` The guide has [a section](https://pyo3.rs/latest/python-from-rust.html) with lots of examples about this topic. ## Tools and libraries - [maturin](https://github.com/PyO3/maturin) _Build and publish crates with pyo3, rust-cpython or cffi bindings as well as rust binaries as python packages_ - [setuptools-rust](https://github.com/PyO3/setuptools-rust) _Setuptools plugin for Rust support_. - [pyo3-built](https://github.com/PyO3/pyo3-built) _Simple macro to expose metadata obtained with the [`built`](https://crates.io/crates/built) crate as a [`PyDict`](https://docs.rs/pyo3/*/pyo3/types/struct.PyDict.html)_ - [rust-numpy](https://github.com/PyO3/rust-numpy) _Rust binding of NumPy C-API_ - [dict-derive](https://github.com/gperinazzo/dict-derive) _Derive FromPyObject to automatically transform Python dicts into Rust structs_ - [pyo3-log](https://github.com/vorner/pyo3-log) _Bridge from Rust to Python logging_ - [pythonize](https://github.com/davidhewitt/pythonize) _Serde serializer for converting Rust objects to JSON-compatible Python objects_ - [pyo3-async-runtimes](https://github.com/PyO3/pyo3-async-runtimes) _Utilities for interoperability with Python's Asyncio library and Rust's async runtimes._ - [rustimport](https://github.com/mityax/rustimport) _Directly import Rust files or crates from Python, without manual compilation step. Provides pyo3 integration by default and generates pyo3 binding code automatically._ - [pyo3-arrow](https://crates.io/crates/pyo3-arrow) _Lightweight [Apache Arrow](https://arrow.apache.org/) integration for pyo3._ - [pyo3-bytes](https://crates.io/crates/pyo3-bytes) _Integration between [`bytes`](https://crates.io/crates/bytes) and pyo3._ - [pyo3-object_store](https://github.com/developmentseed/obstore/tree/main/pyo3-object_store) _Integration between [`object_store`](https://docs.rs/object_store) and [`pyo3`](https://github.com/PyO3/pyo3)._ ## Examples - [anise](https://github.com/nyx-space/anise) _A modern, high-performance toolkit for spacecraft mission design, notably used to help softly land Firefly Blue Ghost on the Moon on 02 Feb 2025._ - [arro3](https://github.com/kylebarron/arro3) _A minimal Python library for Apache Arrow, connecting to the Rust arrow crate._ - [arro3-compute](https://github.com/kylebarron/arro3/tree/main/arro3-compute) _`arro3-compute`_ - [arro3-core](https://github.com/kylebarron/arro3/tree/main/arro3-core) _`arro3-core`_ - [arro3-io](https://github.com/kylebarron/arro3/tree/main/arro3-io) _`arro3-io`_ - [bed-reader](https://github.com/fastlmm/bed-reader) _Read and write the PLINK BED format, simply and efficiently._ - Shows Rayon/ndarray::parallel (including capturing errors, controlling thread num), Python types to Rust generics, Github Actions - [blake3-py](https://github.com/oconnor663/blake3-py) _Python bindings for the [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) cryptographic hash function._ - Parallelized [builds](https://github.com/oconnor663/blake3-py/blob/master/.github/workflows/dists.yml) on GitHub Actions for MacOS, Linux, Windows, including free-threaded 3.13t wheels. - [cellular_raza](https://cellular-raza.com) _A cellular agent-based simulation framework for building complex models from a clean slate._ - [connector-x](https://github.com/sfu-db/connector-x/tree/main/connectorx-python) _Fastest library to load data from DB to DataFrames in Rust and Python._ - [cryptography](https://github.com/pyca/cryptography/tree/main/src/rust) _Python cryptography library with some functionality in Rust._ - [css-inline](https://github.com/Stranger6667/css-inline/tree/master/bindings/python) _CSS inlining for Python implemented in Rust._ - [datafusion-python](https://github.com/apache/arrow-datafusion-python) _A Python library that binds to Apache Arrow in-memory query engine DataFusion._ - [deltalake-python](https://github.com/delta-io/delta-rs/tree/main/python) _Native Delta Lake Python binding based on delta-rs with Pandas integration._ - [fastbloom](https://github.com/yankun1992/fastbloom) _A fast [bloom filter](https://github.com/yankun1992/fastbloom#BloomFilter) | [counting bloom filter](https://github.com/yankun1992/fastbloom#countingbloomfilter) implemented by Rust for Rust and Python!_ - [fastuuid](https://github.com/thedrow/fastuuid/) _Python bindings to Rust's UUID library._ - [fast-paseto](https://github.com/CodingCogs-OSS/Fast-Paseto) _High-performance PASETO (Platform-Agnostic Security Tokens) implementation with Python bindings._ - [feos](https://github.com/feos-org/feos) _Lightning fast thermodynamic modeling in Rust with fully developed Python interface._ - [finalytics](https://github.com/Nnamdi-sys/finalytics) _Investment Analysis library in Rust | Python._ - [forust](https://github.com/jinlow/forust) _A lightweight gradient boosted decision tree library written in Rust._ - [geo-index](https://github.com/kylebarron/geo-index) _A Rust crate and [Python library](https://github.com/kylebarron/geo-index/tree/main/python) for packed, immutable, zero-copy spatial indexes._ - [granian](https://github.com/emmett-framework/granian) _A Rust HTTP server for Python applications._ - [haem](https://github.com/BooleanCat/haem) _A Python library for working on Bioinformatics problems._ - [hifitime](https://github.com/nyx-space/hifitime) _A high fidelity time management library for engineering and scientific applications where general relativity and time dilation matter._ - [html2text-rs](https://github.com/deedy5/html2text_rs) _Python library for converting HTML to markup or plain text._ - [html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever) _Using [html5ever](https://github.com/servo/html5ever) through [kuchiki](https://github.com/kuchiki-rs/kuchiki) to speed up html parsing and css-selecting._ - [hudi-rs](https://github.com/apache/hudi-rs) _The native Rust implementation for Apache Hudi, with C++ & Python API bindings._ - [inline-python](https://github.com/m-ou-se/inline-python) _Inline Python code directly in your Rust code._ - [johnnycanencrypt](https://github.com/kushaldas/johnnycanencrypt) OpenPGP library with Yubikey support. - [jsonschema](https://github.com/Stranger6667/jsonschema/tree/master/crates/jsonschema-py) _A high-performance JSON Schema validator for Python._ - [mocpy](https://github.com/cds-astro/mocpy) _Astronomical Python library offering data structures for describing any arbitrary coverage regions on the unit sphere._ - [obstore](https://github.com/developmentseed/obstore) _The simplest, highest-throughput Python interface to Amazon S3, Google Cloud Storage, Azure Storage, & other S3-compliant APIs, powered by Rust._ - [opendal](https://github.com/apache/opendal/tree/main/bindings/python) _A data access layer that allows users to easily and efficiently retrieve data from various storage services in a unified way._ - [orjson](https://github.com/ijl/orjson) _Fast Python JSON library._ - [ormsgpack](https://github.com/aviramha/ormsgpack) _Fast Python msgpack library._ - [polars](https://github.com/pola-rs/polars) _Fast multi-threaded DataFrame library in Rust | Python | Node.js._ - [pycrdt](https://github.com/jupyter-server/pycrdt) _Python bindings for the Rust CRDT implementation [Yrs](https://github.com/y-crdt/y-crdt)._ - [pydantic-core](https://github.com/pydantic/pydantic-core) _Core validation logic for pydantic written in Rust._ - [primp](https://github.com/deedy5/primp) _The fastest python HTTP client that can impersonate web browsers by mimicking their headers and TLS/JA3/JA4/HTTP2 fingerprints._ - [radiate](https://github.com/pkalivas/radiate): _A high-performance evolution engine for genetic programming and evolutionary algorithms._ - [rateslib](https://github.com/attack68/rateslib) _A fixed income library for Python using Rust extensions._ - [river](https://github.com/online-ml/river) _Online machine learning in python, the computationally heavy statistics algorithms are implemented in Rust._ - [robyn](https://github.com/sparckles/Robyn) A Super Fast Async Python Web Framework with a Rust runtime. - [rust-python-coverage](https://github.com/cjermain/rust-python-coverage) _Example PyO3 project with automated test coverage for Rust and Python._ - [rnet](https://github.com/0x676e67/rnet) Asynchronous Python HTTP Client with Black Magic - [sail](https://github.com/lakehq/sail) _Unifying stream, batch, and AI workloads with Apache Spark compatibility._ - [tiktoken](https://github.com/openai/tiktoken) _A fast BPE tokeniser for use with OpenAI's models._ - [tokenizers](https://github.com/huggingface/tokenizers/tree/main/bindings/python) _Python bindings to the Hugging Face tokenizers (NLP) written in Rust._ - [tzfpy](http://github.com/ringsaturn/tzfpy) _A fast package to convert longitude/latitude to timezone name._ - [toml-rs](https://github.com/lava-sh/toml-rs) _A High-Performance TOML v1.0.0 and v1.1.0 parser for Python written in Rust._ - [utiles](https://github.com/jessekrubin/utiles) _Fast Python web-map tile utilities_ ## Articles and other media - [(Video) Using Rust in Free-Threaded vs Regular Python 3.13](https://www.youtube.com/watch?v=J7phN_M4GLM) - Jun 4, 2025 - [(Video) Techniques learned from five years finding the way for Rust in Python](https://www.youtube.com/watch?v=KTQn_PTHNCw) - Feb 26, 2025 - [(Podcast) Bridging Python and Rust: An Interview with PyO3 Maintainer David Hewitt](https://www.youtube.com/watch?v=P47JUMSQagU) - Aug 30, 2024 - [(Video) PyO3: From Python to Rust and Back Again](https://www.youtube.com/watch?v=UmL_CA-v3O8) - Jul 3, 2024 - [Parsing Python ASTs 20x Faster with Rust](https://www.gauge.sh/blog/parsing-python-asts-20x-faster-with-rust) - Jun 17, 2024 - [(Video) How Python Harnesses Rust through PyO3](https://www.youtube.com/watch?v=UilujdubqVU) - May 18, 2024 - [(Video) Combining Rust and Python: The Best of Both Worlds?](https://www.youtube.com/watch?v=lyG6AKzu4ew) - Mar 1, 2024 - [(Video) Extending Python with Rust using PyO3](https://www.youtube.com/watch?v=T45ZEmSR1-s) - Dec 16, 2023 - [A Week of PyO3 + rust-numpy (How to Speed Up Your Data Pipeline X Times)](https://terencezl.github.io/blog/2023/06/06/a-week-of-pyo3-rust-numpy/) - Jun 6, 2023 - [(Podcast) PyO3 with David Hewitt](https://rustacean-station.org/episode/david-hewitt/) - May 19, 2023 - [Making Python 100x faster with less than 100 lines of Rust](https://ohadravid.github.io/posts/2023-03-rusty-python/) - Mar 28, 2023 - [How Pydantic V2 leverages Rust's Superpowers](https://fosdem.org/2023/schedule/event/rust_how_pydantic_v2_leverages_rusts_superpowers/) - Feb 4, 2023 - [How we extended the River stats module with Rust using PyO3](https://boring-guy.sh/posts/river-rust/) - Dec 23, 2022 - [Nine Rules for Writing Python Extensions in Rust](https://towardsdatascience.com/nine-rules-for-writing-python-extensions-in-rust-d35ea3a4ec29?sk=f8d808d5f414154fdb811e4137011437) - Dec 31, 2021 - [Calling Rust from Python using PyO3](https://saidvandeklundert.net/learn/2021-11-18-calling-rust-from-python-using-pyo3/) - Nov 18, 2021 - [davidhewitt's 2021 talk at Rust Manchester meetup](https://www.youtube.com/watch?v=-XyWG_klSAw&t=320s) - Aug 19, 2021 - [Incrementally porting a small Python project to Rust](https://blog.waleedkhan.name/port-python-to-rust/) - Apr 29, 2021 - [Vortexa - Integrating Rust into Python](https://www.vortexa.com/blog/integrating-rust-into-python) - Apr 12, 2021 - [Writing and publishing a Python module in Rust](https://blog.yossarian.net/2020/08/02/Writing-and-publishing-a-python-module-in-rust) - Aug 2, 2020 ## Contributing Everyone is welcomed to contribute to PyO3! There are many ways to support the project, such as: - help PyO3 users with issues on GitHub and [Discord](https://discord.gg/33kcChzH7f) - improve documentation - write features and bugfixes - publish blogs and examples of how to use PyO3 Our [contributing notes](https://github.com/PyO3/pyo3/blob/main/Contributing.md) and [architecture guide](https://github.com/PyO3/pyo3/blob/main/Architecture.md) have more resources if you wish to volunteer time for PyO3 and are searching where to start. If you don't have time to contribute yourself but still wish to support the project's future success, some of our maintainers have GitHub sponsorship pages: - [davidhewitt](https://github.com/sponsors/davidhewitt) - [messense](https://github.com/sponsors/messense) ## License PyO3 is licensed under the [Apache-2.0 license](LICENSE-APACHE) or the [MIT license](LICENSE-MIT), at your option. Python is licensed under the [Python License](https://docs.python.org/3/license.html). Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in PyO3 by you, as defined in the Apache License, shall be dual-licensed as above, without any additional terms or conditions. Deploys by Netlify ================================================ FILE: Releasing.md ================================================ # Releasing This is notes for the current process of releasing a new PyO3 version. Replace `` in all instructions below with the new version. ## 1. Prepare the release commit Follow the process below to update all required pieces to bump the version. All these changes are done in a single commit because it makes it clear to git readers what happened to bump the version. It also makes it easy to cherry-pick the version bump onto the `main` branch when tidying up branch history at the end of the release process. 1. Replace all instances of the PyO3 current version and the with the new version to be released. Places to check: - `Cargo.toml` for all PyO3 crates in the repository. - Examples in `README.md` - PyO3 version embedded into documentation like the README. - `pre-script.rhai` templates for the examples. - `[towncrier]` section in `pyproject.toml`. Some of the above locations may already have the new version with a `-dev` suffix, which needs to be removed. **Make sure not to modify the CHANGELOG during this step!** 2. Run `towncrier build` to generate the CHANGELOG. The version used by `towncrier` should automatically be correct because of the update to `pyproject.toml` in step 1. 3. Manually edit the CHANGELOG for final notes. Steps to do: - Adjust wording of any release lines to make them clearer for users / fix typos. - Add a new link at the bottom for the new version, and update the `Unreleased` link. 4. Create the commit containing all the above changes, with a message of `release: `. Push to `release-` branch on the main PyO3 repository, where `` depends on whether this is a major or minor release: - for O.X.0 minor releases, just use `0.X`, e.g. `release-0.17`. This will become the maintenance branch after release. - for 0.X.Y patch releases, use the full `0.X.Y`, e.g. `release-0.17.1`. This will be deleted after merge. ## 2. Create the release PR and draft release notes Open a PR for the branch, and confirm that it passes CI. For `0.X.0` minor releases, the PR should be merging into `main`, for `0.X.Y` patch releases, the PR should be merging the `release-0.X` maintenance branch. On https://github.com/PyO3/pyo3/releases, click "Draft a new release". The tag will be a new tag of `v` (note preceding `v`) and target should be the `release-` branch you just pushed. Write release notes which match the style of previous releases. You can get the list of contributors by running `nox -s contributors -- v release-` to get contributors from the previous version tag through to the branch tip you just pushed. (This uses the GitHub API, so you'll need to push the branch first.) Save as a draft and wait for now. ## 3. Leave for a cooling off period Wait a couple of days in case anyone wants to hold up the release to add bugfixes etc. ## 4. Put live To put live: - 1. merge the release PR - 2. publish a release on GitHub targeting the release branch CI will automatically push to `crates.io`. ## 5. Tidy the main branch If the release PR targeted a branch other than main, you will need to cherry-pick the version bumps, CHANGELOG modifications and removal of towncrier `newsfragments` and open another PR to land these on main. ## 6. Delete the release branch (patch releases only) For 0.X.Y patch releases, the release branch is no longer needed, so it should be deleted. ================================================ FILE: assets/script.py ================================================ # Used in PyModule examples. class Blah: pass ================================================ FILE: build.rs ================================================ use std::env; use pyo3_build_config::pyo3_build_script_impl::{cargo_env_var, errors::Result}; use pyo3_build_config::{ add_python_framework_link_args, bail, print_feature_cfgs, InterpreterConfig, }; fn ensure_auto_initialize_ok(interpreter_config: &InterpreterConfig) -> Result<()> { if cargo_env_var("CARGO_FEATURE_AUTO_INITIALIZE").is_some() && !interpreter_config.shared { bail!( "The `auto-initialize` feature is enabled, but your python installation only supports \ embedding the Python interpreter statically. If you are attempting to run tests, or a \ binary which is okay to link dynamically, install a Python distribution which ships \ with the Python shared library.\n\ \n\ Embedding the Python interpreter statically does not yet have first-class support in \ PyO3. If you are sure you intend to do this, disable the `auto-initialize` feature.\n\ \n\ For more information, see \ https://pyo3.rs/v{pyo3_version}/\ building-and-distribution.html#embedding-python-in-rust", pyo3_version = env::var("CARGO_PKG_VERSION").unwrap() ); } Ok(()) } /// Prepares the PyO3 crate for compilation. /// /// This loads the config from pyo3-build-config and then makes some additional checks to improve UX /// for users. /// /// Emits the cargo configuration based on this config as well as a few checks of the Rust compiler /// version to enable features which aren't supported on MSRV. fn configure_pyo3() -> Result<()> { let interpreter_config = pyo3_build_config::get(); ensure_auto_initialize_ok(interpreter_config)?; for cfg in interpreter_config.build_script_outputs() { println!("{cfg}") } print_feature_cfgs(); // Make `cargo test` etc work on macOS with Xcode bundled Python add_python_framework_link_args(); Ok(()) } fn main() { pyo3_build_config::print_expected_cfgs(); if let Err(e) = configure_pyo3() { eprintln!("error: {}", e.report()); std::process::exit(1) } } ================================================ FILE: codecov.yml ================================================ comment: off coverage: status: project: default: target: auto # Allow a tiny drop of overall project coverage in PR due to # not all configurations being tested in PR runs. # # (Note that patch coverage will still be required to be at least # the project coverage.) threshold: 1% ignore: - tests/ - pytests/ - src/test_hygiene/*.rs ================================================ FILE: emscripten/.gitignore ================================================ pybuilddir.txt builddir ================================================ FILE: emscripten/Makefile ================================================ CURDIR=$(abspath .) # These three are passed in from nox. BUILDROOT ?= $(CURDIR)/builddir PYTHON ?= python3 PYMAJORMINORMICRO ?= $(shell $(PYTHON) --version 2>&1 | awk '{print $$2}') export EMSDKDIR = $(PYTHONBUILD)/emsdk-cache PLATFORM=wasm32_emscripten SYSCONFIGDATA_NAME=_sysconfigdata__$(PLATFORM) # Set version variables. version_tuple := $(subst ., ,$(PYMAJORMINORMICRO:v%=%)) PYMAJOR=$(word 1,$(version_tuple)) PYMINOR=$(word 2,$(version_tuple)) PYMICRO=$(word 3,$(version_tuple)) PYVERSION=$(PYMAJORMINORMICRO) PYMAJORMINOR=$(PYMAJOR).$(PYMINOR) PYTHONURL=https://www.python.org/ftp/python/$(PYMAJORMINORMICRO)/Python-$(PYVERSION).tgz # TODO: resume download once 3.14.4 ships with emscripten cache # PYTHONTARBALL=$(BUILDROOT)/downloads/Python-$(PYVERSION).tgz # PYTHONBUILD=$(BUILDROOT)/build/Python-$(PYVERSION) PYTHONBUILD=$(BUILDROOT)/build/cpython PYTHONLIBDIR=$(BUILDROOT)/install/Python-$(PYVERSION)/lib CROSS_PYTHON=$(PYTHONBUILD)/cross-build/wasm32-emscripten/build/python/python.sh all: $(PYTHONLIBDIR)/libpython$(PYMAJORMINOR).a $(BUILDROOT)/.exists: mkdir -p $(BUILDROOT) touch $@ # TODO: use tarball once 3.14.4 ships with emscripten cache # $(PYTHONTARBALL): # [ -d $(BUILDROOT)/downloads ] || mkdir -p $(BUILDROOT)/downloads # wget -q -O $@ $(PYTHONURL) # $(PYTHONBUILD)/.patched: $(PYTHONTARBALL) # [ -d $(PYTHONBUILD) ] || ( \ # mkdir -p $(dir $(PYTHONBUILD));\ # tar -C $(dir $(PYTHONBUILD)) -xf $(PYTHONTARBALL) \ # ) # touch $@ ifneq ($(PYMAJORMINOR),3.14) $(error PYMAJORMINOR must be 3.14, got '$(PYMAJORMINOR)') endif $(PYTHONBUILD)/.patched: $(BUILDROOT)/.exists [ -d $(PYTHONBUILD) ] || ( \ mkdir -p $(dir $(PYTHONBUILD));\ git clone --depth 1 --branch 3.14 https://github.com/python/cpython $(PYTHONBUILD) \ ) touch $@ $(CROSS_PYTHON): $(PYTHONBUILD)/.patched cd $(PYTHONBUILD) && \ $(PYTHON) Tools/wasm/emscripten install-emscripten --quiet --emsdk-cache=$(EMSDKDIR) && \ $(PYTHON) Tools/wasm/emscripten build --quiet --emsdk-cache=$(EMSDKDIR) $(PYTHONLIBDIR)/libpython$(PYMAJORMINOR).a: $(CROSS_PYTHON) # Generate sysconfigdata _PYTHON_SYSCONFIGDATA_NAME=$(SYSCONFIGDATA_NAME) _PYTHON_PROJECT_BASE=$(PYTHONBUILD)/cross-build/wasm32-emscripten/build/python $(CROSS_PYTHON) -m sysconfig --generate-posix-vars cp `cat pybuilddir.txt`/$(SYSCONFIGDATA_NAME).py $(PYTHONBUILD)/Lib mkdir -p $(PYTHONLIBDIR) # Make a static library for _hacl, for some reason these are missing from the build? # source emsdk_env.sh to get emar in PATH, works best when done from the emsdk directory EMSDK_ENV=$$(find $(PYTHONBUILD)/emsdk-cache -name 'emsdk_env.sh' | head -n 1) && \ cd $$(dirname $$EMSDK_ENV) && \ . $$EMSDK_ENV && \ cd $(PYTHONBUILD)/cross-build/wasm32-emscripten/build/python && \ emar rcs Modules/_hacl/libhacl.a Modules/_hacl/*.o # Copy all .a libraries find $(PYTHONBUILD)/cross-build/wasm32-emscripten/ -name '*.a' -exec cp {} $(PYTHONLIBDIR) \; # Install Python stdlib cp -r $(PYTHONBUILD)/Lib $(PYTHONLIBDIR)/python$(PYMAJORMINOR) clean: rm -rf $(BUILDROOT) ================================================ FILE: emscripten/runner.py ================================================ #!/usr/local/bin/python import pathlib import sys import subprocess p = pathlib.Path(sys.argv[1]) command = ["node", p.name, *sys.argv[2:]] print("Running:", " ".join(command)) sys.exit(subprocess.call(command, cwd=p.parent)) ================================================ FILE: examples/Cargo.toml ================================================ [package] name = "pyo3-examples" version = "0.0.0" publish = false edition = "2021" rust-version = "1.83" [dev-dependencies] pyo3 = { path = "..", features = ["auto-initialize"] } [[example]] name = "decorator" path = "decorator/src/lib.rs" crate-type = ["cdylib"] doc-scrape-examples = true ================================================ FILE: examples/README.md ================================================ # PyO3 Examples These example crates are a collection of toy extension modules built with PyO3. They are all tested using `nox` in PyO3's CI. Below is a brief description of each of these: | Example | Description | | ------- | ----------- | | `decorator` | A project showcasing the example from the [Emulating callable objects](https://pyo3.rs/latest/class/call.html) chapter of the guide. | | `maturin-starter` | A template project which is configured to use [`maturin`](https://github.com/PyO3/maturin) for development. | | `setuptools-rust-starter` | A template project which is configured to use [`setuptools_rust`](https://github.com/PyO3/setuptools-rust/) for development. | | `plugin` | Illustrates how to use Python as a scripting language within a Rust application | Note that there are also other examples in the `pyo3-ffi/examples` directory that illustrate how to create rust extensions using raw FFI calls into the CPython C API instead of using PyO3's abstractions. ## Creating new projects from these examples To copy an example, use [`cargo-generate`](https://crates.io/crates/cargo-generate). Follow the commands below, replacing `` with the example to start from: ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/ ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: examples/decorator/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [lib] name = "decorator" crate-type = ["cdylib"] [dependencies] pyo3 = "{{PYO3_VERSION}}" ================================================ FILE: examples/decorator/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.28.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); ================================================ FILE: examples/decorator/.template/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "{{project-name}}" version = "0.1.0" [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: examples/decorator/Cargo.toml ================================================ [package] name = "decorator" version = "0.1.0" edition = "2021" rust-version = "1.83" [lib] name = "decorator" crate-type = ["cdylib"] [dependencies] pyo3 = { path = "../../" } [workspace] ================================================ FILE: examples/decorator/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: examples/decorator/README.md ================================================ # decorator A project showcasing the example from the [Emulating callable objects](https://pyo3.rs/latest/class/call.html) chapter of the guide. ## Building and Testing To build this package, first install `maturin`: ```shell pip install maturin ``` To build and test use `maturin develop`: ```shell pip install -r requirements-dev.txt maturin develop pytest ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/decorator ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: examples/decorator/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: examples/decorator/noxfile.py ================================================ import nox @nox.session def python(session: nox.Session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install(".[dev]") session.run("pytest") ================================================ FILE: examples/decorator/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "decorator" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: examples/decorator/src/lib.rs ================================================ use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; use std::sync::atomic::{AtomicU64, Ordering}; /// A function decorator that keeps track how often it is called. /// /// It otherwise doesn't do anything special. #[pyclass(name = "Counter")] pub struct PyCounter { // Keeps track of how many calls have gone through. // // See the discussion at the end for why `AtomicU64` is used. count: AtomicU64, // This is the actual function being wrapped. wraps: Py, } #[pymethods] impl PyCounter { // Note that we don't validate whether `wraps` is actually callable. // // While we could use `PyAny::is_callable` for that, it has some flaws: // 1. It doesn't guarantee the object can actually be called successfully // 2. We still need to handle any exceptions that the function might raise #[new] fn __new__(wraps: Py) -> Self { PyCounter { count: AtomicU64::new(0), wraps, } } #[getter] fn count(&self) -> u64 { self.count.load(Ordering::Relaxed) } #[pyo3(signature = (*args, **kwargs))] fn __call__( &self, py: Python<'_>, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { let new_count = self.count.fetch_add(1, Ordering::Relaxed); let name = self.wraps.getattr(py, "__name__")?; println!("{name} has been called {new_count} time(s)."); // After doing something, we finally forward the call to the wrapped function let ret = self.wraps.call(py, args, kwargs)?; // We could do something with the return value of // the function before returning it Ok(ret) } } #[pymodule] pub fn decorator(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_class::()?; Ok(()) } ================================================ FILE: examples/decorator/tests/example.py ================================================ from decorator import Counter @Counter def say_hello(): print("hello") say_hello() say_hello() say_hello() say_hello() assert say_hello.count == 4 ================================================ FILE: examples/decorator/tests/test_.py ================================================ from decorator import Counter def test_no_args(): @Counter def say_hello(): print("hello") say_hello() say_hello() say_hello() say_hello() assert say_hello.count == 4 def test_arg(): @Counter def say_hello(name): print(f"hello {name}") say_hello("a") say_hello("b") say_hello("c") say_hello("d") assert say_hello.count == 4 def test_default_arg(): @Counter def say_hello(name="default"): print(f"hello {name}") say_hello("a") say_hello() say_hello("c") say_hello() assert say_hello.count == 4 # https://github.com/PyO3/pyo3/discussions/2598 def test_discussion_2598(): @Counter def say_hello(): if say_hello.count < 2: print("hello from decorator") say_hello() say_hello() ================================================ FILE: examples/getitem/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [lib] name = "getitem" crate-type = ["cdylib"] [dependencies] pyo3 = "{{PYO3_VERSION}}" ================================================ FILE: examples/getitem/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.19.0"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); ================================================ FILE: examples/getitem/.template/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "{{project-name}}" version = "0.1.0" [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: examples/getitem/Cargo.toml ================================================ [package] name = "getitem" version = "0.1.0" edition = "2021" rust-version = "1.83" [lib] name = "getitem" crate-type = ["cdylib"] [dependencies] pyo3 = { path = "../../" } [workspace] ================================================ FILE: examples/getitem/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: examples/getitem/README.md ================================================ # getitem A project showcasing how to create a `__getitem__` override that also showcases how to deal with multiple incoming types ## Relevant Documentation Some of the relevant documentation links for this example: * Converting Slices to Indices: https://docs.rs/pyo3/latest/pyo3/types/struct.PySlice.html#method.indices * GetItem Docs: https://pyo3.rs/latest/class/protocols.html?highlight=__getitem__#mapping--sequence-types * Extract: https://pyo3.rs/latest/conversions/traits.html?highlight=extract#extract-and-the-frompyobject-trait * Downcast and getattr: https://pyo3.rs/v0.19.0/types.html?highlight=getattr#pyany ## Building and Testing To build this package, first install `maturin`: ```shell pip install maturin ``` To build and test use `maturin develop`: ```shell pip install -r requirements-dev.txt maturin develop pytest ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/decorator ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: examples/getitem/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: examples/getitem/noxfile.py ================================================ import nox @nox.session def python(session: nox.Session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install(".[dev]") session.run("pytest") ================================================ FILE: examples/getitem/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "getitem" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: examples/getitem/src/lib.rs ================================================ // This is a very fake example of how to check __getitem__ parameter and handle appropriately use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::types::PySlice; #[derive(FromPyObject)] enum IntOrSlice<'py> { Int(i32), Slice(Bound<'py, PySlice>), } #[pyclass] struct ExampleContainer { // represent the maximum length our container is pretending to be max_length: i32, } #[pymethods] impl ExampleContainer { #[new] fn new() -> Self { ExampleContainer { max_length: 100 } } fn __getitem__(&self, key: &Bound<'_, PyAny>) -> PyResult { if let Ok(position) = key.extract::() { return Ok(position); } else if let Ok(slice) = key.cast::() { // METHOD 1 - the use PySliceIndices to help with bounds checking and for cases when only start or end are provided // in this case the start/stop/step all filled in to give valid values based on the max_length given let index = slice.indices(self.max_length as isize).unwrap(); let _delta = index.stop - index.start; // METHOD 2 - Do the getattr manually really only needed if you have some special cases for stop/_step not being present // convert to indices and this will help you deal with stop being the max length let start: i32 = slice.getattr("start")?.extract()?; // This particular example assumes stop is present, but note that if not present, this will cause us to return due to the // extract failing. Not needing custom code to deal with this is a good reason to use the Indices method. let stop: i32 = slice.getattr("stop")?.extract()?; // example of grabbing step since it is not always present let _step: i32 = match slice.getattr("step")?.extract() { // if no value found assume step is 1 Ok(v) => v, Err(_) => 1 as i32, }; // Use something like this if you don't support negative stepping and want to give users // leeway on how they provide their ordering let (start, stop) = if start > stop { (stop, start) } else { (start, stop) }; let delta = stop - start; return Ok(delta); } else { return Err(PyTypeError::new_err("Unsupported type")); } } fn __setitem__(&self, idx: IntOrSlice, value: u32) -> PyResult<()> { match idx { IntOrSlice::Slice(slice) => { let index = slice.indices(self.max_length as isize).unwrap(); println!( "Got a slice! {}-{}, step: {}, value: {}", index.start, index.stop, index.step, value ); } IntOrSlice::Int(index) => { println!("Got an index! {} : value: {}", index, value); } } Ok(()) } } #[pymodule(name = "getitem")] fn example(m: &Bound<'_, PyModule>) -> PyResult<()> { // ? -https://github.com/PyO3/maturin/issues/475 m.add_class::()?; Ok(()) } ================================================ FILE: examples/getitem/tests/test_getitem.py ================================================ import getitem import pytest def test_simple(): container = getitem.ExampleContainer() assert container[3] == 3 assert container[4] == 4 assert container[-1] == -1 assert container[5:3] == 2 assert container[3:5] == 2 # test setitem, but this just displays, no return to check container[3:5] = 2 container[2] = 2 # and note we will get an error on this one since we didn't # add strings with pytest.raises(TypeError): container["foo"] = 2 ================================================ FILE: examples/maturin-starter/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [lib] name = "maturin_starter" crate-type = ["cdylib"] [dependencies] pyo3 = "{{PYO3_VERSION}}" ================================================ FILE: examples/maturin-starter/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.28.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); ================================================ FILE: examples/maturin-starter/.template/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "{{project-name}}" version = "0.1.0" [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: examples/maturin-starter/Cargo.toml ================================================ [package] name = "maturin-starter" version = "0.1.0" edition = "2021" rust-version = "1.83" [lib] name = "maturin_starter" crate-type = ["cdylib"] [dependencies] pyo3 = { path = "../../" } [features] abi3 = ["pyo3/abi3-py37"] [workspace] ================================================ FILE: examples/maturin-starter/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: examples/maturin-starter/README.md ================================================ # maturin-starter An example of a basic Python extension module built using PyO3 and [`maturin`](https://github.com/PyO3/maturin). ## Building and Testing To build this package, first install `maturin`: ```shell pip install maturin ``` To build and test use `maturin develop`: ```shell pip install -r requirements-dev.txt maturin develop && pytest ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/maturin-starter ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: examples/maturin-starter/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: examples/maturin-starter/maturin_starter/__init__.py ================================================ # import the contents of the Rust library into the Python extension from .maturin_starter import * from .maturin_starter import __all__ # optional: include the documentation from the Rust module from .maturin_starter import __doc__ # noqa: F401 __all__ = __all__ + ["PythonClass"] class PythonClass: def __init__(self, value: int) -> None: self.value = value ================================================ FILE: examples/maturin-starter/noxfile.py ================================================ import nox @nox.session def python(session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install(".[dev]") session.run("pytest") ================================================ FILE: examples/maturin-starter/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "maturin-starter" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: examples/maturin-starter/src/lib.rs ================================================ use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::wrap_pymodule; mod submodule; #[pyclass] struct ExampleClass { #[pyo3(get, set)] value: i32, } #[pymethods] impl ExampleClass { #[new] pub fn new(value: i32) -> Self { ExampleClass { value } } } /// An example module implemented in Rust using PyO3. #[pymodule] fn maturin_starter(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_wrapped(wrap_pymodule!(submodule::submodule))?; // Inserting to sys.modules allows importing submodules nicely from Python // e.g. from maturin_starter.submodule import SubmoduleClass let sys = PyModule::import(py, "sys")?; let sys_modules: Bound<'_, PyDict> = sys.getattr("modules")?.cast_into()?; sys_modules.set_item("maturin_starter.submodule", m.getattr("submodule")?)?; Ok(()) } ================================================ FILE: examples/maturin-starter/src/submodule.rs ================================================ use pyo3::prelude::*; #[pyclass] struct SubmoduleClass {} #[pymethods] impl SubmoduleClass { #[new] pub fn __new__() -> Self { SubmoduleClass {} } pub fn greeting(&self) -> &'static str { "Hello, world!" } } #[pymodule] pub fn submodule(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; Ok(()) } ================================================ FILE: examples/maturin-starter/tests/test_maturin_starter.py ================================================ from maturin_starter import ExampleClass, PythonClass def test_python_class() -> None: py_class = PythonClass(value=10) assert py_class.value == 10 def test_example_class() -> None: example = ExampleClass(value=11) assert example.value == 11 def test_doc() -> None: import maturin_starter assert ( maturin_starter.__doc__ == "An example module implemented in Rust using PyO3." ) ================================================ FILE: examples/maturin-starter/tests/test_submodule.py ================================================ from maturin_starter.submodule import SubmoduleClass def test_submodule_class() -> None: submodule_class = SubmoduleClass() assert submodule_class.greeting() == "Hello, world!" ================================================ FILE: examples/plugin/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [dependencies] pyo3 = "{{PYO3_VERSION}}" plugin_api = { path = "plugin_api" } ================================================ FILE: examples/plugin/.template/plugin_api/Cargo.toml ================================================ [package] name = "plugin_api" version = "0.1.0" description = "Plugin API example" edition = "2021" [lib] name = "plugin_api" crate-type = ["cdylib", "rlib"] [dependencies] pyo3 = "{{PYO3_VERSION}}" ================================================ FILE: examples/plugin/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.28.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml"); file::delete(".template"); ================================================ FILE: examples/plugin/Cargo.toml ================================================ [package] name = "plugin_example" version = "0.1.0" edition = "2021" rust-version = "1.83" [dependencies] pyo3 = { path = "../../", features = ["macros"] } plugin_api = { path = "plugin_api" } [workspace] ================================================ FILE: examples/plugin/README.md ================================================ # plugin An example of a Rust app that uses Python for a plugin. A Python extension module built using PyO3 and [`maturin`](https://github.com/PyO3/maturin) is used to provide interface types that can be used to exchange data between Rust and Python. This also deals with how to separately test and load python modules. # Building and Testing ## Host application To run the app itself, you only need to run ```shell cargo run ``` It will build the app, as well as the plugin API, then run the app, load the plugin and show it working. ## Plugin API testing The plugin API is in a separate crate `plugin_api`, so you can test it separately from the main app. To build the API only package, install and build with `maturin`: ```shell pip install maturin cd plugin_api maturin build ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/plugin ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: examples/plugin/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: examples/plugin/plugin_api/Cargo.toml ================================================ [package] name = "plugin_api" version = "0.1.0" description = "Plugin API example" edition = "2021" [lib] name = "plugin_api" crate-type = ["cdylib", "rlib"] [dependencies] pyo3 = { path = "../../../" } ================================================ FILE: examples/plugin/plugin_api/noxfile.py ================================================ import nox @nox.session def python(session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install(".[dev]") session.run("pytest") ================================================ FILE: examples/plugin/plugin_api/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "plugin_api" requires-python = ">=3.7" classifiers = [ "Programming Language :: Rust", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: examples/plugin/plugin_api/src/lib.rs ================================================ use pyo3::prelude::*; ///this is our Gadget that python plugin code can create, and rust app can then access natively. #[pyclass] pub struct Gadget { #[pyo3(get, set)] pub prop: usize, //this field will only be accessible to rust code pub rustonly: Vec, } #[pymethods] impl Gadget { #[new] fn new() -> Self { Gadget { prop: 777, rustonly: Vec::new(), } } fn push(&mut self, v: usize) { self.rustonly.push(v); } } /// A Python module for plugin interface types #[pymodule] pub fn plugin_api(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; Ok(()) } ================================================ FILE: examples/plugin/plugin_api/tests/test_Gadget.py ================================================ import pytest @pytest.fixture def gadget(): import plugin_api as pa g = pa.Gadget() return g def test_creation(gadget): pass def test_property(gadget): gadget.prop = 42 assert gadget.prop == 42 def test_push(gadget): gadget.push(42) ================================================ FILE: examples/plugin/plugin_api/tests/test_import.py ================================================ def test_import(): import plugin_api # noqa: F401 ================================================ FILE: examples/plugin/python_plugin/gadget_init_plugin.py ================================================ import plugin_api import rng def start(): """create an instance of Gadget, configure it and return to Rust""" g = plugin_api.Gadget() g.push(1) g.push(2) g.push(3) g.prop = rng.get_random_number() return g ================================================ FILE: examples/plugin/python_plugin/rng.py ================================================ def get_random_number(): # verified by the roll of a fair die to be random return 4 ================================================ FILE: examples/plugin/src/main.rs ================================================ use plugin_api::plugin_api as pylib_module; use pyo3::prelude::*; use pyo3::types::PyList; fn main() -> Result<(), Box> { //"export" our API module to the python runtime pyo3::append_to_inittab!(pylib_module); //spawn runtime Python::initialize(); //import path for python let path = "./python_plugin/"; //do useful work Python::attach(|py| { //add the current directory to import path of Python (do not use this in production!) let syspath: Bound = py.import("sys")?.getattr("path")?.extract().map_err(PyErr::from)?; syspath.insert(0, &path)?; println!("Import path is: {:?}", syspath); // Now we can load our python_plugin/gadget_init_plugin.py file. // It can in turn import other stuff as it deems appropriate let plugin = PyModule::import(py, "gadget_init_plugin")?; // and call start function there, which will return a python reference to Gadget. // Gadget here is a "pyclass" object reference let gadget = plugin.getattr("start")?.call0()?; //now we extract (i.e. mutably borrow) the rust struct from python object { //this scope will have mutable access to the gadget instance, which will be dropped on //scope exit so Python can access it again. let mut gadget_rs: PyRefMut<'_, plugin_api::Gadget> = gadget.extract().map_err( PyErr::from)?; // we can now modify it as if it was a native rust struct gadget_rs.prop = 42; //which includes access to rust-only fields that are not visible to python println!("rust-only vec contains {:?}", gadget_rs.rustonly); gadget_rs.rustonly.clear(); } //any modifications we make to rust object are reflected on Python object as well let res: usize = gadget.getattr("prop")?.extract()?; println!("{res}"); Ok(()) }) } ================================================ FILE: examples/setuptools-rust-starter/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [lib] name = "setuptools_rust_starter" crate-type = ["cdylib"] [dependencies] pyo3 = "{{PYO3_VERSION}}" ================================================ FILE: examples/setuptools-rust-starter/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.28.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/setup.cfg", "setup.cfg"); file::delete(".template"); ================================================ FILE: examples/setuptools-rust-starter/.template/setup.cfg ================================================ [metadata] name = {{project-name}} version = 0.1.0 packages = setuptools_rust_starter [options] include_package_data = True zip_safe = False ================================================ FILE: examples/setuptools-rust-starter/Cargo.toml ================================================ [package] name = "setuptools-rust-starter" version = "0.1.0" edition = "2021" rust-version = "1.83" [lib] name = "setuptools_rust_starter" crate-type = ["cdylib"] [dependencies] pyo3 = { path = "../../" } [workspace] ================================================ FILE: examples/setuptools-rust-starter/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: examples/setuptools-rust-starter/README.md ================================================ # setuptools-rust-starter An example of a basic Python extension module built using PyO3 and [`setuptools_rust`](https://github.com/PyO3/setuptools-rust). ## Building and Testing To build this package, first install `setuptools_rust`: ```shell pip install setuptools_rust ``` To build and test use `python setup.py develop`: ```shell pip install -r requirements-dev.txt python setup.py develop && pytest ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/setuptools-rust-starter ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: examples/setuptools-rust-starter/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: examples/setuptools-rust-starter/noxfile.py ================================================ import nox import sys @nox.session def python(session: nox.Session): if sys.version_info < (3, 9): session.skip("Python 3.9 or later is required for setuptools-rust 1.11") session.env["SETUPTOOLS_RUST_CARGO_PROFILE"] = "dev" session.install(".[dev]") session.run("pytest") ================================================ FILE: examples/setuptools-rust-starter/pyproject.toml ================================================ [build-system] requires = ["setuptools>=62.4", "setuptools_rust>=1.11"] [project] name = "setuptools-rust-starter" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] [project.optional-dependencies] dev = ["pytest"] [tool.setuptools.packages.find] include = ["setuptools_rust_starter"] [[tool.setuptools-rust.ext-modules]] target = "setuptools_rust_starter._setuptools_rust_starter" path = "Cargo.toml" ================================================ FILE: examples/setuptools-rust-starter/requirements-dev.txt ================================================ pytest>=3.5.0 setuptools_rust~=1.0.0 pip>=21.3 wheel ================================================ FILE: examples/setuptools-rust-starter/setuptools_rust_starter/__init__.py ================================================ # import the contents of the Rust library into the Python extension from ._setuptools_rust_starter import * from ._setuptools_rust_starter import __all__ # optional: include the documentation from the Rust module from ._setuptools_rust_starter import __doc__ # noqa: F401 __all__ = __all__ + ["PythonClass"] class PythonClass: def __init__(self, value: int) -> None: self.value = value ================================================ FILE: examples/setuptools-rust-starter/src/lib.rs ================================================ use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::wrap_pymodule; mod submodule; #[pyclass] struct ExampleClass { #[pyo3(get, set)] value: i32, } #[pymethods] impl ExampleClass { #[new] pub fn new(value: i32) -> Self { ExampleClass { value } } } /// An example module implemented in Rust using PyO3. #[pymodule] fn _setuptools_rust_starter(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_wrapped(wrap_pymodule!(submodule::submodule))?; // Inserting to sys.modules allows importing submodules nicely from Python // e.g. from setuptools_rust_starter.submodule import SubmoduleClass let sys = PyModule::import(py, "sys")?; let sys_modules: Bound<'_, PyDict> = sys.getattr("modules")?.cast_into()?; sys_modules.set_item("setuptools_rust_starter.submodule", m.getattr("submodule")?)?; Ok(()) } ================================================ FILE: examples/setuptools-rust-starter/src/submodule.rs ================================================ use pyo3::prelude::*; #[pyclass] struct SubmoduleClass {} #[pymethods] impl SubmoduleClass { #[new] pub fn __new__() -> Self { SubmoduleClass {} } pub fn greeting(&self) -> &'static str { "Hello, world!" } } #[pymodule] pub fn submodule(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; Ok(()) } ================================================ FILE: examples/setuptools-rust-starter/tests/test_setuptools_rust_starter.py ================================================ from setuptools_rust_starter import ExampleClass, PythonClass def test_python_class() -> None: py_class = PythonClass(value=10) assert py_class.value == 10 def test_example_class() -> None: example = ExampleClass(value=11) assert example.value == 11 def test_doc() -> None: import setuptools_rust_starter assert ( setuptools_rust_starter.__doc__ == "An example module implemented in Rust using PyO3." ) ================================================ FILE: examples/setuptools-rust-starter/tests/test_submodule.py ================================================ from setuptools_rust_starter.submodule import SubmoduleClass def test_submodule_class() -> None: submodule_class = SubmoduleClass() assert submodule_class.greeting() == "Hello, world!" ================================================ FILE: examples/word-count/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [lib] name = "word_count" crate-type = ["cdylib"] [dependencies] pyo3 = "{{PYO3_VERSION}}" rayon = "1.0.2" ================================================ FILE: examples/word-count/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.28.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); ================================================ FILE: examples/word-count/.template/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "{{project-name}}" version = "0.1.0" [project.optional-dependencies] dev = ["pytest"] [tool.pytest.ini_options] addopts = "--benchmark-disable" ================================================ FILE: examples/word-count/Cargo.toml ================================================ [package] name = "word-count" version = "0.1.0" edition = "2021" rust-version = "1.83" [lib] name = "word_count" crate-type = ["cdylib"] [dependencies] pyo3 = { path = "../.." } rayon = "1.0.2" [workspace] ================================================ FILE: examples/word-count/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: examples/word-count/README.md ================================================ # word-count Demonstrates searching for a file in plain python, with rust singlethreaded and with rust multithreaded. ## Build ```shell pip install . ``` ## Usage ```python from word_count import search_py, search, search_sequential search_py("foo bar", "foo") search("foo bar", "foo") search_sequential("foo bar", "foo") ``` ## Testing To test install nox globally and run ```shell nox ``` ## Benchmark To test install nox globally and run ```shell nox -s bench ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/word-count ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: examples/word-count/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: examples/word-count/noxfile.py ================================================ import nox nox.options.sessions = ["test"] @nox.session def test(session: nox.Session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install(".[dev]") session.run("pytest") @nox.session def bench(session: nox.Session): session.install(".[dev]") session.run("pytest", "--benchmark-enable") ================================================ FILE: examples/word-count/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "word-count" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] [project.optional-dependencies] dev = ["pytest", "pytest-benchmark"] [tool.pytest.ini_options] addopts = "--benchmark-disable" ================================================ FILE: examples/word-count/src/lib.rs ================================================ use pyo3::prelude::*; use rayon::prelude::*; /// Searches for the word, parallelized by rayon #[pyfunction] fn search(contents: &str, needle: &str) -> usize { contents .par_lines() .map(|line| count_line(line, needle)) .sum() } /// Searches for a word in a classic sequential fashion #[pyfunction] fn search_sequential(contents: &str, needle: &str) -> usize { contents.lines().map(|line| count_line(line, needle)).sum() } #[pyfunction] fn search_sequential_detached(py: Python<'_>, contents: &str, needle: &str) -> usize { py.detach(|| search_sequential(contents, needle)) } /// Count the occurrences of needle in line, case insensitive fn count_line(line: &str, needle: &str) -> usize { let mut total = 0; for word in line.split(' ') { if word == needle { total += 1; } } total } #[pymodule] fn word_count(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(search, m)?)?; m.add_function(wrap_pyfunction!(search_sequential, m)?)?; m.add_function(wrap_pyfunction!(search_sequential_detached, m)?)?; Ok(()) } ================================================ FILE: examples/word-count/tests/test_word_count.py ================================================ from concurrent.futures import ThreadPoolExecutor import pytest import word_count @pytest.fixture(scope="session") def contents() -> str: text = """ The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those! """ return text * 1000 def test_word_count_rust_parallel(benchmark, contents): count = benchmark(word_count.search, contents, "is") assert count == 10000 def test_word_count_rust_sequential(benchmark, contents): count = benchmark(word_count.search_sequential, contents, "is") assert count == 10000 def test_word_count_python_sequential(benchmark, contents): count = benchmark(word_count.search_py, contents, "is") assert count == 10000 def run_rust_sequential_twice( executor: ThreadPoolExecutor, contents: str, needle: str ) -> int: future_1 = executor.submit(word_count.search_sequential_detached, contents, needle) future_2 = executor.submit(word_count.search_sequential_detached, contents, needle) result_1 = future_1.result() result_2 = future_2.result() return result_1 + result_2 def test_word_count_rust_sequential_twice_with_threads(benchmark, contents): executor = ThreadPoolExecutor(max_workers=2) count = benchmark(run_rust_sequential_twice, executor, contents, "is") assert count == 20000 ================================================ FILE: examples/word-count/word_count/__init__.py ================================================ from .word_count import search, search_sequential, search_sequential_detached __all__ = [ "search_py", "search", "search_sequential", "search_sequential_detached", ] def search_py(contents: str, needle: str) -> int: total = 0 for line in contents.splitlines(): for word in line.split(" "): if word == needle: total += 1 return total ================================================ FILE: guide/book.toml ================================================ [book] title = "PyO3 user guide" description = "PyO3 user guide" authors = ["PyO3 Project and Contributors"] [preprocessor.pyo3_version] command = "python3 pyo3_version.py" [preprocessor.tabs] [output.html] git-repository-url = "https://github.com/PyO3/pyo3/tree/main/guide" edit-url-template = "https://github.com/PyO3/pyo3/edit/main/guide/{path}" playground.runnable = false additional-css = ["theme/tabs.css"] additional-js = ["theme/tabs.js"] ================================================ FILE: guide/pyclass-parameters.md ================================================ `#[pyclass]` can be used with the following parameters: | Parameter | Description | | :- | :- | | `constructor` | This is currently only allowed on [variants of complex enums][params-constructor]. It allows customization of the generated class constructor for each variant. It uses the same syntax and supports the same options as the `signature` attribute of functions and methods. | | `crate = "some::path"` | Path to import the `pyo3` crate, if it's not accessible at `::pyo3`. | | `dict` | Gives instances of this class an empty `__dict__` to store custom attributes. | | `eq` | Implements `__eq__` using the `PartialEq` implementation of the underlying Rust datatype. | | `eq_int` | Implements `__eq__` using `__int__` for simple enums. | | `extends = BaseType` | Use a custom baseclass. Defaults to [`PyAny`][params-1] | | `freelist = N` | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. | | `from_py_object` | Implement `FromPyObject` for this pyclass. Requires the pyclass to be `Clone`. | | `frozen` | Declares that your pyclass is immutable. It removes the borrow checker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. | | `generic` | Implements runtime parametrization for the class following [PEP 560](https://peps.python.org/pep-0560/). | | `get_all` | Generates getters for all fields of the pyclass. | | `hash` | Implements `__hash__` using the `Hash` implementation of the underlying Rust datatype. *Requires `eq` and `frozen`* | | `immutable_type` | Makes the type object immutable. Supported on 3.14+ with the `abi3` feature active, or 3.10+ otherwise. | | `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. | | `module = "module_name"` | Python code will see the class as being defined in this module. Defaults to `builtins`. | | `name = "python_name"` | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. | | `ord` | Implements `__lt__`, `__gt__`, `__le__`, & `__ge__` using the `PartialOrd` implementation of the underlying Rust datatype. *Requires `eq`* | | `rename_all = "renaming_rule"` | Applies renaming rules to every getters and setters of a struct, or every variants of an enum. Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". | | `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. | | `set_all` | Generates setters for all fields of the pyclass. | | `new = "from_fields"` | Generates a default `__new__` constructor with all fields as parameters in the `new()` method. | | `skip_from_py_object` | Prevents this PyClass from participating in the `FromPyObject: PyClass + Clone` blanket implementation. This allows a custom `FromPyObject` impl, even if `self` is `Clone`. | | `str` | Implements `__str__` using the `Display` implementation of the underlying Rust datatype or by passing an optional format string `str=""`. *Note: The optional format string is only allowed for structs. `name` and `rename_all` are incompatible with the optional format string. Additional details can be found in the discussion on this [PR](https://github.com/PyO3/pyo3/pull/4233).* | | `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. | | `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a thread-safe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread. Also note the Python's GC is multi-threaded and while unsendable classes will not be traversed on foreign threads to avoid UB, this can lead to memory leaks. | | `weakref` | Allows this class to be [weakly referenceable][params-6]. | All of these parameters can either be passed directly on the `#[pyclass(...)]` annotation, or as one or more accompanying `#[pyo3(...)]` annotations, e.g.: ```rust,ignore // Argument supplied directly to the `#[pyclass]` annotation. #[pyclass(name = "SomeName", subclass)] struct MyClass {} // Argument supplied as a separate annotation. #[pyclass] #[pyo3(name = "SomeName", subclass)] struct MyClass {} ``` [params-1]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html [params-2]: https://en.wikipedia.org/wiki/Free_list [params-3]: https://doc.rust-lang.org/std/marker/trait.Send.html [params-4]: https://doc.rust-lang.org/std/rc/struct.Rc.html [params-5]: https://doc.rust-lang.org/std/sync/struct.Arc.html [params-6]: https://docs.python.org/3/library/weakref.html [params-constructor]: https://pyo3.rs/latest/class.html#complex-enums [params-mapping]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types [params-sequence]: https://pyo3.rs/latest/class/protocols.html#mapping--sequence-types ================================================ FILE: guide/pyo3_version.py ================================================ """Simple mdbook preprocessor to inject pyo3 version into the guide. It will replace: - {{#PYO3_VERSION_TAG}} with the contents of the PYO3_VERSION_TAG environment var - {{#PYO3_DOCS_URL}} with the location of docs (e.g. 'https://docs.rs/pyo3/0.13.2') - {{#PYO3_CRATE_VERSION}} with a relevant toml snippet (e.g. 'version = "0.13.2"') Tested against mdbook 0.5.0. """ import json import os import sys # Set PYO3_VERSION in CI to build the correct version into links PYO3_VERSION_TAG = os.environ.get("PYO3_VERSION_TAG", "main") if PYO3_VERSION_TAG == "main": PYO3_DOCS_URL = "https://pyo3.rs/main/doc" PYO3_DOCS_VERSION = "latest" PYO3_CRATE_VERSION = 'git = "https://github.com/pyo3/pyo3"' else: # v0.13.2 -> 0.13.2 version = PYO3_VERSION_TAG.lstrip("v") PYO3_DOCS_URL = f"https://docs.rs/pyo3/{version}" PYO3_DOCS_VERSION = version PYO3_CRATE_VERSION = f'version = "{version}"' def replace_item_content(item): if not isinstance(item, dict) or "Chapter" not in item: return # Replace raw and url-encoded forms item["Chapter"]["content"] = ( item["Chapter"]["content"] .replace("{{#PYO3_VERSION_TAG}}", PYO3_VERSION_TAG) .replace("{{#PYO3_DOCS_URL}}", PYO3_DOCS_URL) .replace("{{#PYO3_DOCS_VERSION}}", PYO3_DOCS_VERSION) .replace("{{#PYO3_CRATE_VERSION}}", PYO3_CRATE_VERSION) ) for sub_item in item["Chapter"]["sub_items"]: replace_item_content(sub_item) for line in sys.stdin: if line: [context, book] = json.loads(line) for item in book["items"]: replace_item_content(item) json.dump(book, fp=sys.stdout) ================================================ FILE: guide/src/SUMMARY.md ================================================ # Summary [Introduction](index.md) --- - [Getting started](getting-started.md) - [Using Rust from Python](rust-from-python.md) - [Python modules](module.md) - [Python functions](function.md) - [Function signatures](function/signature.md) - [Error handling](function/error-handling.md) - [Python classes](class.md) - [Class customizations](class/protocols.md) - [Basic object customization](class/object.md) - [Emulating numeric types](class/numeric.md) - [Emulating callable objects](class/call.md) - [Thread safety](class/thread-safety.md) - [Calling Python from Rust](python-from-rust.md) - [Python object types](types.md) - [Python exceptions](exception.md) - [Calling Python functions](python-from-rust/function-calls.md) - [Executing existing Python code](python-from-rust/calling-existing-code.md) - [Type conversions](conversions.md) - [Mapping of Rust types to Python types](conversions/tables.md) - [Conversion traits](conversions/traits.md) - [Using `async` and `await`](async-await.md) - [Parallelism](parallelism.md) - [Supporting Free-Threaded Python](free-threading.md) - [Debugging](debugging.md) - [Features reference](features.md) - [Performance](performance.md) - [Type stub generation and introspection](type-stub.md) - [Advanced topics](advanced.md) - [Building and distribution](building-and-distribution.md) - [Supporting multiple Python versions](building-and-distribution/multiple-python-versions.md) - [Useful crates](ecosystem.md) - [Logging](ecosystem/logging.md) - [Tracing](ecosystem/tracing.md) - [Using `async` and `await`](ecosystem/async-await.md) - [FAQ and troubleshooting](faq.md) --- [Appendix A: Migration guide](migration.md) [Appendix B: Trait bounds](trait-bounds.md) [Appendix C: Python typing hints](python-typing-hints.md) [CHANGELOG](changelog.md) --- [Contributing](contributing.md) ================================================ FILE: guide/src/advanced.md ================================================ # Advanced topics ## FFI PyO3 exposes much of Python's C API through the `ffi` module. The C API is naturally unsafe and requires you to manage reference counts, errors and specific invariants yourself. Please refer to the [C API Reference Manual](https://docs.python.org/3/c-api/) and [The Rustonomicon](https://doc.rust-lang.org/nightly/nomicon/ffi.html) before using any function from that API. ================================================ FILE: guide/src/async-await.md ================================================ # Using `async` and `await` *This feature is still in active development.* *See [the related issue](https://github.com/PyO3/pyo3/issues/1632).* `#[pyfunction]` and `#[pymethods]` attributes also support `async fn`. ```rust,no_run # #![allow(dead_code)] # #[cfg(feature = "experimental-async")] { use std::{thread, time::Duration}; use futures::channel::oneshot; use pyo3::prelude::*; #[pyfunction] #[pyo3(signature=(seconds, result=None))] async fn sleep(seconds: f64, result: Option>) -> Option> { let (tx, rx) = oneshot::channel(); thread::spawn(move || { thread::sleep(Duration::from_secs_f64(seconds)); tx.send(()).unwrap(); }); rx.await.unwrap(); result } # } ``` *Python awaitables instantiated with this method can only be awaited in `asyncio` context.* *Other Python async runtime may be supported in the future.* ## `Send + 'static` constraint Resulting future of an `async fn` decorated by `#[pyfunction]` must be `Send + 'static` to be embedded in a Python object. As a consequence, `async fn` parameters and return types must also be `Send + 'static`, so it is not possible to have a signature like `async fn does_not_compile<'py>(arg: Bound<'py, PyAny>) -> Bound<'py, PyAny>`. However, there is an exception for method receivers, so async methods can accept `&self`/ `&mut self`. Note that this means that the class instance is borrowed for as long as the returned future is not completed, even across yield points and while waiting for I/O operations to complete. Hence, other methods cannot obtain exclusive borrows while the future is still being polled. This is the same as how async methods in Rust generally work but it is more problematic for Rust code interfacing with Python code due to pervasive shared mutability. This strongly suggests to prefer shared borrows `&self` over exclusive ones `&mut self` to avoid racy borrow check failures at runtime. ## Implicitly attached to the interpreter Even if it is not possible to pass a `py: Python<'py>` token to an `async fn`, we're still attached to the interpreter during the execution of the future – the same as for a regular `fn` without `Python<'py>`/`Bound<'py, PyAny>` parameter It is still possible to get a `Python` marker using [`Python::attach`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.attach); because `attach` is reentrant and optimized, the cost will be negligible. ## Detaching from the interpreter across `.await` There is currently no simple way to detach from the interpreter when awaiting a future, *but solutions are currently in development*. Here is the advised workaround for now: ```rust,ignore use std::{ future::Future, pin::{Pin, pin}, task::{Context, Poll}, }; use pyo3::prelude::*; struct AllowThreads(F); impl Future for AllowThreads where F: Future + Unpin + Send, F::Output: Send, { type Output = F::Output; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let waker = cx.waker(); Python::attach(|py| { py.detach(|| pin!(&mut self.0).poll(&mut Context::from_waker(waker))) }) } } ``` ## Cancellation Cancellation on the Python side can be caught using [`CancelHandle`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.CancelHandle.html) type, by annotating a function parameter with `#[pyo3(cancel_handle)]`. ```rust,no_run # #![allow(dead_code)] # #[cfg(feature = "experimental-async")] { use futures::FutureExt; use pyo3::prelude::*; use pyo3::coroutine::CancelHandle; #[pyfunction] async fn cancellable(#[pyo3(cancel_handle)] mut cancel: CancelHandle) { futures::select! { /* _ = ... => println!("done"), */ _ = cancel.cancelled().fuse() => println!("cancelled"), } } # } ``` ## The `Coroutine` type To make a Rust future awaitable in Python, PyO3 defines a [`Coroutine`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.Coroutine.html) type, which implements the Python [coroutine protocol](https://docs.python.org/3/library/collections.abc.html#collections.abc.Coroutine). Each `coroutine.send` call is translated to a `Future::poll` call. If a [`CancelHandle`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.CancelHandle.html) parameter is declared, the exception passed to `coroutine.throw` call is stored in it and can be retrieved with [`CancelHandle::cancelled`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.CancelHandle.html#method.cancelled); otherwise, it cancels the Rust future, and the exception is reraised; *The type does not yet have a public constructor until the design is finalized.* ================================================ FILE: guide/src/building-and-distribution/multiple-python-versions.md ================================================ # Supporting multiple Python versions PyO3 supports all actively-supported Python 3 and PyPy versions. As much as possible, this is done internally to PyO3 so that your crate's code does not need to adapt to the differences between each version. However, as Python features grow and change between versions, PyO3 cannot offer a completely identical API for every Python version. This may require you to add conditional compilation to your crate or runtime checks for the Python version. This section of the guide first introduces the `pyo3-build-config` crate, which you can use as a `build-dependency` to add additional `#[cfg]` flags which allow you to support multiple Python versions at compile-time. Second, we'll show how to check the Python version at runtime. This can be useful when building for multiple versions with the `abi3` feature, where the Python API compiled against is not always the same as the one in use. ## Conditional compilation for different Python versions The `pyo3-build-config` exposes multiple [`#[cfg]` flags](https://doc.rust-lang.org/rust-by-example/attribute/cfg.html) which can be used to conditionally compile code for a given Python version. PyO3 itself depends on this crate, so by using it you can be sure that you are configured correctly for the Python version PyO3 is building against. This allows us to write code like the following ```rust,ignore #[cfg(Py_3_7)] fn function_only_supported_on_python_3_7_and_up() {} #[cfg(not(Py_3_8))] fn function_only_supported_before_python_3_8() {} #[cfg(not(Py_LIMITED_API))] fn function_incompatible_with_abi3_feature() {} ``` The following sections first show how to add these `#[cfg]` flags to your build process, and then cover some common patterns flags in a little more detail. To see a full reference of all the `#[cfg]` flags provided, see the [`pyo3-build-cfg` docs](https://docs.rs/pyo3-build-config). ### Using `pyo3-build-config` You can use the `#[cfg]` flags in just two steps: 1. Add `pyo3-build-config` with the [`resolve-config`](../features.md#resolve-config) feature enabled to your crate's build dependencies in `Cargo.toml`: ```toml [build-dependencies] pyo3-build-config = { {{#PYO3_CRATE_VERSION}}, features = ["resolve-config"] } ``` 2. Add a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) file to your crate with the following contents: ```rust,ignore fn main() { // If you have an existing build.rs file, just add this line to it. pyo3_build_config::use_pyo3_cfgs(); } ``` After these steps you are ready to annotate your code! ### Common usages of `pyo3-build-cfg` flags The `#[cfg]` flags added by `pyo3-build-cfg` can be combined with all of Rust's logic in the `#[cfg]` attribute to create very precise conditional code generation. The following are some common patterns implemented using these flags: ```text #[cfg(Py_3_7)] ``` This `#[cfg]` marks code that will only be present on Python 3.7 and upwards. There are similar options `Py_3_8`, `Py_3_9`, `Py_3_10` and so on for each minor version. ```text #[cfg(not(Py_3_7))] ``` This `#[cfg]` marks code that will only be present on Python versions before (but not including) Python 3.7. ```text #[cfg(not(Py_LIMITED_API))] ``` This `#[cfg]` marks code that is only available when building for the unlimited Python API (i.e. PyO3's `abi3` feature is not enabled). This might be useful if you want to ship your extension module as an `abi3` wheel and also allow users to compile it from source to make use of optimizations only possible with the unlimited API. ```text #[cfg(any(Py_3_9, not(Py_LIMITED_API)))] ``` This `#[cfg]` marks code which is available when running Python 3.9 or newer, or when using the unlimited API with an older Python version. Patterns like this are commonly seen on Python APIs which were added to the limited Python API in a specific minor version. ```text #[cfg(PyPy)] ``` This `#[cfg]` marks code which is running on PyPy. ## Checking the Python version at runtime When building with PyO3's `abi3` feature, your extension module will be compiled against a specific [minimum version](../building-and-distribution.md#minimum-python-version-for-abi3) of Python, but may be running on newer Python versions. For example with PyO3's `abi3-py38` feature, your extension will be compiled as if it were for Python 3.8. If you were using `pyo3-build-config`, `#[cfg(Py_3_8)]` would be present. Your user could freely install and run your abi3 extension on Python 3.9. There's no way to detect your user doing that at compile time, so instead you need to fall back to runtime checks. PyO3 provides the APIs [`Python::version()`] and [`Python::version_info()`] to query the running Python version. This allows you to do the following, for example: ```rust use pyo3::Python; Python::attach(|py| { // PyO3 supports Python 3.7 and up. assert!(py.version_info() >= (3, 7)); assert!(py.version_info() >= (3, 7, 0)); }); ``` [`Python::version()`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.version [`Python::version_info()`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.version_info ================================================ FILE: guide/src/building-and-distribution.md ================================================ # Building and distribution This chapter of the guide goes into detail on how to build and distribute projects using PyO3. The way to achieve this is very different depending on whether the project is a Python module implemented in Rust, or a Rust binary embedding Python. For both types of project there are also common problems such as the Python version to build for and the [linker](https://en.wikipedia.org/wiki/Linker_(computing)) arguments to use. The material in this chapter is intended for users who have already read the PyO3 [README](./index.md). It covers in turn the choices that can be made for Python modules and for Rust binaries. There is also a section at the end about cross-compiling projects using PyO3. There is an additional sub-chapter dedicated to [supporting multiple Python versions](./building-and-distribution/multiple-python-versions.md). ## Configuring the Python version PyO3 uses a build script (backed by the [`pyo3-build-config`] crate) to determine the Python version and set the correct linker arguments. By default it will attempt to use the following in order: - Any active Python virtualenv. - The `python` executable (if it's a Python 3 interpreter). - The `python3` executable. You can override the Python interpreter by setting the `PYO3_PYTHON` environment variable, e.g. `PYO3_PYTHON=python3.7`, `PYO3_PYTHON=/usr/bin/python3.9`, or even a PyPy interpreter `PYO3_PYTHON=pypy3`. Once the Python interpreter is located, `pyo3-build-config` executes it to query the information in the `sysconfig` module which is needed to configure the rest of the compilation. To validate the configuration which PyO3 will use, you can run a compilation with the environment variable `PYO3_PRINT_CONFIG=1` set. An example output of doing this is shown below: ```console $ PYO3_PRINT_CONFIG=1 cargo build Compiling pyo3 v0.14.1 (/home/david/dev/pyo3) error: failed to run custom build command for `pyo3 v0.14.1 (/home/david/dev/pyo3)` Caused by: process didn't exit successfully: `/home/david/dev/pyo3/target/debug/build/pyo3-7a8cf4fe22e959b7/build-script-build` (exit status: 101) --- stdout cargo:rerun-if-env-changed=PYO3_CROSS cargo:rerun-if-env-changed=PYO3_CROSS_LIB_DIR cargo:rerun-if-env-changed=PYO3_CROSS_PYTHON_VERSION cargo:rerun-if-env-changed=PYO3_PRINT_CONFIG -- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile -- implementation=CPython version=3.8 shared=true abi3=false lib_name=python3.8 lib_dir=/usr/lib executable=/usr/bin/python pointer_width=64 build_flags= suppress_build_script_link_lines=false ``` The `PYO3_ENVIRONMENT_SIGNATURE` environment variable can be used to trigger rebuilds when its value changes, it has no other effect. ### Advanced: config files If you save the above output config from `PYO3_PRINT_CONFIG` to a file, it is possible to manually override the contents and feed it back into PyO3 using the `PYO3_CONFIG_FILE` env var. If your build environment is unusual enough that PyO3's regular configuration detection doesn't work, using a config file like this will give you the flexibility to make PyO3 work for you. To see the full set of options supported, see the documentation for the [`InterpreterConfig` struct](https://docs.rs/pyo3-build-config/{{#PYO3_DOCS_VERSION}}/pyo3_build_config/struct.InterpreterConfig.html). ## Building Python extension modules Python extension modules need to be compiled differently depending on the OS (and architecture) that they are being compiled for. As well as multiple OSes (and architectures), there are also many different Python versions which are actively supported. Packages uploaded to [PyPI](https://pypi.org/) usually want to upload prebuilt "wheels" covering many OS/arch/version combinations so that users on all these different platforms don't have to compile the package themselves. Package vendors can opt-in to the "abi3" limited Python API which allows their wheels to be used on multiple Python versions, reducing the number of wheels they need to compile, but restricts the functionality they can use. There are many ways to go about this: it is possible to use `cargo` to build the extension module (along with some manual work, which varies with OS). The PyO3 ecosystem has two packaging tools, [`maturin`] and [`setuptools-rust`], which abstract over the OS difference and also support building wheels for PyPI upload. PyO3 has some functionality for configuring projects when building Python extension modules: - The `PYO3_BUILD_EXTENSION_MODULE` environment variable, which must be set when building Python extension modules. `maturin` and `setuptools-rust` set this automatically. - The `abi3` Cargo feature and its version-specific `abi3-pyXY` companions, which are used to opt-in to the limited Python API in order to support multiple Python versions in a single wheel. This section describes the packaging tools before describing how to build manually without them. It then proceeds with an explanation of the `PYO3_BUILD_EXTENSION_MODULE` environment variable. Finally, there is a section describing PyO3's `abi3` features. ### Packaging tools The PyO3 ecosystem has two main choices to abstract the process of developing Python extension modules: - [`maturin`] is a command-line tool to build, package and upload Python modules. It makes opinionated choices about project layout meaning it needs very little configuration. This makes it a great choice for users who are building a Python extension from scratch and don't need flexibility. - [`setuptools-rust`] is an add-on for `setuptools` which adds extra keyword arguments to the `setup.py` configuration file. It requires more configuration than `maturin`, however this gives additional flexibility for users adding Rust to an existing Python package that can't satisfy `maturin`'s constraints. Consult each project's documentation for full details on how to get started using them and how to upload wheels to PyPI. It should be noted that while `maturin` is able to build [manylinux](https://github.com/pypa/manylinux)-compliant wheels out-of-the-box, `setuptools-rust` requires a bit more effort, [relying on Docker](https://setuptools-rust.readthedocs.io/en/latest/building_wheels.html) for this purpose. There are also [`maturin-starter`] and [`setuptools-rust-starter`] examples in the PyO3 repository. ### Manual builds To build a PyO3-based Python extension manually, start by running `cargo build` as normal in a library project with the [`cdylib` crate type](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field) while the `PYO3_BUILD_EXTENSION_MODULE` environment variable is set. Once built, symlink (or copy) and rename the shared library from Cargo's `target/` directory to your desired output directory: - on macOS, rename `libyour_module.dylib` to `your_module.so`. - on Windows, rename `libyour_module.dll` to `your_module.pyd`. - on Linux, rename `libyour_module.so` to `your_module.so`. You can then open a Python shell in the output directory and you'll be able to run `import your_module`. If you're packaging your library for redistribution, you should indicate the Python interpreter your library is compiled for by including the [platform tag](#platform-tags) in its name. This prevents incompatible interpreters from trying to import your library. If you're compiling for PyPy you *must* include the platform tag, or PyPy will ignore the module. #### Bazel builds To use PyO3 with bazel one needs to manually configure PyO3, PyO3-ffi and PyO3-macros. In particular, one needs to make sure that it is compiled with the right python flags for the version you intend to use. For example see: 1. [github.com/abrisco/rules_pyo3](https://github.com/abrisco/rules_pyo3) -- General rules for building extension modules. 2. [github.com/OliverFM/pytorch_with_gazelle](https://github.com/OliverFM/pytorch_with_gazelle) -- for a minimal example of a repo that can use PyO3, PyTorch and Gazelle to generate python Build files. 3. [github.com/TheButlah/rules_pyo3](https://github.com/TheButlah/rules_pyo3) -- is somewhat dated. #### Platform tags Rather than using just the `.so` or `.pyd` extension suggested above (depending on OS), you can prefix the shared library extension with a platform tag to indicate the interpreter it is compatible with. You can query your interpreter's platform tag from the `sysconfig` module. Some example outputs of this are seen below: ```bash # CPython 3.10 on macOS .cpython-310-darwin.so # PyPy 7.3 (Python 3.9) on Linux $ python -c 'import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX"))' .pypy39-pp73-x86_64-linux-gnu.so ``` So, for example, a valid module library name on CPython 3.10 for macOS is `your_module.cpython-310-darwin.so`, and its equivalent when compiled for PyPy 7.3 on Linux would be `your_module.pypy38-pp73-x86_64-linux-gnu.so`. See [PEP 3149](https://peps.python.org/pep-3149/) for more background on platform tags. #### macOS On macOS, because the `PYO3_BUILD_EXTENSION_MODULE` environment variable disables linking to `libpython` ([see the next section](#the-extension-module-feature)), some additional linker arguments need to be set. `maturin` and `setuptools-rust` both pass these arguments for PyO3 automatically, but projects using manual builds will need to set these directly in order to support macOS. The easiest way to set the correct linker arguments is to add a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) with the following content: ```rust,ignore fn main() { pyo3_build_config::add_extension_module_link_args(); } ``` Remember to also add `pyo3-build-config` to the `build-dependencies` section in `Cargo.toml`. An alternative to using `pyo3-build-config` is add the following to a cargo configuration file (e.g. `.cargo/config.toml`): ```toml [target.x86_64-apple-darwin] rustflags = [ "-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup", ] [target.aarch64-apple-darwin] rustflags = [ "-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup", ] ``` Using the MacOS system python3 (`/usr/bin/python3`, as opposed to python installed via homebrew, pyenv, nix, etc.) may result in runtime errors such as `Library not loaded: @rpath/Python3.framework/Versions/3.8/Python3`. The easiest way to set the correct linker arguments is to add a `build.rs` with the following content: ```rust,ignore fn main() { pyo3_build_config::add_python_framework_link_args(); } ``` Alternatively it can be resolved with another addition to `.cargo/config.toml`: ```toml [build] rustflags = [ "-C", "link-args=-Wl,-rpath,/Library/Developer/CommandLineTools/Library/Frameworks", ] ``` For more discussion on and workarounds for MacOS linking problems [see this issue](https://github.com/PyO3/pyo3/issues/1800#issuecomment-906786649). Finally, don't forget that on MacOS the `extension-module` feature will cause `cargo test` to fail without the `--no-default-features` flag (see [the FAQ](https://pyo3.rs/main/faq.html#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror)). ### The `PYO3_BUILD_EXTENSION_MODULE` environment variable By default PyO3 links to `libpython`. This makes binaries, tests, and examples "just work". However, Python extensions on Unix must not link to libpython for [manylinux](https://www.python.org/dev/peps/pep-0513/) compliance. The downside of not linking to `libpython` is that binaries, tests, and examples (which usually embed Python) will fail to build. As a result, PyO3 uses an envionment variable `PYO3_BUILD_EXTENSION_MODULE` to disable linking to `libpython`. This should only be set when building a library for distribution. `maturin >= 1.9.4` and `setuptools-rust >= 1.12` will set this for you automatically. > [!NOTE] > Historically PyO3 used an `extension-module` feature to perform the same function now done by the `PYO3_BUILD_EXTENSION_MODULE` env var. > This feature caused linking to be disabled for all compile targets, including Rust tests and benchmarks. > > Projects are encouraged to migrate off the feature, as it caused [major development pain](faq.md#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror) due to the lack of linking. ### `Py_LIMITED_API`/`abi3` By default, Python extension modules can only be used with the same Python version they were compiled against. For example, an extension module built for Python 3.5 can't be imported in Python 3.8. [PEP 384](https://www.python.org/dev/peps/pep-0384/) introduced the idea of the limited Python API, which would have a stable ABI enabling extension modules built with it to be used against multiple Python versions. This is also known as `abi3`. The advantage of building extension modules using the limited Python API is that package vendors only need to build and distribute a single copy (for each OS / architecture), and users can install it on all Python versions from the [minimum version](#minimum-python-version-for-abi3) and up. The downside of this is that PyO3 can't use optimizations which rely on being compiled against a known exact Python version. It's up to you to decide whether this matters for your extension module. It's also possible to design your extension module such that you can distribute `abi3` wheels but allow users compiling from source to benefit from additional optimizations - see the [support for multiple python versions](./building-and-distribution/multiple-python-versions.md) section of this guide, in particular the `#[cfg(Py_LIMITED_API)]` flag. There are three steps involved in making use of `abi3` when building Python packages as wheels: 1. Enable the `abi3` feature in `pyo3`. This ensures `pyo3` only calls Python C-API functions which are part of the stable API, and on Windows also ensures that the project links against the correct shared object (no special behavior is required on other platforms): ```toml [dependencies] pyo3 = { {{#PYO3_CRATE_VERSION}}, features = ["abi3"] } ``` 2. Ensure that the built shared objects are correctly marked as `abi3`. This is accomplished by telling your build system that you're using the limited API. [`maturin`] >= 0.9.0 and [`setuptools-rust`] >= 0.11.4 support `abi3` wheels. See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://github.com/PyO3/setuptools-rust/pull/82) for more. 3. Ensure that the `.whl` is correctly marked as `abi3`. For projects using `setuptools`, this is accomplished by passing `--py-limited-api=cp3x` (where `x` is the minimum Python version supported by the wheel, e.g. `--py-limited-api=cp35` for Python 3.5) to `setup.py bdist_wheel`. #### Minimum Python version for `abi3` Because a single `abi3` wheel can be used with many different Python versions, PyO3 has feature flags `abi3-py37`, `abi3-py38`, `abi3-py39` etc. to set the minimum required Python version for your `abi3` wheel. For example, if you set the `abi3-py37` feature, your extension wheel can be used on all Python 3 versions from Python 3.7 and up. `maturin` and `setuptools-rust` will give the wheel a name like `my-extension-1.0-cp37-abi3-manylinux2020_x86_64.whl`. As your extension module may be run with multiple different Python versions you may occasionally find you need to check the Python version at runtime to customize behavior. See [the relevant section of this guide](./building-and-distribution/multiple-python-versions.md#checking-the-python-version-at-runtime) on supporting multiple Python versions at runtime. PyO3 is only able to link your extension module to abi3 version up to and including your host Python version. E.g., if you set `abi3-py38` and try to compile the crate with a host of Python 3.7, the build will fail. > [!NOTE] > If you set more that one of these `abi3` version feature flags the lowest version always wins. For example, with both `abi3-py37` and `abi3-py38` set, PyO3 would build a wheel which supports Python 3.7 and up. #### 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. 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. #### Missing features Due to limitations in the Python API, there are a few `pyo3` features that do not work when compiling for `abi3`. These are: - `#[pyo3(text_signature = "...")]` does not work on classes until Python 3.10 or greater. - The `dict` and `weakref` options on classes are not supported until Python 3.9 or greater. - The buffer API is not supported until Python 3.11 or greater. - Subclassing native types (e.g. `PyException`) is not supported until Python 3.12 or greater. - Optimizations which rely on knowledge of the exact Python version compiled against. ## Embedding Python in Rust If you want to embed the Python interpreter inside a Rust program, there are two modes in which this can be done: dynamically and statically. We'll cover each of these modes in the following sections. Each of them affect how you must distribute your program. Instead of learning how to do this yourself, you might want to consider using a project like [PyOxidizer] to ship your application and all of its dependencies in a single file. PyO3 automatically switches between the two linking modes depending on whether the Python distribution you have configured PyO3 to use ([see above](#configuring-the-python-version)) contains a shared library or a static library. The static library is most often seen in Python distributions compiled from source without the `--enable-shared` configuration option. ### Dynamically embedding the Python interpreter Embedding the Python interpreter dynamically is much easier than doing so statically. This is done by linking your program against a Python shared library (such as `libpython.3.9.so` on UNIX, or `python39.dll` on Windows). The implementation of the Python interpreter resides inside the shared library. This means that when the OS runs your Rust program it also needs to be able to find the Python shared library. This mode of embedding works well for Rust tests which need access to the Python interpreter. It is also great for Rust software which is installed inside a Python virtualenv, because the virtualenv sets up appropriate environment variables to locate the correct Python shared library. For distributing your program to non-technical users, you will have to consider including the Python shared library in your distribution as well as setting up wrapper scripts to set the right environment variables (such as `LD_LIBRARY_PATH` on UNIX, or `PATH` on Windows). Note that PyPy cannot be embedded in Rust (or any other software). Support for this is tracked on the [PyPy issue tracker](https://github.com/pypy/pypy/issues/3836). ### Statically embedding the Python interpreter Embedding the Python interpreter statically means including the contents of a Python static library directly inside your Rust binary. This means that to distribute your program you only need to ship your binary file: it contains the Python interpreter inside the binary! On Windows static linking is almost never done, so Python distributions don't usually include a static library. The information below applies only to UNIX. The Python static library is usually called `libpython.a`. Static linking has a lot of complications, listed below. For these reasons PyO3 does not yet have first-class support for this embedding mode. See [issue 416 on PyO3's GitHub](https://github.com/PyO3/pyo3/issues/416) for more information and to discuss any issues you encounter. The [`auto-initialize`](features.md#auto-initialize) feature is deliberately disabled when embedding the interpreter statically because this is often unintentionally done by new users to PyO3 running test programs. Trying out PyO3 is much easier using dynamic embedding. The known complications are: - To import compiled extension modules (such as other Rust extension modules, or those written in C), your binary must have the correct linker flags set during compilation to export the original contents of `libpython.a` so that extensions can use them (e.g. `-Wl,--export-dynamic`). - The C compiler and flags which were used to create `libpython.a` must be compatible with your Rust compiler and flags, else you will experience compilation failures. Significantly different compiler versions may see errors like this: ```text lto1: fatal error: bytecode stream in file 'rust-numpy/target/release/deps/libpyo3-6a7fb2ed970dbf26.rlib' generated with LTO version 6.0 instead of the expected 6.2 ``` Mismatching flags may lead to errors like this: ```text /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/libpython3.9.a(zlibmodule.o): relocation R_X86_64_32 against `.data' can not be used when making a PIE object; recompile with -fPIE ``` If you encounter these or other complications when linking the interpreter statically, discuss them on [issue 416 on PyO3's GitHub](https://github.com/PyO3/pyo3/issues/416). It is hoped that eventually that discussion will contain enough information and solutions that PyO3 can offer first-class support for static embedding. ### Import your module when embedding the Python interpreter When you run your Rust binary with an embedded interpreter, any `#[pymodule]` created modules won't be accessible to import unless added to a table called `PyImport_Inittab` before the embedded interpreter is initialized. This will cause Python statements in your embedded interpreter such as `import your_new_module` to fail. You can call the macro [`append_to_inittab`]({{#PYO3_DOCS_URL}}/pyo3/macro.append_to_inittab.html) with your module before initializing the Python interpreter to add the module function into that table. (The Python interpreter will be initialized by calling `Python::initialize`, `with_embedded_python_interpreter`, or `Python::attach` with the [`auto-initialize`](features.md#auto-initialize) feature enabled.) ## Cross Compiling Thanks to Rust's great cross-compilation support, cross-compiling using PyO3 is relatively straightforward. To get started, you'll need a few pieces of software: - A toolchain for your target. - The appropriate options in your Cargo `.config` for the platform you're targeting and the toolchain you are using. - A Python interpreter that's already been compiled for your target (optional when building "abi3" extension modules). - A Python interpreter that is built for your host and available through the `PATH` or setting the [`PYO3_PYTHON`](#configuring-the-python-version) variable (optional when building "abi3" extension modules). After you've obtained the above, you can build a cross-compiled PyO3 module by using Cargo's `--target` flag. PyO3's build script will detect that you are attempting a cross-compile based on your host machine and the desired target. When cross-compiling, PyO3's build script cannot execute the target Python interpreter to query the configuration, so there are a few additional environment variables you may need to set: - `PYO3_CROSS`: If present this variable forces PyO3 to configure as a cross-compilation. - `PYO3_CROSS_LIB_DIR`: This variable can be set to the directory containing the target's libpython DSO and the associated `_sysconfigdata*.py` file for Unix-like targets. This variable is only needed when the output binary must link to libpython explicitly (e.g. when targeting Android or embedding a Python interpreter), or when it is absolutely required to get the interpreter configuration from `_sysconfigdata*.py`. On Windows, this variable is not needed because PyO3 uses `raw-dylib` linking. - `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python installation. This variable is only needed if PyO3 cannot determine the version to target from `abi3-py3*` features, or if `PYO3_CROSS_LIB_DIR` is not set, or if there are multiple versions of Python present in `PYO3_CROSS_LIB_DIR`. - `PYO3_CROSS_PYTHON_IMPLEMENTATION`: Python implementation name ("CPython" or "PyPy") of the target Python installation. CPython is assumed by default when this variable is not set, unless `PYO3_CROSS_LIB_DIR` is set for a Unix-like target and PyO3 can get the interpreter configuration from `_sysconfigdata*.py`. An example might look like the following (assuming your target's sysroot is at `/home/pyo3/cross/sysroot` and that your target is `armv7`): ```sh export PYO3_CROSS_LIB_DIR="/home/pyo3/cross/sysroot/usr/lib" cargo build --target armv7-unknown-linux-gnueabihf ``` If there are multiple python versions at the cross lib directory and you cannot set a more precise location to include both the `libpython` DSO and `_sysconfigdata*.py` files, you can set the required version: ```sh export PYO3_CROSS_PYTHON_VERSION=3.8 export PYO3_CROSS_LIB_DIR="/home/pyo3/cross/sysroot/usr/lib" cargo build --target armv7-unknown-linux-gnueabihf ``` Or another example building for Windows (no `PYO3_CROSS_LIB_DIR` needed thanks to `raw-dylib`): ```sh export PYO3_CROSS_PYTHON_VERSION=3.9 cargo build --target x86_64-pc-windows-gnu ``` Any of the `abi3-py3*` features can be enabled instead of setting `PYO3_CROSS_PYTHON_VERSION` in the above examples. `PYO3_CROSS_LIB_DIR` can often be omitted when cross compiling extension modules for Unix, macOS, and Windows targets. The following resources may also be useful for cross-compiling: - [github.com/japaric/rust-cross](https://github.com/japaric/rust-cross) is a primer on cross compiling Rust. - [github.com/rust-embedded/cross](https://github.com/rust-embedded/cross) uses Docker to make Rust cross-compilation easier. [`pyo3-build-config`]: https://github.com/PyO3/pyo3/tree/main/pyo3-build-config [`maturin-starter`]: https://github.com/PyO3/pyo3/tree/main/examples/maturin-starter [`setuptools-rust-starter`]: https://github.com/PyO3/pyo3/tree/main/examples/setuptools-rust-starter [`maturin`]: https://github.com/PyO3/maturin [`setuptools-rust`]: https://github.com/PyO3/setuptools-rust [PyOxidizer]: https://github.com/indygreg/PyOxidizer ================================================ FILE: guide/src/changelog.md ================================================ {{#include ../../CHANGELOG.md}} ================================================ FILE: guide/src/class/call.md ================================================ # Emulating callable objects Classes can be callable if they have a `#[pymethod]` named `__call__`. This allows instances of a class to behave similar to functions. This method's signature must look like `__call__(, ...) -> object` - here, any argument list can be defined as for normal pymethods ## Example: Implementing a call counter The following pyclass is a basic decorator - its constructor takes a Python object as argument and calls that object when called. An equivalent Python implementation is linked at the end. An example crate containing this pyclass can be found [in the PyO3 GitHub repository](https://github.com/PyO3/pyo3/tree/main/examples/decorator) ```rust,ignore {{#include ../../../examples/decorator/src/lib.rs}} ``` Python code: ```python {{#include ../../../examples/decorator/tests/example.py}} ``` Output: ```text say_hello has been called 1 time(s). hello say_hello has been called 2 time(s). hello say_hello has been called 3 time(s). hello say_hello has been called 4 time(s). hello ``` ### Pure Python implementation A Python implementation of this looks similar to the Rust version: ```python class Counter: def __init__(self, wraps): self.count = 0 self.wraps = wraps def __call__(self, *args, **kwargs): self.count += 1 print(f"{self.wraps.__name__} has been called {self.count} time(s)") self.wraps(*args, **kwargs) ``` Note that it can also be implemented as a higher order function: ```python def Counter(wraps): count = 0 def call(*args, **kwargs): nonlocal count count += 1 print(f"{wraps.__name__} has been called {count} time(s)") return wraps(*args, **kwargs) return call ``` ### What is the `AtomicU64` for? A [previous implementation] used a normal `u64`, which meant it required a `&mut self` receiver to update the count: ```rust,ignore #[pyo3(signature = (*args, **kwargs))] fn __call__( &mut self, py: Python<'_>, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { self.count += 1; let name = self.wraps.getattr(py, "__name__")?; println!("{} has been called {} time(s).", name, self.count); // After doing something, we finally forward the call to the wrapped function let ret = self.wraps.call(py, args, kwargs)?; // We could do something with the return value of // the function before returning it Ok(ret) } ``` The problem with this is that the `&mut self` receiver means PyO3 has to borrow it exclusively, and hold this borrow across the `self.wraps.call(py, args, kwargs)` call. This call returns control to the user's Python code which is free to call arbitrary things, *including* the decorated function. If that happens PyO3 is unable to create a second unique borrow and will be forced to raise an exception. As a result, something innocent like this will raise an exception: ```py @Counter def say_hello(): if say_hello.count < 2: print(f"hello from decorator") say_hello() # RuntimeError: Already borrowed ``` The implementation in this chapter fixes that by never borrowing exclusively; all the methods take `&self` as receivers, of which multiple may exist simultaneously. This requires a shared counter and the most straightforward way to implement thread-safe interior mutability (e.g. the type does not need to accept `&mut self` to modify the "interior" state) for a `u64` is to use [`AtomicU64`], so that's what is used here. This shows the dangers of running arbitrary Python code - note that "running arbitrary Python code" can be far more subtle than the example above: - Python's asynchronous executor may park the current thread in the middle of Python code, even in Python code that *you* control, and let other Python code run. - Dropping arbitrary Python objects may invoke destructors defined in Python (`__del__` methods). - Calling Python's C-api (most PyO3 apis call C-api functions internally) may raise exceptions, which may allow Python code in signal handlers to run. - On the free-threaded build, users might use Python's `threading` module to work with your types simultaneously from multiple OS threads. This is especially important if you are writing unsafe code; Python code must never be able to cause undefined behavior. You must ensure that your Rust code is in a consistent state before doing any of the above things. [previous implementation]: "Thread Safe Decorator · Discussion #2598 · PyO3/pyo3" [`AtomicU64`]: "AtomicU64 in std::sync::atomic - Rust" ================================================ FILE: guide/src/class/numeric.md ================================================ # Emulating numeric types At this point we have a `Number` class that we can't actually do any math on! Before proceeding, we should think about how we want to handle overflows. There are three obvious solutions: - We can have infinite precision just like Python's `int`. However that would be quite boring - we'd be reinventing the wheel. - We can raise exceptions whenever `Number` overflows, but that makes the API painful to use. - We can wrap around the boundary of `i32`. This is the approach we'll take here. To do that we'll just forward to `i32`'s `wrapping_*` methods. ## Fixing our constructor Let's address the first overflow, in `Number`'s constructor: ```python from my_module import Number n = Number(1 << 1337) ``` ```text Traceback (most recent call last): File "example.py", line 3, in n = Number(1 << 1337) OverflowError: Python int too large to convert to C long ``` Instead of relying on the default [`FromPyObject`] extraction to parse arguments, we can specify our own extraction function, using the `#[pyo3(from_py_with = ...)]` attribute. Unfortunately PyO3 doesn't provide a way to wrap Python integers out of the box, but we can do a Python call to mask it and cast it to an `i32`. ```rust,no_run # #![allow(dead_code)] use pyo3::prelude::*; fn wrap(obj: &Bound<'_, PyAny>) -> PyResult { let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?; let val: u32 = val.extract()?; // 👇 This intentionally overflows! Ok(val as i32) } ``` We also add documentation, via `///` comments, which are visible to Python users. ```rust,no_run # #![allow(dead_code)] use pyo3::prelude::*; fn wrap(obj: &Bound<'_, PyAny>) -> PyResult { let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?; let val: u32 = val.extract()?; Ok(val as i32) } /// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not. /// It's not a story C would tell you. It's a Rust legend. #[pyclass(module = "my_module")] struct Number(i32); #[pymethods] impl Number { #[new] fn new(#[pyo3(from_py_with = wrap)] value: i32) -> Self { Self(value) } } ``` With that out of the way, let's implement some operators: ```rust,no_run use pyo3::exceptions::{PyZeroDivisionError, PyValueError}; # use pyo3::prelude::*; # # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __add__(&self, other: &Self) -> Self { Self(self.0.wrapping_add(other.0)) } fn __sub__(&self, other: &Self) -> Self { Self(self.0.wrapping_sub(other.0)) } fn __mul__(&self, other: &Self) -> Self { Self(self.0.wrapping_mul(other.0)) } fn __truediv__(&self, other: &Self) -> PyResult { match self.0.checked_div(other.0) { Some(i) => Ok(Self(i)), None => Err(PyZeroDivisionError::new_err("division by zero")), } } fn __floordiv__(&self, other: &Self) -> PyResult { match self.0.checked_div(other.0) { Some(i) => Ok(Self(i)), None => Err(PyZeroDivisionError::new_err("division by zero")), } } fn __rshift__(&self, other: &Self) -> PyResult { match other.0.try_into() { Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))), Err(_) => Err(PyValueError::new_err("negative shift count")), } } fn __lshift__(&self, other: &Self) -> PyResult { match other.0.try_into() { Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))), Err(_) => Err(PyValueError::new_err("negative shift count")), } } } ``` ### Unary arithmetic operations ```rust,no_run # use pyo3::prelude::*; # # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __pos__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __neg__(&self) -> Self { Self(-self.0) } fn __abs__(&self) -> Self { Self(self.0.abs()) } fn __invert__(&self) -> Self { Self(!self.0) } } ``` ### Support for the `complex()`, `int()` and `float()` built-in functions ```rust,no_run # use pyo3::prelude::*; # # #[pyclass] # struct Number(i32); # use pyo3::types::PyComplex; #[pymethods] impl Number { fn __int__(&self) -> i32 { self.0 } fn __float__(&self) -> f64 { self.0 as f64 } fn __complex__<'py>(&self, py: Python<'py>) -> Bound<'py, PyComplex> { PyComplex::from_doubles(py, self.0 as f64, 0.0) } } ``` We do not implement the in-place operations like `__iadd__` because we do not wish to mutate `Number`. Similarly we're not interested in supporting operations with different types, so we do not implement the reflected operations like `__radd__` either. Now Python can use our `Number` class: ```python from my_module import Number def hash_djb2(s: str): ''' A version of Daniel J. Bernstein's djb2 string hashing algorithm Like many hashing algorithms, it relies on integer wrapping. ''' n = Number(0) five = Number(5) for x in s: n = Number(ord(x)) + ((n << five) - n) return n assert hash_djb2('l50_50') == Number(-1152549421) ``` ### Final code ```rust use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use pyo3::exceptions::{PyValueError, PyZeroDivisionError}; use pyo3::prelude::*; use pyo3::class::basic::CompareOp; use pyo3::types::{PyComplex, PyString}; fn wrap(obj: &Bound<'_, PyAny>) -> PyResult { let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?; let val: u32 = val.extract()?; Ok(val as i32) } /// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not. /// It's not a story C would tell you. It's a Rust legend. #[pyclass(module = "my_module")] struct Number(i32); #[pymethods] impl Number { #[new] fn new(#[pyo3(from_py_with = wrap)] value: i32) -> Self { Self(value) } fn __repr__(slf: &Bound<'_, Self>) -> PyResult { // Get the class name dynamically in case `Number` is subclassed let class_name: Bound<'_, PyString> = slf.get_type().qualname()?; Ok(format!("{}({})", class_name, slf.borrow().0)) } fn __str__(&self) -> String { self.0.to_string() } fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); hasher.finish() } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { match op { CompareOp::Lt => Ok(self.0 < other.0), CompareOp::Le => Ok(self.0 <= other.0), CompareOp::Eq => Ok(self.0 == other.0), CompareOp::Ne => Ok(self.0 != other.0), CompareOp::Gt => Ok(self.0 > other.0), CompareOp::Ge => Ok(self.0 >= other.0), } } fn __bool__(&self) -> bool { self.0 != 0 } fn __add__(&self, other: &Self) -> Self { Self(self.0.wrapping_add(other.0)) } fn __sub__(&self, other: &Self) -> Self { Self(self.0.wrapping_sub(other.0)) } fn __mul__(&self, other: &Self) -> Self { Self(self.0.wrapping_mul(other.0)) } fn __truediv__(&self, other: &Self) -> PyResult { match self.0.checked_div(other.0) { Some(i) => Ok(Self(i)), None => Err(PyZeroDivisionError::new_err("division by zero")), } } fn __floordiv__(&self, other: &Self) -> PyResult { match self.0.checked_div(other.0) { Some(i) => Ok(Self(i)), None => Err(PyZeroDivisionError::new_err("division by zero")), } } fn __rshift__(&self, other: &Self) -> PyResult { match other.0.try_into() { Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))), Err(_) => Err(PyValueError::new_err("negative shift count")), } } fn __lshift__(&self, other: &Self) -> PyResult { match other.0.try_into() { Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))), Err(_) => Err(PyValueError::new_err("negative shift count")), } } fn __xor__(&self, other: &Self) -> Self { Self(self.0 ^ other.0) } fn __or__(&self, other: &Self) -> Self { Self(self.0 | other.0) } fn __and__(&self, other: &Self) -> Self { Self(self.0 & other.0) } fn __int__(&self) -> i32 { self.0 } fn __float__(&self) -> f64 { self.0 as f64 } fn __complex__<'py>(&self, py: Python<'py>) -> Bound<'py, PyComplex> { PyComplex::from_doubles(py, self.0 as f64, 0.0) } } #[pymodule] mod my_module { #[pymodule_export] use super::Number; } # const SCRIPT: &'static std::ffi::CStr = cr#" # def hash_djb2(s: str): # n = Number(0) # five = Number(5) # # for x in s: # n = Number(ord(x)) + ((n << five) - n) # return n # # assert hash_djb2('l50_50') == Number(-1152549421) # assert hash_djb2('logo') == Number(3327403) # assert hash_djb2('horizon') == Number(1097468315) # # # assert Number(2) + Number(2) == Number(4) # assert Number(2) + Number(2) != Number(5) # # assert Number(13) - Number(7) == Number(6) # assert Number(13) - Number(-7) == Number(20) # # assert Number(13) / Number(7) == Number(1) # assert Number(13) // Number(7) == Number(1) # # assert Number(13) * Number(7) == Number(13*7) # # assert Number(13) > Number(7) # assert Number(13) < Number(20) # assert Number(13) == Number(13) # assert Number(13) >= Number(7) # assert Number(13) <= Number(20) # assert Number(13) == Number(13) # # # assert (True if Number(1) else False) # assert (False if Number(0) else True) # # # assert int(Number(13)) == 13 # assert float(Number(13)) == 13 # assert Number.__doc__ == "Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.\nIt's not a story C would tell you. It's a Rust legend." # assert Number(12345234523452) == Number(1498514748) # try: # import inspect # assert inspect.signature(Number).__str__() == '(value)' # except ValueError: # # Not supported with `abi3` before Python 3.10 # pass # assert Number(1337).__str__() == '1337' # assert Number(1337).__repr__() == 'Number(1337)' "#; # # use pyo3::PyTypeInfo; # # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let globals = PyModule::import(py, "__main__")?.dict(); # globals.set_item("Number", Number::type_object(py))?; # # py.run(SCRIPT, Some(&globals), None)?; # Ok(()) # }) # } ``` ## Appendix: Writing some unsafe code At the beginning of this chapter we said that PyO3 doesn't provide a way to wrap Python integers out of the box but that's a half truth. There's not a PyO3 API for it, but there's a Python C API function that does: ```c unsigned long PyLong_AsUnsignedLongMask(PyObject *obj) ``` We can call this function from Rust by using [`pyo3::ffi::PyLong_AsUnsignedLongMask`]. This is an *unsafe* function, which means we have to use an unsafe block to call it and take responsibility for upholding the contracts of this function. Let's review those contracts: - We must be attached to the interpreter. If we're not, calling this function causes a data race. - The pointer must be valid, i.e. it must be properly aligned and point to a valid Python object. Let's create that helper function. The signature has to be `fn(&Bound<'_, PyAny>) -> PyResult`. - `&Bound<'_, PyAny>` represents a checked bound reference, so the pointer derived from it is valid (and not null). - Whenever we have bound references to Python objects in scope, it is guaranteed that we're attached to the interpreter. This reference is also where we can get a [`Python`] token to use in our call to [`PyErr::take`]. ```rust,no_run # #![allow(dead_code)] use std::ffi::c_ulong; use pyo3::prelude::*; use pyo3::ffi; fn wrap(obj: &Bound<'_, PyAny>) -> Result { let py: Python<'_> = obj.py(); unsafe { let ptr = obj.as_ptr(); let ret: c_ulong = ffi::PyLong_AsUnsignedLongMask(ptr); if ret == c_ulong::MAX { if let Some(err) = PyErr::take(py) { return Err(err); } } Ok(ret as i32) } } ``` [`PyErr::take`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/struct.PyErr.html#method.take [`Python`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html [`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html [`pyo3::ffi::PyLong_AsUnsignedLongMask`]: {{#PYO3_DOCS_URL}}/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html ================================================ FILE: guide/src/class/object.md ================================================ # Basic object customization Recall the `Number` class from the previous chapter: ```rust,no_run # #![allow(dead_code)] # fn main() {} use pyo3::prelude::*; #[pyclass] struct Number(i32); #[pymethods] impl Number { #[new] fn new(value: i32) -> Self { Self(value) } } #[pymodule] mod my_module { #[pymodule_export] use super::Number; } ``` At this point Python code can import the module, access the class and create class instances - but nothing else. ```python from my_module import Number n = Number(5) print(n) ``` ```text ``` ## String representations It can't even print an user-readable representation of itself! We can fix that by defining the `__repr__` and `__str__` methods inside a `#[pymethods]` block. We do this by accessing the value contained inside `Number`. ```rust,no_run # use pyo3::prelude::*; # # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { // For `__repr__` we want to return a string that Python code could use to recreate // the `Number`, like `Number(5)` for example. fn __repr__(&self) -> String { // We use the `format!` macro to create a string. Its first argument is a // format string, followed by any number of parameters which replace the // `{}`'s in the format string. // // 👇 Tuple field access in Rust uses a dot format!("Number({})", self.0) } // `__str__` is generally used to create an "informal" representation, so we // just forward to `i32`'s `ToString` trait implementation to print a bare number. fn __str__(&self) -> String { self.0.to_string() } } ``` To automatically generate the `__str__` implementation using a `Display` trait implementation, pass the `str` argument to `pyclass`. ```rust,no_run # use std::fmt::{Display, Formatter}; # use pyo3::prelude::*; # # #[allow(dead_code)] #[pyclass(str)] struct Coordinate { x: i32, y: i32, z: i32, } impl Display for Coordinate { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "({}, {}, {})", self.x, self.y, self.z) } } ``` For convenience, a shorthand format string can be passed to `str` as `str=""` for **structs only**. It expands and is passed into the `format!` macro in the following ways: - `"{x}"` -> `"{}", self.x` - `"{0}"` -> `"{}", self.0` - `"{x:?}"` -> `"{:?}", self.x` *Note: Depending upon the format string you use, this may require implementation of the `Display` or `Debug` traits for the given Rust types.* *Note: the pyclass args `name` and `rename_all` are incompatible with the shorthand format string and will raise a compile time error.* ```rust,no_run # use pyo3::prelude::*; # # #[allow(dead_code)] #[pyclass(str="({x}, {y}, {z})")] struct Coordinate { x: i32, y: i32, z: i32, } ``` ### Accessing the class name In the `__repr__`, we used a hard-coded class name. This is sometimes not ideal, because if the class is subclassed in Python, we would like the repr to reflect the subclass name. This is typically done in Python code by accessing `self.__class__.__name__`. In order to be able to access the Python type information *and* the Rust struct, we need to use a `Bound` as the `self` argument. ```rust,no_run # use pyo3::prelude::*; # use pyo3::types::PyString; # # #[allow(dead_code)] # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __repr__(slf: &Bound<'_, Self>) -> PyResult { // This is the equivalent of `self.__class__.__name__` in Python. let class_name: Bound<'_, PyString> = slf.get_type().qualname()?; // To access fields of the Rust struct, we need to borrow from the Bound object. Ok(format!("{}({})", class_name, slf.borrow().0)) } } ``` ### Hashing Let's also implement hashing. We'll just hash the `i32`. For that we need a [`Hasher`]. The one provided by `std` is [`DefaultHasher`], which uses the [SipHash] algorithm. ```rust,no_run use std::collections::hash_map::DefaultHasher; // Required to call the `.hash` and `.finish` methods, which are defined on traits. use std::hash::{Hash, Hasher}; # use pyo3::prelude::*; # # #[allow(dead_code)] # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); hasher.finish() } } ``` To implement `__hash__` using the Rust [`Hash`] trait implementation, the `hash` option can be used. This option is only available for `frozen` classes to prevent accidental hash changes from mutating the object. If you need an `__hash__` implementation for a mutable class, use the manual method from above. This option also requires `eq`: According to the [Python docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) "If a class does not define an `__eq__()` method it should not define a `__hash__()` operation either" ```rust,no_run # use pyo3::prelude::*; # # #[allow(dead_code)] #[pyclass(frozen, eq, hash)] #[derive(PartialEq, Hash)] struct Number(i32); ``` > [!NOTE] > When implementing `__hash__` and comparisons, it is important that the following property holds: > > ```text > k1 == k2 -> hash(k1) == hash(k2) > ``` > > In other words, if two keys are equal, their hashes must also be equal. In addition you must take > care that your classes' hash doesn't change during its lifetime. In this tutorial we do that by not > letting Python code change our `Number` class. In other words, it is immutable. > > By default, all `#[pyclass]` types have a default hash implementation from Python. > Types which should not be hashable can override this by setting `__hash__` to None. > This is the same mechanism as for a pure-Python class. This is done like so: > > ```rust,no_run > # use pyo3::prelude::*; > #[pyclass] > struct NotHashable {} > > #[pymethods] > impl NotHashable { > #[classattr] > const __hash__: Option> = None; > } > ``` ### Comparisons PyO3 supports the usual magic comparison methods available in Python such as `__eq__`, `__lt__` and so on. It is also possible to support all six operations at once with `__richcmp__`. This method will be called with a value of `CompareOp` depending on the operation. ```rust,no_run use pyo3::class::basic::CompareOp; # use pyo3::prelude::*; # # #[allow(dead_code)] # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { match op { CompareOp::Lt => Ok(self.0 < other.0), CompareOp::Le => Ok(self.0 <= other.0), CompareOp::Eq => Ok(self.0 == other.0), CompareOp::Ne => Ok(self.0 != other.0), CompareOp::Gt => Ok(self.0 > other.0), CompareOp::Ge => Ok(self.0 >= other.0), } } } ``` If you obtain the result by comparing two Rust values, as in this example, you can take a shortcut using `CompareOp::matches`: ```rust,no_run use pyo3::class::basic::CompareOp; # use pyo3::prelude::*; # # #[allow(dead_code)] # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { op.matches(self.0.cmp(&other.0)) } } ``` It checks that the `std::cmp::Ordering` obtained from Rust's `Ord` matches the given `CompareOp`. Alternatively, you can implement just equality using `__eq__`: ```rust # use pyo3::prelude::*; # # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __eq__(&self, other: &Self) -> bool { self.0 == other.0 } } # fn main() -> PyResult<()> { # Python::attach(|py| { # let x = &Bound::new(py, Number(4))?; # let y = &Bound::new(py, Number(4))?; # assert!(x.eq(y)?); # assert!(!x.ne(y)?); # Ok(()) # }) # } ``` To implement `__eq__` using the Rust [`PartialEq`] trait implementation, the `eq` option can be used. ```rust,no_run # use pyo3::prelude::*; # # #[allow(dead_code)] #[pyclass(eq)] #[derive(PartialEq)] struct Number(i32); ``` To implement `__lt__`, `__le__`, `__gt__`, & `__ge__` using the Rust `PartialOrd` trait implementation, the `ord` option can be used. *Note: Requires `eq`.* ```rust,no_run # use pyo3::prelude::*; # # #[allow(dead_code)] #[pyclass(eq, ord)] #[derive(PartialEq, PartialOrd)] struct Number(i32); ``` ### Truthyness We'll consider `Number` to be `True` if it is nonzero: ```rust,no_run # use pyo3::prelude::*; # # #[allow(dead_code)] # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __bool__(&self) -> bool { self.0 != 0 } } ``` ### Final code ```rust,no_run # fn main() {} use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use pyo3::prelude::*; use pyo3::class::basic::CompareOp; use pyo3::types::PyString; #[pyclass] struct Number(i32); #[pymethods] impl Number { #[new] fn new(value: i32) -> Self { Self(value) } fn __repr__(slf: &Bound<'_, Self>) -> PyResult { let class_name: Bound<'_, PyString> = slf.get_type().qualname()?; Ok(format!("{}({})", class_name, slf.borrow().0)) } fn __str__(&self) -> String { self.0.to_string() } fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.0.hash(&mut hasher); hasher.finish() } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { match op { CompareOp::Lt => Ok(self.0 < other.0), CompareOp::Le => Ok(self.0 <= other.0), CompareOp::Eq => Ok(self.0 == other.0), CompareOp::Ne => Ok(self.0 != other.0), CompareOp::Gt => Ok(self.0 > other.0), CompareOp::Ge => Ok(self.0 >= other.0), } } fn __bool__(&self) -> bool { self.0 != 0 } } #[pymodule] mod my_module { #[pymodule_export] use super::Number; } ``` [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html [`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html [`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html [SipHash]: https://en.wikipedia.org/wiki/SipHash [`PartialEq`]: https://doc.rust-lang.org/stable/std/cmp/trait.PartialEq.html ================================================ FILE: guide/src/class/protocols.md ================================================ # Class customizations Python's object model defines several protocols for different object behavior, such as the sequence, mapping, and number protocols. Python classes support these protocols by implementing "magic" methods, such as `__str__` or `__repr__`. Because of the double-underscores surrounding their name, these are also known as "dunder" methods. PyO3 makes it possible for every magic method to be implemented in `#[pymethods]` just as they would be done in a regular Python class, with a few notable differences: - `__new__` is replaced by the [`#[new]` attribute](../class.md#constructor). - `__del__` is not yet supported, but may be in the future. - `__buffer__` and `__release_buffer__` are currently not supported and instead PyO3 supports [`__getbuffer__` and `__releasebuffer__`](#buffer-objects) methods (these predate [PEP 688](https://peps.python.org/pep-0688/#python-level-buffer-protocol)), again this may change in the future. - PyO3 adds [`__traverse__` and `__clear__`](#garbage-collector-integration) methods for controlling garbage collection. - The Python C-API which PyO3 is implemented upon requires many magic methods to have a specific function signature in C and be placed into special "slots" on the class type object. This limits the allowed argument and return types for these methods. They are listed in detail in the section below. If a magic method is not on the list above (for example `__init_subclass__`), then it should just work in PyO3. If this is not the case, please file a bug report. ## Magic Methods handled by PyO3 If a function name in `#[pymethods]` is a magic method which is known to need special handling, it will be automatically placed into the correct slot in the Python type object. The function name is taken from the usual rules for naming `#[pymethods]`: the `#[pyo3(name = "...")]` attribute is used if present, otherwise the Rust function name is used. The magic methods handled by PyO3 are very similar to the standard Python ones on [this page](https://docs.python.org/3/reference/datamodel.html#special-method-names) - in particular they are the subset which have slots as [defined here](https://docs.python.org/3/c-api/typeobj.html). When PyO3 handles a magic method, a couple of changes apply compared to other `#[pymethods]`: - The Rust function signature is restricted to match the magic method. - The `#[pyo3(signature = (...)]` and `#[pyo3(text_signature = "...")]` attributes are not allowed. The following sections list all magic methods for which PyO3 implements the necessary special handling. The given signatures should be interpreted as follows: - All methods take a receiver as first argument, shown as ``. It can be `&self`, `&mut self` or a `Bound` reference like `self_: PyRef<'_, Self>` and `self_: PyRefMut<'_, Self>`, as described [in the parent section](../class.md#inheritance). - An optional `Python<'py>` argument is always allowed as the first argument. - Return values can be optionally wrapped in `PyResult`. - `object` means that any type is allowed that can be extracted from a Python object (if argument) or converted to a Python object (if return value). - Other types must match what's given, e.g. `pyo3::basic::CompareOp` for `__richcmp__`'s second argument. - For the comparison and arithmetic methods, extraction errors are not propagated as exceptions, but lead to a return of `NotImplemented`. - For some magic methods, the return values are not restricted by PyO3, but checked by the Python interpreter. For example, `__str__` needs to return a string object. This is indicated by `object (Python type)`. ### Basic object customization - `__str__() -> object (str)` - `__repr__() -> object (str)` - `__hash__() -> isize` Objects that compare equal must have the same hash value. Any type up to 64 bits may be returned instead of `isize`, PyO3 will convert to an isize automatically (wrapping unsigned types like `u64` and `usize`).
Disabling Python's default hash By default, all `#[pyclass]` types have a default hash implementation from Python. Types which should not be hashable can override this by setting `__hash__` to `None`. This is the same mechanism as for a pure-Python class. This is done like so: ```rust,no_run # use pyo3::prelude::*; # #[pyclass] struct NotHashable {} #[pymethods] impl NotHashable { #[classattr] const __hash__: Option> = None; } ```
- `__lt__(, object) -> object` - `__le__(, object) -> object` - `__eq__(, object) -> object` - `__ne__(, object) -> object` - `__gt__(, object) -> object` - `__ge__(, object) -> object` The implementations of Python's "rich comparison" operators `<`, `<=`, `==`, `!=`, `>` and `>=` respectively. *Note that implementing any of these methods will cause Python not to generate a default `__hash__` implementation, so consider also implementing `__hash__`.*
Return type The return type will normally be `bool` or `PyResult`, however any Python object can be returned.
- `__richcmp__(, object, pyo3::basic::CompareOp) -> object` Implements Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`) in a single method. The `CompareOp` argument indicates the comparison operation being performed. You can use [`CompareOp::matches`] to adapt a Rust `std::cmp::Ordering` result to the requested comparison. *This method cannot be implemented in combination with any of `__lt__`, `__le__`, `__eq__`, `__ne__`, `__gt__`, or `__ge__`.* *Note that implementing `__richcmp__` will cause Python not to generate a default `__hash__` implementation, so consider implementing `__hash__` when implementing `__richcmp__`.*
Return type The return type will normally be `PyResult`, but any Python object can be returned. If you want to leave some operations unimplemented, you can return `py.NotImplemented()` for some of the operations: ```rust,no_run use pyo3::class::basic::CompareOp; use pyo3::types::PyNotImplemented; # use pyo3::prelude::*; # use pyo3::BoundObject; # # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { fn __richcmp__<'py>(&self, other: &Self, op: CompareOp, py: Python<'py>) -> PyResult> { match op { CompareOp::Eq => Ok((self.0 == other.0).into_pyobject(py)?.into_any()), CompareOp::Ne => Ok((self.0 != other.0).into_pyobject(py)?.into_any()), _ => Ok(PyNotImplemented::get(py).into_any()), } } } ``` If the second argument `object` is not of the type specified in the signature, the generated code will automatically `return NotImplemented`.
- `__getattr__(, object) -> object` - `__getattribute__(, object) -> object`
Differences between __getattr__ and __getattribute__ As in Python, `__getattr__` is only called if the attribute is not found by normal attribute lookup. `__getattribute__`, on the other hand, is called for *every* attribute access. If it wants to access existing attributes on `self`, it needs to be very careful not to introduce infinite recursion, and use `baseclass.__getattribute__()`.
- `__setattr__(, value: object) -> ()` - `__delattr__(, object) -> ()` Overrides attribute access. - `__bool__() -> bool` Determines the "truthyness" of an object. - `__call__(, ...) -> object` - here, any argument list can be defined as for normal `pymethods` ### Iterable objects Iterators can be defined using these methods: - `__iter__() -> object` - `__next__() -> Option or IterNextOutput` ([see details](#returning-a-value-from-iteration)) Returning `None` from `__next__` indicates that that there are no further items. Example: ```rust,no_run use pyo3::prelude::*; use std::sync::Mutex; #[pyclass] struct MyIterator { iter: Mutex> + Send>>, } #[pymethods] impl MyIterator { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __next__(slf: PyRefMut<'_, Self>) -> Option> { slf.iter.lock().unwrap().next() } } ``` In many cases you'll have a distinction between the type being iterated over (i.e. the *iterable*) and the iterator it provides. In this case, the iterable only needs to implement `__iter__()` while the iterator must implement both `__iter__()` and `__next__()`. For example: ```rust,no_run # use pyo3::prelude::*; #[pyclass] struct Iter { inner: std::vec::IntoIter, } #[pymethods] impl Iter { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { slf.inner.next() } } #[pyclass] struct Container { iter: Vec, } #[pymethods] impl Container { fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { let iter = Iter { inner: slf.iter.clone().into_iter(), }; Py::new(slf.py(), iter) } } # Python::attach(|py| { # let container = Container { iter: vec![1, 2, 3, 4] }; # let inst = pyo3::Py::new(py, container).unwrap(); # pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]"); # pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]"); # }); ``` For more details on Python's iteration protocols, check out [the "Iterator Types" section of the library documentation](https://docs.python.org/library/stdtypes.html#iterator-types). #### Returning a value from iteration This guide has so far shown how to use `Option` to implement yielding values during iteration. In Python a generator can also return a value. This is done by raising a `StopIteration` exception. To express this in Rust, return `PyResult::Err` with a `PyStopIteration` as the error. ### Awaitable objects - `__await__() -> object` - `__aiter__() -> object` - `__anext__() -> Option` ### Mapping & Sequence types The magic methods in this section can be used to implement Python container types. They are two main categories of container in Python: "mappings" such as `dict`, with arbitrary keys, and "sequences" such as `list` and `tuple`, with integer keys. The Python C-API which PyO3 is built upon has separate "slots" for sequences and mappings. When writing a `class` in pure Python, there is no such distinction in the implementation - a `__getitem__` implementation will fill the slots for both the mapping and sequence forms, for example. By default PyO3 reproduces the Python behaviour of filling both mapping and sequence slots. This makes sense for the "simple" case which matches Python, and also for sequences, where the mapping slot is used anyway to implement slice indexing. Mapping types usually will not want the sequence slots filled. Having them filled will lead to outcomes which may be unwanted, such as: - The mapping type will successfully cast to [`PySequence`]. This may lead to consumers of the type handling it incorrectly. - Python provides a default implementation of `__iter__` for sequences, which calls `__getitem__` with consecutive positive integers starting from 0 until an `IndexError` is returned. Unless the mapping only contains consecutive positive integer keys, this `__iter__` implementation will likely not be the intended behavior. Use the `#[pyclass(mapping)]` annotation to instruct PyO3 to only fill the mapping slots, leaving the sequence ones empty. This will apply to `__getitem__`, `__setitem__`, and `__delitem__`. Use the `#[pyclass(sequence)]` annotation to instruct PyO3 to fill the `sq_length` slot instead of the `mp_length` slot for `__len__`. This will help libraries such as `numpy` recognise the class as a sequence, however will also cause CPython to automatically add the sequence length to any negative indices before passing them to `__getitem__`. (`__getitem__`, `__setitem__` and `__delitem__` mapping slots are still used for sequences, for slice operations.) - `__len__() -> usize` Implements the built-in function `len()`. - `__contains__(, object) -> bool` Implements membership test operators. Should return true if `item` is in `self`, false otherwise. For objects that don’t define `__contains__()`, the membership test simply traverses the sequence until it finds a match.
Disabling Python's default contains By default, all `#[pyclass]` types with an `__iter__` method support a default implementation of the `in` operator. Types which do not want this can override this by setting `__contains__` to `None`. This is the same mechanism as for a pure-Python class. This is done like so: ```rust,no_run # use pyo3::prelude::*; # #[pyclass] struct NoContains {} #[pymethods] impl NoContains { #[classattr] const __contains__: Option> = None; } ```
- `__getitem__(, object) -> object` Implements retrieval of the `self[a]` element. *Note:* Negative integer indexes are not handled specially by PyO3. However, for classes with `#[pyclass(sequence)]`, when a negative index is accessed via `PySequence::get_item`, the underlying C API already adjusts the index to be positive. - `__setitem__(, object, object) -> ()` Implements assignment to the `self[a]` element. Should only be implemented if elements can be replaced. Same behavior regarding negative indices as for `__getitem__`. - `__delitem__(, object) -> ()` Implements deletion of the `self[a]` element. Should only be implemented if elements can be deleted. Same behavior regarding negative indices as for `__getitem__`. - `fn __concat__(&self, other: impl FromPyObject) -> PyResult` Concatenates two sequences. Used by the `+` operator, after trying the numeric addition via the `__add__` and `__radd__` methods. - `fn __repeat__(&self, count: isize) -> PyResult` Repeats the sequence `count` times. Used by the `*` operator, after trying the numeric multiplication via the `__mul__` and `__rmul__` methods. - `fn __inplace_concat__(&self, other: impl FromPyObject) -> PyResult` Concatenates two sequences. Used by the `+=` operator, after trying the numeric addition via the `__iadd__` method. - `fn __inplace_repeat__(&self, count: isize) -> PyResult` Concatenates two sequences. Used by the `*=` operator, after trying the numeric multiplication via the `__imul__` method. ### Descriptors - `__get__(, object, object) -> object` - `__set__(, object, object) -> ()` - `__delete__(, object) -> ()` ### Numeric types Binary arithmetic operations (`+`, `-`, `*`, `@`, `/`, `//`, `%`, `divmod()`, `pow()` and `**`, `<<`, `>>`, `&`, `^`, and `|`) and their reflected versions: (If the `object` is not of the type specified in the signature, the generated code will automatically `return NotImplemented`.) - `__add__(, object) -> object` - `__radd__(, object) -> object` - `__sub__(, object) -> object` - `__rsub__(, object) -> object` - `__mul__(, object) -> object` - `__rmul__(, object) -> object` - `__matmul__(, object) -> object` - `__rmatmul__(, object) -> object` - `__floordiv__(, object) -> object` - `__rfloordiv__(, object) -> object` - `__truediv__(, object) -> object` - `__rtruediv__(, object) -> object` - `__divmod__(, object) -> object` - `__rdivmod__(, object) -> object` - `__mod__(, object) -> object` - `__rmod__(, object) -> object` - `__lshift__(, object) -> object` - `__rlshift__(, object) -> object` - `__rshift__(, object) -> object` - `__rrshift__(, object) -> object` - `__and__(, object) -> object` - `__rand__(, object) -> object` - `__xor__(, object) -> object` - `__rxor__(, object) -> object` - `__or__(, object) -> object` - `__ror__(, object) -> object` - `__pow__(, object, object) -> object` - `__rpow__(, object, object) -> object` In-place assignment operations (`+=`, `-=`, `*=`, `@=`, `/=`, `//=`, `%=`, `**=`, `<<=`, `>>=`, `&=`, `^=`, `|=`): - `__iadd__(, object) -> ()` - `__isub__(, object) -> ()` - `__imul__(, object) -> ()` - `__imatmul__(, object) -> ()` - `__itruediv__(, object) -> ()` - `__ifloordiv__(, object) -> ()` - `__imod__(, object) -> ()` - `__ipow__(, object, object) -> ()` - `__ilshift__(, object) -> ()` - `__irshift__(, object) -> ()` - `__iand__(, object) -> ()` - `__ixor__(, object) -> ()` - `__ior__(, object) -> ()` Unary operations (`-`, `+`, `abs()` and `~`): - `__pos__() -> object` - `__neg__() -> object` - `__abs__() -> object` - `__invert__() -> object` Coercions: - `__index__() -> object (int)` - `__int__() -> object (int)` - `__float__() -> object (float)` ### Buffer objects - `__getbuffer__(, *mut ffi::Py_buffer, flags) -> ()` - `__releasebuffer__(, *mut ffi::Py_buffer) -> ()` Errors returned from `__releasebuffer__` will be sent to `sys.unraiseablehook`. It is strongly advised to never return an error from `__releasebuffer__`, and if it really is necessary, to make best effort to perform any required freeing operations before returning. `__releasebuffer__` will not be called a second time; anything not freed will be leaked. ### Garbage Collector Integration If your type owns references to other Python objects, you will need to integrate with Python's garbage collector so that the GC is aware of those references. To do this, implement the two methods `__traverse__` and `__clear__`. These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API. `__traverse__` must call `visit.call()` for each reference to another Python object. `__clear__` must clear out any mutable references to other Python objects (thus breaking reference cycles). Immutable references do not have to be cleared, as every cycle must contain at least one mutable reference. - `__traverse__(, pyo3::class::gc::PyVisit<'_>) -> Result<(), pyo3::class::gc::PyTraverseError>` - `__clear__() -> ()` > [!NOTE] > `__traverse__` does not work with [`#[pyo3(warn(...))]`](../function.md#warn). Example: ```rust,no_run use pyo3::prelude::*; use pyo3::PyTraverseError; use pyo3::gc::PyVisit; #[pyclass] struct ClassWithGCSupport { obj: Option>, } #[pymethods] impl ClassWithGCSupport { fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { visit.call(&self.obj)?; Ok(()) } fn __clear__(&mut self) { // Clear reference, this decrements ref counter. self.obj = None; } } ``` > [!NOTE] > When a class inherits from either a Python builtins type or another type declared in Rust and implement either or both `__traverse__` and `__clear__`, the parent class `__traverse__` and `__clear__` is called automatically. > There is no need to explicitly call it from inside the class implementation. Usually, an implementation of `__traverse__` should do nothing but calls to `visit.call`. Most importantly, safe access to the interpreter is prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic. > [!NOTE] > These methods are part of the C API, PyPy does not necessarily honor them. If you are building for PyPy you should measure memory consumption to make sure you do not have runaway memory growth. See [this issue on the PyPy bug tracker](https://github.com/pypy/pypy/issues/3848). [`PySequence`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PySequence.html [`CompareOp::matches`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/enum.CompareOp.html#method.matches ================================================ FILE: guide/src/class/thread-safety.md ================================================ # `#[pyclass]` thread safety Python objects are freely shared between threads by the Python interpreter. This means that: - there is no control which thread might eventually drop the `#[pyclass]` object, meaning `Send` is required. - multiple threads can potentially be reading the `#[pyclass]` data simultaneously, meaning `Sync` is required. This section of the guide discusses various data structures which can be used to make types satisfy these requirements. In special cases where it is known that your Python application is never going to use threads (this is rare!), these thread-safety requirements can be opted-out with [`#[pyclass(unsendable)]`](../class.md#customizing-the-class), at the cost of making concurrent access to the Rust data be runtime errors. This is only for very specific use cases; it is almost always better to make proper thread-safe types. ## Making `#[pyclass]` types thread-safe The general challenge with thread-safety is to make sure that two threads cannot produce a data race, i.e. unsynchronized writes to the same data at the same time. A data race produces an unpredictable result and is forbidden by Rust. By default, `#[pyclass]` employs an ["interior mutability" pattern](../class.md#bound-and-interior-mutability) to allow for either multiple `&T` references or a single exclusive `&mut T` reference to access the data. This allows for simple `#[pyclass]` types to be thread-safe automatically, at the cost of runtime checking for concurrent access. Errors will be raised if the usage overlaps. For example, the below simple class is thread-safe: ```rust,no_run # use pyo3::prelude::*; #[pyclass] struct MyClass { x: i32, y: i32, } #[pymethods] impl MyClass { fn get_x(&self) -> i32 { self.x } fn set_y(&mut self, value: i32) { self.y = value; } } ``` In the above example, if calls to `get_x` and `set_y` overlap (from two different threads) then at least one of those threads will experience a runtime error indicating that the data was "already borrowed". To avoid these errors, you can take control of the interior mutability yourself in one of the following ways. ### Using atomic data structures To remove the possibility of having overlapping `&self` and `&mut self` references produce runtime errors, consider using `#[pyclass(frozen)]` and use [atomic data structures](https://doc.rust-lang.org/std/sync/atomic/) to control modifications directly. For example, a thread-safe version of the above `MyClass` using atomic integers would be as follows: ```rust,no_run # use pyo3::prelude::*; use std::sync::atomic::{AtomicI32, Ordering}; #[pyclass(frozen)] struct MyClass { x: AtomicI32, y: AtomicI32, } #[pymethods] impl MyClass { fn get_x(&self) -> i32 { self.x.load(Ordering::Relaxed) } fn set_y(&self, value: i32) { self.y.store(value, Ordering::Relaxed) } } ``` ### Using locks An alternative to atomic data structures is to use [locks](https://doc.rust-lang.org/std/sync/struct.Mutex.html) to make threads wait for access to shared data. For example, a thread-safe version of the above `MyClass` using locks would be as follows: ```rust,no_run # use pyo3::prelude::*; use std::sync::Mutex; struct MyClassInner { x: i32, y: i32, } #[pyclass(frozen)] struct MyClass { inner: Mutex } #[pymethods] impl MyClass { fn get_x(&self) -> i32 { self.inner.lock().expect("lock not poisoned").x } fn set_y(&self, value: i32) { self.inner.lock().expect("lock not poisoned").y = value; } } ``` If you need to lock around state stored in the Python interpreter or otherwise call into the Python C API while a lock is held, you might find the `MutexExt` trait useful. It provides a `lock_py_attached` method for `std::sync::Mutex` that avoids deadlocks with the GIL or other global synchronization events in the interpreter. Additionally, support for the `parking_lot` and `lock_api` synchronization libraries is gated behind the `parking_lot` and `lock_api` features. You can also enable the `arc_lock` feature if you need the `arc_lock` features of either library. ### Wrapping unsynchronized data In some cases, the data structures stored within a `#[pyclass]` may themselves not be thread-safe. Rust will therefore not implement `Send` and `Sync` on the `#[pyclass]` type. To achieve thread-safety, a manual `Send` and `Sync` implementation is required which is `unsafe` and should only be done following careful review of the soundness of the implementation. Doing this for PyO3 types is no different than for any other Rust code, [the Rustonomicon](https://doc.rust-lang.org/nomicon/send-and-sync.html) has a great discussion on this. ================================================ FILE: guide/src/class.md ================================================ # Python classes PyO3 exposes a group of attributes powered by Rust's proc macro system for defining Python classes as Rust structs. The main attribute is `#[pyclass]`, which is placed upon a Rust `struct` or `enum` to generate a Python type for it. They will usually also have *one* `#[pymethods]`-annotated `impl` block for the struct, which is used to define Python methods and constants for the generated Python type. (If the [`multiple-pymethods`] feature is enabled, each `#[pyclass]` is allowed to have multiple `#[pymethods]` blocks.) `#[pymethods]` may also have implementations for Python magic methods such as `__str__`. This chapter will discuss the functionality and configuration these attributes offer. Below is a list of links to the relevant section of this chapter for each: - [`#[pyclass]`](#defining-a-new-class) - [`#[pyo3(get, set)]`](#object-properties-using-pyo3get-set) - [`#[pymethods]`](#instance-methods) - [`#[new]`](#constructor) - [`#[getter]`](#object-properties-using-getter-and-setter) - [`#[setter]`](#object-properties-using-getter-and-setter) - [`#[deleter]`](#object-properties-using-getter-and-setter) - [`#[staticmethod]`](#static-methods) - [`#[classmethod]`](#class-methods) - [`#[classattr]`](#class-attributes) - [`#[args]`](#method-arguments) - [Magic methods and slots](class/protocols.md) - [Classes as function arguments](#classes-as-function-arguments) ## Defining a new class To define a custom Python class, add the `#[pyclass]` attribute to a Rust struct or enum. ```rust # #![allow(dead_code)] use pyo3::prelude::*; #[pyclass] struct MyClass { inner: i32, } // A "tuple" struct #[pyclass] struct Number(i32); // PyO3 supports unit-only enums (which contain only unit variants) // These simple enums behave similarly to Python's enumerations (enum.Enum) #[pyclass(eq, eq_int)] #[derive(PartialEq)] enum MyEnum { Variant, OtherVariant = 30, // PyO3 supports custom discriminants. } // PyO3 supports custom discriminants in unit-only enums #[pyclass(eq, eq_int)] #[derive(PartialEq)] enum HttpResponse { Ok = 200, NotFound = 404, Teapot = 418, // ... } // PyO3 also supports enums with Struct and Tuple variants // These complex enums have slightly different behavior from the simple enums above // They are meant to work with instance checks and match statement patterns // The variants can be mixed and matched // Struct variants have named fields while tuple enums generate generic names for fields in order _0, _1, _2, ... // Apart from this both types are functionally identical #[pyclass] enum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 }, RegularPolygon(u32, f64), Nothing(), } ``` The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass`, `Number`, `MyEnum`, `HttpResponse`, and `Shape`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ### Restrictions To integrate Rust types with Python, PyO3 needs to place some restrictions on the types which can be annotated with `#[pyclass]`. In particular, they must have no lifetime parameters, no generic parameters, and must be thread-safe. The reason for each of these is explained below. #### No lifetime parameters Rust lifetimes are used by the Rust compiler to reason about a program's memory safety. They are a compile-time only concept; there is no way to access Rust lifetimes at runtime from a dynamic language like Python. As soon as Rust data is exposed to Python, there is no guarantee that the Rust compiler can make on how long the data will live. Python is a reference-counted language and those references can be held for an arbitrarily long time which is untraceable by the Rust compiler. The only possible way to express this correctly is to require that any `#[pyclass]` does not borrow data for any lifetime shorter than the `'static` lifetime, i.e. the `#[pyclass]` cannot have any lifetime parameters. When you need to share ownership of data between Python and Rust, instead of using borrowed references with lifetimes consider using reference-counted smart pointers such as [`Arc`] or [`Py`][`Py`]. #### No generic parameters A Rust `struct Foo` with a generic parameter `T` generates new compiled implementations each time it is used with a different concrete type for `T`. These new implementations are generated by the compiler at each usage site. This is incompatible with wrapping `Foo` in Python, where there needs to be a single compiled implementation of `Foo` which is integrated with the Python interpreter. Currently, the best alternative is to write a macro which expands to a new `#[pyclass]` for each instantiation you want: ```rust # #![allow(dead_code)] use pyo3::prelude::*; struct GenericClass { data: T, } macro_rules! create_interface { ($name: ident, $type: ident) => { #[pyclass] pub struct $name { inner: GenericClass<$type>, } #[pymethods] impl $name { #[new] pub fn new(data: $type) -> Self { Self { inner: GenericClass { data: data }, } } } }; } create_interface!(IntClass, i64); create_interface!(FloatClass, String); ``` #### Must be thread-safe Python objects are freely shared between threads by the Python interpreter. This means that: - Python objects may be created and destroyed by different Python threads; therefore `#[pyclass]` objects must be `Send`. - Python objects may be accessed by multiple Python threads simultaneously; therefore `#[pyclass]` objects must be `Sync`. For now, don't worry about these requirements; simple classes will already be thread-safe. There is a [detailed discussion on thread-safety](./class/thread-safety.md) later in the guide. ## Constructor By default, it is not possible to create an instance of a custom class from Python code. To declare a constructor, you need to define a method and annotate it with the `#[new]` attribute. A constructor is accessible as Python's `__new__` method. ```rust # #![allow(dead_code)] # use pyo3::prelude::*; # #[pyclass] # struct Number(i32); # #[pymethods] impl Number { #[new] fn new(value: i32) -> Self { Number(value) } } ``` Alternatively, if your `new` method may fail you can return `PyResult`. ```rust # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::exceptions::PyValueError; # #[pyclass] # struct Nonzero(i32); # #[pymethods] impl Nonzero { #[new] fn py_new(value: i32) -> PyResult { if value == 0 { Err(PyValueError::new_err("cannot be zero")) } else { Ok(Nonzero(value)) } } } ``` If you want to return an existing object (for example, because your `new` method caches the values it returns), `new` can return `pyo3::Py`. As you can see, the Rust method name is not important here; this way you can still, use `new()` for a Rust-level constructor. If no method marked with `#[new]` is declared, object instances can only be created from Rust, but not from Python. For arguments, see the [`Method arguments`](#method-arguments) section below. ## Initializer An initializer implements Python's `__init__` method. It may be required when it's needed to control an object initalization flow on the Rust code. If possible handling this in `__new__` should be preferred, but in some cases, like subclassing native types, overwriting `__init__` might be necessary. For example, you define a class that extends `PyDict` and don't want that the original `__init__` method of `PyDict` been called. In this case by defining an own `__init__` method it's possible to stop initialization flow. If you declare an `__init__` method you may need to call a super class' `__init__` method explicitly like in Python code. To declare an initializer, you need to define the `__init__` method. Like in Python `__init__` must have the `self` receiver as the first argument, followed by the same arguments as the constructor. It can either return `()` or `PyResult<()>`. ```rust # #![allow(dead_code)] # use pyo3::prelude::*; # #[cfg(not(any(Py_LIMITED_API, GraalPy)))] use pyo3::types::{PyDict, PyTuple, PySuper}; # #[cfg(not(any(Py_LIMITED_API, GraalPy)))] use crate::pyo3::PyTypeInfo; # #[cfg(not(any(Py_LIMITED_API, GraalPy)))] #[pyclass(extends = PyDict)] struct MyDict; # #[cfg(not(any(Py_LIMITED_API, GraalPy)))] #[pymethods] impl MyDict { # #[allow(unused_variables)] #[new] #[pyo3(signature = (*args, **kwargs))] fn __new__( args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult { Ok(Self) } #[pyo3(signature = (*args, **kwargs))] fn __init__( slf: &Bound<'_, Self>, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult<()> { // call the super types __init__ PySuper::new(&PyDict::type_object(slf.py()), slf)? .call_method("__init__", args.to_owned(), kwargs)?; // Note: if `MyDict` allows further subclassing, and this is called from such a subclass, // then this will not that any overrides into account that such a subclass may have defined. // In such a case it may be preferred to just call `slf.set_item` and let Python figure it out. slf.as_super().set_item("my_key", "always insert this key")?; Ok(()) } } # #[cfg(not(any(Py_LIMITED_API, GraalPy)))] # fn main() { # Python::attach(|py| { # let typeobj = py.get_type::(); # let obj = typeobj.call((), None).unwrap().cast_into::().unwrap(); # // check __init__ was called # assert_eq!(obj.get_item("my_key").unwrap().extract::<&str>().unwrap(), "always insert this key"); # }); # } # #[cfg(any(Py_LIMITED_API, GraalPy))] # fn main() {} ``` ## Adding the class to a module The next step is to create the Python module and add our class to it: ```rust # #![allow(dead_code)] # use pyo3::prelude::*; # fn main() {} # #[pyclass] # struct Number(i32); # #[pymodule] mod my_module { #[pymodule_export] use super::Number; } ``` ## `Bound` and interior mutability { #bound-and-interior-mutability } It is often useful to turn a `#[pyclass]` type `T` into a Python object and access it from Rust code. The [`Py`] and [`Bound<'py, T>`] smart pointers are the ways to represent a Python object in PyO3's API. More detail can be found about them [in the Python objects](./types.md#pyo3s-smart-pointers) section of the guide. Most Python objects do not offer exclusive (`&mut`) access (see the [section on Python's memory model](./python-from-rust.md#pythons-memory-model)). However, Rust structs wrapped as Python objects (called `pyclass` types) often *do* need `&mut` access. However, the Rust borrow checker cannot reason about `&mut` references once an object's ownership has been passed to the Python interpreter. To solve this, PyO3 does borrow checking at runtime using a scheme very similar to `std::cell::RefCell`. This is known as [interior mutability](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html). Users who are familiar with `RefCell` can use `Py` and `Bound<'py, T>` just like `RefCell`. For users who are not very familiar with `RefCell`, here is a reminder of Rust's rules of borrowing: - At any given time, you can have either (but not both of) one mutable reference or any number of immutable references. - References can never outlast the data they refer to. `Py` and `Bound<'py, T>`, like `RefCell`, ensure these borrowing rules by tracking references at runtime. ```rust # use pyo3::prelude::*; #[pyclass] struct MyClass { #[pyo3(get)] num: i32, } Python::attach(|py| { let obj = Bound::new(py, MyClass { num: 3 }).unwrap(); { let obj_ref = obj.borrow(); // Get PyRef assert_eq!(obj_ref.num, 3); // You cannot get PyRefMut unless all PyRefs are dropped assert!(obj.try_borrow_mut().is_err()); } { let mut obj_mut = obj.borrow_mut(); // Get PyRefMut obj_mut.num = 5; // You cannot get any other refs until the PyRefMut is dropped assert!(obj.try_borrow().is_err()); assert!(obj.try_borrow_mut().is_err()); } // You can convert `Bound` to a Python object pyo3::py_run!(py, obj, "assert obj.num == 5"); }); ``` A `Bound<'py, T>` is restricted to the Python lifetime `'py`. To make the object longer lived (for example, to store it in a struct on the Rust side), use `Py`. `Py` needs a `Python<'_>` token to allow access: ```rust # use pyo3::prelude::*; #[pyclass] struct MyClass { num: i32, } fn return_myclass() -> Py { Python::attach(|py| Py::new(py, MyClass { num: 1 }).unwrap()) } let obj = return_myclass(); Python::attach(move |py| { let bound = obj.bind(py); // Py::bind returns &Bound<'py, MyClass> let obj_ref = bound.borrow(); // Get PyRef assert_eq!(obj_ref.num, 1); }); ``` ### frozen classes: Opting out of interior mutability As detailed above, runtime borrow checking is currently enabled by default. But a class can opt of out it by declaring itself `frozen`. It can still use interior mutability via standard Rust types like `RefCell` or `Mutex`, but it is not bound to the implementation provided by PyO3 and can choose the most appropriate strategy on field-by-field basis. Classes which are `frozen` and also `Sync`, e.g. they do use `Mutex` but not `RefCell`, can be accessed without needing a `Python` token via the `Bound::get` and `Py::get` methods: ```rust use std::sync::atomic::{AtomicUsize, Ordering}; # use pyo3::prelude::*; #[pyclass(frozen)] struct FrozenCounter { value: AtomicUsize, } let py_counter: Py = Python::attach(|py| { let counter = FrozenCounter { value: AtomicUsize::new(0), }; Py::new(py, counter).unwrap() }); py_counter.get().value.fetch_add(1, Ordering::Relaxed); Python::attach(move |_py| drop(py_counter)); ``` Frozen classes are likely to become the default thereby guiding the PyO3 ecosystem towards a more deliberate application of interior mutability. Eventually, this should enable further optimizations of PyO3's internals and avoid downstream code paying the cost of interior mutability when it is not actually required. ## Customizing the class {{#include ../pyclass-parameters.md}} These parameters are covered in various sections of this guide. ### Return type Generally, `#[new]` methods have to return `T: Into>` or `PyResult where T: Into>`. For constructors that may fail, you should wrap the return type in a PyResult as well. Consult the table below to determine which type your constructor should return: | | **Cannot fail** | **May fail** | |-----------------------------|---------------------------|-----------------------------------| |**No inheritance** | `T` | `PyResult` | |**Inheritance(T Inherits U)**| `(T, U)` | `PyResult<(T, U)>` | |**Inheritance(General Case)**| [`PyClassInitializer`] | `PyResult>` | ## Inheritance By default, `object`, i.e. `PyAny` is used as the base class. To override this default, use the `extends` parameter for `pyclass` with the full path to the base class. Currently, only classes defined in Rust and builtins provided by PyO3 can be inherited from; inheriting from other classes defined in Python is not yet supported ([#991](https://github.com/PyO3/pyo3/issues/991)). For convenience, `(T, U)` implements `Into>` where `U` is the base class of `T`. But for a more deeply nested inheritance, you have to return `PyClassInitializer` explicitly. To get a parent class from a child, use [`PyRef`] instead of `&self` for methods, or [`PyRefMut`] instead of `&mut self`. Then you can access a parent class by `self_.as_super()` as `&PyRef`, or by `self_.into_super()` as `PyRef` (and similar for the `PyRefMut` case). For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass` directly; however, this approach does not let you access base classes higher in the inheritance hierarchy, for which you would need to chain multiple `as_super` or `into_super` calls. ```rust # use pyo3::prelude::*; #[pyclass(subclass)] struct BaseClass { val1: usize, } #[pymethods] impl BaseClass { #[new] fn new() -> Self { BaseClass { val1: 10 } } pub fn method1(&self) -> PyResult { Ok(self.val1) } } #[pyclass(extends=BaseClass, subclass)] struct SubClass { val2: usize, } #[pymethods] impl SubClass { #[new] fn new() -> (Self, BaseClass) { (SubClass { val2: 15 }, BaseClass::new()) } fn method2(self_: PyRef<'_, Self>) -> PyResult { let super_ = self_.as_super(); // Get &PyRef super_.method1().map(|x| x * self_.val2) } } #[pyclass(extends=SubClass)] struct SubSubClass { val3: usize, } #[pymethods] impl SubSubClass { #[new] fn new() -> PyClassInitializer { PyClassInitializer::from(SubClass::new()).add_subclass(SubSubClass { val3: 20 }) } fn method3(self_: PyRef<'_, Self>) -> PyResult { let base = self_.as_super().as_super(); // Get &PyRef<'_, BaseClass> base.method1().map(|x| x * self_.val3) } fn method4(self_: PyRef<'_, Self>) -> PyResult { let v = self_.val3; let super_ = self_.into_super(); // Get PyRef<'_, SubClass> SubClass::method2(super_).map(|x| x * v) } fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) { let val1 = self_.as_super().as_super().val1; let val2 = self_.as_super().val2; (val1, val2, self_.val3) } fn double_values(mut self_: PyRefMut<'_, Self>) { self_.as_super().as_super().val1 *= 2; self_.as_super().val2 *= 2; self_.val3 *= 2; } #[staticmethod] fn factory_method(py: Python<'_>, val: usize) -> PyResult> { let base = PyClassInitializer::from(BaseClass::new()); let sub = base.add_subclass(SubClass { val2: val }); if val % 2 == 0 { Ok(Py::new(py, sub)?.into_any()) } else { let sub_sub = sub.add_subclass(SubSubClass { val3: val }); Ok(Py::new(py, sub_sub)?.into_any()) } } } # Python::attach(|py| { # let subsub = pyo3::Py::new(py, SubSubClass::new()).unwrap(); # pyo3::py_run!(py, subsub, "assert subsub.method1() == 10"); # pyo3::py_run!(py, subsub, "assert subsub.method2() == 150"); # pyo3::py_run!(py, subsub, "assert subsub.method3() == 200"); # pyo3::py_run!(py, subsub, "assert subsub.method4() == 3000"); # pyo3::py_run!(py, subsub, "assert subsub.get_values() == (10, 15, 20)"); # pyo3::py_run!(py, subsub, "assert subsub.double_values() == None"); # pyo3::py_run!(py, subsub, "assert subsub.get_values() == (20, 30, 40)"); # let subsub = SubSubClass::factory_method(py, 2).unwrap(); # let subsubsub = SubSubClass::factory_method(py, 3).unwrap(); # let cls = py.get_type::(); # pyo3::py_run!(py, subsub cls, "assert not isinstance(subsub, cls)"); # pyo3::py_run!(py, subsubsub cls, "assert isinstance(subsubsub, cls)"); # }); ``` You can inherit native types such as `PyDict`, if they implement [`PySizedLayout`]({{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PySizedLayout.html). This is not supported when building for the Python limited API (aka the `abi3` feature of PyO3). To convert between the Rust type and its native base class, you can take `slf` as a Python object. To access the Rust fields use `slf.borrow()` or `slf.borrow_mut()`, and to access the base class use `slf.cast::()`. ```rust # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] { # use pyo3::prelude::*; use pyo3::types::PyDict; use std::collections::HashMap; #[pyclass(extends=PyDict)] #[derive(Default)] struct DictWithCounter { counter: HashMap, } #[pymethods] impl DictWithCounter { #[new] fn new() -> Self { Self::default() } fn set(slf: &Bound<'_, Self>, key: String, value: Bound<'_, PyAny>) -> PyResult<()> { slf.borrow_mut().counter.entry(key.clone()).or_insert(0); let dict = slf.cast::()?; dict.set_item(key, value) } } # Python::attach(|py| { # let cnt = pyo3::Py::new(py, DictWithCounter::new()).unwrap(); # pyo3::py_run!(py, cnt, "cnt.set('abc', 10); assert cnt['abc'] == 10") # }); # } ``` If `SubClass` does not provide a base class initialization, the compilation fails. ```rust,compile_fail # use pyo3::prelude::*; #[pyclass] struct BaseClass { val1: usize, } #[pyclass(extends=BaseClass)] struct SubClass { val2: usize, } #[pymethods] impl SubClass { #[new] fn new() -> Self { SubClass { val2: 15 } } } ``` The `__new__` constructor of a native base class is called implicitly when creating a new instance from Python. Be sure to accept arguments in the `#[new]` method that you want the base class to get, even if they are not used in that `fn`: ```rust # #[allow(dead_code)] # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] { # use pyo3::prelude::*; use pyo3::types::PyDict; #[pyclass(extends=PyDict)] struct MyDict { private: i32, } #[pymethods] impl MyDict { #[new] #[pyo3(signature = (*args, **kwargs))] fn new(args: &Bound<'_, PyAny>, kwargs: Option<&Bound<'_, PyAny>>) -> Self { Self { private: 0 } } // some custom methods that use `private` here... } # Python::attach(|py| { # let cls = py.get_type::(); # pyo3::py_run!(py, cls, "cls(a=1, b=2)") # }); # } ``` Here, the `args` and `kwargs` allow creating instances of the subclass passing initial items, such as `MyDict(item_sequence)` or `MyDict(a=1, b=2)`. ## Object properties PyO3 supports two ways to add properties to your `#[pyclass]`: - For simple struct fields with no side effects, a `#[pyo3(get, set)]` attribute can be added directly to the field definition in the `#[pyclass]`. - For properties which require computation you can define `#[getter]`, `#[setter]` and `#[deleter]` functions in the [`#[pymethods]`](#instance-methods) block. We'll cover each of these in the following sections. ### Object properties using `#[pyo3(get, set)]` For simple cases where a member variable is just read and written with no side effects, you can declare getters and setters in your `#[pyclass]` field definition using the `pyo3` attribute, like in the example below: ```rust # use pyo3::prelude::*; # #[allow(dead_code)] #[pyclass] struct MyClass { #[pyo3(get, set)] num: i32, } ``` The above would make the `num` field available for reading and writing as a `self.num` Python property. To expose the property with a different name to the field, specify this alongside the rest of the options, e.g. `#[pyo3(get, set, name = "custom_name")]`. Properties can be readonly or writeonly by using just `#[pyo3(get)]` or `#[pyo3(set)]` respectively. To use these annotations, your field type must implement some conversion traits: - For `get` the field type `T` must implement either `&T: IntoPyObject` or `T: IntoPyObject + Clone`. - For `set` the field type must implement `FromPyObject`. For example, implementations of those traits are provided for the `Cell` type, if the inner type also implements the trait. This means you can use `#[pyo3(get, set)]` on fields wrapped in a `Cell`. ### Object properties using `#[getter]` and `#[setter]` For cases which don't satisfy the `#[pyo3(get, set)]` trait requirements, or need side effects, descriptor methods can be defined in a `#[pymethods]` `impl` block. This is done using the `#[getter]` and `#[setter]` attributes, like in the example below: ```rust # use pyo3::prelude::*; #[pyclass] struct MyClass { num: i32, } #[pymethods] impl MyClass { #[getter] fn num(&self) -> PyResult { Ok(self.num) } #[setter] fn set_num(&mut self, num: i32) { self.num = num; } } ``` The `#[deleter]` attribute is also available to delete the property. This corresponds to the `del my_object.my_property` python operation. ```rust # use pyo3::prelude::*; # use pyo3::exceptions::PyAttributeError; #[pyclass] struct MyClass { num: Option, } #[pymethods] impl MyClass { #[getter] fn num(&self) -> PyResult { self.num.ok_or_else(|| PyAttributeError::new_err("num has been deleted")) } #[deleter] fn delete_num(&mut self) { self.num = None; } } ``` A getter, setter or deleters's function name is used as the property name by default. There are several ways how to override the name. If a function name starts with `get_`, `set_` or `delete_` for getter, setter or deleter, respectively, the descriptor name becomes the function name with this prefix removed. This is also useful in case of Rust keywords like `type` ([raw identifiers](https://doc.rust-lang.org/edition-guide/rust-2018/module-system/raw-identifiers.html) can be used since Rust 2018). ```rust # use pyo3::prelude::*; # #[pyclass] # struct MyClass { # num: i32, # } #[pymethods] impl MyClass { #[getter] fn get_num(&self) -> PyResult { Ok(self.num) } #[setter] fn set_num(&mut self, value: i32) -> PyResult<()> { self.num = value; Ok(()) } } ``` In this case, a property `num` is defined and available from Python code as `self.num`. The `#[getter]`, `#[setter]` and `#[deleter]` attributes accept one parameter. If this parameter is specified, it is used as the property name, i.e. ```rust # use pyo3::prelude::*; # #[pyclass] # struct MyClass { # num: i32, # } #[pymethods] impl MyClass { #[getter(number)] fn num(&self) -> PyResult { Ok(self.num) } #[setter(number)] fn set_num(&mut self, value: i32) -> PyResult<()> { self.num = value; Ok(()) } } ``` In this case, the property `number` is defined and available from Python code as `self.number`. ## Instance methods To define a Python compatible method, an `impl` block for your struct has to be annotated with the `#[pymethods]` attribute. PyO3 generates Python compatible wrappers for all functions in this block with some variations, like descriptors, class method static methods, etc. Since Rust allows any number of `impl` blocks, you can easily split methods between those accessible to Python (and Rust) and those accessible only to Rust. However to have multiple `#[pymethods]`-annotated `impl` blocks for the same struct you must enable the [`multiple-pymethods`] feature of PyO3. ```rust # use pyo3::prelude::*; # #[pyclass] # struct MyClass { # num: i32, # } #[pymethods] impl MyClass { fn method1(&self) -> PyResult { Ok(10) } fn set_method(&mut self, value: i32) -> PyResult<()> { self.num = value; Ok(()) } } ``` Both `&self` and `&mut self` can be used, due to the use of [runtime borrow checking](#bound-and-interior-mutability). The return type must be `PyResult` or `T` for some `T` that implements `IntoPyObject`; the latter is allowed if the method cannot raise Python exceptions. A `Python` parameter can be specified as part of method signature, in this case the `py` argument gets injected by the method wrapper, e.g. ```rust # use pyo3::prelude::*; # #[pyclass] # struct MyClass { # #[allow(dead_code)] # num: i32, # } #[pymethods] impl MyClass { fn method2(&self, py: Python<'_>) -> PyResult { Ok(10) } } ``` From the Python perspective, the `method2` in this example does not accept any arguments. ## Class methods To create a class method for a custom class, the method needs to be annotated with the `#[classmethod]` attribute. This is the equivalent of the Python decorator `@classmethod`. ```rust # use pyo3::prelude::*; # use pyo3::types::PyType; # #[pyclass] # struct MyClass { # #[allow(dead_code)] # num: i32, # } #[pymethods] impl MyClass { #[classmethod] fn cls_method(cls: &Bound<'_, PyType>) -> PyResult { Ok(10) } } ``` Declares a class method callable from Python. - The first parameter is the type object of the class on which the method is called. This may be the type object of a derived class. - The first parameter implicitly has type `&Bound<'_, PyType>`. - For details on `parameter-list`, see the documentation of `Method arguments` section. - The return type must be `PyResult` or `T` for some `T` that implements `IntoPyObject`. ### Constructors which accept a class argument To create a constructor which takes a positional class argument, you can combine the `#[classmethod]` and `#[new]` modifiers: ```rust # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::PyType; # #[pyclass] # struct BaseClass(Py); # #[pymethods] impl BaseClass { #[new] #[classmethod] fn py_new(cls: &Bound<'_, PyType>) -> PyResult { // Get an abstract attribute (presumably) declared on a subclass of this class. let subclass_attr: Bound<'_, PyAny> = cls.getattr("a_class_attr")?; Ok(Self(subclass_attr.unbind())) } } ``` ## Static methods To create a static method for a custom class, the method needs to be annotated with the `#[staticmethod]` attribute. The return type must be `T` or `PyResult` for some `T` that implements `IntoPyObject`. ```rust # use pyo3::prelude::*; # #[pyclass] # struct MyClass { # #[allow(dead_code)] # num: i32, # } #[pymethods] impl MyClass { #[staticmethod] fn static_method(param1: i32, param2: &str) -> PyResult { Ok(10) } } ``` ## Class attributes To create a class attribute (also called [class variable][classattr]), a method without any arguments can be annotated with the `#[classattr]` attribute. ```rust,no_run # use pyo3::prelude::*; # #[pyclass] # struct MyClass {} #[pymethods] impl MyClass { #[classattr] fn my_attribute() -> String { "hello".to_string() } } Python::attach(|py| { let my_class = py.get_type::(); pyo3::py_run!(py, my_class, "assert my_class.my_attribute == 'hello'") }); ``` > [!NOTE] > If the method has a `Result` return type and returns an `Err`, PyO3 will panic during class creation. > [!NOTE] > `#[classattr]` does not work with [`#[pyo3(warn(...))]`](./function.md#warn) attribute. If the class attribute is defined with `const` code only, one can also annotate associated constants: ```rust,no_run # use pyo3::prelude::*; # #[pyclass] # struct MyClass {} #[pymethods] impl MyClass { #[classattr] const MY_CONST_ATTRIBUTE: &'static str = "foobar"; } ``` ## Classes as function arguments Class objects can be used as arguments to `#[pyfunction]`s and `#[pymethods]` in the same way as the self parameters of instance methods, i.e. they can be passed as: - `Py` or `Bound<'py, T>` smart pointers to the class Python object, - `&T` or `&mut T` references to the Rust data contained in the Python object, or - `PyRef` and `PyRefMut` reference wrappers. Examples of each of these below: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyclass] struct MyClass { my_field: i32, } // Take a reference to Rust data when the Python object is irrelevant. #[pyfunction] fn increment_field(my_class: &mut MyClass) { my_class.my_field += 1; } // Take a reference wrapper when borrowing should be automatic, // but access to the Python object is still needed #[pyfunction] fn print_field_and_return_me(my_class: PyRef<'_, MyClass>) -> PyRef<'_, MyClass> { println!("{}", my_class.my_field); my_class } // Take (a reference to) a Python object smart pointer when borrowing needs to be managed manually. #[pyfunction] fn increment_then_print_field(my_class: &Bound<'_, MyClass>) { my_class.borrow_mut().my_field += 1; println!("{}", my_class.borrow().my_field); } // When the Python object smart pointer needs to be stored elsewhere prefer `Py` over `Bound<'py, T>` // to avoid the lifetime restrictions. #[pyfunction] fn print_is_none(my_class: Py, py: Python<'_>) { println!("{}", my_class.is_none(py)); } ``` Classes can also be passed by value if they can be cloned, i.e. they automatically implement `FromPyObject` if they implement `Clone`, e.g. via `#[derive(Clone)]`: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyclass(from_py_object)] #[derive(Clone)] struct MyClass { my_field: Box, } #[pyfunction] fn disassemble_clone(my_class: MyClass) { let MyClass { mut my_field } = my_class; *my_field += 1; } ``` Note that `#[derive(FromPyObject)]` on a class is usually not useful as it tries to construct a new Rust value by filling in the fields by looking up attributes of any given Python value. ## Method arguments Similar to `#[pyfunction]`, the `#[pyo3(signature = (...))]` attribute can be used to specify the way that `#[pymethods]` accept arguments. Consult the documentation for [`function signatures`](./function/signature.md) to see the parameters this attribute accepts. The following example defines a class `MyClass` with a method `method`. This method has a signature that sets default values for `num` and `name`, and indicates that `py_args` should collect all extra positional arguments and `py_kwargs` all extra keyword arguments: ```rust,no_run # use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; # # #[pyclass] # struct MyClass { # num: i32, # } #[pymethods] impl MyClass { #[new] #[pyo3(signature = (num=-1))] fn new(num: i32) -> Self { MyClass { num } } #[pyo3(signature = (num=10, *py_args, name="Hello", **py_kwargs))] fn method( &mut self, num: i32, py_args: &Bound<'_, PyTuple>, name: &str, py_kwargs: Option<&Bound<'_, PyDict>>, ) -> String { let num_before = self.num; self.num = num; format!( "num={} (was previously={}), py_args={:?}, name={}, py_kwargs={:?} ", num, num_before, py_args, name, py_kwargs, ) } } ``` In Python, this might be used like: ```python >>> import mymodule >>> mc = mymodule.MyClass() >>> print(mc.method(44, False, "World", 666, x=44, y=55)) py_args=('World', 666), py_kwargs=Some({'x': 44, 'y': 55}), name=Hello, num=44, num_before=-1 >>> print(mc.method(num=-1, name="World")) py_args=(), py_kwargs=None, name=World, num=-1, num_before=44 ``` The [`#[pyo3(text_signature = "...")`](./function/signature.md#overriding-the-generated-signature) option for `#[pyfunction]` also works for `#[pymethods]`. ```rust # #![allow(dead_code)] use pyo3::prelude::*; use pyo3::types::PyType; #[pyclass] struct MyClass {} #[pymethods] impl MyClass { #[new] #[pyo3(text_signature = "(c, d)")] fn new(c: i32, d: &str) -> Self { Self {} } // the self argument should be written $self #[pyo3(text_signature = "($self, e, f)")] fn my_method(&self, e: i32, f: i32) -> i32 { e + f } // similarly for classmethod arguments, use $cls #[classmethod] #[pyo3(text_signature = "($cls, e, f)")] fn my_class_method(cls: &Bound<'_, PyType>, e: i32, f: i32) -> i32 { e + f } #[staticmethod] #[pyo3(text_signature = "(e, f)")] fn my_static_method(e: i32, f: i32) -> i32 { e + f } } # # fn main() -> PyResult<()> { # Python::attach(|py| { # let inspect = PyModule::import(py, "inspect")?.getattr("signature")?; # let module = PyModule::new(py, "my_module")?; # module.add_class::()?; # let class = module.getattr("MyClass")?; # # if cfg!(not(Py_LIMITED_API)) || py.version_info() >= (3, 10) { # let doc: String = class.getattr("__doc__")?.extract()?; # assert_eq!(doc, ""); # # let sig: String = inspect # .call1((&class,))? # .call_method0("__str__")? # .extract()?; # assert_eq!(sig, "(c, d)"); # } else { # let doc: String = class.getattr("__doc__")?.extract()?; # assert_eq!(doc, ""); # # inspect.call1((&class,)).expect_err("`text_signature` on classes is not compatible with compilation in `abi3` mode until Python 3.10 or greater"); # } # # { # let method = class.getattr("my_method")?; # # assert!(method.getattr("__doc__")?.is_none()); # # let sig: String = inspect # .call1((method,))? # .call_method0("__str__")? # .extract()?; # assert_eq!(sig, "(self, /, e, f)"); # } # # { # let method = class.getattr("my_class_method")?; # # assert!(method.getattr("__doc__")?.is_none()); # # let sig: String = inspect # .call1((method,))? # .call_method0("__str__")? # .extract()?; # assert_eq!(sig, "(e, f)"); // inspect.signature skips the $cls arg # } # # { # let method = class.getattr("my_static_method")?; # # assert!(method.getattr("__doc__")?.is_none()); # # let sig: String = inspect # .call1((method,))? # .call_method0("__str__")? # .extract()?; # assert_eq!(sig, "(e, f)"); # } # # Ok(()) # }) # } ``` Note that `text_signature` on `#[new]` is not compatible with compilation in `abi3` mode until Python 3.10 or greater. ### Method receivers and lifetime elision PyO3 supports writing instance methods using the normal method receivers for shared `&self` and unique `&mut self` references. This interacts with [lifetime elision][lifetime-elision] insofar as the lifetime of a such a receiver is assigned to all elided output lifetime parameters. This is a good default for general Rust code where return values are more likely to borrow from the receiver than from the other arguments, if they contain any lifetimes at all. However, when returning bound references `Bound<'py, T>` in PyO3-based code, the Python lifetime `'py` should usually be derived from a `py: Python<'py>` token passed as an argument instead of the receiver. Specifically, signatures like ```rust,ignore fn frobnicate(&self, py: Python) -> Bound; ``` will not work as they are inferred as ```rust,ignore fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'a, Foo>; ``` instead of the intended ```rust,ignore fn frobnicate<'a, 'py>(&'a self, py: Python<'py>) -> Bound<'py, Foo>; ``` and should usually be written as ```rust,ignore fn frobnicate<'py>(&self, py: Python<'py>) -> Bound<'py, Foo>; ``` The same problem does not exist for `#[pyfunction]`s as the special case for receiver lifetimes does not apply and indeed a signature like ```rust,ignore fn frobnicate(bar: &Bar, py: Python) -> Bound; ``` will yield compiler error [E0106 "missing lifetime specifier"][compiler-error-e0106]. ## `#[pyclass]` enums Enum support in PyO3 comes in two flavors, depending on what kind of variants the enum has: simple and complex. ### Simple enums A simple enum (a.k.a. C-like enum) has only unit variants. PyO3 adds a class attribute for each variant, so you can access them in Python without defining `#[new]`. PyO3 also provides default implementations of `__richcmp__` and `__int__`, so they can be compared using `==`: ```rust # use pyo3::prelude::*; #[pyclass(eq, eq_int)] #[derive(PartialEq)] enum MyEnum { Variant, OtherVariant, } Python::attach(|py| { let x = Py::new(py, MyEnum::Variant).unwrap(); let y = Py::new(py, MyEnum::OtherVariant).unwrap(); let cls = py.get_type::(); pyo3::py_run!(py, x y cls, r#" assert x == cls.Variant assert y == cls.OtherVariant assert x != y "#) }) ``` You can also convert your simple enums into `int`: ```rust # use pyo3::prelude::*; #[pyclass(eq, eq_int)] #[derive(PartialEq)] enum MyEnum { Variant, OtherVariant = 10, } Python::attach(|py| { let cls = py.get_type::(); let x = MyEnum::Variant as i32; // The exact value is assigned by the compiler. pyo3::py_run!(py, cls x, r#" assert int(cls.Variant) == x assert int(cls.OtherVariant) == 10 "#) }) ``` PyO3 also provides `__repr__` for enums: ```rust # use pyo3::prelude::*; #[pyclass(eq, eq_int)] #[derive(PartialEq)] enum MyEnum{ Variant, OtherVariant, } Python::attach(|py| { let cls = py.get_type::(); let x = Py::new(py, MyEnum::Variant).unwrap(); pyo3::py_run!(py, cls x, r#" assert repr(x) == 'MyEnum.Variant' assert repr(cls.OtherVariant) == 'MyEnum.OtherVariant' "#) }) ``` All methods defined by PyO3 can be overridden. For example here's how you override `__repr__`: ```rust # use pyo3::prelude::*; #[pyclass(eq, eq_int)] #[derive(PartialEq)] enum MyEnum { Answer = 42, } #[pymethods] impl MyEnum { fn __repr__(&self) -> &'static str { "42" } } Python::attach(|py| { let cls = py.get_type::(); pyo3::py_run!(py, cls, "assert repr(cls.Answer) == '42'") }) ``` Enums and their variants can also be renamed using `#[pyo3(name)]`. ```rust # use pyo3::prelude::*; #[pyclass(eq, eq_int, name = "RenamedEnum")] #[derive(PartialEq)] enum MyEnum { #[pyo3(name = "UPPERCASE")] Variant, } Python::attach(|py| { let x = Py::new(py, MyEnum::Variant).unwrap(); let cls = py.get_type::(); pyo3::py_run!(py, x cls, r#" assert repr(x) == 'RenamedEnum.UPPERCASE' assert x == cls.UPPERCASE "#) }) ``` Ordering of enum variants is optionally added using `#[pyo3(ord)]`. *Note: Implementation of the `PartialOrd` trait is required when passing the `ord` argument.* *If not implemented, a compile time error is raised.* ```rust # use pyo3::prelude::*; #[pyclass(eq, ord)] #[derive(PartialEq, PartialOrd)] enum MyEnum{ A, B, C, } Python::attach(|py| { let cls = py.get_type::(); let a = Py::new(py, MyEnum::A).unwrap(); let b = Py::new(py, MyEnum::B).unwrap(); let c = Py::new(py, MyEnum::C).unwrap(); pyo3::py_run!(py, cls a b c, r#" assert (a < b) == True assert (c <= b) == False assert (c > a) == True "#) }) ``` You may not use enums as a base class or let enums inherit from other classes. ```rust,compile_fail # use pyo3::prelude::*; #[pyclass(subclass)] enum BadBase { Var1, } ``` ```rust,compile_fail # use pyo3::prelude::*; #[pyclass(subclass)] struct Base; #[pyclass(extends=Base)] enum BadSubclass { Var1, } ``` `#[pyclass]` enums are currently not interoperable with `IntEnum` in Python. ### Complex enums An enum is complex if it has any non-unit (struct or tuple) variants. PyO3 supports only struct and tuple variants in a complex enum. Unit variants aren't supported at present (the recommendation is to use an empty tuple enum instead). PyO3 adds a class attribute for each variant, which may be used to construct values and in match patterns. PyO3 also provides getter methods for all fields of each variant. ```rust # use pyo3::prelude::*; #[pyclass] enum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 }, RegularPolygon(u32, f64), Nothing { }, } # #[cfg(Py_3_10)] Python::attach(|py| { let circle = Shape::Circle { radius: 10.0 }.into_pyobject(py)?; let square = Shape::RegularPolygon(4, 10.0).into_pyobject(py)?; let cls = py.get_type::(); pyo3::py_run!(py, circle square cls, r#" assert isinstance(circle, cls) assert isinstance(circle, cls.Circle) assert circle.radius == 10.0 assert isinstance(square, cls) assert isinstance(square, cls.RegularPolygon) assert square[0] == 4 # Gets _0 field assert square[1] == 10.0 # Gets _1 field def count_vertices(cls, shape): match shape: case cls.Circle(): return 0 case cls.Rectangle(): return 4 case cls.RegularPolygon(n): return n case cls.Nothing(): return 0 assert count_vertices(cls, circle) == 0 assert count_vertices(cls, square) == 4 "#); # Ok::<_, PyErr>(()) }) # .unwrap(); ``` WARNING: `Py::new` and `.into_pyobject` are currently inconsistent. Note how the constructed value is *not* an instance of the specific variant. For this reason, constructing values is only recommended using `.into_pyobject`. ```rust # use pyo3::prelude::*; #[pyclass] enum MyEnum { Variant { i: i32 }, } Python::attach(|py| { let x = Py::new(py, MyEnum::Variant { i: 42 }).unwrap(); let cls = py.get_type::(); pyo3::py_run!(py, x cls, r#" assert isinstance(x, cls) assert not isinstance(x, cls.Variant) "#) }) ``` The constructor of each generated class can be customized using the `#[pyo3(constructor = (...))]` attribute. This uses the same syntax as the [`#[pyo3(signature = (...))]`](function/signature.md) attribute on function and methods and supports the same options. To apply this attribute simply place it on top of a variant in a `#[pyclass]` complex enum as shown below: ```rust # use pyo3::prelude::*; #[pyclass] enum Shape { #[pyo3(constructor = (radius=1.0))] Circle { radius: f64 }, #[pyo3(constructor = (*, width, height))] Rectangle { width: f64, height: f64 }, #[pyo3(constructor = (side_count, radius=1.0))] RegularPolygon { side_count: u32, radius: f64 }, Nothing { }, } # #[cfg(Py_3_10)] Python::attach(|py| { let cls = py.get_type::(); pyo3::py_run!(py, cls, r#" circle = cls.Circle() assert isinstance(circle, cls) assert isinstance(circle, cls.Circle) assert circle.radius == 1.0 square = cls.Rectangle(width = 1, height = 1) assert isinstance(square, cls) assert isinstance(square, cls.Rectangle) assert square.width == 1 assert square.height == 1 hexagon = cls.RegularPolygon(6) assert isinstance(hexagon, cls) assert isinstance(hexagon, cls.RegularPolygon) assert hexagon.side_count == 6 assert hexagon.radius == 1 "#) }) ``` ## Implementation details The `#[pyclass]` macros rely on a lot of conditional code generation: each `#[pyclass]` can optionally have a `#[pymethods]` block. To support this flexibility the `#[pyclass]` macro expands to a blob of boilerplate code which sets up the structure for ["dtolnay specialization"](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md). This implementation pattern enables the Rust compiler to use `#[pymethods]` implementations when they are present, and fall back to default (empty) definitions when they are not. This simple technique works for the case when there is zero or one implementations. To support multiple `#[pymethods]` for a `#[pyclass]` (in the [`multiple-pymethods`] feature), a registry mechanism provided by the [`inventory`](https://github.com/dtolnay/inventory) crate is used instead. This collects `impl`s at library load time, but isn't supported on all platforms. See [inventory: how it works](https://github.com/dtolnay/inventory#how-it-works) for more details. The `#[pyclass]` macro expands to roughly the code seen below. The `PyClassImplCollector` is the type used internally by PyO3 for dtolnay specialization: ```rust # #[cfg(not(feature = "multiple-pymethods"))] { # use pyo3::prelude::*; // Note: the implementation differs slightly with the `multiple-pymethods` feature enabled. # #[allow(dead_code)] struct MyClass { # #[allow(dead_code)] num: i32, } impl pyo3::types::DerefToPyAny for MyClass {} unsafe impl pyo3::type_object::PyTypeInfo for MyClass { const NAME: &'static str = "MyClass"; const MODULE: ::std::option::Option<&'static str> = ::std::option::Option::None; #[inline] fn type_object_raw(py: pyo3::Python<'_>) -> *mut pyo3::ffi::PyTypeObject { ::lazy_type_object() .get_or_try_init(py) .unwrap_or_else(|e| pyo3::impl_::pyclass::type_object_init_failed( py, e, ::NAME )) .as_type_ptr() } } impl pyo3::PyClass for MyClass { const NAME: &str = "MyClass"; type Frozen = pyo3::pyclass::boolean_struct::False; } impl pyo3::impl_::pyclass::PyClassImpl for MyClass { const MODULE: Option<&str> = None; const IS_BASETYPE: bool = false; const IS_SUBCLASS: bool = false; const IS_MAPPING: bool = false; const IS_SEQUENCE: bool = false; type Layout = ::Layout; type BaseType = PyAny; type ThreadChecker = pyo3::impl_::pyclass::NoopThreadChecker; type PyClassMutability = <::PyClassMutability as pyo3::impl_::pycell::PyClassMutability>::MutableChild; type Dict = pyo3::impl_::pyclass::PyClassDummySlot; type WeakRef = pyo3::impl_::pyclass::PyClassDummySlot; type BaseNativeType = pyo3::PyAny; const RAW_DOC: &'static std::ffi::CStr = c"..."; const DOC: &'static std::ffi::CStr = c"..."; fn items_iter() -> pyo3::impl_::pyclass::PyClassItemsIter { use pyo3::impl_::pyclass::*; let collector = PyClassImplCollector::::new(); static INTRINSIC_ITEMS: PyClassItems = PyClassItems { slots: &[], methods: &[] }; PyClassItemsIter::new(&INTRINSIC_ITEMS, collector.py_methods()) } fn lazy_type_object() -> &'static pyo3::impl_::pyclass::LazyTypeObject { use pyo3::impl_::pyclass::LazyTypeObject; static TYPE_OBJECT: LazyTypeObject = LazyTypeObject::new(); &TYPE_OBJECT } } # Python::attach(|py| { # let cls = py.get_type::(); # pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") # }); # } ``` [`PyTypeInfo`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeInfo.html [`Py`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html [`Bound<'py, T>`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html [`PyClass`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/trait.PyClass.html [`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html [`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html [`PyClassInitializer`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass_init/struct.PyClassInitializer.html [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html [classattr]: https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables [`multiple-pymethods`]: features.md#multiple-pymethods [lifetime-elision]: https://doc.rust-lang.org/reference/lifetime-elision.html [compiler-error-e0106]: https://doc.rust-lang.org/error_codes/E0106.html ================================================ FILE: guide/src/contributing.md ================================================ {{#include ../../Contributing.md}} ================================================ FILE: guide/src/conversions/tables.md ================================================ # Mapping of Rust types to Python types When writing functions callable from Python (such as a `#[pyfunction]` or in a `#[pymethods]` block), the trait `FromPyObject` is required for function arguments, and `IntoPyObject` is required for function return values. Consult the tables in the following section to find the Rust types provided by PyO3 which implement these traits. ## Argument Types When accepting a function argument, it is possible to either use Rust library types or PyO3's Python-native types. (See the next section for discussion on when to use each.) The table below contains the Python type and the corresponding function argument types that will accept them: | Python | Rust | Rust (Python-native) | | ------------- |:-------------------------------:|:--------------------:| | `object` | - | `PyAny` | | `str` | `String`, `Cow`, `&str`, `char`, `OsString`, `PathBuf`, `Path` | `PyString` | | `bytes` | `Vec`, `&[u8]`, `Cow<[u8]>` | `PyBytes` | | `bool` | `bool` | `PyBool` | | `int` | `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`, `num_bigint::BigInt`[^1], `num_bigint::BigUint`[^1] | `PyInt` | | `float` | `f32`, `f64`, `ordered_float::NotNan`[^2], `ordered_float::OrderedFloat`[^2] | `PyFloat` | | `complex` | `num_complex::Complex`[^3] | `PyComplex` | | `fractions.Fraction`| `num_rational::Ratio`[^4] | - | | `list[T]` | `Vec` | `PyList` | | `dict[K, V]` | `HashMap`, `BTreeMap`, `hashbrown::HashMap`[^5], `indexmap::IndexMap`[^6] | `PyDict` | | `tuple[T, U]` | `(T, U)`, `Vec` | `PyTuple` | | `set[T]` | `HashSet`, `BTreeSet`, `hashbrown::HashSet`[^5] | `PySet` | | `frozenset[T]` | `HashSet`, `BTreeSet`, `hashbrown::HashSet`[^5] | `PyFrozenSet` | | `bytearray` | `Vec`, `Cow<[u8]>` | `PyByteArray` | | `slice` | - | `PySlice` | | `type` | - | `PyType` | | `module` | - | `PyModule` | | `collections.abc.Buffer` | - | `PyBuffer` | | `datetime.datetime` | `SystemTime`, `chrono::DateTime`[^7], `chrono::NaiveDateTime`[^7] | `PyDateTime` | | `datetime.date` | `chrono::NaiveDate`[^7] | `PyDate` | | `datetime.time` | `chrono::NaiveTime`[^7] | `PyTime` | | `datetime.tzinfo` | `chrono::FixedOffset`[^7], `chrono::Utc`[^7], `chrono_tz::TimeZone`[^8] | `PyTzInfo` | | `datetime.timedelta` | `Duration`, `chrono::Duration`[^7] | `PyDelta` | | `decimal.Decimal` | `rust_decimal::Decimal`[^9] | - | | `decimal.Decimal` | `bigdecimal::BigDecimal`[^10] | - | | `ipaddress.IPv4Address` | `std::net::IpAddr`, `std::net::Ipv4Addr` | - | | `ipaddress.IPv6Address` | `std::net::IpAddr`, `std::net::Ipv6Addr` | - | | `os.PathLike` | `PathBuf`, `Path` | `PyString` | | `pathlib.Path` | `PathBuf`, `Path` | `PyString` | | `typing.Optional[T]` | `Option` | - | | `typing.Sequence[T]` | `Vec` | `PySequence` | | `typing.Mapping[K, V]` | `HashMap`, `BTreeMap`, `hashbrown::HashMap`[^5], `indexmap::IndexMap`[^6] | `&PyMapping` | | `typing.Iterator[Any]` | - | `PyIterator` | | `typing.Union[...]` | See [`#[derive(FromPyObject)]`](traits.md#deriving-frompyobject-for-enums) | - | It is also worth remembering the following special types: | What | Description | | ---------------- | ------------------------------------- | | `Python<'py>` | A token used to prove attachment to the Python interpreter. | | `Bound<'py, T>` | A Python object with a lifetime which binds it to the attachment to the Python interpreter. This provides access to most of PyO3's APIs. | | `Py` | A Python object not connected to any lifetime of attachment to the Python interpreter. This can be sent to other threads. | | `PyRef` | A `#[pyclass]` borrowed immutably. | | `PyRefMut` | A `#[pyclass]` borrowed mutably. | For more detail on accepting `#[pyclass]` values as function arguments, see [the section of this guide on Python Classes](../class.md). ### Using Rust library types vs Python-native types Using Rust library types as function arguments will incur a conversion cost compared to using the Python-native types. Using the Python-native types is almost zero-cost (they just require a type check similar to the Python builtin function `isinstance()`). However, once that conversion cost has been paid, the Rust standard library types offer a number of benefits: - You can write functionality in native-speed Rust code (free of Python's runtime costs). - You get better interoperability with the rest of the Rust ecosystem. - You can use `Python::detach` to detach from the interpreter and let other Python threads make progress while your Rust code is executing. - You also benefit from stricter type checking. For example you can specify `Vec`, which will only accept a Python `list` containing integers. The Python-native equivalent, `&PyList`, would accept a Python `list` containing Python objects of any type. For most PyO3 usage the conversion cost is worth paying to get these benefits. As always, if you're not sure it's worth it in your case, benchmark it! ## Returning Rust values to Python When returning values from functions callable from Python, [PyO3's smart pointers](../types.md#pyo3s-smart-pointers) (`Py`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`) can be used with zero cost. Because `Bound<'py, T>` and `Borrowed<'a, 'py, T>` have lifetime parameters, the Rust compiler may ask for lifetime annotations to be added to your function. See the [section of the guide dedicated to this](../types.md#function-argument-lifetimes). If your function is fallible, it should return `PyResult` or `Result` where `E` implements `From for PyErr`. This will raise a `Python` exception if the `Err` variant is returned. Finally, the following Rust types are also able to convert to Python as return values: | Rust type | Resulting Python Type | | ------------- |:-------------------------------:| | `String` | `str` | | `&str` | `str` | | `bool` | `bool` | | Any integer type (`i32`, `u32`, `usize`, etc) | `int` | | `f32`, `f64` | `float` | | `Option` | `Optional[T]` | | `(T, U)` | `Tuple[T, U]` | | `Vec` | `List[T]` | | `Cow<[u8]>` | `bytes` | | `HashMap` | `Dict[K, V]` | | `BTreeMap` | `Dict[K, V]` | | `HashSet` | `Set[T]` | | `BTreeSet` | `Set[T]` | | `Py` | `T` | | `Bound` | `T` | | `PyRef` | `T` | | `PyRefMut` | `T` | [^1]: Requires the `num-bigint` optional feature. [^2]: Requires the `ordered-float` optional feature. [^3]: Requires the `num-complex` optional feature. [^4]: Requires the `num-rational` optional feature. [^5]: Requires the `hashbrown` optional feature. [^6]: Requires the `indexmap` optional feature. [^7]: Requires the `chrono` (and maybe `chrono-local`) optional feature(s). [^8]: Requires the `chrono-tz` optional feature. [^9]: Requires the `rust_decimal` optional feature. [^10]: Requires the `bigdecimal` optional feature. ================================================ FILE: guide/src/conversions/traits.md ================================================ # Conversion traits PyO3 provides some handy traits to convert between Python types and Rust types. ## `.extract()` and the `FromPyObject` trait The easiest way to convert a Python object to a Rust value is using `.extract()`. It returns a `PyResult` with a type error if the conversion fails, so usually you will use something like ```rust # use pyo3::prelude::*; # use pyo3::types::PyList; # fn main() -> PyResult<()> { # Python::attach(|py| { # let list = PyList::new(py, b"foo")?; let v: Vec = list.extract()?; # assert_eq!(&v, &[102, 111, 111]); # Ok(()) # }) # } ``` This method is available for many Python object types, and can produce a wide variety of Rust types, which you can check out in the implementor list of [`FromPyObject`]. [`FromPyObject`] is also implemented for your own Rust types wrapped as Python objects (see [the chapter about classes](../class.md)). There, in order to both be able to operate on mutable references *and* satisfy Rust's rules of non-aliasing mutable references, you have to extract the PyO3 reference wrappers [`PyRef`] and [`PyRefMut`]. They work like the reference wrappers of `std::cell::RefCell` and ensure (at runtime) that Rust borrows are allowed. ### Deriving [`FromPyObject`] [`FromPyObject`] can be automatically derived for many kinds of structs and enums if the member types themselves implement `FromPyObject`. This even includes members with a generic type `T: FromPyObject`. Derivation for empty enums, enum variants and structs is not supported. ### Deriving [`FromPyObject`] for structs The derivation generates code that will attempt to access the attribute `my_string` on the Python object, i.e. `obj.getattr("my_string")`, and call `extract()` on the attribute. ```rust use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyStruct { my_string: String, } # # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let module = PyModule::from_code( # py, # c"class Foo: # def __init__(self): # self.my_string = 'test'", # c"", # c"", # )?; # # let class = module.getattr("Foo")?; # let instance = class.call0()?; # let rustystruct: RustyStruct = instance.extract()?; # assert_eq!(rustystruct.my_string, "test"); # Ok(()) # }) # } ``` By setting the `#[pyo3(item)]` attribute on the field, PyO3 will attempt to extract the value by calling the `get_item` method on the Python object. ```rust use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyStruct { #[pyo3(item)] my_string: String, } # # use pyo3::types::PyDict; # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let dict = PyDict::new(py); # dict.set_item("my_string", "test")?; # # let rustystruct: RustyStruct = dict.extract()?; # assert_eq!(rustystruct.my_string, "test"); # Ok(()) # }) # } ``` The argument passed to `getattr` and `get_item` can also be configured: ```rust use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyStruct { #[pyo3(item("key"))] string_in_mapping: String, #[pyo3(attribute("name"))] string_attr: String, } # # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let module = PyModule::from_code( # py, # c"class Foo(dict): # def __init__(self): # self.name = 'test' # self['key'] = 'test2'", # c"", # c"", # )?; # # let class = module.getattr("Foo")?; # let instance = class.call0()?; # let rustystruct: RustyStruct = instance.extract()?; # assert_eq!(rustystruct.string_attr, "test"); # assert_eq!(rustystruct.string_in_mapping, "test2"); # # Ok(()) # }) # } ``` This tries to extract `string_attr` from the attribute `name` and `string_in_mapping` from a mapping with the key `"key"`. The arguments for `attribute` are restricted to non-empty string literals while `item` can take any valid literal that implements `ToBorrowedObject`. You can use `#[pyo3(from_item_all)]` on a struct to extract every field with `get_item` method. In this case, you can't use `#[pyo3(attribute)]` or barely use `#[pyo3(item)]` on any field. However, using `#[pyo3(item("key"))]` to specify the key for a field is still allowed. ```rust use pyo3::prelude::*; #[derive(FromPyObject)] #[pyo3(from_item_all)] struct RustyStruct { foo: String, bar: String, #[pyo3(item("foobar"))] baz: String, } # # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let py_dict = py.eval(c"{'foo': 'foo', 'bar': 'bar', 'foobar': 'foobar'}", None, None)?; # let rustystruct: RustyStruct = py_dict.extract()?; # assert_eq!(rustystruct.foo, "foo"); # assert_eq!(rustystruct.bar, "bar"); # assert_eq!(rustystruct.baz, "foobar"); # # Ok(()) # }) # } ``` ### Deriving [`FromPyObject`] for tuple structs Tuple structs are also supported but do not allow customizing the extraction. The input is always assumed to be a Python tuple with the same length as the Rust type, the `n`th field is extracted from the `n`th item in the Python tuple. ```rust use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyTuple(String, String); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let tuple = PyTuple::new(py, vec!["test", "test2"])?; # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!(rustytuple.0, "test"); # assert_eq!(rustytuple.1, "test2"); # # Ok(()) # }) # } ``` Tuple structs with a single field are treated as wrapper types which are described in the following section. To override this behaviour and ensure that the input is in fact a tuple, specify the struct as ```rust use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyTuple((String,)); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let tuple = PyTuple::new(py, vec!["test"])?; # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!((rustytuple.0).0, "test"); # # Ok(()) # }) # } ``` ### Deriving [`FromPyObject`] for wrapper types The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants with a single field. ```rust use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyTransparentTupleStruct(String); #[derive(FromPyObject)] #[pyo3(transparent)] struct RustyTransparentStruct { inner: String, } # use pyo3::types::PyString; # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # let s = PyString::new(py, "test"); # # let tup: RustyTransparentTupleStruct = s.extract()?; # assert_eq!(tup.0, "test"); # # let stru: RustyTransparentStruct = s.extract()?; # assert_eq!(stru.inner, "test"); # # Ok(()) # }) # } ``` ### Deriving [`FromPyObject`] for enums The `FromPyObject` derivation for enums generates code that tries to extract the variants in the order of the fields. As soon as a variant can be extracted successfully, that variant is returned. This makes it possible to extract Python union types like `str | int`. The same customizations and restrictions described for struct derivations apply to enum variants, i.e. a tuple variant assumes that the input is a Python tuple, and a struct variant defaults to extracting fields as attributes but can be configured in the same manner. The `transparent` attribute can be applied to single-field-variants. ```rust use pyo3::prelude::*; #[derive(FromPyObject)] # #[derive(Debug)] enum RustyEnum<'py> { Int(usize), // input is a positive int String(String), // input is a string IntTuple(usize, usize), // input is a 2-tuple with positive ints StringIntTuple(String, usize), // input is a 2-tuple with String and int Coordinates3d { // needs to be in front of 2d x: usize, y: usize, z: usize, }, Coordinates2d { // only gets checked if the input did not have `z` #[pyo3(attribute("x"))] a: usize, #[pyo3(attribute("y"))] b: usize, }, #[pyo3(transparent)] CatchAll(Bound<'py, PyAny>), // This extraction never fails } # # use pyo3::types::{PyBytes, PyString}; # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # { # let thing = 42_u8.into_pyobject(py)?; # let rust_thing: RustyEnum<'_> = thing.extract()?; # # assert_eq!( # 42, # match rust_thing { # RustyEnum::Int(i) => i, # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # { # let thing = PyString::new(py, "text"); # let rust_thing: RustyEnum<'_> = thing.extract()?; # # assert_eq!( # "text", # match rust_thing { # RustyEnum::String(i) => i, # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # { # let thing = (32_u8, 73_u8).into_pyobject(py)?; # let rust_thing: RustyEnum<'_> = thing.extract()?; # # assert_eq!( # (32, 73), # match rust_thing { # RustyEnum::IntTuple(i, j) => (i, j), # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # { # let thing = ("foo", 73_u8).into_pyobject(py)?; # let rust_thing: RustyEnum<'_> = thing.extract()?; # # assert_eq!( # (String::from("foo"), 73), # match rust_thing { # RustyEnum::StringIntTuple(i, j) => (i, j), # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # { # let module = PyModule::from_code( # py, # c"class Foo(dict): # def __init__(self): # self.x = 0 # self.y = 1 # self.z = 2", # c"", # c"", # )?; # # let class = module.getattr("Foo")?; # let instance = class.call0()?; # let rust_thing: RustyEnum<'_> = instance.extract()?; # # assert_eq!( # (0, 1, 2), # match rust_thing { # RustyEnum::Coordinates3d { x, y, z } => (x, y, z), # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # # { # let module = PyModule::from_code( # py, # c"class Foo(dict): # def __init__(self): # self.x = 3 # self.y = 4", # c"", # c"", # )?; # # let class = module.getattr("Foo")?; # let instance = class.call0()?; # let rust_thing: RustyEnum<'_> = instance.extract()?; # # assert_eq!( # (3, 4), # match rust_thing { # RustyEnum::Coordinates2d { a, b } => (a, b), # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # # { # let thing = PyBytes::new(py, b"text"); # let rust_thing: RustyEnum<'_> = thing.extract()?; # # assert_eq!( # b"text", # match rust_thing { # RustyEnum::CatchAll(ref i) => i.cast::()?.as_bytes(), # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # Ok(()) # }) # } ``` If none of the enum variants match, a `PyTypeError` containing the names of the tested variants is returned. The names reported in the error message can be customized through the `#[pyo3(annotation = "name")]` attribute, e.g. to use conventional Python type names: ```rust use pyo3::prelude::*; #[derive(FromPyObject)] # #[derive(Debug)] enum RustyEnum { #[pyo3(transparent, annotation = "str")] String(String), #[pyo3(transparent, annotation = "int")] Int(isize), } # # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # { # let thing = 42_u8.into_pyobject(py)?; # let rust_thing: RustyEnum = thing.extract()?; # # assert_eq!( # 42, # match rust_thing { # RustyEnum::Int(i) => i, # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # # { # let thing = "foo".into_pyobject(py)?; # let rust_thing: RustyEnum = thing.extract()?; # # assert_eq!( # "foo", # match rust_thing { # RustyEnum::String(i) => i, # other => unreachable!("Error extracting: {:?}", other), # } # ); # } # # { # let thing = b"foo".into_pyobject(py)?; # let error = thing.extract::().unwrap_err(); # assert!(error.is_instance_of::(py)); # } # # Ok(()) # }) # } ``` If the input is neither a string nor an integer, the error message will be: `"'' is not an instance of 'str | int'"`. ### `#[derive(FromPyObject)]` Container Attributes - `pyo3(transparent)` - extract the field directly from the object as `obj.extract()` instead of `get_item()` or `getattr()` - Newtype structs and tuple-variants are treated as transparent per default. - only supported for single-field structs and enum variants - `pyo3(annotation = "name")` - changes the name of the failed variant in the generated error message in case of failure. - e.g. `pyo3("int")` reports the variant's type as `int`. - only supported for enum variants - `pyo3(rename_all = "...")` - renames all attributes/item keys according to the specified renaming rule - Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". - fields with an explicit renaming via `attribute(...)`/`item(...)` are not affected ### `#[derive(FromPyObject)]` Field Attributes - `pyo3(attribute)`, `pyo3(attribute("name"))` - retrieve the field from an attribute, possibly with a custom name specified as an argument - argument must be a string-literal. - `pyo3(item)`, `pyo3(item("key"))` - retrieve the field from a mapping, possibly with the custom key specified as an argument. - can be any literal that implements `ToBorrowedObject` - `pyo3(from_py_with = ...)` - apply a custom function to convert the field from Python the desired Rust type. - the argument must be the path to the function. - the function signature must be `fn(&Bound) -> PyResult` where `T` is the Rust type of the argument. - `pyo3(default)`, `pyo3(default = ...)` - if the argument is set, uses the given default value. - in this case, the argument must be a Rust expression returning a value of the desired Rust type. - if the argument is not set, [`Default::default`](https://doc.rust-lang.org/std/default/trait.Default.html#tymethod.default) is used. - note that the default value is only used if the field is not set. If the field is set and the conversion function from Python to Rust fails, an exception is raised and the default value is not used. - this attribute is only supported on named fields. For example, the code below applies the given conversion function on the `"value"` dict item to compute its length or fall back to the type default value (0): ```rust use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyStruct { #[pyo3(item("value"), default, from_py_with = Bound::<'_, PyAny>::len)] len: usize, #[pyo3(item)] other: usize, } # # use pyo3::types::PyDict; # fn main() -> PyResult<()> { # Python::attach(|py| -> PyResult<()> { # // Filled case # let dict = PyDict::new(py); # dict.set_item("value", (1,)).unwrap(); # dict.set_item("other", 1).unwrap(); # let result = dict.extract::()?; # assert_eq!(result.len, 1); # assert_eq!(result.other, 1); # # // Empty case # let dict = PyDict::new(py); # dict.set_item("other", 1).unwrap(); # let result = dict.extract::()?; # assert_eq!(result.len, 0); # assert_eq!(result.other, 1); # Ok(()) # }) # } ``` ### ⚠ Phase-Out of `FromPyObject` blanket implementation for cloneable PyClasses ⚠ Historically PyO3 has provided a blanket implementation for `#[pyclass]` types that also implement `Clone`, to allow extraction of such types by value. Over time this has turned out problematic for a few reasons, the major one being the prevention of custom conversions by downstream crates if their type is `Clone`. Over the next few releases the blanket implementation is gradually phased out, and eventually replaced by an opt-in option. As a first step of this migration a new `skip_from_py_object` option for `#[pyclass]` was introduced, to opt-out of the blanket implementation and allow downstream users to provide their own implementation: ```rust # #![allow(dead_code)] # use pyo3::prelude::*; #[pyclass(skip_from_py_object)] // opt-out of the PyO3 FromPyObject blanket #[derive(Clone)] struct Number(i32); impl<'py> FromPyObject<'_, 'py> for Number { type Error = PyErr; fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result { if let Ok(obj) = obj.cast::() { // first try extraction via class object Ok(obj.borrow().clone()) } else { obj.extract::().map(Self) // otherwise try integer directly } } } ``` As a second step the `from_py_object` option was introduced. This option also opts-out of the blanket implementation and instead generates a custom `FromPyObject` implementation for the pyclass which is functionally equivalent to the blanket. ## `IntoPyObject` The [`IntoPyObject`] trait defines the to-python conversion for a Rust type. All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`. This trait defines a single method, `into_pyobject()`, which returns a [`Result`] with `Ok` and `Err` types depending on the input value. For convenience, there is a companion [`IntoPyObjectExt`] trait which adds methods such as `into_py_any()` which converts the `Ok` and `Err` types to commonly used types (in the case of `into_py_any()`, `Py` and `PyErr` respectively). Occasionally you may choose to implement this for custom types which are mapped to Python types *without* having a unique python type. ### derive macro `IntoPyObject` can be implemented using our derive macro. Both `struct`s and `enum`s are supported. `struct`s will turn into a `PyDict` using the field names as keys, tuple `struct`s will turn convert into `PyTuple` with the fields in declaration order. ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use std::collections::HashMap; # use std::hash::Hash; // structs convert into `PyDict` with field names as keys #[derive(IntoPyObject)] struct Struct { count: usize, obj: Py, } // tuple structs convert into `PyTuple` // lifetimes and generics are supported, the impl will be bounded by // `K: IntoPyObject, V: IntoPyObject` #[derive(IntoPyObject)] struct Tuple<'a, K: Hash + Eq, V>(&'a str, HashMap); ``` For structs with a single field (newtype pattern) the `#[pyo3(transparent)]` option can be used to forward the implementation to the inner type. ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; // newtype tuple structs are implicitly `transparent` #[derive(IntoPyObject)] struct TransparentTuple(Py); #[derive(IntoPyObject)] #[pyo3(transparent)] struct TransparentStruct<'py> { inner: Bound<'py, PyAny>, // `'py` lifetime will be used as the Python lifetime } ``` For `enum`s each variant is converted according to the rules for `struct`s above. ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use std::collections::HashMap; # use std::hash::Hash; #[derive(IntoPyObject)] enum Enum<'a, 'py, K: Hash + Eq, V> { // enums are supported and convert using the same TransparentTuple(Py), // rules on the variants as the structs above #[pyo3(transparent)] TransparentStruct { inner: Bound<'py, PyAny> }, Tuple(&'a str, HashMap), Struct { count: usize, obj: Py } } ``` Additionally `IntoPyObject` can be derived for a reference to a struct or enum using the `IntoPyObjectRef` derive macro. All the same rules from above apply as well. #### `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` Field Attributes - `pyo3(into_py_with = ...)` - apply a custom function to convert the field from Rust into Python. - the argument must be the function identifier - the function signature must be `fn(Cow<'_, T>, Python<'py>) -> PyResult>` where `T` is the Rust type of the argument. - `#[derive(IntoPyObject)]` will invoke the function with `Cow::Owned` - `#[derive(IntoPyObjectRef)]` will invoke the function with `Cow::Borrowed` ```rust,no_run # use pyo3::prelude::*; # use pyo3::IntoPyObjectExt; # use std::borrow::Cow; #[derive(Clone)] struct NotIntoPy(usize); #[derive(IntoPyObject, IntoPyObjectRef)] struct MyStruct { #[pyo3(into_py_with = convert)] not_into_py: NotIntoPy, } /// Convert `NotIntoPy` into Python fn convert<'py>(not_into_py: Cow<'_, NotIntoPy>, py: Python<'py>) -> PyResult> { not_into_py.0.into_bound_py_any(py) } ``` ### manual implementation If the derive macro is not suitable for your use case, `IntoPyObject` can be implemented manually as demonstrated below. ```rust,no_run # use pyo3::prelude::*; # #[allow(dead_code)] struct MyPyObjectWrapper(Py); impl<'py> IntoPyObject<'py> for MyPyObjectWrapper { type Target = PyAny; // the Python type type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` type Error = std::convert::Infallible; // the conversion error type, has to be convertible to `PyErr` fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.0.into_bound(py)) } } // equivalent to former `ToPyObject` implementations impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { type Target = PyAny; type Output = Borrowed<'a, 'py, Self::Target>; // `Borrowed` can be used to optimized reference counting type Error = std::convert::Infallible; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.0.bind_borrowed(py)) } } ``` ### `BoundObject` for conversions that may be `Bound` or `Borrowed` `IntoPyObject::into_py_object` returns either `Bound` or `Borrowed` depending on the implementation for a concrete type. For example, the `IntoPyObject` implementation for `u32` produces a `Bound<'py, PyInt>` and the `bool` implementation produces a `Borrowed<'py, 'py, PyBool>`: ```rust,no_run use pyo3::prelude::*; use pyo3::IntoPyObject; use pyo3::types::{PyBool, PyInt}; let ints: Vec = vec![1, 2, 3, 4]; let bools = vec![true, false, false, true]; Python::attach(|py| { let ints_as_pyint: Vec> = ints .iter() .map(|x| Ok(x.into_pyobject(py)?)) .collect::>() .unwrap(); let bools_as_pybool: Vec> = bools .iter() .map(|x| Ok(x.into_pyobject(py)?)) .collect::>() .unwrap(); }); ``` In this example if we wanted to combine `ints_as_pyints` and `bools_as_pybool` into a single `Vec>` to return from the `Python::attach` closure, we would have to manually convert the concrete types for the smart pointers and the python types. Instead, we can write a function that generically converts vectors of either integers or bools into a vector of `Py` using the [`BoundObject`] trait: ```rust,no_run # use pyo3::prelude::*; # use pyo3::BoundObject; # use pyo3::IntoPyObject; # let bools = vec![true, false, false, true]; # let ints = vec![1, 2, 3, 4]; fn convert_to_vec_of_pyobj<'py, T>(py: Python<'py>, the_vec: Vec) -> PyResult>> where T: IntoPyObject<'py> + Copy { the_vec.iter() .map(|x| { Ok( // Note: the below is equivalent to `x.into_py_any()` // from the `IntoPyObjectExt` trait x.into_pyobject(py) .map_err(Into::into)? .into_any() .unbind() ) }) .collect() } let vec_of_pyobjs: Vec> = Python::attach(|py| { let mut bools_as_pyany = convert_to_vec_of_pyobj(py, bools).unwrap(); let mut ints_as_pyany = convert_to_vec_of_pyobj(py, ints).unwrap(); let mut result: Vec> = vec![]; result.append(&mut bools_as_pyany); result.append(&mut ints_as_pyany); result }); ``` In the example above we used `BoundObject::into_any` and `BoundObject::unbind` to manipulate the python types and smart pointers into the result type we wanted to produce from the function. [`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html [`IntoPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObject.html [`IntoPyObjectExt`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObjectExt.html [`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html [`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html [`BoundObject`]: {{#PYO3_DOCS_URL}}/pyo3/instance/trait.BoundObject.html [`Result`]: https://doc.rust-lang.org/stable/std/result/enum.Result.html ================================================ FILE: guide/src/conversions.md ================================================ # Type conversions In this portion of the guide we'll talk about the mapping of Python types to Rust types offered by PyO3, as well as the traits available to perform conversions between them. See also the conversion [tables](conversions/tables.md) and [traits](conversions/traits.md). ================================================ FILE: guide/src/debugging.md ================================================ # Debugging ## Macros PyO3's attributes (`#[pyclass]`, `#[pymodule]`, etc.) are [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html), which means that they rewrite the source of the annotated item. You can view the generated source with the following command, which also expands a few other things: ```bash cargo rustc --profile=check -- -Z unstable-options --pretty=expanded > expanded.rs; rustfmt expanded.rs ``` (You might need to install [rustfmt](https://github.com/rust-lang-nursery/rustfmt) if you don't already have it.) You can also debug classic `!`-macros by adding `-Z trace-macros`: ```bash cargo rustc --profile=check -- -Z unstable-options --pretty=expanded -Z trace-macros > expanded.rs; rustfmt expanded.rs ``` Note that those commands require using the nightly build of rust and may occasionally have bugs. See [cargo expand](https://github.com/dtolnay/cargo-expand) for a more elaborate and stable version of those commands. ## Running with Valgrind Valgrind is a tool to detect memory management bugs such as memory leaks. You first need to install a debug build of Python, otherwise Valgrind won't produce usable results. In Ubuntu there's e.g. a `python3-dbg` package. Activate an environment with the debug interpreter and recompile. If you're on Linux, use `ldd` with the name of your binary and check that you're linking e.g. `libpython3.7d.so.1.0` instead of `libpython3.7.so.1.0`. [Download the suppressions file for CPython](https://raw.githubusercontent.com/python/cpython/master/Misc/valgrind-python.supp). Run Valgrind with `valgrind --suppressions=valgrind-python.supp ./my-command --with-options` ## Getting a stacktrace The best start to investigate a crash such as an segmentation fault is a backtrace. You can set `RUST_BACKTRACE=1` as an environment variable to get the stack trace on a `panic!`. Alternatively you can use a debugger such as `gdb` to explore the issue. Rust provides a wrapper, `rust-gdb`, which has pretty-printers for inspecting Rust variables. Since PyO3 uses `cdylib` for Python shared objects, it does not receive the pretty-print debug hooks in `rust-gdb` ([rust-lang/rust#96365](https://github.com/rust-lang/rust/issues/96365)). The mentioned issue contains a workaround for enabling pretty-printers in this case. - Link against a debug build of python as described in the previous chapter - Run `rust-gdb ` - Set a breakpoint (`b`) on `rust_panic` if you are investigating a `panic!` - Enter `r` to run - After the crash occurred, enter `bt` or `bt full` to print the stacktrace Often it is helpful to run a small piece of Python code to exercise a section of Rust. ```console rust-gdb --args python -c "import my_package; my_package.sum_to_string(1, 2)" ``` ## Setting breakpoints in your Rust code One of the preferred ways by developers to debug their code is by setting breakpoints. This can be achieved in PyO3 by using a debugger like `rust-gdb` or `rust-lldb` with your Python interpreter. For more information about how to use both `lldb` and `gdb` you can read the [gdb to lldb command map](https://lldb.llvm.org/use/map.html) from the lldb documentation. ### Common setup 1. Compile your extension with debug symbols: ```bash # Debug is the default for maturin, but you can explicitly ensure debug symbols with: RUSTFLAGS="-g" maturin develop # For setuptools-rust users: pip install -e . ``` > **Note**: When using debuggers, make sure that `python` resolves to an actual Python binary or symlink and not a shim script. Some tools like pyenv use shim scripts which can interfere with debugging. ### Debugger specific setup Depending on your OS and your preferences you can use two different debuggers, `rust-gdb` or `rust-lldb`. {{#tabs }} {{#tab name="Using rust-gdb" }} 1. Launch rust-gdb with the Python interpreter: ```bash rust-gdb --args python ``` 2. Once in gdb, set a breakpoint in your Rust code: ```bash (gdb) break your_module.rs:42 ``` 3. Run your Python script that imports and uses your Rust extension: ```bash # Option 1: Run an inline Python command (gdb) run -c "import your_module; your_module.your_function()" # Option 2: Run a Python script (gdb) run your_script.py # Option 3: Run pytest tests (gdb) run -m pytest tests/test_something.py::TestName ``` {{#endtab }} {{#tab name="Using rust-lldb (for macOS users)" }} 1. Start rust-lldb with Python: ```bash rust-lldb -- python ``` 2. Set breakpoints in your Rust code: ```bash (lldb) breakpoint set --file your_module.rs --line 42 ``` 3. Run your Python script: ```bash # Option 1: Run an inline Python command (lldb) run -c "import your_module; your_module.your_function()" # Option 2: Run a Python script (lldb) run your_script.py # Option 3: Run pytest tests (lldb) run -m pytest tests/test_something.py::TestName ``` {{#endtab }} {{#endtabs }} ### Using VS Code VS Code with the Rust and Python extensions provides an integrated debugging experience: 1. First, install the necessary VS Code extensions: - [Rust Analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) - [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) 2. Create a `.vscode/launch.json` file with a configuration that uses the LLDB Debug Launcher: ```json { "version": "0.2.0", "configurations": [ { "name": "Debug PyO3", "type": "lldb", "request": "attach", "program": "${workspaceFolder}/.venv/bin/python", "pid": "${command:pickProcess}", "sourceLanguages": [ "rust" ] }, { "name": "Launch Python with PyO3", "type": "lldb", "request": "launch", "program": "${workspaceFolder}/.venv/bin/python", "args": ["${file}"], "cwd": "${workspaceFolder}", "sourceLanguages": ["rust"] }, { "name": "Debug PyO3 with Args", "type": "lldb", "request": "launch", "program": "${workspaceFolder}/.venv/bin/python", "args": ["path/to/your/script.py", "arg1", "arg2"], "cwd": "${workspaceFolder}", "sourceLanguages": ["rust"] }, { "name": "Debug PyO3 Tests", "type": "lldb", "request": "launch", "program": "${workspaceFolder}/.venv/bin/python", "args": ["-m", "pytest", "tests/your_test.py::test_function", "-v"], "cwd": "${workspaceFolder}", "sourceLanguages": ["rust"] } ] } ``` This configuration supports multiple debugging scenarios: - Attaching to a running Python process - Launching the currently open Python file - Running a specific script with command-line arguments - Running pytest tests 3. Set breakpoints in your Rust code by clicking in the gutter next to line numbers. 4. Start debugging: - For attaching to a running Python process: First start the process, then select the "Debug PyO3" configuration and click Start Debugging (F5). You'll be prompted to select the Python process to attach to. - For launching a Python script: Open your Python script, select the "Launch Python with PyO3" configuration and click Start Debugging (F5). - For running with arguments: Select "Debug PyO3 with Args" (remember to edit the configuration with your actual script path and arguments). - For running tests: Select "Debug PyO3 Tests" (edit the test path as needed). 5. When debugging PyO3 code: - You can inspect Rust variables and data structures - Use the debug console to evaluate expressions - Step through Rust code line by line using the step controls - Set conditional breakpoints for more complex debugging scenarios ### Advanced Debugging Configurations For advanced debugging scenarios, you might want to add environment variables or enable specific Rust debug flags: ```json { "name": "Debug PyO3 with Environment", "type": "lldb", "request": "launch", "program": "${workspaceFolder}/.venv/bin/python", "args": ["${file}"], "env": { "RUST_BACKTRACE": "1", "PYTHONPATH": "${workspaceFolder}" }, "sourceLanguages": ["rust"] } ``` ### Debugging from Jupyter Notebooks For Jupyter Notebooks run from VS Code, you can use the following helper functions to automate the launch configuration: ```python from pathlib import Path import os import json import sys def update_launch_json(vscode_config_file_path=None): """Update VSCode launch.json with the correct Jupyter kernel PID. Args: vscode_config_file_path (str, optional): Path to the .vscode/launch.json file. If not provided, will use the current working directory. """ pid = get_jupyter_kernel_pid() if not pid: print("Could not determine Jupyter kernel PID.") return # Determine launch.json path if vscode_config_file_path: launch_json_path = vscode_config_file_path else: launch_json_path = os.path.join(Path(os.getcwd()), ".vscode", "launch.json") # Get Python interpreter path python_path = sys.executable # Default debugger config debug_config = { "version": "0.2.0", "configurations": [ { "name": "Debug PyO3 (Jupyter)", "type": "lldb", "request": "attach", "program": python_path, "pid": pid, "sourceLanguages": ["rust"], }, { "name": "Launch Python with PyO3", "type": "lldb", "request": "launch", "program": python_path, "args": ["${file}"], "cwd": "${workspaceFolder}", "sourceLanguages": ["rust"] } ], } # Create .vscode directory if it doesn't exist try: os.makedirs(os.path.dirname(launch_json_path), exist_ok=True) # If launch.json already exists, try to update it instead of overwriting if os.path.exists(launch_json_path): try: with open(launch_json_path, "r") as f: existing_config = json.load(f) # Check if our configuration already exists config_exists = False for config in existing_config.get("configurations", []): if config.get("name") == "Debug PyO3 (Jupyter)": config["pid"] = pid config["program"] = python_path config_exists = True if not config_exists: existing_config.setdefault("configurations", []).append(debug_config["configurations"][0]) debug_config = existing_config except Exception: # If reading fails, we'll just overwrite with our new configuration pass with open(launch_json_path, "w") as f: json.dump(debug_config, f, indent=4) print(f"Updated launch.json with PID: {pid} at {launch_json_path}") except Exception as e: print(f"Error updating launch.json: {e}") def get_jupyter_kernel_pid(): """Find the process ID (PID) of the running Jupyter kernel. Returns: int: The process ID of the Jupyter kernel, or None if not found. """ # Check if we're running in a Jupyter environment if 'ipykernel' in sys.modules: pid = os.getpid() print(f"Jupyter kernel PID: {pid}") return pid else: print("Not running in a Jupyter environment.") return None ``` To use these functions: 1. Run the cell containing these functions in your Jupyter notebook 2. Run `update_launch_json()` in a cell 3. In VS Code, select the "Debug PyO3 (Jupyter)" configuration and start debugging ## Thread Safety and Compiler Sanitizers PyO3 attempts to match the Rust language-level guarantees for thread safety, but that does not preclude other code outside of the control of PyO3 or buggy code managed by a PyO3 extension from creating a thread safety issue. Analyzing whether or not a piece of Rust code that uses the CPython C API is thread safe can be quite complicated, since many Python operations can lead to arbitrary Python code execution. Automated ways to discover thread safety issues can often be more fruitful than code analysis. [ThreadSanitizer](https://clang.llvm.org/docs/ThreadSanitizer.html) is a thread safety checking runtime that can be used to detect data races triggered by thread safety bugs or incorrect use of thread-unsafe data structures. While it can only detect data races triggered by code at runtime, if it does detect something the reports often point to exactly where the problem is happening. To use `ThreadSanitizer` with a library that depends on PyO3, you will need to install a nightly Rust toolchain, along with the `rust-src` component, since you will need to compile the Rust standard library: ```bash rustup install nightly rustup override set nightly rustup component add rust-src ``` You will also need a version of CPython compiled using LLVM/Clang with the same major version of LLVM as is currently used to compile nightly Rust. As of March 2025, Rust nightly uses LLVM 20. The [cpython_sanity docker images](https://github.com/nascheme/cpython_sanity) contain a development environment with a pre-compiled version of CPython 3.13 or 3.14 as well as optionally NumPy and SciPy, all compiled using LLVM 20 and ThreadSanitizer. After activating a nightly Rust toolchain, you can build your project using `ThreadSanitizer` with the following command: ```bash RUSTFLAGS="-Zsanitizer=thread" maturin develop -Zbuild-std --target x86_64-unknown-linux-gnu ``` If you are not running on an x86_64 Linux machine, you should replace `x86_64-unknown-linux-gnu` with the [target triple](https://doc.rust-lang.org/rustc/platform-support.html#tier-1-with-host-tools) that is appropriate for your system. You can also replace `maturin develop` with `cargo test` to run `cargo` tests. Note that `cargo` runs tests in a thread pool, so `cargo` tests can be a good way to find thread safety issues. You can also replace `-Zsanitizer=thread` with `-Zsanitizer=address` or any of the other sanitizers that are [supported by Rust](https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html). Note that you'll need to build CPython from source with the appropriate [configure script flags](https://docs.python.org/3/using/configure.html#cmdoption-with-address-sanitizer) to use the same sanitizer environment as you want to use for your Rust code. ================================================ FILE: guide/src/ecosystem/async-await.md ================================================ # Using `async` and `await` *`async`/`await` support is currently being integrated in PyO3.* *See the [dedicated documentation](../async-await.md)* If you are working with a Python library that makes use of async functions or wish to provide Python bindings for an async Rust library, [`pyo3-async-runtimes`](https://github.com/PyO3/pyo3-async-runtimes) likely has the tools you need. It provides conversions between async functions in both Python and Rust and was designed with first-class support for popular Rust runtimes such as [`tokio`](https://tokio.rs/) and [`async-std`](https://async.rs/). In addition, all async Python code runs on the default `asyncio` event loop, so `pyo3-async-runtimes` should work just fine with existing Python libraries. ## Additional Information - Managing event loop references can be tricky with `pyo3-async-runtimes`. See [Event Loop References](https://docs.rs/pyo3-async-runtimes/#event-loop-references-and-contextvars) in the API docs to get a better intuition for how event loop references are managed in this library. - Testing `pyo3-async-runtimes` libraries and applications requires a custom test harness since Python requires control over the main thread. You can find a testing guide in the [API docs for the `testing` module](https://docs.rs/pyo3-async-runtimes/latest/pyo3_async_runtimes/testing) ================================================ FILE: guide/src/ecosystem/logging.md ================================================ # Logging It is desirable if both the Python and Rust parts of the application end up logging using the same configuration into the same place. This section of the guide briefly discusses how to connect the two languages' logging ecosystems together. The recommended way for Python extension modules is to configure Rust's logger to send log messages to Python using the `pyo3-log` crate. For users who want to do the opposite and send Python log messages to Rust, see the note at the end of this guide. ## Using `pyo3-log` to send Rust log messages to Python The [pyo3-log] crate allows sending the messages from the Rust side to Python's [logging] system. This is mostly suitable for writing native extensions for Python programs. Use [`pyo3_log::init`][init] to install the logger in its default configuration. It's also possible to tweak its configuration (mostly to tune its performance). ```rust,no_run #[pyo3::pymodule] mod my_module { use log::info; use pyo3::prelude::*; #[pyfunction] fn log_something() { // This will use the logger installed in `my_module` to send the `info` // message to the Python logging facilities. info!("Something!"); } #[pymodule_init] fn init(m: &Bound<'_, PyModule>) -> PyResult<()> { // A good place to install the Rust -> Python logger. pyo3_log::init(); } } ``` Then it is up to the Python side to actually output the messages somewhere. ```python import logging import my_module FORMAT = '%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s' logging.basicConfig(format=FORMAT) logging.getLogger().setLevel(logging.INFO) my_module.log_something() ``` It is important to initialize the Python loggers first, before calling any Rust functions that may log. This limitation can be worked around if it is not possible to satisfy, read the documentation about [caching]. ## The Python to Rust direction To have python logs be handled by Rust, one need only register a rust function to handle logs emitted from the core python logging module. This has been implemented within the [pyo3-pylogger] crate. ```rust,no_run use log::{info, warn}; use pyo3::prelude::*; fn main() -> PyResult<()> { // register the host handler with python logger, providing a logger target // set the name here to something appropriate for your application pyo3_pylogger::register("example_application_py_logger"); // initialize up a logger env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); // Log some messages from Rust. info!("Just some normal information!"); warn!("Something spooky happened!"); // Log some messages from Python Python::attach(|py| { py.run( " import logging logging.error('Something bad happened') ", None, None, ) }) } ``` [logging]: https://docs.python.org/3/library/logging.html [pyo3-log]: https://crates.io/crates/pyo3-log [init]: https://docs.rs/pyo3-log/*/pyo3_log/fn.init.html [caching]: https://docs.rs/pyo3-log/*/pyo3_log/#performance-filtering-and-caching [pyo3-pylogger]: https://crates.io/crates/pyo3-pylogger ================================================ FILE: guide/src/ecosystem/tracing.md ================================================ # Tracing Python projects that write extension modules for performance reasons may want to tap into [Rust's `tracing` ecosystem] to gain insight into the performance of their extension module. This section of the guide describes a few crates that provide ways to do that. They build on [`tracing_subscriber`][tracing-subscriber] and require code changes in both Python and Rust to integrate. Note that each extension module must configure its own `tracing` integration; one extension module will not see `tracing` data from a different module. ## `pyo3-tracing-subscriber` ([documentation][pyo3-tracing-subscriber-docs]) [`pyo3-tracing-subscriber`][pyo3-tracing-subscriber] provides a way for Python projects to configure `tracing_subscriber`. It exposes a few `tracing_subscriber` layers: - `tracing_subscriber::fmt` for writing human-readable output to file or stdout - `opentelemetry-stdout` for writing OTLP output to file or stdout - `opentelemetry-otlp` for writing OTLP output to an OTLP endpoint The extension module must call [`pyo3_tracing_subscriber::add_submodule`][add-submodule] to export the Python classes needed to configure and initialize `tracing`. On the Python side, use the `Tracing` context manager to initialize tracing and run Rust code inside the context manager's block. `Tracing` takes a `GlobalTracingConfig` instance describing the layers to be used. See [the README on crates.io][pyo3-tracing-subscriber] for example code. ## `pyo3-python-tracing-subscriber` ([documentation][pyo3-python-tracing-subscriber-docs]) The similarly-named [`pyo3-python-tracing-subscriber`][pyo3-python-tracing-subscriber] implements a shim in Rust that forwards `tracing` data to a `Layer` implementation defined in and passed in from Python. There are many ways an extension module could integrate `pyo3-python-tracing-subscriber` but a simple one may look something like this: ```rust,no_run #[tracing::instrument] #[pyfunction] fn fibonacci(index: usize, use_memoized: bool) -> PyResult { // ... } #[pyfunction] pub fn initialize_tracing(py_impl: Bound<'_, PyAny>) { tracing_subscriber::registry() .with(pyo3_python_tracing_subscriber::PythonCallbackLayerBridge::new(py_impl)) .init(); } ``` The extension module must provide some way for Python to pass in one or more Python objects that implement [the `Layer` interface]. Then it should construct [`pyo3_python_tracing_subscriber::PythonCallbackLayerBridge`][PythonCallbackLayerBridge] instances with each of those Python objects and initialize `tracing_subscriber` as shown above. The Python objects implement a modified version of the `Layer` interface: - `on_new_span()` may return some state that will stored inside the Rust span - other callbacks will be given that state as an additional positional argument A dummy `Layer` implementation may look like this: ```python import rust_extension class MyPythonLayer: def __init__(self): pass # `on_new_span` can return some state def on_new_span(self, span_attrs: str, span_id: str) -> int: print(f"[on_new_span]: {span_attrs} | {span_id}") return random.randint(1, 1000) # The state from `on_new_span` is passed back into other trait methods def on_event(self, event: str, state: int): print(f"[on_event]: {event} | {state}") def on_close(self, span_id: str, state: int): print(f"[on_close]: {span_id} | {state}") def on_record(self, span_id: str, values: str, state: int): print(f"[on_record]: {span_id} | {values} | {state}") def main(): rust_extension.initialize_tracing(MyPythonLayer()) print("10th fibonacci number: ", rust_extension.fibonacci(10, True)) ``` `pyo3-python-tracing-subscriber` has [working examples] showing both the Rust side and the Python side of an integration. [pyo3-tracing-subscriber]: https://crates.io/crates/pyo3-tracing-subscriber [pyo3-tracing-subscriber-docs]: https://docs.rs/pyo3-tracing-subscriber [add-submodule]: https://docs.rs/pyo3-tracing-subscriber/*/pyo3_tracing_subscriber/fn.add_submodule.html [pyo3-python-tracing-subscriber]: https://crates.io/crates/pyo3-python-tracing-subscriber [pyo3-python-tracing-subscriber-docs]: https://docs.rs/pyo3-python-tracing-subscriber [PythonCallbackLayerBridge]: https://docs.rs/pyo3-python-tracing-subscriber/*/pyo3_python_tracing_subscriber/struct.PythonCallbackLayerBridge.html [working examples]: https://github.com/getsentry/pyo3-python-tracing-subscriber/tree/main/demo [Rust's `tracing` ecosystem]: https://crates.io/crates/tracing [tracing-subscriber]: https://docs.rs/tracing-subscriber/*/tracing_subscriber/ [the `Layer` interface]: https://docs.rs/tracing-subscriber/*/tracing_subscriber/layer/trait.Layer.html ================================================ FILE: guide/src/ecosystem.md ================================================ # The PyO3 ecosystem This portion of the guide is dedicated to crates which are external to the main PyO3 project and provide additional functionality you might find useful. Because these projects evolve independently of the PyO3 repository the content of these articles may fall out of date over time; please file issues on the PyO3 GitHub to alert maintainers when this is the case. ================================================ FILE: guide/src/exception.md ================================================ # Python exceptions ## Defining a new exception Use the [`create_exception!`] macro: ```rust use pyo3::create_exception; create_exception!(module, MyError, pyo3::exceptions::PyException); ``` - `module` is the name of the containing module. - `MyError` is the name of the new exception type. For example: ```rust use pyo3::prelude::*; use pyo3::create_exception; use pyo3::types::IntoPyDict; use pyo3::exceptions::PyException; create_exception!(mymodule, CustomError, PyException); # fn main() -> PyResult<()> { Python::attach(|py| { let ctx = [("CustomError", py.get_type::())].into_py_dict(py)?; pyo3::py_run!( py, *ctx, "assert str(CustomError) == \"\"" ); pyo3::py_run!(py, *ctx, "assert CustomError('oops').args == ('oops',)"); # Ok(()) }) # } ``` When using PyO3 to create an extension module, you can add the new exception to the module like this, so that it is importable from Python: ```rust,no_run # fn main() {} use pyo3::prelude::*; use pyo3::exceptions::PyException; pyo3::create_exception!(mymodule, CustomError, PyException); #[pymodule] mod mymodule { #[pymodule_export] use super::CustomError; // ... other elements added to module ... } ``` ## Raising an exception As described in the [function error handling](./function/error-handling.md) chapter, to raise an exception from a `#[pyfunction]` or `#[pymethods]`, return an `Err(PyErr)`. PyO3 will automatically raise this exception for you when returning the result to Python. You can also manually write and fetch errors in the Python interpreter's global state: ```rust use pyo3::{Python, PyErr}; use pyo3::exceptions::PyTypeError; Python::attach(|py| { PyTypeError::new_err("Error").restore(py); assert!(PyErr::occurred(py)); drop(PyErr::fetch(py)); }); ``` ## Checking exception types Python has an [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance) method to check an object's type. In PyO3 every object has the [`PyAny::is_instance`] and [`PyAny::is_instance_of`] methods which do the same thing. ```rust,no_run use pyo3::prelude::*; use pyo3::types::{PyBool, PyList}; # fn main() -> PyResult<()> { Python::attach(|py| { assert!(PyBool::new(py, true).is_instance_of::()); let list = PyList::new(py, &[1, 2, 3, 4])?; assert!(!list.is_instance_of::()); assert!(list.is_instance_of::()); # Ok(()) }) # } ``` To check the type of an exception, you can similarly do: ```rust,no_run # use pyo3::exceptions::PyTypeError; # use pyo3::prelude::*; # Python::attach(|py| { # let err = PyTypeError::new_err(()); err.is_instance_of::(py); # }); ``` ## Using exceptions defined in Python code It is possible to use an exception defined in Python code as a native Rust type. The [`import_exception!`] macro allows importing a specific exception class and defines a Rust type for that exception. ```rust,no_run #![allow(dead_code)] use pyo3::prelude::*; mod io { pyo3::import_exception!(io, UnsupportedOperation); } fn tell(file: &Bound<'_, PyAny>) -> PyResult { match file.call_method0("tell") { Err(_) => Err(io::UnsupportedOperation::new_err("not supported: tell")), Ok(x) => x.extract::(), } } ``` [`pyo3::exceptions`]({{#PYO3_DOCS_URL}}/pyo3/exceptions/index.html) defines exceptions for several standard library modules. ## Creating more complex exceptions If you need to create an exception with more complex behavior, you can also manually create a subclass of `PyException`: ```rust #![allow(dead_code)] # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] { use pyo3::prelude::*; use pyo3::types::IntoPyDict; use pyo3::exceptions::PyException; #[pyclass(extends=PyException)] struct CustomError { #[pyo3(get)] url: String, #[pyo3(get)] message: String, } #[pymethods] impl CustomError { #[new] fn new(url: String, message: String) -> Self { Self { url, message } } } # fn main() -> PyResult<()> { Python::attach(|py| { let ctx = [("CustomError", py.get_type::())].into_py_dict(py)?; pyo3::py_run!( py, *ctx, "assert str(CustomError) == \"\", repr(CustomError)" ); pyo3::py_run!(py, *ctx, "assert CustomError('https://example.com', 'something went bad').args == ('https://example.com', 'something went bad')"); pyo3::py_run!(py, *ctx, "assert CustomError('https://example.com', 'something went bad').url == 'https://example.com'"); # Ok(()) }) # } # } ``` Note that when the `abi3` feature is enabled, subclassing `PyException` is only possible on Python 3.12 or greater. [`create_exception!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.create_exception.html [`import_exception!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.import_exception.html [`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance [`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.is_instance_of ================================================ FILE: guide/src/faq.md ================================================ # Frequently Asked Questions and troubleshooting Sorry that you're having trouble using PyO3. If you can't find the answer to your problem in the list below, you can also reach out for help on [GitHub Discussions](https://github.com/PyO3/pyo3/discussions) and on [Discord](https://discord.gg/33kcChzH7f). ## I'm experiencing deadlocks using PyO3 with `std::sync::OnceLock`, `std::sync::LazyLock`, `lazy_static`, and `once_cell` `OnceLock`, `LazyLock`, and their thirdparty predecessors use blocking to ensure only one thread ever initializes them. Because the Python interpreter can introduce additional locks (the Python GIL and GC can both require all other threads to pause) this can lead to deadlocks in the following way: 1. A thread (thread A) which is attached to the Python interpreter starts initialization of a `OnceLock` value. 2. The initialization code calls some Python API which temporarily detaches from the interpreter e.g. `Python::import`. 3. Another thread (thread B) attaches to the Python interpreter and attempts to access the same `OnceLock` value. 4. Thread B is blocked, because it waits for `OnceLock`'s initialization to lock to release. 5. On non-free-threaded Python, thread A is now also blocked, because it waits to re-attach to the interpreter (by taking the GIL which thread B still holds). 6. Deadlock. PyO3 provides a struct [`PyOnceLock`] which implements a single-initialization API based on these types that avoids deadlocks. You can also make use of the [`OnceExt`] and [`OnceLockExt`] extension traits that enable using the standard library types for this purpose by providing new methods for these types that avoid the risk of deadlocking with the Python interpreter. This means they can be used in place of other choices when you are experiencing the deadlock described above. See the documentation for [`PyOnceLock`] and [`OnceExt`] for further details and an example how to use them. [`PyOnceLock`]: {{#PYO3_DOCS_URL}}/pyo3/sync/struct.PyOnceLock.html [`OnceExt`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceExt.html [`OnceLockExt`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceLockExt.html ## I can't run `cargo test`; or I can't build in a Cargo workspace: I'm having linker issues like "Symbol not found" or "Undefined reference to _PyExc_SystemError" The `extension-module` feature (now deprecated) disables linking to `libpython`. This breaks binaries and tests which need to load symbols from `libpython` to execute. Remove the `extension-module` feature and upgrade to `maturin >= 1.9.4` or `setuptools-rust 1.12`. If building manually, see the [`PYO3_BUILD_EXTENSION_MODULE` environment variable](./building-and-distribution.md#the-pyo3_build_extension_module-environment-variable). ## I can't run `cargo test`: my crate cannot be found for tests in `tests/` directory The Rust book suggests to [put integration tests inside a `tests/` directory](https://doc.rust-lang.org/book/ch11-03-test-organization.html#integration-tests). For a PyO3 project where the `crate-type` is set to `"cdylib"` in your `Cargo.toml`, the compiler won't be able to find your crate and will display errors such as `E0432` or `E0463`: ```text error[E0432]: unresolved import `my_crate` --> tests/test_my_crate.rs:1:5 | 1 | use my_crate; | ^^^^^^^^^^^^ no external crate `my_crate` ``` The best solution is to make your crate types include both `rlib` and `cdylib`: ```toml # Cargo.toml [lib] crate-type = ["cdylib", "rlib"] ``` ## Ctrl-C doesn't do anything while my Rust code is executing This is because Ctrl-C raises a SIGINT signal, which is handled by the calling Python process by simply setting a flag to action upon later. This flag isn't checked while Rust code called from Python is executing, only once control returns to the Python interpreter. You can give the Python interpreter a chance to process the signal properly by calling `Python::check_signals`. It's good practice to call this function regularly if you have a long-running Rust function so that your users can cancel it. ## `#[pyo3(get)]` clones my field You may have a nested struct similar to this: ```rust,no_run # use pyo3::prelude::*; #[pyclass(from_py_object)] #[derive(Clone)] struct Inner {/* fields omitted */} #[pyclass] struct Outer { #[pyo3(get)] inner: Inner, } #[pymethods] impl Outer { #[new] fn __new__() -> Self { Self { inner: Inner {} } } } ``` When Python code accesses `Outer`'s field, PyO3 will return a new object on every access (note that their addresses are different): ```python outer = Outer() a = outer.inner b = outer.inner assert a is b, f"a: {a}\nb: {b}" ``` ```text AssertionError: a: b: ``` This can be especially confusing if the field is mutable, as getting the field and then mutating it won't persist - you'll just get a fresh clone of the original on the next access. Unfortunately Python and Rust don't agree about ownership - if PyO3 gave out references to (possibly) temporary Rust objects to Python code, Python code could then keep that reference alive indefinitely. Therefore returning Rust objects requires cloning. If you don't want that cloning to happen, a workaround is to allocate the field on the Python heap and store a reference to that, by using [`Py<...>`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html): ```rust,no_run # use pyo3::prelude::*; #[pyclass] struct Inner {/* fields omitted */} #[pyclass] struct Outer { inner: Py, } #[pymethods] impl Outer { #[new] fn __new__(py: Python<'_>) -> PyResult { Ok(Self { inner: Py::new(py, Inner {})?, }) } #[getter] fn inner(&self, py: Python<'_>) -> Py { self.inner.clone_ref(py) } } ``` This time `a` and `b` *are* the same object: ```python outer = Outer() a = outer.inner b = outer.inner assert a is b, f"a: {a}\nb: {b}" print(f"a: {a}\nb: {b}") ``` ```text a: b: ``` The downside to this approach is that any Rust code working on the `Outer` struct potentially has to attach to the Python interpreter to do anything with the `inner` field. (If `Inner` is `#[pyclass(frozen)]` and implements `Sync`, then `Py::get` may be used to access the `Inner` contents from `Py` without needing to attach to the interpreter.) ## I want to use the `pyo3` crate re-exported from dependency but the proc-macros fail All PyO3 proc-macros (`#[pyclass]`, `#[pyfunction]`, `#[derive(FromPyObject)]` and so on) expect the `pyo3` crate to be available under that name in your crate root, which is the normal situation when `pyo3` is a direct dependency of your crate. However, when the dependency is renamed, or your crate only indirectly depends on `pyo3`, you need to let the macro code know where to find the crate. This is done with the `crate` attribute: ```rust,no_run # use pyo3::prelude::*; # pub extern crate pyo3; # mod reexported { pub use ::pyo3; } # #[allow(dead_code)] #[pyclass] #[pyo3(crate = "reexported::pyo3")] struct MyClass; ``` ## I'm trying to call Python from Rust but I get `STATUS_DLL_NOT_FOUND` or `STATUS_ENTRYPOINT_NOT_FOUND` This happens on Windows when linking to the python DLL fails or the wrong one is linked. The Python DLL on Windows will usually be called something like: - `python3X.dll` for Python 3.X, e.g. `python310.dll` for Python 3.10 - `python3.dll` when using PyO3's `abi3` feature The DLL needs to be locatable using the [Windows DLL search order](https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#standard-search-order-for-unpackaged-apps). Some ways to achieve this are: - Put the Python DLL in the same folder as your build artifacts - Add the directory containing the Python DLL to your `PATH` environment variable, for example `C:\Users\\AppData\Local\Programs\Python\Python310` - If this happens when you are *distributing* your program, consider using [PyOxidizer](https://github.com/indygreg/PyOxidizer) to package it with your binary. If the wrong DLL is linked it is possible that this happened because another program added itself and its own Python DLLs to `PATH`. Rearrange your `PATH` variables to give the correct DLL priority. > [!NOTE] > Changes to `PATH` (or any other environment variable) are not visible to existing shells. Restart it for changes to take effect. For advanced troubleshooting, [Dependency Walker](https://www.dependencywalker.com/) can be used to diagnose linking errors. ================================================ FILE: guide/src/features.md ================================================ # Features reference PyO3 provides a number of Cargo features to customize functionality. This chapter of the guide provides detail on each of them. By default, only the `macros` feature is enabled. ## Features for extension module authors ### `extension-module` Deprecated, users should remove this feature and upgrade to `maturin >= 1.9.4` or `setuptools-rust >= 1.12`. See the [building and distribution](building-and-distribution.md#the-extension-module-feature) section for further detail. ### `abi3` This feature is used when building Python extension modules to create wheels which are compatible with multiple Python versions. It restricts PyO3's API to a subset of the full Python API which is guaranteed by [PEP 384](https://www.python.org/dev/peps/pep-0384/) to be forwards-compatible with future Python versions. See the [building and distribution](building-and-distribution.md#py_limited_apiabi3) section for further detail. ### The `abi3-pyXY` features (`abi3-py37`, `abi3-py38`, `abi3-py39`, `abi3-py310`, `abi3-py311`, `abi3-py312`, `abi3-py313` and `abi3-py314`) These features are extensions of the `abi3` feature to specify the exact minimum Python version which the multiple-version-wheel will support. See the [building and distribution](building-and-distribution.md#minimum-python-version-for-abi3) section for further detail. ### `generate-import-lib` This feature is deprecated and has no effect. PyO3 now uses Rust's `raw-dylib` linking feature to link against the Python DLL on Windows, eliminating the need for import library (`.lib`) files entirely. Cross-compiling for Windows targets works without any additional setup. ## Features for embedding Python in Rust ### `auto-initialize` This feature changes [`Python::attach`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.attach) to automatically initialize a Python interpreter (by calling [`Python::initialize`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.initialize)) if needed. If you do not enable this feature, you should call `Python::initialize()` before attempting to call any other Python APIs. ## Advanced Features ### `experimental-async` This feature adds support for `async fn` in `#[pyfunction]` and `#[pymethods]`. The feature has some unfinished refinements and performance improvements. To help finish this off, see [issue #1632](https://github.com/PyO3/pyo3/issues/1632) and its associated draft PRs. ### `experimental-inspect` This feature adds to the built binaries introspection data that can be then retrieved using the `pyo3-introspection` crate to generate [type stubs](https://typing.readthedocs.io/en/latest/source/stubs.html). Also, this feature adds the `pyo3::inspect` module, as well as `IntoPy::type_output` and `FromPyObject::type_input` APIs to produce Python type "annotations" for Rust types. This is a first step towards adding first-class support for generating type annotations automatically in PyO3, however work is needed to finish this off. All feedback and offers of help welcome on [issue #2454](https://github.com/PyO3/pyo3/issues/2454). ### `py-clone` This feature was introduced to ease migration. It was found that delayed reference counting (which PyO3 used historically) could not be made sound and hence `Clone`-ing an instance of `Py` is impossible when not attached to Python interpreter (it will panic). To avoid migrations introducing new panics without warning, the `Clone` implementation itself is now gated behind this feature. ### `pyo3_disable_reference_pool` This is a performance-oriented conditional compilation flag, e.g. [set via `$RUSTFLAGS`][set-configuration-options], which disabled the global reference pool and the associated overhead for the crossing the Python-Rust boundary. However, if enabled, `Drop`ping an instance of `Py` when not attached to the Python interpreter will abort the process. ### `macros` This feature enables a dependency on the `pyo3-macros` crate, which provides the procedural macros portion of PyO3's API: - `#[pymodule]` - `#[pyfunction]` - `#[pyclass]` - `#[pymethods]` - `#[derive(FromPyObject)]` It also provides the `py_run!` macro. These macros require a number of dependencies which may not be needed by users who just need PyO3 for Python FFI. Disabling this feature enables faster builds for those users, as these dependencies will not be built if this feature is disabled. > [!NOTE] > This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml. ### `multiple-pymethods` This feature enables each `#[pyclass]` to have more than one `#[pymethods]` block. Most users should only need a single `#[pymethods]` per `#[pyclass]`. In addition, not all platforms (e.g. Wasm) are supported by `inventory`, which is used in the implementation of the feature. For this reason this feature is not enabled by default, meaning fewer dependencies and faster compilation for the majority of users. See [the `#[pyclass]` implementation details](class.md#implementation-details) for more information. ### `nightly` The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use the `auto_traits` and `negative_impls` features to fix the `Python::detach` function. ### `resolve-config` The `resolve-config` feature of the `pyo3-build-config` crate controls whether that crate's build script automatically resolves a Python interpreter / build configuration. This feature is primarily useful when building PyO3 itself. By default this feature is not enabled, meaning you can freely use `pyo3-build-config` as a standalone library to read or write PyO3 build configuration files or resolve metadata about a Python interpreter. ## Optional Dependencies These features enable conversions between Python types and types from other Rust crates, enabling easy access to the rest of the Rust ecosystem. ### `anyhow` Adds a dependency on [anyhow](https://docs.rs/anyhow). Enables a conversion from [anyhow](https://docs.rs/anyhow)’s [`Error`](https://docs.rs/anyhow/latest/anyhow/struct.Error.html) type to [`PyErr`]({{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html), for easy error handling. ### `arc_lock` Enables Pyo3's `MutexExt` trait for all Mutexes that extend on [`lock_api::Mutex`](https://docs.rs/lock_api/latest/lock_api/struct.Mutex.html) or [`parking_lot::ReentrantMutex`](https://docs.rs/lock_api/latest/lock_api/struct.ReentrantMutex.html) and are wrapped in an [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) type. Like [`Arc`](https://docs.rs/parking_lot/latest/parking_lot/type.Mutex.html#method.lock_arc) ### `bigdecimal` Adds a dependency on [bigdecimal](https://docs.rs/bigdecimal) and enables conversions into its [`BigDecimal`](https://docs.rs/bigdecimal/latest/bigdecimal/struct.BigDecimal.html) type. ### `bytes` Adds a dependency on [bytes](https://docs.rs/bytes/latest/bytes) and enables conversions into its [`Bytes`](https://docs.rs/bytes/latest/bytes/struct.Bytes.html) type. ### `chrono` Adds a dependency on [chrono](https://docs.rs/chrono). Enables a conversion from [chrono](https://docs.rs/chrono)'s types to python: - [TimeDelta](https://docs.rs/chrono/latest/chrono/struct.TimeDelta.html) -> [`PyDelta`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDelta.html) - [FixedOffset](https://docs.rs/chrono/latest/chrono/offset/struct.FixedOffset.html) -> [`PyDelta`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDelta.html) - [Utc](https://docs.rs/chrono/latest/chrono/offset/struct.Utc.html) -> [`PyTzInfo`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTzInfo.html) - [NaiveDate](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html) -> [`PyDate`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDate.html) - [NaiveTime](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html) - [DateTime](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) ### `chrono-local` Enables conversion from and to [Local](https://docs.rs/chrono/latest/chrono/struct.Local.html) timezones. The current system timezone as determined by [`iana_time_zone::get_timezone()`](https://docs.rs/iana-time-zone/latest/iana_time_zone/fn.get_timezone.html) will be used for conversions. `chrono::DateTime` will convert from either of: - `datetime` objects with `tzinfo` equivalent to the current system timezone. - "naive" `datetime` objects (those without a `tzinfo`), as it is a convention that naive datetime objects should be treated as using the system timezone. When converting to Python, `Local` tzinfo is converted to a `zoneinfo.ZoneInfo` matching the current system timezone. ### `chrono-tz` Adds a dependency on [chrono-tz](https://docs.rs/chrono-tz). Enables conversion from and to [`Tz`](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html). ### `either` Adds a dependency on [either](https://docs.rs/either). Enables a conversions into [either](https://docs.rs/either)’s [`Either`](https://docs.rs/either/latest/either/enum.Either.html) type. ### `eyre` Adds a dependency on [eyre](https://docs.rs/eyre). Enables a conversion from [eyre](https://docs.rs/eyre)’s [`Report`](https://docs.rs/eyre/latest/eyre/struct.Report.html) type to [`PyErr`]({{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html), for easy error handling. ### `hashbrown` Adds a dependency on [hashbrown](https://docs.rs/hashbrown) and enables conversions into its [`HashMap`](https://docs.rs/hashbrown/latest/hashbrown/struct.HashMap.html) and [`HashSet`](https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html) types. ### `indexmap` Adds a dependency on [indexmap](https://docs.rs/indexmap) and enables conversions into its [`IndexMap`](https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html) type. ### `jiff-02` Adds a dependency on [jiff@0.2](https://docs.rs/jiff/0.2). Enables a conversion from [jiff](https://docs.rs/jiff)'s types to python: - [SignedDuration](https://docs.rs/jiff/0.2/jiff/struct.SignedDuration.html) -> [`PyDelta`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDelta.html) - [TimeZone](https://docs.rs/jiff/0.2/jiff/tz/struct.TimeZone.html) -> [`PyTzInfo`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTzInfo.html) - [Offset](https://docs.rs/jiff/0.2/jiff/tz/struct.Offset.html) -> [`PyTzInfo`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTzInfo.html) - [Date](https://docs.rs/jiff/0.2/jiff/civil/struct.Date.html) -> [`PyDate`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDate.html) - [Time](https://docs.rs/jiff/0.2/jiff/civil/struct.Time.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html) - [DateTime](https://docs.rs/jiff/0.2/jiff/civil/struct.DateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) - [Zoned](https://docs.rs/jiff/0.2/jiff/struct.Zoned.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) - [Timestamp](https://docs.rs/jiff/0.2/jiff/struct.Timestamp.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) - [ISOWeekDate](https://docs.rs/jiff/0.2/jiff/civil/struct.ISOWeekDate.html) -> [`PyDate`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDate.html) ### `lock_api` Adds a dependency on [lock_api](https://docs.rs/lock_api) and enables Pyo3's `MutexExt` trait for all mutexes that extend on [`lock_api::Mutex`](https://docs.rs/lock_api/latest/lock_api/struct.Mutex.html) and [`parking_lot::ReentrantMutex`](https://docs.rs/lock_api/latest/lock_api/struct.ReentrantMutex.html) (like `parking_lot` or `spin`). ### `num-bigint` Adds a dependency on [num-bigint](https://docs.rs/num-bigint) and enables conversions into its [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html) types. ### `num-complex` Adds a dependency on [num-complex](https://docs.rs/num-complex) and enables conversions into its [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type. ### `num-rational` Adds a dependency on [num-rational](https://docs.rs/num-rational) and enables conversions into its [`Ratio`](https://docs.rs/num-rational/latest/num_rational/struct.Ratio.html) type. ### `ordered-float` Adds a dependency on [ordered-float](https://docs.rs/ordered-float) and enables conversions between [ordered-float](https://docs.rs/ordered-float)'s types and Python: - [NotNan](https://docs.rs/ordered-float/latest/ordered_float/struct.NotNan.html) -> [`PyFloat`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyFloat.html) - [OrderedFloat](https://docs.rs/ordered-float/latest/ordered_float/struct.OrderedFloat.html) -> [`PyFloat`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyFloat.html) ### `parking-lot` Adds a dependency on [parking_lot](https://docs.rs/parking_lot) and enables Pyo3's `OnceExt` & `MutexExt` traits for [`parking_lot::Once`](https://docs.rs/parking_lot/latest/parking_lot/struct.Once.html) [`parking_lot::Mutex`](https://docs.rs/parking_lot/latest/parking_lot/type.Mutex.html) and [`parking_lot::ReentrantMutex`](https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html) types. ### `rust_decimal` Adds a dependency on [rust_decimal](https://docs.rs/rust_decimal) and enables conversions into its [`Decimal`](https://docs.rs/rust_decimal/latest/rust_decimal/struct.Decimal.html) type. ### `time` Adds a dependency on [time](https://docs.rs/time). Enables conversions between [time](https://docs.rs/time)'s types and Python: - [Date](https://docs.rs/time/0.3.38/time/struct.Date.html) -> [`PyDate`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDate.html) - [Time](https://docs.rs/time/0.3.38/time/struct.Time.html) -> [`PyTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTime.html) - [OffsetDateTime](https://docs.rs/time/0.3.38/time/struct.OffsetDateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) - [PrimitiveDateTime](https://docs.rs/time/0.3.38/time/struct.PrimitiveDateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) - [Duration](https://docs.rs/time/0.3.38/time/struct.Duration.html) -> [`PyDelta`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDelta.html) - [UtcOffset](https://docs.rs/time/0.3.38/time/struct.UtcOffset.html) -> [`PyTzInfo`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyTzInfo.html) - [UtcDateTime](https://docs.rs/time/0.3.38/time/struct.UtcDateTime.html) -> [`PyDateTime`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyDateTime.html) ### `serde` Enables (de)serialization of `Py` objects via [serde](https://serde.rs/). This allows to use [`#[derive(Serialize, Deserialize)`](https://serde.rs/derive.html) on structs that hold references to `#[pyclass]` instances ```rust,no_run # #[cfg(feature = "serde")] # #[allow(dead_code)] # mod serde_only { # use pyo3::prelude::*; # use serde::{Deserialize, Serialize}; #[pyclass] #[derive(Serialize, Deserialize)] struct Permission { name: String, } #[pyclass] #[derive(Serialize, Deserialize)] struct User { username: String, permissions: Vec>, } # } ``` ### `smallvec` Adds a dependency on [smallvec](https://docs.rs/smallvec) and enables conversions into its [`SmallVec`](https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html) type. [set-configuration-options]: https://doc.rust-lang.org/reference/conditional-compilation.html#set-configuration-options ### `uuid` Adds a dependency on [uuid](https://docs.rs/uuid) and enables conversions into its [`Uuid`](https://docs.rs/uuid/latest/uuid/struct.Uuid.html) type. ================================================ FILE: guide/src/free-threading.md ================================================ # Supporting Free-Threaded CPython CPython 3.14 declared support for the "free-threaded" build of CPython that does not rely on the [global interpreter lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock) (often referred to as the GIL) for thread safety. Since version 0.23, PyO3 supports building Rust extensions for the free-threaded Python build and calling into free-threaded Python from Rust. If you want more background on free-threaded Python in general, see the [what's new](https://docs.python.org/3/whatsnew/3.13.html#whatsnew313-free-threaded-cpython) entry in the 3.13 release notes (when the "free-threaded" build was first added as an experimental mode), the [free-threading HOWTO guide](https://docs.python.org/3/howto/free-threading-extensions.html#freethreading-extensions-howto) in the CPython docs, the [extension porting guide](https://py-free-threading.github.io/porting-extensions/) in the community-maintained Python free-threading guide, and [PEP 703](https://peps.python.org/pep-0703/), which provides the technical background for the free-threading implementation in CPython. In the GIL-enabled build (the only choice before the "free-threaded" build was introduced), the global interpreter lock serializes access to the Python runtime. The GIL is therefore a fundamental limitation to parallel scaling of multithreaded Python workflows, due to [Amdahl's law](https://en.wikipedia.org/wiki/Amdahl%27s_law), because any time spent executing a parallel processing task on only one execution context fundamentally cannot be sped up using parallelism. The free-threaded build removes this limit on multithreaded Python scaling. This means it's much more straightforward to achieve parallelism using the Python [`threading`] module. If you have ever needed to use [`multiprocessing`](https://docs.python.org/3/library/multiprocessing.html) to achieve a parallel speedup for some Python code, free-threading will likely allow the use of Python threads instead for the same workflow. PyO3's support for free-threaded Python will enable authoring native Python extensions that are thread-safe by construction, with much stronger safety guarantees than C extensions. Our goal is to enable ["fearless concurrency"](https://doc.rust-lang.org/book/ch16-00-concurrency.html) in the native Python runtime by building on the Rust [`Send` and `Sync`](https://doc.rust-lang.org/nomicon/send-and-sync.html) traits. This document provides advice for porting Rust code using PyO3 to run under free-threaded Python. ## Supporting free-threaded Python with PyO3 Since PyO3 0.28, PyO3 defaults to assuming Python modules created with it are thread-safe. This will be the case except for Rust code which has used `unsafe` to assume thread-safety incorrectly. An example of this is `unsafe` code which was written with the historical assumption that Python was single-threaded due to the GIL, and so the `Python<'py>` token used by PyO3 could be used to guarantee thread-safety. A module can opt-out of supporting free-threaded Python until it has audited its `unsafe` code for correctness by declaring the module with `#[pymodule(gil_used = true)]` (see below). Complicated `#[pyclass]` types may need to deal with thread-safety directly; there is [a dedicated section of the guide](./class/thread-safety.md) to discuss this. At a low-level, annotating a module sets the `Py_MOD_GIL` slot on modules defined by an extension to `Py_MOD_GIL_NOT_USED`, which allows the interpreter to see at runtime that the author of the extension thinks the extension is thread-safe. By opting-out of supporting free-threaded Python, the Python interpreter will re-enable the GIL at runtime while importing your module and print a `RuntimeWarning` with a message containing the name of the module causing it to re-enable the GIL. You can force the GIL to remain disabled by setting the `PYTHON_GIL=0` as an environment variable or passing `-Xgil=0` when starting Python (`0` means the GIL is turned off). If you are sure that all data structures exposed in a `PyModule` are thread-safe, then pass `gil_used = false` as a parameter to the `pymodule` procedural macro declaring the module or call `PyModule::gil_used` on a `PyModule` instance. For example: ### Example opting-in (Note: for PyO3 versions 0.23 through 0.27, the default was `gil_used = true` and so the opposite was needed; modules needed to opt-in to free-threaded Python support with `gil_used = false`.) ```rust,no_run /// This module relies on the GIL for thread safety #[pyo3::pymodule(gil_used = true)] mod my_extension { use pyo3::prelude::*; // this type is not thread-safe #[pyclass] struct MyNotThreadSafeType { // insert not thread-safe code } } ``` Or for a module that is set up without using the `pymodule` macro: ```rust,no_run use pyo3::prelude::*; # #[allow(dead_code)] fn register_child_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { let child_module = PyModule::new(parent_module.py(), "child_module")?; child_module.gil_used(true)?; parent_module.add_submodule(&child_module) } ``` See the [`string-sum`](https://github.com/PyO3/pyo3/tree/main/pyo3-ffi/examples/string-sum) example for how to declare free-threaded support using raw FFI calls for modules using single-phase initialization and the [`sequential`](https://github.com/PyO3/pyo3/tree/main/pyo3-ffi/examples/sequential) example for modules using multi-phase initialization. If you would like to use conditional compilation to trigger different code paths under the free-threaded build, you can use the `Py_GIL_DISABLED` attribute once you have configured your crate to generate the necessary build configuration data. See [the guide section](./building-and-distribution/multiple-python-versions.md) for more details about supporting multiple different Python versions, including the free-threaded build. ## Special considerations for the free-threaded build The free-threaded interpreter does not have a GIL. Many existing extensions providing mutable data structures relied on the GIL to lock Python objects and make interior mutability thread-safe. Calling into the CPython C API is only legal when an OS thread is explicitly attached to the interpreter runtime. In the GIL-enabled build, this happens when the GIL is acquired. In the free-threaded build there is no GIL, but the same C macros that release or acquire the GIL in the GIL-enabled build instead ask the interpreter to attach the thread to the Python runtime, and there can be many threads simultaneously attached. See [PEP 703](https://peps.python.org/pep-0703/#thread-states) for more background about how threads can be attached and detached from the interpreter runtime, in a manner analogous to releasing and acquiring the GIL in the GIL-enabled build. In the GIL-enabled build, PyO3 uses the [`Python<'py>`] type and the `'py` lifetime to signify that the global interpreter lock is held. In the freethreaded build, holding a `'py` lifetime means only that the thread is currently attached to the Python interpreter -- other threads can be simultaneously interacting with the interpreter. ### Attaching to the runtime You still need to obtain a `'py` lifetime to interact with Python objects or call into the CPython C API. If you are not yet attached to the Python runtime, you can register a thread using the [`Python::attach`] function. Threads created via the Python [`threading`] module do not need to do this, and pyo3 will handle setting up the [`Python<'py>`] token when CPython calls into your extension. ### Detaching to avoid hangs and deadlocks The free-threaded build triggers global synchronization events in the following situations: - During garbage collection in order to get a globally consistent view of reference counts and references between objects - In Python 3.13, when the first background thread is started in order to mark certain objects as immortal - When either `sys.settrace` or `sys.setprofile` are called in order to instrument running code objects and threads - During a call to `os.fork()`, to ensure a process-wide consistent state. This is a non-exhaustive list and there may be other situations in future Python versions that can trigger global synchronization events. This means that you should detach from the interpreter runtime using [`Python::detach`] in exactly the same situations as you should detach from the runtime in the GIL-enabled build: when doing long-running tasks that do not require the CPython runtime or when doing any task that needs to re-attach to the runtime (see the [guide section](parallelism.md#sharing-python-objects-between-rust-threads) that covers this). In the former case, you would observe a hang on threads that are waiting on the long-running task to complete, and in the latter case you would see a deadlock while a thread tries to attach after the runtime triggers a global synchronization event, but the spawning thread prevents the synchronization event from completing. ### Exceptions and panics for multithreaded access of mutable `pyclass` instances Data attached to `pyclass` instances is protected from concurrent access by a `RefCell`-like pattern of runtime borrow checking. Like a `RefCell`, PyO3 will raise exceptions (or in some cases panic) to enforce exclusive access for mutable borrows. It was always possible to generate panics like this in PyO3 in code that releases the GIL with [`Python::detach`] or calling a python method accepting `&self` from a `&mut self` (see [the docs on interior mutability](./class.md#bound-and-interior-mutability),) but now in free-threaded Python there are more opportunities to trigger these panics from Python because there is no GIL to lock concurrent access to mutably borrowed data from Python. The most straightforward way to trigger this problem is to use the Python [`threading`] module to simultaneously call a rust function that mutably borrows a [`pyclass`]({{#PYO3_DOCS_URL}}/pyo3/attr.pyclass.html) in multiple threads. For example, consider the following implementation: ```rust,no_run # use pyo3::prelude::*; #[pyclass] #[derive(Default)] struct ThreadIter { count: usize, } #[pymethods] impl ThreadIter { #[new] pub fn new() -> Self { Default::default() } fn __next__(&mut self, py: Python<'_>) -> usize { self.count += 1; self.count } } ``` And then if we do something like this in Python: ```python import concurrent.futures from my_module import ThreadIter i = ThreadIter() def increment(): next(i) with concurrent.futures.ThreadPoolExecutor(max_workers=16) as tpe: futures = [tpe.submit(increment) for _ in range(100)] [f.result() for f in futures] ``` We will see an exception: ```text Traceback (most recent call last) File "example.py", line 5, in next(i) RuntimeError: Already borrowed ``` We may allow user-selectable semantics for mutable pyclass definitions in a future version of PyO3, allowing some form of opt-in locking to emulate the GIL if that is needed. For now you should explicitly add locking, possibly using conditional compilation or using the critical section API, to avoid creating deadlocks with the GIL. ### Cannot build extensions using the limited API The free-threaded build uses a completely new ABI and there is not yet an equivalent to the limited API for the free-threaded ABI. That means if your crate depends on PyO3 using the `abi3` feature or an an `abi3-pyxx` feature, PyO3 will print a warning and ignore that setting when building extensions using the free-threaded interpreter. This means that if your package makes use of the ABI forward compatibility provided by the limited API to upload only one wheel for each release of your package, you will need to update your release procedure to also upload a version-specific free-threaded wheel. See [the guide section](./building-and-distribution/multiple-python-versions.md) for more details about supporting multiple different Python versions, including the free-threaded build. ### Thread-safe single initialization To initialize data exactly once, use the [`PyOnceLock`] type, which is a close equivalent to [`std::sync::OnceLock`][`OnceLock`] that also helps avoid deadlocks by detaching from the Python interpreter when threads are blocking waiting for another thread to complete initialization. If already using [`OnceLock`] and it is impractical to replace with a [`PyOnceLock`], there is the [`OnceLockExt`] extension trait which adds [`OnceLockExt::get_or_init_py_attached`] to detach from the interpreter when blocking in the same fashion as [`PyOnceLock`]. Here is an example using [`PyOnceLock`] to single-initialize a runtime cache holding a `Py`: ```rust # use pyo3::prelude::*; use pyo3::sync::PyOnceLock; use pyo3::types::PyDict; let cache: PyOnceLock> = PyOnceLock::new(); Python::attach(|py| { // guaranteed to be called once and only once cache.get_or_init(py, || PyDict::new(py).unbind()) }); ``` In cases where a function must run exactly once, you can bring the [`OnceExt`] trait into scope. The [`OnceExt`] trait adds [`OnceExt::call_once_py_attached`] and [`OnceExt::call_once_force_py_attached`] functions to the api of `std::sync::Once`, enabling use of [`Once`] in contexts where the thread is attached to the Python interpreter. These functions are analogous to [`Once::call_once`], [`Once::call_once_force`] except they accept a [`Python<'py>`] token in addition to an `FnOnce`. All of these functions detach from the interpreter before blocking and re-attach before executing the function, avoiding deadlocks that are possible without using the PyO3 extension traits. Here the same example as above built using a [`Once`] instead of a [`PyOnceLock`]: ```rust # use pyo3::prelude::*; use std::sync::Once; use pyo3::sync::OnceExt; use pyo3::types::PyDict; struct RuntimeCache { once: Once, cache: Option> } let mut cache = RuntimeCache { once: Once::new(), cache: None }; Python::attach(|py| { // guaranteed to be called once and only once cache.once.call_once_py_attached(py, || { cache.cache = Some(PyDict::new(py).unbind()); }); }); ``` ### `GILProtected` has been removed [`GILProtected`] was a PyO3 type that allowed mutable access to static data by leveraging the GIL to lock concurrent access from other threads. In free-threaded Python there is no GIL, so this type had to be replaced with alternative forms of locking. In many cases, a type from [`std::sync::atomic`](https://doc.rust-lang.org/std/sync/atomic/) or a [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) was sufficient. Before: ```rust,ignore # fn main() { # #[cfg(not(Py_GIL_DISABLED))] { # use pyo3::prelude::*; use pyo3::sync::GILProtected; use pyo3::types::{PyDict, PyNone}; use std::cell::RefCell; static OBJECTS: GILProtected>>> = GILProtected::new(RefCell::new(Vec::new())); Python::attach(|py| { // stand-in for something that executes arbitrary Python code let d = PyDict::new(py); d.set_item(PyNone::get(py), PyNone::get(py)).unwrap(); OBJECTS.get(py).borrow_mut().push(d.unbind()); }); # }} ``` After (using a `Mutex`): ```rust # use pyo3::prelude::*; # fn main() { use pyo3::types::{PyDict, PyNone}; use std::sync::Mutex; static OBJECTS: Mutex>> = Mutex::new(Vec::new()); Python::attach(|py| { // stand-in for something that executes arbitrary Python code let d = PyDict::new(py); d.set_item(PyNone::get(py), PyNone::get(py)).unwrap(); // as with any `Mutex` usage, lock the mutex for as little time as possible // in this case, we do it just while pushing into the `Vec` OBJECTS.lock().unwrap().push(d.unbind()); }); # } ``` If you are executing arbitrary Python code while holding the lock, then you should import the [`MutexExt`] trait and use the `lock_py_attached` method instead of `lock`. This ensures that global synchronization events started by the Python runtime can proceed, avoiding possible deadlocks with the interpreter. [`GILProtected`]: https://docs.rs/pyo3/0.22/pyo3/sync/struct.GILProtected.html [`MutexExt`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.MutexExt.html [`Once`]: https://doc.rust-lang.org/stable/std/sync/struct.Once.html [`Once::call_once`]: https://doc.rust-lang.org/stable/std/sync/struct.Once.html#method.call_once [`Once::call_once_force`]: https://doc.rust-lang.org/stable/std/sync/struct.Once.html#method.call_once_force [`OnceExt`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceExt.html [`OnceExt::call_once_py_attached`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceExt.html#tymethod.call_once_py_attached [`OnceExt::call_once_force_py_attached`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceExt.html#tymethod.call_once_force_py_attached [`OnceLockExt`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceLockExt.html [`OnceLockExt::get_or_init_py_attached`]: {{#PYO3_DOCS_URL}}/pyo3/sync/trait.OnceLockExt.html#tymethod.get_or_init_py_attached [`OnceLock`]: https://doc.rust-lang.org/stable/std/sync/struct.OnceLock.html [`Python::detach`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.detach [`Python::attach`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.attach [`Python<'py>`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html [`PyOnceLock`]: {{#PYO3_DOCS_URL}}/pyo3/sync/struct.PyOnceLock.html [`threading`]: https://docs.python.org/3/library/threading.html ================================================ FILE: guide/src/function/error-handling.md ================================================ # Error handling This chapter contains a little background of error handling in Rust and how PyO3 integrates this with Python exceptions. This covers enough detail to create a `#[pyfunction]` which raises Python exceptions from errors originating in Rust. There is a later section of the guide on [Python exceptions](../exception.md) which covers exception types in more detail. ## Representing Python exceptions Rust code uses the generic [`Result`] enum to propagate errors. The error type `E` is chosen by the code author to describe the possible errors which can happen. PyO3 has the [`PyErr`] type which represents a Python exception. If a PyO3 API could result in a Python exception being raised, the return type of that `API` will be [`PyResult`], which is an alias for the type `Result`. In summary: - When Python exceptions are raised and caught by PyO3, the exception will be stored in the `Err` variant of the `PyResult`. - Passing Python exceptions through Rust code then uses all the "normal" techniques such as the `?` operator, with `PyErr` as the error type. - Finally, when a `PyResult` crosses from Rust back to Python via PyO3, if the result is an `Err` variant the contained exception will be raised. (There are many great tutorials on Rust error handling and the `?` operator, so this guide will not go into detail on Rust-specific topics.) ## Raising an exception from a function As indicated in the previous section, when a `PyResult` containing an `Err` crosses from Rust to Python, PyO3 will raise the exception contained within. Accordingly, to raise an exception from a `#[pyfunction]`, change the return type `T` to `PyResult`. When the function returns an `Err` it will raise a Python exception. (Other `Result` types can be used as long as the error `E` has a `From` conversion for `PyErr`, see [custom Rust error types](#custom-rust-error-types) below.) This also works for functions in `#[pymethods]`. For example, the following `check_positive` function raises a `ValueError` when the input is negative: ```rust use pyo3::exceptions::PyValueError; use pyo3::prelude::*; #[pyfunction] fn check_positive(x: i32) -> PyResult<()> { if x < 0 { Err(PyValueError::new_err("x is negative")) } else { Ok(()) } } # # fn main(){ # Python::attach(|py|{ # let fun = pyo3::wrap_pyfunction!(check_positive, py).unwrap(); # fun.call1((-1,)).unwrap_err(); # fun.call1((1,)).unwrap(); # }); # } ``` All built-in Python exception types are defined in the [`pyo3::exceptions`] module. They have a `new_err` constructor to directly build a `PyErr`, as seen in the example above. ## Custom Rust error types PyO3 will automatically convert a `Result` returned by a `#[pyfunction]` into a `PyResult` as long as there is an implementation of `std::from::From for PyErr`. Many error types in the Rust standard library have a [`From`] conversion defined in this way. If the type `E` you are handling is defined in a third-party crate, see the section on [foreign rust error types](#foreign-rust-error-types) below for ways to work with this error. The following example makes use of the implementation of `From for PyErr` to raise exceptions encountered when parsing strings as integers: ```rust # use pyo3::prelude::*; use std::num::ParseIntError; #[pyfunction] fn parse_int(x: &str) -> Result { x.parse() } # fn main() { # Python::attach(|py| { # let fun = pyo3::wrap_pyfunction!(parse_int, py).unwrap(); # let value: usize = fun.call1(("5",)).unwrap().extract().unwrap(); # assert_eq!(value, 5); # }); # } ``` When passed a string which doesn't contain a floating-point number, the exception raised will look like the below: ```python >>> parse_int("bar") Traceback (most recent call last): File "", line 1, in ValueError: invalid digit found in string ``` As a more complete example, the following snippet defines a Rust error named `CustomIOError`. It then defines a `From for PyErr`, which returns a `PyErr` representing Python's `OSError`. Therefore, it can use this error in the result of a `#[pyfunction]` directly, relying on the conversion if it has to be propagated into a Python exception. ```rust use pyo3::exceptions::PyOSError; use pyo3::prelude::*; use std::fmt; #[derive(Debug)] struct CustomIOError; impl std::error::Error for CustomIOError {} impl fmt::Display for CustomIOError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Oh no!") } } impl std::convert::From for PyErr { fn from(err: CustomIOError) -> PyErr { PyOSError::new_err(err.to_string()) } } pub struct Connection {/* ... */} fn bind(addr: String) -> Result { if &addr == "0.0.0.0" { Err(CustomIOError) } else { Ok(Connection{ /* ... */}) } } #[pyfunction] fn connect(s: String) -> Result<(), CustomIOError> { bind(s)?; // etc. Ok(()) } fn main() { Python::attach(|py| { let fun = pyo3::wrap_pyfunction!(connect, py).unwrap(); let err = fun.call1(("0.0.0.0",)).unwrap_err(); assert!(err.is_instance_of::(py)); }); } ``` If lazy construction of the Python exception instance is desired, the [`PyErrArguments`]({{#PYO3_DOCS_URL}}/pyo3/trait.PyErrArguments.html) trait can be implemented instead of `From`. In that case, actual exception argument creation is delayed until the `PyErr` is needed. A final note is that any errors `E` which have a `From` conversion can be used with the `?` ("try") operator with them. An alternative implementation of the above `parse_int` which instead returns `PyResult` is below: ```rust use pyo3::prelude::*; fn parse_int(s: String) -> PyResult { let x = s.parse()?; Ok(x) } # # use pyo3::exceptions::PyValueError; # # fn main() { # Python::attach(|py| { # assert_eq!(parse_int(String::from("1")).unwrap(), 1); # assert_eq!(parse_int(String::from("1337")).unwrap(), 1337); # # assert!(parse_int(String::from("-1")) # .unwrap_err() # .is_instance_of::(py)); # assert!(parse_int(String::from("foo")) # .unwrap_err() # .is_instance_of::(py)); # assert!(parse_int(String::from("13.37")) # .unwrap_err() # .is_instance_of::(py)); # }) # } ``` ## Foreign Rust error types The Rust compiler will not permit implementation of traits for types outside of the crate where the type is defined. (This is known as the "orphan rule".) Given a type `OtherError` which is defined in third-party code, there are two main strategies available to integrate it with PyO3: - Create a newtype wrapper, e.g. `MyOtherError`. Then implement `From for PyErr` (or `PyErrArguments`), as well as `From` for `MyOtherError`. - Use Rust's Result combinators such as `map_err` to write code freely to convert `OtherError` into whatever is needed. This requires boilerplate at every usage however gives unlimited flexibility. To detail the newtype strategy a little further, the key trick is to return `Result` from the `#[pyfunction]`. This means that PyO3 will make use of `From for PyErr` to create Python exceptions while the `#[pyfunction]` implementation can use `?` to convert `OtherError` to `MyOtherError` automatically. The following example demonstrates this for some imaginary third-party crate `some_crate` with a function `get_x` returning `Result`: ```rust # mod some_crate { # pub struct OtherError(()); # impl OtherError { # pub fn message(&self) -> &'static str { "some error occurred" } # } # pub fn get_x() -> Result { Ok(5) } # } use pyo3::prelude::*; use pyo3::exceptions::PyValueError; use some_crate::{OtherError, get_x}; struct MyOtherError(OtherError); impl From for PyErr { fn from(error: MyOtherError) -> Self { PyValueError::new_err(error.0.message()) } } impl From for MyOtherError { fn from(other: OtherError) -> Self { Self(other) } } #[pyfunction] fn wrapped_get_x() -> Result { // get_x is a function returning Result let x: i32 = get_x()?; Ok(x) } # fn main() { # Python::attach(|py| { # let fun = pyo3::wrap_pyfunction!(wrapped_get_x, py).unwrap(); # let value: usize = fun.call0().unwrap().extract().unwrap(); # assert_eq!(value, 5); # }); # } ``` ## Notes In Python 3.11 and up, notes can be added to Python exceptions to provide additional debugging information when printing the exception. In PyO3, you can use the `add_note` method on `PyErr` to accomplish this functionality. [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html [`Result`]: https://doc.rust-lang.org/stable/std/result/enum.Result.html [`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/type.PyResult.html [`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html [`pyo3::exceptions`]: {{#PYO3_DOCS_URL}}/pyo3/exceptions/index.html ================================================ FILE: guide/src/function/signature.md ================================================ # Function signatures The `#[pyfunction]` attribute also accepts parameters to control how the generated Python function accepts arguments. Just like in Python, arguments can be positional-only, keyword-only, or accept either. `*args` lists and `**kwargs` dicts can also be accepted. These parameters also work for `#[pymethods]` which will be introduced in the [Python Classes](../class.md) section of the guide. Like Python, by default PyO3 accepts all arguments as either positional or keyword arguments. All arguments are required by default. This behaviour can be configured by the `#[pyo3(signature = (...))]` option which allows writing a signature in Python syntax. This section of the guide goes into detail about use of the `#[pyo3(signature = (...))]` option and its related option `#[pyo3(text_signature = "...")]` ## Using `#[pyo3(signature = (...))]` For example, below is a function that accepts arbitrary keyword arguments (`**kwargs` in Python syntax) and returns the number that was passed: ```rust,no_run #[pyo3::pymodule] mod module_with_functions { use pyo3::prelude::*; use pyo3::types::PyDict; #[pyfunction] #[pyo3(signature = (**kwds))] fn num_kwds(kwds: Option<&Bound<'_, PyDict>>) -> usize { kwds.map_or(0, |dict| dict.len()) } } ``` Just like in Python, the following constructs can be part of the signature:: - `/`: positional-only arguments separator, each parameter defined before `/` is a positional-only parameter. - `*`: var arguments separator, each parameter defined after `*` is a keyword-only parameter. - `*args`: "args" is var args. Type of the `args` parameter has to be `&Bound<'_, PyTuple>`. - `**kwargs`: "kwargs" receives keyword arguments. The type of the `kwargs` parameter has to be `Option<&Bound<'_, PyDict>>`. - `arg=Value`: arguments with default value. If the `arg` argument is defined after var arguments, it is treated as a keyword-only argument. Note that `Value` has to be valid rust code, PyO3 just inserts it into the generated code unmodified. Example: ```rust,no_run # use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; # # #[pyclass] # struct MyClass { # num: i32, # } #[pymethods] impl MyClass { #[new] #[pyo3(signature = (num=-1))] fn new(num: i32) -> Self { MyClass { num } } #[pyo3(signature = (num=10, *py_args, name="Hello", **py_kwargs))] fn method( &mut self, num: i32, py_args: &Bound<'_, PyTuple>, name: &str, py_kwargs: Option<&Bound<'_, PyDict>>, ) -> String { let num_before = self.num; self.num = num; format!( "num={} (was previously={}), py_args={:?}, name={}, py_kwargs={:?} ", num, num_before, py_args, name, py_kwargs, ) } fn make_change(&mut self, num: i32) -> PyResult { self.num = num; Ok(format!("num={}", self.num)) } } ``` Arguments of type `Python` must not be part of the signature: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyfunction] #[pyo3(signature = (lambda))] pub fn simple_python_bound_function(py: Python<'_>, lambda: Py) -> PyResult<()> { Ok(()) } ``` N.B. the position of the `/` and `*` arguments (if included) control the system of handling positional and keyword arguments. In Python: ```python import mymodule mc = mymodule.MyClass() print(mc.method(44, False, "World", 666, x=44, y=55)) print(mc.method(num=-1, name="World")) print(mc.make_change(44)) ``` Produces output: ```text num=44 (was previously=-1), py_args=(False, 'World', 666), name=Hello, py_kwargs=Some({'x': 44, 'y': 55}) num=-1 (was previously=44), py_args=(), name=World, py_kwargs=None num=44 ``` > [!NOTE] > To use keywords like `struct` as a function argument, use "raw identifier" syntax `r#struct` in both the signature and the function definition: > > ```rust,no_run > # #![allow(dead_code)] > # use pyo3::prelude::*; > #[pyfunction(signature = (r#struct = "foo"))] > fn function_with_keyword(r#struct: &str) { > # let _ = r#struct; > /* ... */ > } > ``` ## Making the function signature available to Python The function signature is exposed to Python via the `__text_signature__` attribute. PyO3 automatically generates this for every `#[pyfunction]` and all `#[pymethods]` directly from the Rust function, taking into account any override done with the `#[pyo3(signature = (...))]` option. This automatic generation can only display the value of default arguments for strings, integers, boolean types, and `None`. Any other default arguments will be displayed as `...`. (`.pyi` type stub files commonly also use `...` for default arguments in the same way.) In cases where the automatically-generated signature needs adjusting, it can [be overridden](#overriding-the-generated-signature) using the `#[pyo3(text_signature)]` option.) The example below creates a function `add` which accepts two positional-only arguments `a` and `b`, where `b` has a default value of zero. ```rust use pyo3::prelude::*; /// This function adds two unsigned 64-bit integers. #[pyfunction] #[pyo3(signature = (a, b=0, /))] fn add(a: u64, b: u64) -> u64 { a + b } # # fn main() -> PyResult<()> { # Python::attach(|py| { # let fun = pyo3::wrap_pyfunction!(add, py)?; # # let doc: String = fun.getattr("__doc__")?.extract()?; # assert_eq!(doc, "This function adds two unsigned 64-bit integers."); # # let inspect = PyModule::import(py, "inspect")?.getattr("signature")?; # let sig: String = inspect # .call1((fun,))? # .call_method0("__str__")? # .extract()?; # # #[cfg(Py_3_8)] // on 3.7 the signature doesn't render b, upstream bug? # assert_eq!(sig, "(a, b=0, /)"); # # Ok(()) # }) # } ``` The following IPython output demonstrates how this generated signature will be seen from Python tooling: ```text >>> pyo3_test.add.__text_signature__ '(a, b=..., /)' >>> pyo3_test.add? Signature: pyo3_test.add(a, b=0, /) Docstring: This function adds two unsigned 64-bit integers. Type: builtin_function_or_method ``` ### Overriding the generated signature The `#[pyo3(text_signature = "()")]` attribute can be used to override the default generated signature. In the snippet below, the text signature attribute is used to include the default value of `0` for the argument `b`, instead of the automatically-generated default value of `...`: ```rust use pyo3::prelude::*; /// This function adds two unsigned 64-bit integers. #[pyfunction] #[pyo3(signature = (a, b=0, /), text_signature = "(a, b=0, /)")] fn add(a: u64, b: u64) -> u64 { a + b } # # fn main() -> PyResult<()> { # Python::attach(|py| { # let fun = pyo3::wrap_pyfunction!(add, py)?; # # let doc: String = fun.getattr("__doc__")?.extract()?; # assert_eq!(doc, "This function adds two unsigned 64-bit integers."); # # let inspect = PyModule::import(py, "inspect")?.getattr("signature")?; # let sig: String = inspect # .call1((fun,))? # .call_method0("__str__")? # .extract()?; # assert_eq!(sig, "(a, b=0, /)"); # # Ok(()) # }) # } ``` PyO3 will include the contents of the annotation unmodified as the `__text_signature__`. Below shows how IPython will now present this (see the default value of 0 for b): ```text >>> pyo3_test.add.__text_signature__ '(a, b=0, /)' >>> pyo3_test.add? Signature: pyo3_test.add(a, b=0, /) Docstring: This function adds two unsigned 64-bit integers. Type: builtin_function_or_method ``` If no signature is wanted at all, `#[pyo3(text_signature = None)]` will disable the built-in signature. The snippet below demonstrates use of this: ```rust use pyo3::prelude::*; /// This function adds two unsigned 64-bit integers. #[pyfunction] #[pyo3(signature = (a, b=0, /), text_signature = None)] fn add(a: u64, b: u64) -> u64 { a + b } # # fn main() -> PyResult<()> { # Python::attach(|py| { # let fun = pyo3::wrap_pyfunction!(add, py)?; # # let doc: String = fun.getattr("__doc__")?.extract()?; # assert_eq!(doc, "This function adds two unsigned 64-bit integers."); # assert!(fun.getattr("__text_signature__")?.is_none()); # # Ok(()) # }) # } ``` Now the function's `__text_signature__` will be set to `None`, and IPython will not display any signature in the help: ```text >>> pyo3_test.add.__text_signature__ == None True >>> pyo3_test.add? Docstring: This function adds two unsigned 64-bit integers. Type: builtin_function_or_method ``` ### Type annotations in the signature When the `experimental-inspect` Cargo feature is enabled, the `signature` attribute can also contain type hints: ```rust # #[cfg(feature = "experimental-inspect")] { use pyo3::prelude::*; #[pymodule] pub mod example { use pyo3::prelude::*; #[pyfunction] #[pyo3(signature = (arg: "list[int]") -> "list[int]")] fn list_of_int_identity(arg: Bound<'_, PyAny>) -> Bound<'_, PyAny> { arg } } # } ``` It enables the [work-in-progress capacity of PyO3 to autogenerate type stubs](../type-stub.md) to generate a file with the correct type hints: ```python def list_of_int_identity(arg: list[int]) -> list[int]: ... ``` instead of the generic: ```python import typing def list_of_int_identity(arg: typing.Any) -> typing.Any: ... ``` Note that currently type annotations must be written as Rust strings. ================================================ FILE: guide/src/function-calls.md ================================================ # Calling Python functions ================================================ FILE: guide/src/function.md ================================================ # Python functions The `#[pyfunction]` attribute is used to define a Python function from a Rust function. Once defined, the function needs to be added to a [module](./module.md). The following example defines a function called `double` in a Python module called `my_extension`: ```rust,no_run #[pyo3::pymodule] mod my_extension { use pyo3::prelude::*; #[pyfunction] fn double(x: usize) -> usize { x * 2 } } ``` This chapter of the guide explains full usage of the `#[pyfunction]` attribute. In this first section, the following topics are covered: - [Function options](#function-options) - [`#[pyo3(name = "...")]`](#name) - [`#[pyo3(signature = (...))]`](#signature) - [`#[pyo3(text_signature = "...")]`](#text_signature) - [`#[pyo3(pass_module)]`](#pass_module) - [`#[pyo3(warn(message = "...", category = ...))]`](#warn) - [Per-argument options](#per-argument-options) - [Advanced function patterns](#advanced-function-patterns) There are also additional sections on the following topics: - [Function Signatures](./function/signature.md) - [Error Handling](./function/error-handling.md) ## Function options The `#[pyo3]` attribute can be used to modify properties of the generated Python function. It can take any combination of the following options: - `#[pyo3(name = "...")]` Overrides the name exposed to Python. In the following example, the Rust function `no_args_py` will be added to the Python module `module_with_functions` as the Python function `no_args`: ```rust # use pyo3::prelude::*; #[pyo3::pymodule] mod module_with_functions { use pyo3::prelude::*; #[pyfunction] #[pyo3(name = "no_args")] fn no_args_py() -> usize { 42 } } # Python::attach(|py| { # let m = pyo3::wrap_pymodule!(module_with_functions)(py); # assert!(m.getattr(py, "no_args").is_ok()); # assert!(m.getattr(py, "no_args_py").is_err()); # }); ``` - `#[pyo3(signature = (...))]` Defines the function signature in Python. See [Function Signatures](./function/signature.md). - `#[pyo3(text_signature = "...")]` Overrides the PyO3-generated function signature visible in Python tooling (such as via [`inspect.signature`]). See the [corresponding topic in the Function Signatures subchapter](./function/signature.md#making-the-function-signature-available-to-python). - `#[pyo3(pass_module)]` Set this option to make PyO3 pass the containing module as the first argument to the function. It is then possible to use the module in the function body. The first argument **must** be of type `&Bound<'_, PyModule>`, `Bound<'_, PyModule>`, or `Py`. The following example creates a function `pyfunction_with_module` which returns the containing module's name (i.e. `module_with_fn`): ```rust,no_run #[pyo3::pymodule] mod module_with_fn { use pyo3::prelude::*; use pyo3::types::PyString; #[pyfunction] #[pyo3(pass_module)] fn pyfunction_with_module<'py>( module: &Bound<'py, PyModule>, ) -> PyResult> { module.name() } } ``` - `#[pyo3(warn(message = "...", category = ...))]` This option is used to display a warning when the function is used in Python. It is equivalent to [`warnings.warn(message, category)`](https://docs.python.org/3/library/warnings.html#warnings.warn). The `message` parameter is a string that will be displayed when the function is called, and the `category` parameter is optional and has to be a subclass of [`Warning`](https://docs.python.org/3/library/exceptions.html#Warning). When the `category` parameter is not provided, the warning will be defaulted to [`UserWarning`](https://docs.python.org/3/library/exceptions.html#UserWarning). > Note: when used with `#[pymethods]`, this attribute does not work with `#[classattr]` nor `__traverse__` magic method. The following are examples of using the `#[pyo3(warn)]` attribute: ```rust use pyo3::prelude::*; #[pymodule] mod raising_warning_fn { use pyo3::prelude::pyfunction; use pyo3::exceptions::PyFutureWarning; #[pyfunction] #[pyo3(warn(message = "This is a warning message"))] fn function_with_warning() -> usize { 42 } #[pyfunction] #[pyo3(warn(message = "This function is warning with FutureWarning", category = PyFutureWarning))] fn function_with_warning_and_custom_category() -> usize { 42 } } # use pyo3::exceptions::{PyFutureWarning, PyUserWarning}; # use pyo3::types::{IntoPyDict, PyList}; # use pyo3::PyTypeInfo; # # fn catch_warning(py: Python<'_>, f: impl FnOnce(&Bound<'_, PyList>) -> ()) -> PyResult<()> { # let warnings = py.import("warnings")?; # let kwargs = [("record", true)].into_py_dict(py)?; # let catch_warnings = warnings # .getattr("catch_warnings")? # .call((), Some(&kwargs))?; # let list = catch_warnings.call_method0("__enter__")?.cast_into()?; # warnings.getattr("simplefilter")?.call1(("always",))?; // show all warnings # f(&list); # catch_warnings # .call_method1("__exit__", (py.None(), py.None(), py.None())) # .unwrap(); # Ok(()) # } # # macro_rules! assert_warnings { # ($py:expr, $body:expr, [$(($category:ty, $message:literal)),+] $(,)? ) => { # catch_warning($py, |list| { # $body; # let expected_warnings = [$((<$category as PyTypeInfo>::type_object($py), $message)),+]; # assert_eq!(list.len(), expected_warnings.len()); # for (warning, (category, message)) in list.iter().zip(expected_warnings) { # assert!(warning.getattr("category").unwrap().is(&category)); # assert_eq!( # warning.getattr("message").unwrap().str().unwrap().to_string_lossy(), # message # ); # } # }).unwrap(); # }; # } # # Python::attach(|py| { # assert_warnings!( # py, # { # let m = pyo3::wrap_pymodule!(raising_warning_fn)(py); # let f1 = m.getattr(py, "function_with_warning").unwrap(); # let f2 = m.getattr(py, "function_with_warning_and_custom_category").unwrap(); # f1.call0(py).unwrap(); # f2.call0(py).unwrap(); # }, # [ # (PyUserWarning, "This is a warning message"), # ( # PyFutureWarning, # "This function is warning with FutureWarning" # ) # ] # ); # }); ``` When the functions are called as the following, warnings will be displayed. ```python import warnings from raising_warning_fn import function_with_warning, function_with_warning_and_custom_category function_with_warning() function_with_warning_and_custom_category() ``` The warning output will be: ```plaintext UserWarning: This is a warning message FutureWarning: This function is warning with FutureWarning ``` ## Per-argument options The `#[pyo3]` attribute can be used on individual arguments to modify properties of them in the generated function. It can take any combination of the following options: - `#[pyo3(from_py_with = ...)]` Set this on an option to specify a custom function to convert the function argument from Python to the desired Rust type, instead of using the default `FromPyObject` extraction. The function signature must be `fn(&Bound<'_, PyAny>) -> PyResult` where `T` is the Rust type of the argument. The following example uses `from_py_with` to convert the input Python object to its length: ```rust use pyo3::prelude::*; fn get_length(obj: &Bound<'_, PyAny>) -> PyResult { obj.len() } #[pyfunction] fn object_length(#[pyo3(from_py_with = get_length)] argument: usize) -> usize { argument } # Python::attach(|py| { # let f = pyo3::wrap_pyfunction!(object_length)(py).unwrap(); # assert_eq!(f.call1((vec![1, 2, 3],)).unwrap().extract::().unwrap(), 3); # }); ``` ## Advanced function patterns ### Calling Python functions in Rust You can pass Python `def`'d functions and built-in functions to Rust functions [`PyFunction`] corresponds to regular Python functions while [`PyCFunction`] describes built-ins such as `repr()`. You can also use [`Bound<'_, PyAny>::is_callable`] to check if you have a callable object. `is_callable` will return `true` for functions (including lambdas), methods and objects with a `__call__` method. You can call the object with [`Bound<'_, PyAny>::call`] with the args as first parameter and the kwargs (or `None`) as second parameter. There are also [`Bound<'_, PyAny>::call0`] with no args and [`Bound<'_, PyAny>::call1`] with only positional args. ### Calling Rust functions in Python The ways to convert a Rust function into a Python object vary depending on the function: - Named functions, e.g. `fn foo()`: add `#[pyfunction]` and then use [`wrap_pyfunction!`] to get the corresponding [`PyCFunction`]. - Anonymous functions (or closures), e.g. `foo: fn()` either: - use a `#[pyclass]` struct which stores the function as a field and implement `__call__` to call the stored function. - use `PyCFunction::new_closure` to create an object directly from the function. ### Accessing the FFI functions In order to make Rust functions callable from Python, PyO3 generates an `extern "C"` function whose exact signature depends on the Rust signature. (PyO3 chooses the optimal Python argument passing convention.) It then embeds the call to the Rust function inside this FFI-wrapper function. This wrapper handles extraction of the regular arguments and the keyword arguments from the input `PyObject`s. The `wrap_pyfunction` macro can be used to directly get a `Bound` given a `#[pyfunction]` and a `Bound`: `wrap_pyfunction!(rust_fun, module)`. [`Bound<'_, PyAny>::is_callable`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.is_callable [`Bound<'_, PyAny>::call`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.call [`Bound<'_, PyAny>::call0`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.call0 [`Bound<'_, PyAny>::call1`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.call1 [`wrap_pyfunction!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.wrap_pyfunction.html [`PyFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyFunction.html [`PyCFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyCFunction.html [`inspect.signature`]: https://docs.python.org/3/library/inspect.html#inspect.signature ================================================ FILE: guide/src/getting-started.md ================================================ # Installation To get started using PyO3 you will need three things: a Rust toolchain, a Python environment, and a way to build. We'll cover each of these below. > [!TIP] > If you'd like to chat to the PyO3 maintainers and other PyO3 users, consider joining the [PyO3 Discord server](https://discord.gg/33kcChzH7f). We're keen to hear about your experience getting started, so we can make PyO3 as accessible as possible for everyone! ## Rust First, make sure you have Rust installed on your system. If you haven't already done so, try following the instructions [on the Rust website](https://www.rust-lang.org/tools/install). PyO3 runs on both the `stable` and `nightly` versions so you can choose whichever one fits you best. The minimum required Rust version is 1.83. If you can run `rustc --version` and the version is new enough you're good to go! ## Python To use PyO3, you need at least Python 3.7. While you can simply use the default Python interpreter on your system, it is recommended to use a virtual environment. ## Virtualenvs While you can use any virtualenv manager you like, we recommend the use of `pyenv` in particular if you want to develop or test for multiple different Python versions, so that is what the examples in this book will use. The installation instructions for `pyenv` can be found [in the `pyenv` GitHub repository](https://github.com/pyenv/pyenv#a-getting-pyenv). (Note: To get the `pyenv activate` and `pyenv virtualenv` commands, you will also need to install the [`pyenv-virtualenv`](https://github.com/pyenv/pyenv-virtualenv) plugin. The [pyenv installer](https://github.com/pyenv/pyenv-installer#installation--update--uninstallation) will install both together.) It can be useful to keep the sources used when installing using `pyenv` so that future debugging can see the original source files. This can be done by passing the `--keep` flag as part of the `pyenv install` command. For example: ```bash pyenv install 3.12 --keep ``` ### Building There are a number of build and Python package management systems such as [`setuptools-rust`](https://github.com/PyO3/setuptools-rust) or [manually](./building-and-distribution.md#manual-builds). We recommend the use of `maturin`, which you can install [as per the `maturin` documentation](https://maturin.rs/installation.html). It is developed to work with PyO3 and provides the most "batteries included" experience, especially if you are aiming to publish to PyPI. `maturin` is just a Python package, so you can add it in the same way you already install Python packages. System Python: ```bash pip install maturin --user ``` pipx: ```bash pipx install maturin ``` pyenv: ```bash pyenv activate pyo3 pip install maturin ``` poetry: ```bash poetry add -G dev maturin ``` After installation, you can run `maturin --version` to check that you have correctly installed it. ## Starting a new project First you should create the folder and virtual environment that are going to contain your new project. Here we will use the recommended `pyenv`: ```bash mkdir pyo3-example cd pyo3-example pyenv virtualenv pyo3 pyenv local pyo3 ``` After this, you should install your build manager. In this example, we will use `maturin`. After you've activated your virtualenv, add `maturin` to it: ```bash pip install maturin ``` Now you can initialize the new project: ```bash maturin init ``` If `maturin` is already installed, you can create a new project using that directly as well: ```bash maturin new -b pyo3 pyo3-example cd pyo3-example pyenv virtualenv pyo3 pyenv local pyo3 ``` ## Adding to an existing project Sadly, `maturin` cannot currently be run in existing projects, so if you want to use Python in an existing project you basically have two options: 1. Create a new project as above and move your existing code into that project 2. Manually edit your project configuration as necessary If you opt for the second option, here are the things you need to pay attention to: ## Cargo.toml Make sure that the Rust crate you want to be able to access from Python is compiled into a library. You can have a binary output as well, but the code you want to access from Python has to be in the library part. Also, make sure that the crate type is `cdylib` and add PyO3 as a dependency as so: ```toml # If you already have [package] information in `Cargo.toml`, you can ignore # this section! [package] # `name` here is name of the package. name = "pyo3_start" # these are good defaults: version = "0.1.0" edition = "2021" [lib] # The name of the native library. This is the name which will be used in Python to import the # library (i.e. `import string_sum`). If you change this, you must also change the name of the # `#[pymodule]` in `src/lib.rs`. name = "pyo3_example" # "cdylib" is necessary to produce a shared library for Python to import from. crate-type = ["cdylib"] [dependencies] pyo3 = {{#PYO3_CRATE_VERSION}} ``` ## pyproject.toml You should also create a `pyproject.toml` with the following contents: ```toml [build-system] requires = ["maturin>=1.9.4,<2"] build-backend = "maturin" [project] name = "pyo3_example" requires-python = ">=3.7" classifiers = [ "Programming Language :: Rust", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] ``` ## Running code After this you can setup Rust code to be available in Python as below; for example, you can place this code in `src/lib.rs`: ```rust,no_run /// A Python module implemented in Rust. The name of this function must match /// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to /// import the module. #[pyo3::pymodule] mod pyo3_example { use pyo3::prelude::*; /// Formats the sum of two numbers as string. #[pyfunction] fn sum_as_string(a: usize, b: usize) -> PyResult { Ok((a + b).to_string()) } } ``` Now you can run `maturin develop` to prepare the Python package, after which you can use it like so: ```bash $ maturin develop # lots of progress output as maturin runs the compilation... $ python >>> import pyo3_example >>> pyo3_example.sum_as_string(5, 20) '25' ``` For more instructions on how to use Python code from Rust, see the [Python from Rust](python-from-rust.md) page. ## Maturin Import Hook In development, any changes in the code would require running `maturin develop` before testing. To streamline the development process, you may want to install [Maturin Import Hook](https://github.com/PyO3/maturin-import-hook) which will run `maturin develop` automatically when the library with code changes is being imported. ================================================ FILE: guide/src/index.md ================================================ # The PyO3 user guide Welcome to the PyO3 user guide! This book is a companion to [PyO3's API docs](https://docs.rs/pyo3). It contains examples and documentation to explain all of PyO3's use cases in detail. The rough order of material in this user guide is as follows: 1. Getting started 2. Wrapping Rust code for use from Python 3. How to use Python code from Rust 4. Remaining topics which go into advanced concepts in detail Please choose from the chapters on the left to jump to individual topics, or continue below to start with PyO3's README.
{{#include ../../README.md}} ================================================ FILE: guide/src/migration.md ================================================ # Migrating from older PyO3 versions This guide can help you upgrade code through breaking changes from one PyO3 version to the next. For a detailed list of all changes, see the [CHANGELOG](changelog.md). ## from 0.28.* to 0.29 ### Removed implementations of `From`, `From`, and `From` for `PyErr` Previously the implementations of `From`, `From`, `From`, `From`, and `From` failed to construct the correct Python exception class, as reported in . The implementations for `string::FromUtf8Error` and `ffi::IntoStringError` were fixed in this release. For `str::Utf8Error`, the Rust error does not contain the source bytes required to construct the Python exception. Instead, `PyUnicodeDecodeError::new_err_from_utf8` can be used to convert the error to a `PyErr`. Before: ```rust,ignore fn bytes_to_str(bytes: &[u8]) -> PyResult<&str> { Ok(std::str::from_utf8(bytes)?) } ``` After: ```rust # use pyo3::prelude::*; use pyo3::exceptions::PyUnicodeDecodeError; # #[expect(dead_code)] fn bytes_to_str<'a>(py: Python<'_>, bytes: &'a [u8]) -> PyResult<&'a str> { std::str::from_utf8(bytes).map_err(|e| PyUnicodeDecodeError::new_err_from_utf8(py, bytes, e)) } ``` For `string::FromUtf16Error` and `char::DecodeUtf16Error` the Rust error types do not contain any of the information required to construct a `UnicodeDecodeError`. To raise a Python `UnicodeDecodeError` a new error should be manually constructed by calling `PyUnicodeDecodeError::new_err(...)`. ## from 0.27.* to 0.28 ### Default to supporting free-threaded Python
Click to expand When PyO3 0.23 added support for free-threaded Python, this was as an opt-in feature for modules by annotating with `#[pymodule(gil_used = false)]`. As the support has matured and PyO3's own API has evolved to remove reliance on the GIL, the time is right to switch the default. Modules now automatically allow use on free-threaded Python, unless they directly state they require the GIL with `#[pymodule(gil_used = true)]`.
### Deprecation of automatic `FromPyObject` for `#[pyclass]` types which implement `Clone`
Click to expand `#[pyclass]` types which implement `Clone` used to also implement `FromPyObject` automatically. This behavior is being phased out and replaced by an explicit opt-in, which will allow [better error messages and more user control](https://github.com/PyO3/pyo3/issues/5419). Affected types will by marked by a deprecation message. To migrate use either - `#[pyclass(from_py_object)]` to keep the automatic derive, or - `#[pyclass(skip_from_py_object)]` to accept the new behavior. Before: ```rust # #![allow(deprecated)] # use pyo3::prelude::*; #[pyclass] #[derive(Clone)] struct PyClass {} ``` After: ```rust # use pyo3::prelude::*; // If the automatic implementation of `FromPyObject` is desired, opt in: #[pyclass(from_py_object)] #[derive(Clone)] struct PyClass {} // or if the `FromPyObject` implementation is not needed: #[pyclass(skip_from_py_object)] #[derive(Clone)] struct PyClassWithoutFromPyObject {} ``` The `#[pyclass(skip_from_py_object)]` option will eventually be deprecated and removed as it becomes the default behavior.
### Deprecation of `Py` constructors from raw pointer
Click to expand The constructors `Py::from_owned_ptr`, `Py::from_owned_ptr_or_opt`, and `Py::from_owned_ptr_or_err` (and similar "borrowed" variants) perform an unchecked cast to the `Py` target type `T`. This unchecked cast is a footgun on APIs where the primary concern is about constructing PyO3's safe smart pointer types correctly from the raw pointer value. The equivalent constructors on `Bound` always produce a `Bound`, which encourages any subsequent cast to be done explicitly as either checked or unchecked. These should be used instead. Before: ```rust # #![allow(deprecated)] # use pyo3::prelude::*; # use pyo3::types::PyNone; # Python::attach(|py| { let raw_ptr = py.None().into_ptr(); let _: Py = unsafe { Py::from_borrowed_ptr(py, raw_ptr) }; let _: Py = unsafe { Py::from_owned_ptr(py, raw_ptr) }; # }) ``` After: ```rust # use pyo3::prelude::*; # use pyo3::types::PyNone; # Python::attach(|py| { let raw_ptr = py.None().into_ptr(); // Bound APIs require choice of doing unchecked or checked cast. Optionally `.unbind()` to // produce `Py` values. let _: Bound<'_, PyNone> = unsafe { Bound::from_borrowed_ptr(py, raw_ptr).cast_into_unchecked() }; let _: Bound<'_, PyNone> = unsafe { Bound::from_owned_ptr(py, raw_ptr).cast_into_unchecked() }; # }) ```
### Removal of `From` and `From> for PyClassInitializer`
Click to expand As part of refactoring the initialization code these impls were removed and its functionality was moved into the generated code for `#[new]`. As a small side side effect the following pattern will not be accepted anymore: ```rust,ignore # use pyo3::prelude::*; # Python::attach(|py| { # let existing_py: Py = py.None(); let obj_1 = Py::new(py, existing_py); # let existing_bound: Bound<'_, PyAny> = py.None().into_bound(py); let obj_2 = Bound::new(py, existing_bound); # }) ``` To migrate use `clone` or `clone_ref`: ```rust # use pyo3::prelude::*; # Python::attach(|py| { # let existing_py: Py = py.None(); let obj_1 = existing_py.clone_ref(py); # let existing_bound: Bound<'_, PyAny> = py.None().into_bound(py); let obj_2 = existing_bound.clone(); # }) ```
### Untyped buffer API moved to PyUntypedBuffer
Click to expand `PyBuffer` now is a typed wrapper around `PyUntypedBuffer`. Many methods such as `PyBuffer::format` have been moved to `PyUntypedBuffer::format`. `PyBuffer` dereferences to `PyUntypedBuffer`, so method call syntax will continue to work as-is. Users may need to update references to the moved functions.
### Internal change to use multi-phase initialization
Click to expand [PEP 489](https://peps.python.org/pep-0489/) introduced "multi-phase initialization" for extension modules which provides ways to allocate and clean up per-module state. This is a necessary step towards supporting Python "subinterpreters" which run on their own copy of state. Starting in PyO3 0.28, the `#[pymodule]` macro machinery has been reworked to use multi-phase initialization. The possibility of creating and consuming per-module state (and supporting subinterpreters) is left for a future PyO3 version. This should not require migration, nor is there expected to be breakage caused by the change. Nevertheless, this affects the order of initialization so seemed worth noting in this guide.
## from 0.26.* to 0.27 ### `FromPyObject` reworked for flexibility and efficiency
Click to expand With the removal of the `gil-ref` API in PyO3 0.23 it is now possible to fully split the Python lifetime `'py` and the input lifetime `'a`. This allows borrowing from the input data without extending the lifetime of being attached to the interpreter. `FromPyObject` now takes an additional lifetime `'a` describing the input lifetime. The argument type of the `extract` method changed from `&Bound<'py, PyAny>` to `Borrowed<'a, 'py, PyAny>`. This was done because `&'a Bound<'py, PyAny>` would have an implicit restriction `'py: 'a` due to the reference type. This new form was partly implemented already in 0.22 using the internal `FromPyObjectBound` trait and is now extended to all types. Most implementations can just add an elided lifetime to migrate. Additionally `FromPyObject` gained an associated type `Error`. This is the error type that can be used in case of a conversion error. During migration using `PyErr` is a good default, later a custom error type can be introduced to prevent unnecessary creation of Python exception objects and improved type safety. Before: ```rust,ignore impl<'py> FromPyObject<'py> for IpAddr { fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { ... } } ``` After ```rust,ignore impl<'py> FromPyObject<'_, 'py> for IpAddr { type Error = PyErr; fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { ... // since `Borrowed` derefs to `&Bound`, the body often // needs no changes, or adding an occasional `&` } } ``` Occasionally, more steps are necessary. For generic types, the bounds need to be adjusted. The correct bound depends on how the type is used. For simple wrapper types usually it's possible to just forward the bound. Before: ```rust,ignore struct MyWrapper(T); impl<'py, T> FromPyObject<'py> for MyWrapper where T: FromPyObject<'py> { fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { ob.extract().map(MyWrapper) } } ``` After: ```rust # use pyo3::prelude::*; # #[allow(dead_code)] # pub struct MyWrapper(T); impl<'a, 'py, T> FromPyObject<'a, 'py> for MyWrapper where T: FromPyObject<'a, 'py> { type Error = T::Error; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { obj.extract().map(MyWrapper) } } ``` Container types that need to create temporary Python references during extraction, for example extracting from a `PyList`, requires a stronger bound. For these the `FromPyObjectOwned` trait was introduced. It is automatically implemented for any type that implements `FromPyObject` and does not borrow from the input. It is intended to be used as a trait bound in these situations. Before: ```rust,ignore struct MyVec(Vec); impl<'py, T> FromPyObject<'py> for Vec where T: FromPyObject<'py>, { fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { let mut v = MyVec(Vec::new()); for item in obj.try_iter()? { v.0.push(item?.extract::()?); } Ok(v) } } ``` After: ```rust # use pyo3::prelude::*; # #[allow(dead_code)] # pub struct MyVec(Vec); impl<'py, T> FromPyObject<'_, 'py> for MyVec where T: FromPyObjectOwned<'py> // 👈 can only extract owned values, because each `item` below // is a temporary short lived owned reference { type Error = PyErr; fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { let mut v = MyVec(Vec::new()); for item in obj.try_iter()? { v.0.push(item?.extract::().map_err(Into::into)?); // `map_err` is needed because `?` uses `From`, not `Into` 🙁 } Ok(v) } } ``` This is very similar to `serde`s [`Deserialize`] and [`DeserializeOwned`] traits, see [the `serde` docs](https://serde.rs/lifetimes.html). [`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html [`DeserializeOwned`]: https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html
### `.downcast()` and `DowncastError` replaced with `.cast()` and `CastError`
Click to expand The `.downcast()` family of functions were only available on `Bound`. In corner cases (particularly related to `.downcast_into()`) this would require use of `.as_any().downcast()` or `.into_any().downcast_into()` chains. Additionally, `DowncastError` produced Python exception messages which are not very Pythonic due to use of Rust type names in the error messages. The `.cast()` family of functions are available on all `Bound` and `Borrowed` smart pointers, whatever the type, and have error messages derived from the actual type at runtime. This produces a nicer experience for both PyO3 module authors and consumers. To migrate, replace `.downcast()` with `.cast()` and `DowncastError` with `CastError` (and similar with `.downcast_into()` / `DowncastIntoError` etc). `CastError` requires a Python `type` object (or other "classinfo" object compatible with `isinstance()`) as the second object, so in the rare case where `DowncastError` was manually constructed, small adjustments to code may apply.
### `PyTypeCheck` is now an `unsafe trait`
Click to expand Because `PyTypeCheck` is the trait used to guard the `.cast()` functions to treat Python objects as specific concrete types, the trait is `unsafe` to implement. This should always have been the case, it was an unfortunate omission from its original implementation which is being corrected in this release.
## from 0.25.* to 0.26 ### Rename of `Python::with_gil`, `Python::allow_threads`, and `pyo3::prepare_freethreaded_python`
Click to expand The names for these APIs were created when the global interpreter lock (GIL) was mandatory. With the introduction of free-threading in Python 3.13 this is no longer the case, and the naming has no universal meaning anymore. For this reason, we chose to rename these to more modern terminology introduced in free-threading: - `Python::with_gil` is now called `Python::attach`, it attaches a Python thread-state to the current thread. In GIL enabled builds there can only be 1 thread attached to the interpreter, in free-threading there can be more. - `Python::allow_threads` is now called `Python::detach`, it detaches a previously attached thread-state. - `pyo3::prepare_freethreaded_python` is now called `Python::initialize`.
### Deprecation of `PyObject` type alias
Click to expand The type alias `PyObject` (aka `Py`) is often confused with the identically named FFI definition `pyo3::ffi::PyObject`. For this reason we are deprecating its usage. To migrate simply replace its usage by the target type `Py`.
### Replacement of `GILOnceCell` with `PyOnceLock`
Click to expand Similar to the above renaming of `Python::with_gil` and related APIs, the `GILOnceCell` type was designed for a Python interpreter which was limited by the GIL. Aside from its name, it allowed for the "once" initialization to race because the racing was mediated by the GIL and was extremely unlikely to manifest in practice. With the introduction of free-threaded Python the racy initialization behavior is more likely to be problematic and so a new type `PyOnceLock` has been introduced which performs true single-initialization correctly while attached to the Python interpreter. It exposes the same API as `GILOnceCell`, so should be a drop-in replacement with the notable exception that if the racy initialization of `GILOnceCell` was inadvertently relied on (e.g. due to circular references) then the stronger once-ever guarantee of `PyOnceLock` may lead to deadlocking which requires refactoring. Before: ```rust,ignore # use pyo3::prelude::*; # use pyo3::sync::GILOnceCell; # use pyo3::types::PyType; # fn main() -> PyResult<()> { # Python::attach(|py| { static DECIMAL_TYPE: GILOnceCell> = GILOnceCell::new(); DECIMAL_TYPE.import(py, "decimal", "Decimal")?; # Ok(()) # }) # } ``` After: ```rust # use pyo3::prelude::*; # use pyo3::sync::PyOnceLock; # use pyo3::types::PyType; # fn main() -> PyResult<()> { # Python::attach(|py| { static DECIMAL_TYPE: PyOnceLock> = PyOnceLock::new(); DECIMAL_TYPE.import(py, "decimal", "Decimal")?; # Ok(()) # }) # } ```
### Deprecation of `GILProtected`
Click to expand As another cleanup related to concurrency primitives designed for a Python constrained by the GIL, the `GILProtected` type is now deprecated. Prefer to use concurrency primitives which are compatible with free-threaded Python, such as [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) (in combination with PyO3's [`MutexExt`]({{#PYO3_DOCS_URL}}/pyo3/sync/trait.MutexExt.html) trait). Before: ```rust,ignore # use pyo3::prelude::*; # fn main() { # #[cfg(not(Py_GIL_DISABLED))] { use pyo3::sync::GILProtected; use std::cell::RefCell; # Python::attach(|py| { static NUMBERS: GILProtected>> = GILProtected::new(RefCell::new(Vec::new())); Python::attach(|py| { NUMBERS.get(py).borrow_mut().push(42); }); # }) # } # } ``` After: ```rust # use pyo3::prelude::*; use pyo3::sync::MutexExt; use std::sync::Mutex; # fn main() { # Python::attach(|py| { static NUMBERS: Mutex> = Mutex::new(Vec::new()); Python::attach(|py| { NUMBERS.lock_py_attached(py).expect("no poisoning").push(42); }); # }) # } ```
### `PyMemoryError` now maps to `io::ErrorKind::OutOfMemory` when converted to `io::Error`
Click to expand Previously, converting a `PyMemoryError` into a Rust `io::Error` would result in an error with kind `Other`. Now, it produces an error with kind `OutOfMemory`. Similarly, converting an `io::Error` with kind `OutOfMemory` back into a Python error would previously yield a generic `PyOSError`. Now, it yields a `PyMemoryError`. This change makes error conversions more precise and matches the semantics of out-of-memory errors between Python and Rust.
## from 0.24.* to 0.25 ### `AsPyPointer` removal
Click to expand The `AsPyPointer` trait is mostly a leftover from the now removed gil-refs API. The last remaining uses were the GC API, namely `PyVisit::call`, and identity comparison (`PyAnyMethods::is` and `Py::is`). `PyVisit::call` has been updated to take `T: Into>>`, which allows for arguments of type `&Py`, `&Option>` and `Option<&Py>`. It is unlikely any changes are needed here to migrate. `PyAnyMethods::is`/ `Py::is` has been updated to take `T: AsRef>>`. Additionally `AsRef>>` implementations were added for `Py`, `Bound` and `Borrowed`. Because of the existing `AsRef> for Bound` implementation this may cause inference issues in non-generic code. This can be easily migrated by switching to `as_any` instead of `as_ref` for these calls.
## from 0.23.* to 0.24
Click to expand There were no significant changes from 0.23 to 0.24 which required documenting in this guide.
## from 0.22.* to 0.23
Click to expand PyO3 0.23 is a significant rework of PyO3's internals for two major improvements: - Support of Python 3.13's new freethreaded build (aka "3.13t") - Rework of to-Python conversions with a new `IntoPyObject` trait. These changes are both substantial and reasonable efforts have been made to allow as much code as possible to continue to work as-is despite the changes. The impacts are likely to be seen in three places when upgrading: - PyO3's data structures [are now thread-safe](#free-threaded-python-support) instead of reliant on the GIL for synchronization. In particular, `#[pyclass]` types are [now required to be `Sync`](./class/thread-safety.md). - The [`IntoPyObject` trait](#new-intopyobject-trait-unifies-to-python-conversions) may need to be implemented for types in your codebase. In most cases this can simply be done with [`#[derive(IntoPyObject)]`](#intopyobject-and-intopyobjectref-derive-macros). There will be many deprecation warnings from the replacement of `IntoPy` and `ToPyObject` traits. - There will be many deprecation warnings from the [final removal of the `gil-refs` feature](#gil-refs-feature-removed), which opened up API space for a cleanup and simplification to PyO3's "Bound" API. The sections below discuss the rationale and details of each change in more depth.
### Free-threaded Python Support
Click to expand PyO3 0.23 introduces initial support for the new free-threaded build of CPython 3.13, aka "3.13t". Because this build allows multiple Python threads to operate simultaneously on underlying Rust data, the `#[pyclass]` macro now requires that types it operates on implement `Sync`. Aside from the change to `#[pyclass]`, most features of PyO3 work unchanged, as the changes have been to the internal data structures to make them thread-safe. An example of this is the `GILOnceCell` type, which used the GIL to synchronize single-initialization. It now uses internal locks to guarantee that only one write ever succeeds, however it allows for multiple racing runs of the initialization closure. It may be preferable to instead use `std::sync::OnceLock` in combination with the `pyo3::sync::OnceLockExt` trait which adds `OnceLock::get_or_init_py_attached` for single-initialization where the initialization closure is guaranteed only ever to run once and without deadlocking with the GIL. Future PyO3 versions will likely add more traits and data structures to make working with free-threaded Python easier. Some features are inaccessible on the free-threaded build: - The `GILProtected` type, which relied on the GIL to expose synchronized access to inner contents - `PyList::get_item_unchecked`, which cannot soundly be used due to races between time-of-check and time-of-use If you make use of these features then you will need to account for the unavailability of the API in the free-threaded build. One way to handle it is via conditional compilation -- extensions can use `pyo3-build-config` to get access to a `#[cfg(Py_GIL_DISABLED)]` guard. See [the guide section on free-threaded Python](free-threading.md) for more details about supporting free-threaded Python in your PyO3 extensions.
### New `IntoPyObject` trait unifies to-Python conversions
Click to expand PyO3 0.23 introduces a new `IntoPyObject` trait to convert Rust types into Python objects which replaces both `IntoPy` and `ToPyObject`. Notable features of this new trait include: - conversions can now return an error - it is designed to work efficiently for both `T` owned types and `&T` references - compared to `IntoPy` the generic `T` moved into an associated type, so - there is now only one way to convert a given type - the output type is stronger typed and may return any Python type instead of just `PyAny` - byte collections are specialized to convert into `PyBytes` now, see [below](#to-python-conversions-changed-for-byte-collections-vecu8-u8-n-and-smallvecu8-n) - `()` (unit) is now only specialized in return position of `#[pyfunction]` and `#[pymethods]` to return `None`, in normal usage it converts into an empty `PyTuple` All PyO3 provided types as well as `#[pyclass]`es already implement `IntoPyObject`. Other types will need to adapt an implementation of `IntoPyObject` to stay compatible with the Python APIs. In many cases the new [`#[derive(IntoPyObject)]`](#intopyobject-and-intopyobjectref-derive-macros) macro can be used instead of [manual implementations](#intopyobject-manual-implementation). Since `IntoPyObject::into_pyobject` may return either a `Bound` or `Borrowed`, you may find the [`BoundObject`](conversions/traits.md#boundobject-for-conversions-that-may-be-bound-or-borrowed) trait to be useful to write code that generically handles either type of smart pointer. Together with the introduction of `IntoPyObject` the old conversion traits `ToPyObject` and `IntoPy` are deprecated and will be removed in a future PyO3 version. #### `IntoPyObject` and `IntoPyObjectRef` derive macros To implement the new trait you may use the new `IntoPyObject` and `IntoPyObjectRef` derive macros as below. ```rust,no_run # use pyo3::prelude::*; #[derive(IntoPyObject, IntoPyObjectRef)] struct Struct { count: usize, obj: Py, } ``` The `IntoPyObjectRef` derive macro derives implementations for references (e.g. for `&Struct` in the example above), which is a replacement for the `ToPyObject` trait. #### `IntoPyObject` manual implementation Before: ```rust,ignore # use pyo3::prelude::*; # #[allow(dead_code)] struct MyPyObjectWrapper(PyObject); impl IntoPy for MyPyObjectWrapper { fn into_py(self, py: Python<'_>) -> PyObject { self.0 } } impl ToPyObject for MyPyObjectWrapper { fn to_object(&self, py: Python<'_>) -> PyObject { self.0.clone_ref(py) } } ``` After: ```rust,ignore # use pyo3::prelude::*; # #[allow(dead_code)] # struct MyPyObjectWrapper(PyObject); impl<'py> IntoPyObject<'py> for MyPyObjectWrapper { type Target = PyAny; // the Python type type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` type Error = std::convert::Infallible; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.0.into_bound(py)) } } // `ToPyObject` implementations should be converted to implementations on reference types impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { type Target = PyAny; type Output = Borrowed<'a, 'py, Self::Target>; // `Borrowed` can be used to optimized reference counting type Error = std::convert::Infallible; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.0.bind_borrowed(py)) } } ```
### To-Python conversions changed for byte collections (`Vec`, `[u8; N]` and `SmallVec<[u8; N]>`)
Click to expand With the introduction of the `IntoPyObject` trait, PyO3's macros now prefer `IntoPyObject` implementations over `IntoPy` when producing Python values. This applies to `#[pyfunction]` and `#[pymethods]` return values and also fields accessed via `#[pyo3(get)]`. This change has an effect on functions and methods returning _byte_ collections like - `Vec` - `[u8; N]` - `SmallVec<[u8; N]>` In their new `IntoPyObject` implementation these will now turn into `PyBytes` rather than a `PyList`. All other `T`s are unaffected and still convert into a `PyList`. ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyfunction] fn foo() -> Vec { // would previously turn into a `PyList`, now `PyBytes` vec![0, 1, 2, 3] } #[pyfunction] fn bar() -> Vec { // unaffected, returns `PyList` vec![0, 1, 2, 3] } ``` If this conversion is _not_ desired, consider building a list manually using `PyList::new`. The following types were previously _only_ implemented for `u8` and now allow other `T`s turn into `PyList`: - `&[T]` - `Cow<[T]>` This is purely additional and should just extend the possible return types.
### `gil-refs` feature removed
Click to expand PyO3 0.23 completes the removal of the "GIL Refs" API in favour of the new "Bound" API introduced in PyO3 0.21. With the removal of the old API, many "Bound" API functions which had been introduced with `_bound` suffixes no longer need the suffixes as these names have been freed up. For example, `PyTuple::new_bound` is now just `PyTuple::new` (the existing name remains but is deprecated). Before: ```rust,ignore # #![allow(deprecated)] # use pyo3::prelude::*; # use pyo3::types::PyTuple; # fn main() { # Python::attach(|py| { // For example, for PyTuple. Many such APIs have been changed. let tup = PyTuple::new_bound(py, [1, 2, 3]); # }) # } ``` After: ```rust # use pyo3::prelude::*; # use pyo3::types::PyTuple; # fn main() { # Python::attach(|py| { // For example, for PyTuple. Many such APIs have been changed. let tup = PyTuple::new(py, [1, 2, 3]); # }) # } ``` #### `IntoPyDict` trait adjusted for removal of `gil-refs` As part of this API simplification, the `IntoPyDict` trait has had a small breaking change: `IntoPyDict::into_py_dict_bound` method has been renamed to `IntoPyDict::into_py_dict`. It is also now fallible as part of the `IntoPyObject` trait addition. If you implemented `IntoPyDict` for your type, you should implement `into_py_dict` instead of `into_py_dict_bound`. The old name is still available for calling but deprecated. Before: ```rust,ignore # use pyo3::prelude::*; # use pyo3::types::{PyDict, IntoPyDict}; # use std::collections::HashMap; struct MyMap(HashMap); impl IntoPyDict for MyMap where K: ToPyObject, V: ToPyObject, { fn into_py_dict_bound(self, py: Python<'_>) -> Bound<'_, PyDict> { let dict = PyDict::new_bound(py); for (key, value) in self.0 { dict.set_item(key, value) .expect("Failed to set_item on dict"); } dict } } ``` After: ```rust,no_run # use pyo3::prelude::*; # use pyo3::types::{PyDict, IntoPyDict}; # use std::collections::HashMap; # #[allow(dead_code)] struct MyMap(HashMap); impl<'py, K, V> IntoPyDict<'py> for MyMap where K: IntoPyObject<'py>, V: IntoPyObject<'py>, { fn into_py_dict(self, py: Python<'py>) -> PyResult> { let dict = PyDict::new(py); for (key, value) in self.0 { dict.set_item(key, value)?; } Ok(dict) } } ```
## from 0.21.* to 0.22 ### Deprecation of `gil-refs` feature continues
Click to expand Following the introduction of the "Bound" API in PyO3 0.21 and the planned removal of the "GIL Refs" API, all functionality related to GIL Refs is now gated behind the `gil-refs` feature and emits a deprecation warning on use. See the 0.21 migration entry for help upgrading.
### Deprecation of implicit default for trailing optional arguments
Click to expand With `pyo3` 0.22 the implicit `None` default for trailing `Option` type argument is deprecated. To migrate, place a `#[pyo3(signature = (...))]` attribute on affected functions or methods and specify the desired behavior. The migration warning specifies the corresponding signature to keep the current behavior. With 0.23 the signature will be required for any function containing `Option` type parameters to prevent accidental and unnoticed changes in behavior. With 0.24 this restriction will be lifted again and `Option` type arguments will be treated as any other argument _without_ special handling. Before: ```rust,no_run # #![allow(deprecated, dead_code)] # use pyo3::prelude::*; #[pyfunction] fn increment(x: u64, amount: Option) -> u64 { x + amount.unwrap_or(1) } ``` After: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyfunction] #[pyo3(signature = (x, amount=None))] fn increment(x: u64, amount: Option) -> u64 { x + amount.unwrap_or(1) } ```
### `Py::clone` is now gated behind the `py-clone` feature
Click to expand If you rely on `impl Clone for Py` to fulfil trait requirements imposed by existing Rust code written without PyO3-based code in mind, the newly introduced feature `py-clone` must be enabled. However, take care to note that the behaviour is different from previous versions. If `Clone` was called without the GIL being held, we tried to delay the application of these reference count increments until PyO3-based code would re-acquire it. This turned out to be impossible to implement in a sound manner and hence was removed. Now, if `Clone` is called without the GIL being held, we panic instead for which calling code might not be prepared. It is advised to migrate off the `py-clone` feature. The simplest way to remove dependency on `impl Clone for Py` is to wrap `Py` as `Arc>` and use cloning of the arc. Related to this, we also added a `pyo3_disable_reference_pool` conditional compilation flag which removes the infrastructure necessary to apply delayed reference count decrements implied by `impl Drop for Py`. They do not appear to be a soundness hazard as they should lead to memory leaks in the worst case. However, the global synchronization adds significant overhead to cross the Python-Rust boundary. Enabling this feature will remove these costs and make the `Drop` implementation abort the process if called without the GIL being held instead.
### Require explicit opt-in for comparison for simple enums
Click to expand With `pyo3` 0.22 the new `#[pyo3(eq)]` options allows automatic implementation of Python equality using Rust's `PartialEq`. Previously simple enums automatically implemented equality in terms of their discriminants. To make PyO3 more consistent, this automatic equality implementation is deprecated in favour of having opt-ins for all `#[pyclass]` types. Similarly, simple enums supported comparison with integers, which is not covered by Rust's `PartialEq` derive, so has been split out into the `#[pyo3(eq_int)]` attribute. To migrate, place a `#[pyo3(eq, eq_int)]` attribute on simple enum classes. Before: ```rust,no_run # #![allow(deprecated, dead_code)] # use pyo3::prelude::*; #[pyclass] enum SimpleEnum { VariantA, VariantB = 42, } ``` After: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyclass(eq, eq_int)] #[derive(PartialEq)] enum SimpleEnum { VariantA, VariantB = 42, } ```
### `PyType::name` reworked to better match Python `__name__`
Click to expand This function previously would try to read directly from Python type objects' C API field (`tp_name`), in which case it would return a `Cow::Borrowed`. However the contents of `tp_name` don't have well-defined semantics. Instead `PyType::name()` now returns the equivalent of Python `__name__` and returns `PyResult>`. The closest equivalent to PyO3 0.21's version of `PyType::name()` has been introduced as a new function `PyType::fully_qualified_name()`, which is equivalent to `__module__` and `__qualname__` joined as `module.qualname`. Before: ```rust,ignore # #![allow(deprecated, dead_code)] # use pyo3::prelude::*; # use pyo3::types::{PyBool}; # fn main() -> PyResult<()> { Python::with_gil(|py| { let bool_type = py.get_type_bound::(); let name = bool_type.name()?.into_owned(); println!("Hello, {}", name); let mut name_upper = bool_type.name()?; name_upper.to_mut().make_ascii_uppercase(); println!("Hello, {}", name_upper); Ok(()) }) # } ``` After: ```rust,ignore # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::{PyBool}; # fn main() -> PyResult<()> { Python::with_gil(|py| { let bool_type = py.get_type_bound::(); let name = bool_type.name()?; println!("Hello, {}", name); // (if the full dotted path was desired, switch from `name()` to `fully_qualified_name()`) let mut name_upper = bool_type.fully_qualified_name()?.to_string(); name_upper.make_ascii_uppercase(); println!("Hello, {}", name_upper); Ok(()) }) # } ```
## from 0.20.* to 0.21
Click to expand PyO3 0.21 introduces a new `Bound<'py, T>` smart pointer which replaces the existing "GIL Refs" API to interact with Python objects. For example, in PyO3 0.20 the reference `&'py PyAny` would be used to interact with Python objects. In PyO3 0.21 the updated type is `Bound<'py, PyAny>`. Making this change moves Rust ownership semantics out of PyO3's internals and into user code. This change fixes [a known soundness edge case of interaction with gevent](https://github.com/PyO3/pyo3/issues/3668) as well as improves CPU and [memory performance](https://github.com/PyO3/pyo3/issues/1056). For a full history of discussion see . For a full history of discussion see . The "GIL Ref" `&'py PyAny` and similar types such as `&'py PyDict` continue to be available as a deprecated API. Due to the advantages of the new API it is advised that all users make the effort to upgrade as soon as possible. In addition to the major API type overhaul, PyO3 has needed to make a few small breaking adjustments to other APIs to close correctness and soundness gaps. The recommended steps to update to PyO3 0.21 is as follows: 1. Enable the `gil-refs` feature to silence deprecations related to the API change 2. Fix all other PyO3 0.21 migration steps 3. Disable the `gil-refs` feature and migrate off the deprecated APIs The following sections are laid out in this order.
### Enable the `gil-refs` feature
Click to expand To make the transition for the PyO3 ecosystem away from the GIL Refs API as smooth as possible, in PyO3 0.21 no APIs consuming or producing GIL Refs have been altered. Instead, variants using `Bound` smart pointers have been introduced, for example `PyTuple::new_bound` which returns `Bound` is the replacement form of `PyTuple::new`. The GIL Ref APIs have been deprecated, but to make migration easier it is possible to disable these deprecation warnings by enabling the `gil-refs` feature. > [!TIP] > The one single exception where an existing API was changed in-place is the `pyo3::intern!` macro. Almost all uses of this macro did not need to update code to account it changing to return `&Bound` immediately, and adding an `intern_bound!` replacement was perceived as adding more work for users. It is recommended that users do this as a first step of updating to PyO3 0.21 so that the deprecation warnings do not get in the way of resolving the rest of the migration steps. Before: ```toml # Cargo.toml [dependencies] pyo3 = "0.20" ``` After: ```toml # Cargo.toml [dependencies] pyo3 = { version = "0.21", features = ["gil-refs"] } ```
### `PyTypeInfo` and `PyTryFrom` have been adjusted
Click to expand The `PyTryFrom` trait has aged poorly, its `try_from` method now conflicts with `TryFrom::try_from` in the 2021 edition prelude. A lot of its functionality was also duplicated with `PyTypeInfo`. To tighten up the PyO3 traits as part of the deprecation of the GIL Refs API the `PyTypeInfo` trait has had a simpler companion `PyTypeCheck`. The methods `PyAny::downcast` and `PyAny::downcast_exact` no longer use `PyTryFrom` as a bound, instead using `PyTypeCheck` and `PyTypeInfo` respectively. To migrate, switch all type casts to use `obj.downcast()` instead of `try_from(obj)` (and similar for `downcast_exact`). Before: ```rust,ignore # #![allow(deprecated)] # use pyo3::prelude::*; # use pyo3::types::{PyInt, PyList}; # fn main() -> PyResult<()> { Python::with_gil(|py| { let list = PyList::new(py, 0..5); let b = ::try_from(list.get_item(0).unwrap())?; Ok(()) }) # } ``` After: ```rust,ignore # use pyo3::prelude::*; # use pyo3::types::{PyInt, PyList}; # fn main() -> PyResult<()> { Python::with_gil(|py| { // Note that PyList::new is deprecated for PyList::new_bound as part of the GIL Refs API removal, // see the section below on migration to Bound. #[allow(deprecated)] let list = PyList::new(py, 0..5); let b = list.get_item(0).unwrap().downcast::()?; Ok(()) }) # } ```
### `Iter(A)NextOutput` are deprecated
Click to expand The `__next__` and `__anext__` magic methods can now return any type convertible into Python objects directly just like all other `#[pymethods]`. The `IterNextOutput` used by `__next__` and `IterANextOutput` used by `__anext__` are subsequently deprecated. Most importantly, this change allows returning an awaitable from `__anext__` without non-sensically wrapping it into `Yield` or `Some`. Only the return types `Option` and `Result, E>` are still handled in a special manner where `Some(val)` yields `val` and `None` stops iteration. Starting with an implementation of a Python iterator using `IterNextOutput`, e.g. ```rust,ignore use pyo3::prelude::*; use pyo3::iter::IterNextOutput; #[pyclass] struct PyClassIter { count: usize, } #[pymethods] impl PyClassIter { fn __next__(&mut self) -> IterNextOutput { if self.count < 5 { self.count += 1; IterNextOutput::Yield(self.count) } else { IterNextOutput::Return("done") } } } ``` If returning `"done"` via `StopIteration` is not really required, this should be written as ```rust,no_run use pyo3::prelude::*; #[pyclass] struct PyClassIter { count: usize, } #[pymethods] impl PyClassIter { fn __next__(&mut self) -> Option { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } } ``` This form also has additional benefits: It has already worked in previous PyO3 versions, it matches the signature of Rust's [`Iterator` trait](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html) and it allows using a fast path in CPython which completely avoids the cost of raising a `StopIteration` exception. Note that using [`Option::transpose`](https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.transpose) and the `Result, E>` variant, this form can also be used to wrap fallible iterators. Alternatively, the implementation can also be done as it would in Python itself, i.e. by "raising" a `StopIteration` exception ```rust,no_run use pyo3::prelude::*; use pyo3::exceptions::PyStopIteration; #[pyclass] struct PyClassIter { count: usize, } #[pymethods] impl PyClassIter { fn __next__(&mut self) -> PyResult { if self.count < 5 { self.count += 1; Ok(self.count) } else { Err(PyStopIteration::new_err("done")) } } } ``` Finally, an asynchronous iterator can directly return an awaitable without confusing wrapping ```rust,no_run use pyo3::prelude::*; #[pyclass] struct PyClassAwaitable { number: usize, } #[pymethods] impl PyClassAwaitable { fn __next__(&self) -> usize { self.number } fn __await__(slf: Py) -> Py { slf } } #[pyclass] struct PyClassAsyncIter { number: usize, } #[pymethods] impl PyClassAsyncIter { fn __anext__(&mut self) -> PyClassAwaitable { self.number += 1; PyClassAwaitable { number: self.number, } } fn __aiter__(slf: Py) -> Py { slf } } ```
### `PyType::name` has been renamed to `PyType::qualname`
Click to expand `PyType::name` has been renamed to `PyType::qualname` to indicate that it does indeed return the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name), matching the `__qualname__` attribute. The newly added `PyType::name` yields the full name including the module name now which corresponds to `__module__.__name__` on the level of attributes.
### `PyCell` has been deprecated
Click to expand Interactions with Python objects implemented in Rust no longer need to go though `PyCell`. Instead interactions with Python object now consistently go through `Bound` or `Py` independently of whether `T` is native Python object or a `#[pyclass]` implemented in Rust. Use `Bound::new` or `Py::new` respectively to create and `Bound::borrow(_mut)` / `Py::borrow(_mut)` to borrow the Rust object.
### Migrating from the GIL Refs API to `Bound`
Click to expand To minimise breakage of code using the GIL Refs API, the `Bound` smart pointer has been introduced by adding complements to all functions which accept or return GIL Refs. This allows code to migrate by replacing the deprecated APIs with the new ones. To identify what to migrate, temporarily switch off the `gil-refs` feature to see deprecation warnings on [almost](#cases-where-pyo3-cannot-emit-gil-ref-deprecation-warnings) all uses of APIs accepting and producing GIL Refs . Over one or more PRs it should be possible to follow the deprecation hints to update code. Depending on your development environment, switching off the `gil-refs` feature may introduce [some very targeted breakages](#deactivating-the-gil-refs-feature), so you may need to fixup those first. For example, the following APIs have gained updated variants: - `PyList::new`, `PyTuple::new` and similar constructors have replacements `PyList::new_bound`, `PyTuple::new_bound` etc. - `FromPyObject::extract` has a new `FromPyObject::extract_bound` (see the section below) - The `PyTypeInfo` trait has had new `_bound` methods added to accept / return `Bound`. Because the new `Bound` API brings ownership out of the PyO3 framework and into user code, there are a few places where user code is expected to need to adjust while switching to the new API: - Code will need to add the occasional `&` to borrow the new smart pointer as `&Bound` to pass these types around (or use `.clone()` at the very small cost of increasing the Python reference count) - `Bound` and `Bound` cannot support indexing with `list[0]`, you should use `list.get_item(0)` instead. - `Bound::iter_borrowed` is slightly more efficient than `Bound::iter`. The default iteration of `Bound` cannot return borrowed references because Rust does not (yet) have "lending iterators". Similarly `Bound::get_borrowed_item` is more efficient than `Bound::get_item` for the same reason. - `&Bound` does not implement `FromPyObject` (although it might be possible to do this in the future once the GIL Refs API is completely removed). Use `bound_any.downcast::()` instead of `bound_any.extract::<&Bound>()`. - `Bound::to_str` now borrows from the `Bound` rather than from the `'py` lifetime, so code will need to store the smart pointer as a value in some cases where previously `&PyString` was just used as a temporary. (There are some more details relating to this in [the section below](#deactivating-the-gil-refs-feature).) - `.extract::<&str>()` now borrows from the source Python object. The simplest way to update is to change to `.extract::()`, which retains ownership of the Python reference. See more information [in the section on deactivating the `gil-refs` feature](#deactivating-the-gil-refs-feature). To convert between `&PyAny` and `&Bound` use the `as_borrowed()` method: ```rust,ignore let gil_ref: &PyAny = ...; let bound: &Bound = &gil_ref.as_borrowed(); ``` To convert between `Py` and `Bound` use the `bind()` / `into_bound()` methods, and `as_unbound()` / `unbind()` to go back from `Bound` to `Py`. ```rust,ignore let obj: Py = ...; let bound: &Bound<'py, PyList> = obj.bind(py); let bound: Bound<'py, PyList> = obj.into_bound(py); let obj: &Py = bound.as_unbound(); let obj: Py = bound.unbind(); ``` > [!WARNING] > Dangling pointer trap 💣 > > Because of the ownership changes, code which uses `.as_ptr()` to convert `&PyAny` and other GIL Refs to a `*mut pyo3_ffi::PyObject` should take care to avoid creating dangling pointers now that `Bound` carries ownership. > > For example, the following pattern with `Option<&PyAny>` can easily create a dangling pointer when migrating to the `Bound` smart pointer: > > ```rust,ignore > let opt: Option<&PyAny> = ...; > let p: *mut ffi::PyObject = opt.map_or(std::ptr::null_mut(), |any| any.as_ptr()); > ``` > > The correct way to migrate this code is to use `.as_ref()` to avoid dropping the `Bound` in the `map_or` closure: > > ```rust,ignore > let opt: Option> = ...; > let p: *mut ffi::PyObject = opt.as_ref().map_or(std::ptr::null_mut(), Bound::as_ptr); > ``` #### Migrating `FromPyObject` implementations `FromPyObject` has had a new method `extract_bound` which takes `&Bound<'py, PyAny>` as an argument instead of `&PyAny`. Both `extract` and `extract_bound` have been given default implementations in terms of the other, to avoid breaking code immediately on update to 0.21. All implementations of `FromPyObject` should be switched from `extract` to `extract_bound`. Before: ```rust,ignore impl<'py> FromPyObject<'py> for MyType { fn extract(obj: &'py PyAny) -> PyResult { /* ... */ } } ``` After: ```rust,ignore impl<'py> FromPyObject<'py> for MyType { fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { /* ... */ } } ``` The expectation is that in 0.22 `extract_bound` will have the default implementation removed and in 0.23 `extract` will be removed. #### Cases where PyO3 cannot emit GIL Ref deprecation warnings Despite a large amount of deprecations warnings produced by PyO3 to aid with the transition from GIL Refs to the Bound API, there are a few cases where PyO3 cannot automatically warn on uses of GIL Refs. It is worth checking for these cases manually after the deprecation warnings have all been addressed: - Individual implementations of the `FromPyObject` trait cannot be deprecated, so PyO3 cannot warn about uses of code patterns like `.extract<&PyAny>()` which produce a GIL Ref. - GIL Refs in `#[pyfunction]` arguments emit a warning, but if the GIL Ref is wrapped inside another container such as `Vec<&PyAny>` then PyO3 cannot warn against this. - The `wrap_pyfunction!(function)(py)` deferred argument form of the `wrap_pyfunction` macro taking `py: Python<'py>` produces a GIL Ref, and due to limitations in type inference PyO3 cannot warn against this specific case.
### Deactivating the `gil-refs` feature
Click to expand As a final step of migration, deactivating the `gil-refs` feature will set up code for best performance and is intended to set up a forward-compatible API for PyO3 0.22. At this point code that needed to manage GIL Ref memory can safely remove uses of `GILPool` (which are constructed by calls to `Python::new_pool` and `Python::with_pool`). Deprecation warnings will highlight these cases. There is just one case of code that changes upon disabling these features: `FromPyObject` trait implementations for types that borrow directly from the input data cannot be implemented by PyO3 without GIL Refs (while the GIL Refs API is in the process of being removed). The main types affected are `&str`, `Cow<'_, str>`, `&[u8]`, `Cow<'_, u8>`. To make PyO3's core functionality continue to work while the GIL Refs API is in the process of being removed, disabling the `gil-refs` feature moves the implementations of `FromPyObject` for `&str`, `Cow<'_, str>`, `&[u8]`, `Cow<'_, u8>` to a new temporary trait `FromPyObjectBound`. This trait is the expected future form of `FromPyObject` and has an additional lifetime `'a` to enable these types to borrow data from Python objects. PyO3 0.21 has introduced the [`PyBackedStr`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedStr.html) and [`PyBackedBytes`]({{#PYO3_DOCS_URL}}/pyo3/pybacked/struct.PyBackedBytes.html) types to help with this case. The easiest way to avoid lifetime challenges from extracting `&str` is to use these. For more complex types like `Vec<&str>`, is now impossible to extract directly from a Python object and `Vec` is the recommended upgrade path. A key thing to note here is because extracting to these types now ties them to the input lifetime, some extremely common patterns may need to be split into multiple Rust lines. For example, the following snippet of calling `.extract::<&str>()` directly on the result of `.getattr()` needs to be adjusted when deactivating the `gil-refs` feature. Before: ```rust,ignore # #[cfg(feature = "gil-refs")] { # use pyo3::prelude::*; # use pyo3::types::{PyList, PyType}; # fn example<'py>(py: Python<'py>) -> PyResult<()> { #[allow(deprecated)] // GIL Ref API let obj: &'py PyType = py.get_type::(); let name: &'py str = obj.getattr("__name__")?.extract()?; assert_eq!(name, "list"); # Ok(()) # } # Python::with_gil(example).unwrap(); # } ``` After: ```rust,ignore # #[cfg(any(not(Py_LIMITED_API), Py_3_10))] { # use pyo3::prelude::*; # use pyo3::types::{PyList, PyType}; # fn example<'py>(py: Python<'py>) -> PyResult<()> { let obj: Bound<'py, PyType> = py.get_type_bound::(); let name_obj: Bound<'py, PyAny> = obj.getattr("__name__")?; // the lifetime of the data is no longer `'py` but the much shorter // lifetime of the `name_obj` smart pointer above let name: &'_ str = name_obj.extract()?; assert_eq!(name, "list"); # Ok(()) # } # Python::with_gil(example).unwrap(); # } ``` To avoid needing to worry about lifetimes at all, it is also possible to use the new `PyBackedStr` type, which stores a reference to the Python `str` without a lifetime attachment. In particular, `PyBackedStr` helps for `abi3` builds for Python older than 3.10. Due to limitations in the `abi3` CPython API for those older versions, PyO3 cannot offer a `FromPyObjectBound` implementation for `&str` on those versions. The easiest way to migrate for older `abi3` builds is to replace any cases of `.extract::<&str>()` with `.extract::()`. Alternatively, use `.extract::>()`, `.extract::()` to copy the data into Rust. The following example uses the same snippet as those just above, but this time the final extracted type is `PyBackedStr`: ```rust,ignore # use pyo3::prelude::*; # use pyo3::types::{PyList, PyType}; # fn example<'py>(py: Python<'py>) -> PyResult<()> { use pyo3::pybacked::PyBackedStr; let obj: Bound<'py, PyType> = py.get_type_bound::(); let name: PyBackedStr = obj.getattr("__name__")?.extract()?; assert_eq!(&*name, "list"); # Ok(()) # } # Python::with_gil(example).unwrap(); ```
## from 0.19.* to 0.20 ### Drop support for older technologies
Click to expand PyO3 0.20 has increased minimum Rust version to 1.56. This enables use of newer language features and simplifies maintenance of the project.
### `PyDict::get_item` now returns a `Result`
Click to expand `PyDict::get_item` in PyO3 0.19 and older was implemented using a Python API which would suppress all exceptions and return `None` in those cases. This included errors in `__hash__` and `__eq__` implementations of the key being looked up. Newer recommendations by the Python core developers advise against using these APIs which suppress exceptions, instead allowing exceptions to bubble upwards. `PyDict::get_item_with_error` already implemented this recommended behavior, so that API has been renamed to `PyDict::get_item`. Before: ```rust,ignore use pyo3::prelude::*; use pyo3::exceptions::PyTypeError; use pyo3::types::{PyDict, IntoPyDict}; # fn main() { # let _ = Python::with_gil(|py| { let dict: &PyDict = [("a", 1)].into_py_dict(py); // `a` is in the dictionary, with value 1 assert!(dict.get_item("a").map_or(Ok(false), |x| x.eq(1))?); // `b` is not in the dictionary assert!(dict.get_item("b").is_none()); // `dict` is not hashable, so this fails with a `TypeError` assert!(dict .get_item_with_error(dict) .unwrap_err() .is_instance_of::(py)); }); # } ``` After: ```rust,ignore use pyo3::prelude::*; use pyo3::exceptions::PyTypeError; use pyo3::types::{PyDict, IntoPyDict}; # fn main() { # let _ = Python::with_gil(|py| -> PyResult<()> { let dict: &PyDict = [("a", 1)].into_py_dict(py); // `a` is in the dictionary, with value 1 assert!(dict.get_item("a")?.map_or(Ok(false), |x| x.eq(1))?); // `b` is not in the dictionary assert!(dict.get_item("b")?.is_none()); // `dict` is not hashable, so this fails with a `TypeError` assert!(dict .get_item(dict) .unwrap_err() .is_instance_of::(py)); Ok(()) }); # } ```
### Required arguments are no longer accepted after optional arguments
Click to expand Trailing `Option` arguments have an automatic default of `None`. To avoid unwanted changes when modifying function signatures, in PyO3 0.18 it was deprecated to have a required argument after an `Option` argument without using `#[pyo3(signature = (...))]` to specify the intended defaults. In PyO3 0.20, this becomes a hard error. Before: ```rust,ignore #[pyfunction] fn x_or_y(x: Option, y: u64) -> u64 { x.unwrap_or(y) } ``` After: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyfunction] #[pyo3(signature = (x, y))] // both x and y have no defaults and are required fn x_or_y(x: Option, y: u64) -> u64 { x.unwrap_or(y) } ```
### Remove deprecated function forms
Click to expand In PyO3 0.18 the `#[args]` attribute for `#[pymethods]`, and directly specifying the function signature in `#[pyfunction]`, was deprecated. This functionality has been removed in PyO3 0.20. Before: ```rust,ignore #[pyfunction] #[pyo3(a, b = "0", "/")] fn add(a: u64, b: u64) -> u64 { a + b } ``` After: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; #[pyfunction] #[pyo3(signature = (a, b=0, /))] fn add(a: u64, b: u64) -> u64 { a + b } ```
### `IntoPyPointer` trait removed
Click to expand The trait `IntoPyPointer`, which provided the `into_ptr` method on many types, has been removed. `into_ptr` is now available as an inherent method on all types that previously implemented this trait.
### `AsPyPointer` now `unsafe` trait
Click to expand The trait `AsPyPointer` is now `unsafe trait`, meaning any external implementation of it must be marked as `unsafe impl`, and ensure that they uphold the invariant of returning valid pointers.
## from 0.18.* to 0.19 ### Access to `Python` inside `__traverse__` implementations are now forbidden
Click to expand During `__traverse__` implementations for Python's Garbage Collection it is forbidden to do anything other than visit the members of the `#[pyclass]` being traversed. This means making Python function calls or other API calls are forbidden. Previous versions of PyO3 would allow access to `Python` (e.g. via `Python::with_gil`), which could cause the Python interpreter to crash or otherwise confuse the garbage collection algorithm. Attempts to acquire the GIL will now panic. See [#3165](https://github.com/PyO3/pyo3/issues/3165) for more detail. ```rust,ignore # use pyo3::prelude::*; #[pyclass] struct SomeClass {} impl SomeClass { fn __traverse__(&self, pyo3::class::gc::PyVisit<'_>) -> Result<(), pyo3::class::gc::PyTraverseError>` { Python::with_gil(|| { /*...*/ }) // ERROR: this will panic } } ```
### Smarter `anyhow::Error` / `eyre::Report` conversion when inner error is "simple" `PyErr`
Click to expand When converting from `anyhow::Error` or `eyre::Report` to `PyErr`, if the inner error is a "simple" `PyErr` (with no source error), then the inner error will be used directly as the `PyErr` instead of wrapping it in a new `PyRuntimeError` with the original information converted into a string. ```rust,ignore # #[cfg(feature = "anyhow")] # #[allow(dead_code)] # mod anyhow_only { # use pyo3::prelude::*; # use pyo3::exceptions::PyValueError; #[pyfunction] fn raise_err() -> anyhow::Result<()> { Err(PyValueError::new_err("original error message").into()) } fn main() { Python::with_gil(|py| { let rs_func = wrap_pyfunction!(raise_err, py).unwrap(); pyo3::py_run!( py, rs_func, r" try: rs_func() except Exception as e: print(repr(e)) " ); }) } # } ``` Before, the above code would have printed `RuntimeError('ValueError: original error message')`, which might be confusing. After, the same code will print `ValueError: original error message`, which is more straightforward. However, if the `anyhow::Error` or `eyre::Report` has a source, then the original exception will still be wrapped in a `PyRuntimeError`.
### The deprecated `Python::acquire_gil` was removed and `Python::with_gil` must be used instead
Click to expand While the API provided by [`Python::acquire_gil`](https://docs.rs/pyo3/0.18.3/pyo3/marker/struct.Python.html#method.acquire_gil) seems convenient, it is somewhat brittle as the design of the [`Python`](https://docs.rs/pyo3/0.18.3/pyo3/marker/struct.Python.html) token relies on proper nesting and panics if not used correctly, e.g. ```rust,ignore # #![allow(dead_code, deprecated)] # use pyo3::prelude::*; #[pyclass] struct SomeClass {} struct ObjectAndGuard { object: Py, guard: GILGuard, } impl ObjectAndGuard { fn new() -> Self { let guard = Python::acquire_gil(); let object = Py::new(guard.python(), SomeClass {}).unwrap(); Self { object, guard } } } let first = ObjectAndGuard::new(); let second = ObjectAndGuard::new(); // Panics because the guard within `second` is still alive. drop(first); drop(second); ``` The replacement is [`Python::with_gil`](https://docs.rs/pyo3/0.18.3/pyo3/marker/struct.Python.html#method.with_gil) which is more cumbersome but enforces the proper nesting by design, e.g. ```rust,ignore # #![allow(dead_code)] # use pyo3::prelude::*; #[pyclass] struct SomeClass {} struct Object { object: Py, } impl Object { fn new(py: Python<'_>) -> Self { let object = Py::new(py, SomeClass {}).unwrap(); Self { object } } } // It either forces us to release the GIL before acquiring it again. let first = Python::with_gil(|py| Object::new(py)); let second = Python::with_gil(|py| Object::new(py)); drop(first); drop(second); // Or it ensures releasing the inner lock before the outer one. Python::with_gil(|py| { let first = Object::new(py); let second = Python::with_gil(|py| Object::new(py)); drop(first); drop(second); }); ``` Furthermore, `Python::acquire_gil` provides ownership of a `GILGuard` which can be freely stored and passed around. This is usually not helpful as it may keep the lock held for a long time thereby blocking progress in other parts of the program. Due to the generative lifetime attached to the Python token supplied by `Python::with_gil`, the problem is avoided as the Python token can only be passed down the call chain. Often, this issue can also be avoided entirely as any GIL-bound reference `&'py PyAny` implies access to a Python token `Python<'py>` via the [`PyAny::py`](https://docs.rs/pyo3/0.22.5/pyo3/types/struct.PyAny.html#method.py) method.
## from 0.17.* to 0.18 ### Required arguments after `Option<_>` arguments will no longer be automatically inferred
Click to expand In `#[pyfunction]` and `#[pymethods]`, if a "required" function input such as `i32` came after an `Option<_>` input, then the `Option<_>` would be implicitly treated as required. (All trailing `Option<_>` arguments were treated as optional with a default value of `None`). Starting with PyO3 0.18, this is deprecated and a future PyO3 version will require a [`#[pyo3(signature = (...))]` option](./function/signature.md) to explicitly declare the programmer's intention. Before, x in the below example would be required to be passed from Python code: ```rust,compile_fail,ignore # #![allow(dead_code)] # use pyo3::prelude::*; #[pyfunction] fn required_argument_after_option(x: Option, y: i32) {} ``` After, specify the intended Python signature explicitly: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; // If x really was intended to be required #[pyfunction(signature = (x, y))] fn required_argument_after_option_a(x: Option, y: i32) {} // If x was intended to be optional, y needs a default too #[pyfunction(signature = (x=None, y=0))] fn required_argument_after_option_b(x: Option, y: i32) {} ```
### `__text_signature__` is now automatically generated for `#[pyfunction]` and `#[pymethods]`
Click to expand The [`#[pyo3(text_signature = "...")]` option](./function/signature.md#making-the-function-signature-available-to-python) was previously the only supported way to set the `__text_signature__` attribute on generated Python functions. PyO3 is now able to automatically populate `__text_signature__` for all functions automatically based on their Rust signature (or the [new `#[pyo3(signature = (...))]` option](./function/signature.md)). These automatically-generated `__text_signature__` values will currently only render `...` for all default values. Many `#[pyo3(text_signature = "...")]` options can be removed from functions when updating to PyO3 0.18, however in cases with default values a manual implementation may still be preferred for now. As examples: ```rust # use pyo3::prelude::*; // The `text_signature` option here is no longer necessary, as PyO3 will automatically // generate exactly the same value. #[pyfunction(text_signature = "(a, b, c)")] fn simple_function(a: i32, b: i32, c: i32) {} // The `text_signature` still provides value here as of PyO3 0.18, because the automatically // generated signature would be "(a, b=..., c=...)". #[pyfunction(signature = (a, b = 1, c = 2), text_signature = "(a, b=1, c=2)")] fn function_with_defaults(a: i32, b: i32, c: i32) {} # fn main() { # Python::attach(|py| { # let simple = wrap_pyfunction!(simple_function, py).unwrap(); # assert_eq!(simple.getattr("__text_signature__").unwrap().to_string(), "(a, b, c)"); # let defaulted = wrap_pyfunction!(function_with_defaults, py).unwrap(); # assert_eq!(defaulted.getattr("__text_signature__").unwrap().to_string(), "(a, b=1, c=2)"); # }) # } ```
## from 0.16.* to 0.17 ### Type checks have been changed for `PyMapping` and `PySequence` types
Click to expand Previously the type checks for `PyMapping` and `PySequence` (implemented in `PyTryFrom`) used the Python C-API functions `PyMapping_Check` and `PySequence_Check`. Unfortunately these functions are not sufficient for distinguishing such types, leading to inconsistent behavior (see [pyo3/pyo3#2072](https://github.com/PyO3/pyo3/issues/2072)). PyO3 0.17 changes these downcast checks to explicitly test if the type is a subclass of the corresponding abstract base class `collections.abc.Mapping` or `collections.abc.Sequence`. Note this requires calling into Python, which may incur a performance penalty over the previous method. If this performance penalty is a problem, you may be able to perform your own checks and use `try_from_unchecked` (unsafe). Another side-effect is that a pyclass defined in Rust with PyO3 will need to be _registered_ with the corresponding Python abstract base class for downcasting to succeed. `PySequence::register` and `PyMapping:register` have been added to make it easy to do this from Rust code. These are equivalent to calling `collections.abc.Mapping.register(MappingPyClass)` or `collections.abc.Sequence.register(SequencePyClass)` from Python. For example, for a mapping class defined in Rust: ```rust,compile_fail use pyo3::prelude::*; use std::collections::HashMap; #[pyclass(mapping)] struct Mapping { index: HashMap, } #[pymethods] impl Mapping { #[new] fn new(elements: Option<&PyList>) -> PyResult { // ... // truncated implementation of this mapping pyclass - basically a wrapper around a HashMap } ``` You must register the class with `collections.abc.Mapping` before the downcast will work: ```rust,compile_fail let m = Py::new(py, Mapping { index }).unwrap(); assert!(m.as_ref(py).downcast::().is_err()); PyMapping::register::(py).unwrap(); assert!(m.as_ref(py).downcast::().is_ok()); ``` Note that this requirement may go away in the future when a pyclass is able to inherit from the abstract base class directly (see [pyo3/pyo3#991](https://github.com/PyO3/pyo3/issues/991)).
### The `multiple-pymethods` feature now requires Rust 1.62
Click to expand Due to limitations in the `inventory` crate which the `multiple-pymethods` feature depends on, this feature now requires Rust 1.62. For more information see [dtolnay/inventory#32](https://github.com/dtolnay/inventory/issues/32).
### Added `impl IntoPy> for &str`
Click to expand This may cause inference errors. Before: ```rust,compile_fail # use pyo3::prelude::*; # # fn main() { Python::with_gil(|py| { // Cannot infer either `Py` or `Py` let _test = "test".into_py(py); }); # } ``` After, some type annotations may be necessary: ```rust,ignore # #![allow(deprecated)] # use pyo3::prelude::*; # # fn main() { Python::with_gil(|py| { let _test: Py = "test".into_py(py); }); # } ```
### The `pyproto` feature is now disabled by default
Click to expand In preparation for removing the deprecated `#[pyproto]` attribute macro in a future PyO3 version, it is now gated behind an opt-in feature flag. This also gives a slight saving to compile times for code which does not use the deprecated macro.
### `PyTypeObject` trait has been deprecated
Click to expand The `PyTypeObject` trait already was near-useless; almost all functionality was already on the `PyTypeInfo` trait, which `PyTypeObject` had a blanket implementation based upon. In PyO3 0.17 the final method, `PyTypeObject::type_object` was moved to `PyTypeInfo::type_object`. To migrate, update trait bounds and imports from `PyTypeObject` to `PyTypeInfo`. Before: ```rust,ignore use pyo3::Python; use pyo3::type_object::PyTypeObject; use pyo3::types::PyType; fn get_type_object(py: Python<'_>) -> &PyType { T::type_object(py) } ``` After ```rust,ignore use pyo3::{Python, PyTypeInfo}; use pyo3::types::PyType; fn get_type_object(py: Python<'_>) -> &PyType { T::type_object(py) } # Python::with_gil(|py| { get_type_object::(py); }); ```
### `impl IntoPy for [T; N]` now requires `T: IntoPy` rather than `T: ToPyObject`
Click to expand If this leads to errors, simply implement `IntoPy`. Because pyclasses already implement `IntoPy`, you probably don't need to worry about this.
### Each `#[pymodule]` can now only be initialized once per process
Click to expand To make PyO3 modules sound in the presence of Python sub-interpreters, for now it has been necessary to explicitly disable the ability to initialize a `#[pymodule]` more than once in the same process. Attempting to do this will now raise an `ImportError`.
## from 0.15.* to 0.16 ### Drop support for older technologies
Click to expand PyO3 0.16 has increased minimum Rust version to 1.48 and minimum Python version to 3.7. This enables use of newer language features (enabling some of the other additions in 0.16) and simplifies maintenance of the project.
### `#[pyproto]` has been deprecated
Click to expand In PyO3 0.15, the `#[pymethods]` attribute macro gained support for implementing "magic methods" such as `__str__` (aka "dunder" methods). This implementation was not quite finalized at the time, with a few edge cases to be decided upon. The existing `#[pyproto]` attribute macro was left untouched, because it covered these edge cases. In PyO3 0.16, the `#[pymethods]` implementation has been completed and is now the preferred way to implement magic methods. To allow the PyO3 project to move forward, `#[pyproto]` has been deprecated (with expected removal in PyO3 0.18). Migration from `#[pyproto]` to `#[pymethods]` is straightforward; copying the existing methods directly from the `#[pyproto]` trait implementation is all that is needed in most cases. Before: ```rust,compile_fail use pyo3::prelude::*; use pyo3::class::{PyObjectProtocol, PyIterProtocol}; use pyo3::types::PyString; #[pyclass] struct MyClass {} #[pyproto] impl PyObjectProtocol for MyClass { fn __str__(&self) -> &'static [u8] { b"hello, world" } } #[pyproto] impl PyIterProtocol for MyClass { fn __iter__(slf: PyRef) -> PyResult<&PyAny> { PyString::new(slf.py(), "hello, world").iter() } } ``` After ```rust,compile_fail use pyo3::prelude::*; use pyo3::types::PyString; #[pyclass] struct MyClass {} #[pymethods] impl MyClass { fn __str__(&self) -> &'static [u8] { b"hello, world" } fn __iter__(slf: PyRef) -> PyResult<&PyAny> { PyString::new(slf.py(), "hello, world").iter() } } ```
### Removed `PartialEq` for object wrappers
Click to expand The Python object wrappers `Py` and `PyAny` had implementations of `PartialEq` so that `object_a == object_b` would compare the Python objects for pointer equality, which corresponds to the `is` operator, not the `==` operator in Python. This has been removed in favor of a new method: use `object_a.is(object_b)`. This also has the advantage of not requiring the same wrapper type for `object_a` and `object_b`; you can now directly compare a `Py` with a `&PyAny` without having to convert. To check for Python object equality (the Python `==` operator), use the new method `eq()`.
### Container magic methods now match Python behavior
Click to expand In PyO3 0.15, `__getitem__`, `__setitem__` and `__delitem__` in `#[pymethods]` would generate only the _mapping_ implementation for a `#[pyclass]`. To match the Python behavior, these methods now generate both the _mapping_ **and** _sequence_ implementations. This means that classes implementing these `#[pymethods]` will now also be treated as sequences, same as a Python `class` would be. Small differences in behavior may result: - PyO3 will allow instances of these classes to be cast to `PySequence` as well as `PyMapping`. - Python will provide a default implementation of `__iter__` (if the class did not have one) which repeatedly calls `__getitem__` with integers (starting at 0) until an `IndexError` is raised. To explain this in detail, consider the following Python class: ```python class ExampleContainer: def __len__(self): return 5 def __getitem__(self, idx: int) -> int: if idx < 0 or idx > 5: raise IndexError() return idx ``` This class implements a Python [sequence](https://docs.python.org/3/glossary.html#term-sequence). The `__len__` and `__getitem__` methods are also used to implement a Python [mapping](https://docs.python.org/3/glossary.html#term-mapping). In the Python C-API, these methods are not shared: the sequence `__len__` and `__getitem__` are defined by the `sq_length` and `sq_item` slots, and the mapping equivalents are `mp_length` and `mp_subscript`. There are similar distinctions for `__setitem__` and `__delitem__`. Because there is no such distinction from Python, implementing these methods will fill the mapping and sequence slots simultaneously. A Python class with `__len__` implemented, for example, will have both the `sq_length` and `mp_length` slots filled. The PyO3 behavior in 0.16 has been changed to be closer to this Python behavior by default.
### `wrap_pymodule!` and `wrap_pyfunction!` now respect privacy correctly
Click to expand Prior to PyO3 0.16 the `wrap_pymodule!` and `wrap_pyfunction!` macros could use modules and functions whose defining `fn` was not reachable according Rust privacy rules. For example, the following code was legal before 0.16, but in 0.16 is rejected because the `wrap_pymodule!` macro cannot access the `private_submodule` function: ```rust,compile_fail mod foo { use pyo3::prelude::*; #[pymodule] fn private_submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> { Ok(()) } } use pyo3::prelude::*; use foo::*; #[pymodule] fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(private_submodule))?; Ok(()) } ``` To fix it, make the private submodule visible, e.g. with `pub` or `pub(crate)`. ```rust,ignore mod foo { use pyo3::prelude::*; #[pymodule] pub(crate) fn private_submodule(_py: Python<'_>, m: &PyModule) -> PyResult<()> { Ok(()) } } use pyo3::prelude::*; use pyo3::wrap_pymodule; use foo::*; #[pymodule] fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(private_submodule))?; Ok(()) } ```
## from 0.14.* to 0.15 ### Changes in sequence indexing
Click to expand For all types that take sequence indices (`PyList`, `PyTuple` and `PySequence`), the API has been made consistent to only take `usize` indices, for consistency with Rust's indexing conventions. Negative indices, which were only sporadically supported even in APIs that took `isize`, now aren't supported anywhere. Further, the `get_item` methods now always return a `PyResult` instead of panicking on invalid indices. The `Index` trait has been implemented instead, and provides the same panic behavior as on Rust vectors. Note that _slice_ indices (accepted by `PySequence::get_slice` and other) still inherit the Python behavior of clamping the indices to the actual length, and not panicking/returning an error on out of range indices. An additional advantage of using Rust's indexing conventions for these types is that these types can now also support Rust's indexing operators as part of a consistent API: ```rust,ignore #![allow(deprecated)] use pyo3::{Python, types::PyList}; Python::with_gil(|py| { let list = PyList::new(py, &[1, 2, 3]); assert_eq!(list[0..2].to_string(), "[1, 2]"); }); ```
## from 0.13.* to 0.14 ### `auto-initialize` feature is now opt-in
Click to expand For projects embedding Python in Rust, PyO3 no longer automatically initializes a Python interpreter on the first call to `Python::with_gil` (or `Python::acquire_gil`) unless the [`auto-initialize` feature](features.md#auto-initialize) is enabled.
### New `multiple-pymethods` feature
Click to expand `#[pymethods]` have been reworked with a simpler default implementation which removes the dependency on the `inventory` crate. This reduces dependencies and compile times for the majority of users. The limitation of the new default implementation is that it cannot support multiple `#[pymethods]` blocks for the same `#[pyclass]`. If you need this functionality, you must enable the `multiple-pymethods` feature which will switch `#[pymethods]` to the inventory-based implementation.
### Deprecated `#[pyproto]` methods
Click to expand Some protocol (aka `__dunder__`) methods such as `__bytes__` and `__format__` have been possible to implement two ways in PyO3 for some time: via a `#[pyproto]` (e.g. `PyObjectProtocol` for the methods listed here), or by writing them directly in `#[pymethods]`. This is only true for a handful of the `#[pyproto]` methods (for technical reasons to do with the way PyO3 currently interacts with the Python C-API). In the interest of having only one way to do things, the `#[pyproto]` forms of these methods have been deprecated. To migrate just move the affected methods from a `#[pyproto]` to a `#[pymethods]` block. Before: ```rust,compile_fail use pyo3::prelude::*; use pyo3::class::basic::PyObjectProtocol; #[pyclass] struct MyClass {} #[pyproto] impl PyObjectProtocol for MyClass { fn __bytes__(&self) -> &'static [u8] { b"hello, world" } } ``` After: ```rust,no_run use pyo3::prelude::*; #[pyclass] struct MyClass {} #[pymethods] impl MyClass { fn __bytes__(&self) -> &'static [u8] { b"hello, world" } } ```
## from 0.12.* to 0.13 ### Minimum Rust version increased to Rust 1.45
Click to expand PyO3 `0.13` makes use of new Rust language features stabilized between Rust 1.40 and Rust 1.45. If you are using a Rust compiler older than Rust 1.45, you will need to update your toolchain to be able to continue using PyO3.
### Runtime changes to support the CPython limited API
Click to expand In PyO3 `0.13` support was added for compiling against the CPython limited API. This had a number of implications for _all_ PyO3 users, described here. The largest of these is that all types created from PyO3 are what CPython calls "heap" types. The specific implications of this are: - If you wish to subclass one of these types _from Rust_ you must mark it `#[pyclass(subclass)]`, as you would if you wished to allow subclassing it from Python code. - Type objects are now mutable - Python code can set attributes on them. - `__module__` on types without `#[pyclass(module="mymodule")]` no longer returns `builtins`, it now raises `AttributeError`.
## from 0.11.* to 0.12 ### `PyErr` has been reworked
Click to expand In PyO3 `0.12` the `PyErr` type has been re-implemented to be significantly more compatible with the standard Rust error handling ecosystem. Specifically `PyErr` now implements `Error + Send + Sync`, which are the standard traits used for error types. While this has necessitated the removal of a number of APIs, the resulting `PyErr` type should now be much more easier to work with. The following sections list the changes in detail and how to migrate to the new APIs.
#### `PyErr::new` and `PyErr::from_type` now require `Send + Sync` for their argument
Click to expand For most uses no change will be needed. If you are trying to construct `PyErr` from a value that is not `Send + Sync`, you will need to first create the Python object and then use `PyErr::from_instance`. Similarly, any types which implemented `PyErrArguments` will now need to be `Send + Sync`.
#### `PyErr`'s contents are now private
Click to expand It is no longer possible to access the fields `.ptype`, `.pvalue` and `.ptraceback` of a `PyErr`. You should instead now use the new methods `PyErr::ptype`, `PyErr::pvalue` and `PyErr::ptraceback`.
#### `PyErrValue` and `PyErr::from_value` have been removed
Click to expand As these were part the internals of `PyErr` which have been reworked, these APIs no longer exist. If you used this API, it is recommended to use `PyException::new_err` (see [the section on Exception types](#exception-types-have-been-reworked)).
#### `Into>` for `PyErr` has been removed
Click to expand This implementation was redundant. Just construct the `Result::Err` variant directly. Before: ```rust,compile_fail let result: PyResult<()> = PyErr::new::("error message").into(); ``` After (also using the new reworked exception types; see the following section): ```rust,no_run # use pyo3::{PyResult, exceptions::PyTypeError}; let result: PyResult<()> = Err(PyTypeError::new_err("error message")); ```
### Exception types have been reworked
Click to expand Previously exception types were zero-sized marker types purely used to construct `PyErr`. In PyO3 0.12, these types have been replaced with full definitions and are usable in the same way as `PyAny`, `PyDict` etc. This makes it possible to interact with Python exception objects. The new types also have names starting with the "Py" prefix. For example, before: ```rust,ignore let err: PyErr = TypeError::py_err("error message"); ``` After: ```rust,ignore # use pyo3::{PyErr, PyResult, Python, type_object::PyTypeObject}; # use pyo3::exceptions::{PyBaseException, PyTypeError}; # Python::with_gil(|py| -> PyResult<()> { let err: PyErr = PyTypeError::new_err("error message"); // Uses Display for PyErr, new for PyO3 0.12 assert_eq!(err.to_string(), "TypeError: error message"); // Now possible to interact with exception instances, new for PyO3 0.12 let instance: &PyBaseException = err.instance(py); assert_eq!( instance.getattr("__class__")?, PyTypeError::type_object(py).as_ref() ); # Ok(()) # }).unwrap(); ```
### `FromPy` has been removed
Click to expand To simplify the PyO3 conversion traits, the `FromPy` trait has been removed. Previously there were two ways to define the to-Python conversion for a type: `FromPy for PyObject` and `IntoPy for T`. Now there is only one way to define the conversion, `IntoPy`, so downstream crates may need to adjust accordingly. Before: ```rust,compile_fail # use pyo3::prelude::*; struct MyPyObjectWrapper(PyObject); impl FromPy for PyObject { fn from_py(other: MyPyObjectWrapper, _py: Python<'_>) -> Self { other.0 } } ``` After ```rust,ignore # use pyo3::prelude::*; # #[allow(dead_code)] struct MyPyObjectWrapper(PyObject); # #[allow(deprecated)] impl IntoPy for MyPyObjectWrapper { fn into_py(self, _py: Python<'_>) -> PyObject { self.0 } } ``` Similarly, code which was using the `FromPy` trait can be trivially rewritten to use `IntoPy`. Before: ```rust,compile_fail # use pyo3::prelude::*; # Python::with_gil(|py| { let obj = PyObject::from_py(1.234, py); # }) ``` After: ```rust,ignore # #![allow(deprecated)] # use pyo3::prelude::*; # Python::with_gil(|py| { let obj: PyObject = 1.234.into_py(py); # }) ```
### `PyObject` is now a type alias of `Py`
Click to expand This should change very little from a usage perspective. If you implemented traits for both `PyObject` and `Py`, you may find you can just remove the `PyObject` implementation.
### `AsPyRef` has been removed
Click to expand As `PyObject` has been changed to be just a type alias, the only remaining implementor of `AsPyRef` was `Py`. This removed the need for a trait, so the `AsPyRef::as_ref` method has been moved to `Py::as_ref`. This should require no code changes except removing `use pyo3::AsPyRef` for code which did not use `pyo3::prelude::*`. Before: ```rust,ignore use pyo3::{AsPyRef, Py, types::PyList}; # pyo3::Python::with_gil(|py| { let list_py: Py = PyList::empty(py).into(); let list_ref: &PyList = list_py.as_ref(py); # }) ``` After: ```rust,ignore use pyo3::{Py, types::PyList}; # pyo3::Python::with_gil(|py| { let list_py: Py = PyList::empty(py).into(); let list_ref: &PyList = list_py.as_ref(py); # }) ```
## from 0.10.* to 0.11 ### Stable Rust
Click to expand PyO3 now supports the stable Rust toolchain. The minimum required version is 1.39.0.
### `#[pyclass]` structs must now be `Send` or `unsendable`
Click to expand Because `#[pyclass]` structs can be sent between threads by the Python interpreter, they must implement `Send` or declared as `unsendable` (by `#[pyclass(unsendable)]`). Note that `unsendable` is added in PyO3 `0.11.1` and `Send` is always required in PyO3 `0.11.0`. This may "break" some code which previously was accepted, even though it could be unsound. There can be two fixes: 1. If you think that your `#[pyclass]` actually must be `Send`able, then let's implement `Send`. A common, safer way is using thread-safe types. E.g., `Arc` instead of `Rc`, `Mutex` instead of `RefCell`, and `Box` instead of `Box`. Before: ```rust,compile_fail use pyo3::prelude::*; use std::rc::Rc; use std::cell::RefCell; #[pyclass] struct NotThreadSafe { shared_bools: Rc>>, closure: Box, } ``` After: ```rust,ignore # #![allow(dead_code)] use pyo3::prelude::*; use std::sync::{Arc, Mutex}; #[pyclass] struct ThreadSafe { shared_bools: Arc>>, closure: Box, } ``` In situations where you cannot change your `#[pyclass]` to automatically implement `Send` (e.g., when it contains a raw pointer), you can use `unsafe impl Send`. In such cases, care should be taken to ensure the struct is actually thread safe. See [the Rustonomicon](https://doc.rust-lang.org/nomicon/send-and-sync.html) for more. 2. If you think that your `#[pyclass]` should not be accessed by another thread, you can use `unsendable` flag. A class marked with `unsendable` panics when accessed by another thread, making it thread-safe to expose an unsendable object to the Python interpreter. Before: ```rust,compile_fail use pyo3::prelude::*; #[pyclass] struct Unsendable { pointers: Vec<*mut std::ffi::c_char>, } ``` After: ```rust,no_run # #![allow(dead_code)] use pyo3::prelude::*; #[pyclass(unsendable)] struct Unsendable { pointers: Vec<*mut std::ffi::c_char>, } ```
### All `PyObject` and `Py` methods now take `Python` as an argument
Click to expand Previously, a few methods such as `Object::get_refcnt` did not take `Python` as an argument (to ensure that the Python GIL was held by the current thread). Technically, this was not sound. To migrate, just pass a `py` argument to any calls to these methods. Before: ```rust,compile_fail # pyo3::Python::attach(|py| { py.None().get_refcnt(); # }) ``` After: ```rust,ignore # pyo3::Python::attach(|py| { py.None().get_refcnt(py); # }) ```
## from 0.9.* to 0.10 ### `ObjectProtocol` is removed
Click to expand All methods are moved to [`PyAny`]. And since now all native types (e.g., `PyList`) implements `Deref`, all you need to do is remove `ObjectProtocol` from your code. Or if you use `ObjectProtocol` by `use pyo3::prelude::*`, you have to do nothing. Before: ```rust,compile_fail,ignore use pyo3::ObjectProtocol; # pyo3::Python::with_gil(|py| { let obj = py.eval("lambda: 'Hi :)'", None, None).unwrap(); let hi: &pyo3::types::PyString = obj.call0().unwrap().downcast().unwrap(); assert_eq!(hi.len().unwrap(), 5); # }) ``` After: ```rust,ignore # pyo3::Python::with_gil(|py| { let obj = py.eval("lambda: 'Hi :)'", None, None).unwrap(); let hi: &pyo3::types::PyString = obj.call0().unwrap().downcast().unwrap(); assert_eq!(hi.len().unwrap(), 5); # }) ```
### No `#![feature(specialization)]` in user code
Click to expand While PyO3 itself still requires specialization and nightly Rust, now you don't have to use `#![feature(specialization)]` in your crate.
## from 0.8.* to 0.9 ### `#[new]` interface
Click to expand [`PyRawObject`](https://docs.rs/pyo3/0.8.5/pyo3/type_object/struct.PyRawObject.html) is now removed and our syntax for constructors has changed. Before: ```rust,compile_fail #[pyclass] struct MyClass {} #[pymethods] impl MyClass { #[new] fn new(obj: &PyRawObject) { obj.init(MyClass {}) } } ``` After: ```rust,no_run # use pyo3::prelude::*; #[pyclass] struct MyClass {} #[pymethods] impl MyClass { #[new] fn new() -> Self { MyClass {} } } ``` Basically you can return `Self` or `Result` directly. For more, see [the constructor section](class.md#constructor) of this guide.
### PyCell
Click to expand PyO3 0.9 introduces `PyCell`, which is a [`RefCell`]-like object wrapper for ensuring Rust's rules regarding aliasing of references are upheld. For more detail, see the [Rust Book's section on Rust's rules of references](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references) For `#[pymethods]` or `#[pyfunction]`s, your existing code should continue to work without any change. Python exceptions will automatically be raised when your functions are used in a way which breaks Rust's rules of references. Here is an example. ```rust # use pyo3::prelude::*; #[pyclass] struct Names { names: Vec, } #[pymethods] impl Names { #[new] fn new() -> Self { Names { names: vec![] } } fn merge(&mut self, other: &mut Names) { self.names.append(&mut other.names) } } # Python::attach(|py| { # let names = Py::new(py, Names::new()).unwrap(); # pyo3::py_run!(py, names, r" # try: # names.merge(names) # assert False, 'Unreachable' # except RuntimeError as e: # assert str(e) == 'Already borrowed' # "); # }) ``` `Names` has a `merge` method, which takes `&mut self` and another argument of type `&mut Self`. Given this `#[pyclass]`, calling `names.merge(names)` in Python raises a [`PyBorrowMutError`] exception, since it requires two mutable borrows of `names`. However, for `#[pyproto]` and some functions, you need to manually fix the code. #### Object creation In 0.8 object creation was done with `PyRef::new` and `PyRefMut::new`. In 0.9 these have both been removed. To upgrade code, please use `PyCell::new` instead. If you need [`PyRef`] or [`PyRefMut`], just call `.borrow()` or `.borrow_mut()` on the newly-created `PyCell`. Before: ```rust,compile_fail # use pyo3::prelude::*; # #[pyclass] # struct MyClass {} # Python::with_gil(|py| { let obj_ref = PyRef::new(py, MyClass {}).unwrap(); # }) ``` After: ```rust,ignore # use pyo3::prelude::*; # #[pyclass] # struct MyClass {} # Python::with_gil(|py| { let obj = PyCell::new(py, MyClass {}).unwrap(); let obj_ref = obj.borrow(); # }) ``` #### Object extraction For `PyClass` types `T`, `&T` and `&mut T` no longer have [`FromPyObject`] implementations. Instead you should extract `PyRef` or `PyRefMut`, respectively. If `T` implements `Clone`, you can extract `T` itself. In addition, you can also extract `&PyCell`, though you rarely need it. Before: ```compile_fail let obj: &PyAny = create_obj(); let obj_ref: &MyClass = obj.extract().unwrap(); let obj_ref_mut: &mut MyClass = obj.extract().unwrap(); ``` After: ```rust,ignore # use pyo3::prelude::*; # use pyo3::types::IntoPyDict; # #[pyclass] #[derive(Clone)] struct MyClass {} # #[pymethods] impl MyClass { #[new]fn new() -> Self { MyClass {} }} # Python::with_gil(|py| { # let typeobj = py.get_type::(); # let d = [("c", typeobj)].into_py_dict(py); # let create_obj = || py.eval("c()", None, Some(d)).unwrap(); let obj: &PyAny = create_obj(); let obj_cell: &PyCell = obj.extract().unwrap(); let obj_cloned: MyClass = obj.extract().unwrap(); // extracted by cloning the object { let obj_ref: PyRef<'_, MyClass> = obj.extract().unwrap(); // we need to drop obj_ref before we can extract a PyRefMut due to Rust's rules of references } let obj_ref_mut: PyRefMut<'_, MyClass> = obj.extract().unwrap(); # }) ``` #### `#[pyproto]` Most of the arguments to methods in `#[pyproto]` impls require a [`FromPyObject`] implementation. So if your protocol methods take `&T` or `&mut T` (where `T: PyClass`), please use [`PyRef`] or [`PyRefMut`] instead. Before: ```rust,compile_fail # use pyo3::prelude::*; # use pyo3::class::PySequenceProtocol; #[pyclass] struct ByteSequence { elements: Vec, } #[pyproto] impl PySequenceProtocol for ByteSequence { fn __concat__(&self, other: &Self) -> PyResult { let mut elements = self.elements.clone(); elements.extend_from_slice(&other.elements); Ok(Self { elements }) } } ``` After: ```rust,compile_fail # use pyo3::prelude::*; # use pyo3::class::PySequenceProtocol; #[pyclass] struct ByteSequence { elements: Vec, } #[pyproto] impl PySequenceProtocol for ByteSequence { fn __concat__(&self, other: PyRef<'p, Self>) -> PyResult { let mut elements = self.elements.clone(); elements.extend_from_slice(&other.elements); Ok(Self { elements }) } } ```
[`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html [`PyAny`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html [`PyBorrowMutError`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyBorrowMutError.html [`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html [`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html [`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html ================================================ FILE: guide/src/module.md ================================================ # Python modules You can create a module using `#[pymodule]`: ```rust,no_run # mod declarative_module_basic_test { use pyo3::prelude::*; #[pyfunction] fn double(x: usize) -> usize { x * 2 } /// This module is implemented in Rust. #[pymodule] mod my_extension { use pyo3::prelude::*; #[pymodule_export] use super::double; // The double function is made available from Python, works also with classes #[pyfunction] // Inline definition of a pyfunction, also made available to Python fn triple(x: usize) -> usize { x * 3 } } # } ``` The `#[pymodule]` procedural macro takes care of creating the initialization function of your module and exposing it to Python. The module's name defaults to the name of the Rust module. You can override the module name by using `#[pyo3(name = "custom_name")]`: ```rust,no_run # mod declarative_module_custom_name_test { use pyo3::prelude::*; #[pyfunction] fn double(x: usize) -> usize { x * 2 } #[pymodule(name = "custom_name")] mod my_extension { #[pymodule_export] use super::double; } # } ``` The name of the module must match the name of the `.so` or `.pyd` file. Otherwise, you will get an import error in Python with the following message: `ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)` To import the module, either: - copy the shared library as described in [Manual builds](building-and-distribution.md#manual-builds), or - use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or `python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust). ## Documentation The [Rust doc comments](https://doc.rust-lang.org/stable/book/ch03-04-comments.html) of the Rust module will be applied automatically as the Python docstring of your module. For example, building off of the above code, this will print `This module is implemented in Rust.`: ```python import my_extension print(my_extension.__doc__) ``` ## Python submodules You can create a module hierarchy within a single extension module by just `use`ing modules like functions or classes. For example, you could define the modules `parent_module` and `parent_module.child_module`: ```rust use pyo3::prelude::*; #[pymodule] mod parent_module { #[pymodule_export] use super::child_module; } #[pymodule] mod child_module { #[pymodule_export] use super::func; } #[pyfunction] fn func() -> String { "func".to_string() } # # fn main() { # Python::attach(|py| { # use pyo3::wrap_pymodule; # use pyo3::types::IntoPyDict; # let parent_module = wrap_pymodule!(parent_module)(py); # let ctx = [("parent_module", parent_module)].into_py_dict(py).unwrap(); # # py.run(c"assert parent_module.child_module.func() == 'func'", None, Some(&ctx)).unwrap(); # }) # } ``` Note that this does not define a package, so this won’t allow Python code to directly import submodules by using `from parent_module import child_module`. For more information, see [#759](https://github.com/PyO3/pyo3/issues/759) and [#1517](https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021). You can provide the `submodule` argument to `#[pymodule()]` for modules that are not top-level modules in order for them to properly generate the `#[pyclass]` `module` attribute automatically. ## Inline declaration It is possible to declare functions, classes, sub-modules and constants inline in a module: For example: ```rust,no_run # mod declarative_module_test { #[pyo3::pymodule] mod my_extension { use pyo3::prelude::*; #[pymodule_export] const PI: f64 = std::f64::consts::PI; // Exports PI constant as part of the module #[pyfunction] // This will be part of the module fn double(x: usize) -> usize { x * 2 } #[pyclass] // This will be part of the module struct Unit; #[pymodule] mod submodule { // This is a submodule use pyo3::prelude::*; #[pyclass] struct Nested; } } # } ``` In this case, `#[pymodule]` macro automatically sets the `module` attribute of the `#[pyclass]` macros declared inside of it with its name. For nested modules, the name of the parent module is automatically added. In the previous example, the `Nested` class will have for `module` `my_extension.submodule`. ## Procedural initialization If the macros provided by PyO3 are not enough, it is possible to run code at the module initialization: ```rust,no_run # mod procedural_module_test { #[pyo3::pymodule] mod my_extension { use pyo3::prelude::*; #[pyfunction] fn double(x: usize) -> usize { x * 2 } #[pymodule_init] fn init(m: &Bound<'_, PyModule>) -> PyResult<()> { // Arbitrary code to run at the module initialization m.add("double2", m.getattr("double")?) } } # } ``` ================================================ FILE: guide/src/parallelism.md ================================================ # Parallelism Historically, CPython was limited by the [global interpreter lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock) (GIL), which only allowed a single thread to drive the Python interpreter at a time. This made threading in Python a bad fit for [CPU-bound](https://en.wikipedia.org/wiki/CPU-bound) tasks and often forced developers to accept the overhead of multiprocessing. Rust is well-suited to multithreaded code, and libraries like [`rayon`] can help you leverage safe parallelism with minimal effort. The [`Python::detach`] method can be used to allow the Python interpreter to do other work while the Rust work is ongoing. To enable full parallelism in your application, consider also using [free-threaded Python](./free-threading.md) which is supported since Python 3.14. ## Parallelism under the Python GIL Let's take a look at our [word-count](https://github.com/PyO3/pyo3/blob/main/examples/word-count/src/lib.rs) example, where we have a `search` function that utilizes the [`rayon`] crate to count words in parallel. ```rust,no_run # #![allow(dead_code)] use pyo3::prelude::*; // These traits let us use `par_lines` and `map`. use rayon::str::ParallelString; use rayon::iter::ParallelIterator; /// Count the occurrences of needle in line, case insensitive fn count_line(line: &str, needle: &str) -> usize { let mut total = 0; for word in line.split(' ') { if word == needle { total += 1; } } total } #[pyfunction] fn search(contents: &str, needle: &str) -> usize { contents .par_lines() .map(|line| count_line(line, needle)) .sum() } ``` But let's assume you have a long running Rust function which you would like to execute several times in parallel. For the sake of example let's take a sequential version of the word count: ```rust,no_run # #![allow(dead_code)] # fn count_line(line: &str, needle: &str) -> usize { # let mut total = 0; # for word in line.split(' ') { # if word == needle { # total += 1; # } # } # total # } # fn search_sequential(contents: &str, needle: &str) -> usize { contents.lines().map(|line| count_line(line, needle)).sum() } ``` To enable parallel execution of this function, the [`Python::detach`] method can be used to temporarily release the GIL, thus allowing other Python threads to run. We then have a function exposed to the Python runtime which calls `search_sequential` inside a closure passed to [`Python::detach`] to enable true parallelism: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # # fn count_line(line: &str, needle: &str) -> usize { # let mut total = 0; # for word in line.split(' ') { # if word == needle { # total += 1; # } # } # total # } # # fn search_sequential(contents: &str, needle: &str) -> usize { # contents.lines().map(|line| count_line(line, needle)).sum() # } #[pyfunction] fn search_sequential_detached(py: Python<'_>, contents: &str, needle: &str) -> usize { py.detach(|| search_sequential(contents, needle)) } ``` Now Python threads can use more than one CPU core, resolving the limitation which usually makes multi-threading in Python only good for IO-bound tasks: ```Python from concurrent.futures import ThreadPoolExecutor from word_count import search_sequential_detached executor = ThreadPoolExecutor(max_workers=2) future_1 = executor.submit( word_count.search_sequential_detached, contents, needle ) future_2 = executor.submit( word_count.search_sequential_detached, contents, needle ) result_1 = future_1.result() result_2 = future_2.result() ``` ## Benchmark Let's benchmark the `word-count` example to verify that we really did unlock parallelism with PyO3. We are using `pytest-benchmark` to benchmark four word count functions: 1. Pure Python version 2. Rust parallel version 3. Rust sequential version 4. Rust sequential version executed twice with two Python threads The benchmark script can be found [in the PyO3 GitHub repository](https://github.com/PyO3/pyo3/blob/main/examples/word-count/tests/test_word_count.py), and we can run `nox` in the `word-count` folder to benchmark these functions. While the results of the benchmark of course depend on your machine, the relative results should be similar to this (mid 2020): ```text -------------------------------------------------------------------------------------------------- benchmark: 4 tests ------------------------------------------------------------------------------------------------- Name (time in ms) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- test_word_count_rust_parallel 1.7315 (1.0) 4.6495 (1.0) 1.9972 (1.0) 0.4299 (1.0) 1.8142 (1.0) 0.2049 (1.0) 40;46 500.6943 (1.0) 375 1 test_word_count_rust_sequential 7.3348 (4.24) 10.3556 (2.23) 8.0035 (4.01) 0.7785 (1.81) 7.5597 (4.17) 0.8641 (4.22) 26;5 124.9457 (0.25) 121 1 test_word_count_rust_sequential_twice_with_threads 7.9839 (4.61) 10.3065 (2.22) 8.4511 (4.23) 0.4709 (1.10) 8.2457 (4.55) 0.3927 (1.92) 17;17 118.3274 (0.24) 114 1 test_word_count_python_sequential 27.3985 (15.82) 45.4527 (9.78) 28.9604 (14.50) 4.1449 (9.64) 27.5781 (15.20) 0.4638 (2.26) 3;5 34.5299 (0.07) 35 1 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ``` You can see that the Python threaded version is not much slower than the Rust sequential version, which means compared to an execution on a single CPU core the speed has doubled. ## Sharing Python objects between Rust threads In the example above we made a Python interface to a low-level rust function, and then leveraged the python `threading` module to run the low-level function in parallel. It is also possible to spawn threads in Rust that acquire the GIL and operate on Python objects. However, care must be taken to avoid writing code that deadlocks with the GIL in these cases. - Note: This example is meant to illustrate how to drop and re-acquire the GIL to avoid creating deadlocks. Unless the spawned threads subsequently release the GIL or you are using the free-threaded build of CPython, you will not see any speedups due to multi-threaded parallelism using `rayon` to parallelize code that acquires and holds the GIL for the entire execution of the spawned thread. In the example below, we share a `Vec` of User ID objects defined using the `pyclass` macro and spawn threads to process the collection of data into a `Vec` of booleans based on a predicate using a `rayon` parallel iterator: ```rust,no_run use pyo3::prelude::*; // These traits let us use int_par_iter and map use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; #[pyclass] struct UserID { id: i64, } let allowed_ids: Vec = Python::attach(|outer_py| { let instances: Vec> = (0..10).map(|x| Py::new(outer_py, UserID { id: x }).unwrap()).collect(); outer_py.detach(|| { instances.par_iter().map(|instance| { Python::attach(|inner_py| { instance.borrow(inner_py).id > 5 }) }).collect() }) }); assert!(allowed_ids.into_iter().filter(|b| *b).count() == 4); ``` It's important to note that there is an `outer_py` Python token as well as an `inner_py` token. Sharing Python tokens between threads is not allowed and threads must individually attach to the interpreter to access data wrapped by a Python object. It's also important to see that this example uses [`Python::detach`] to wrap the code that spawns OS threads via `rayon`. If this example didn't use `detach`, a `rayon` worker thread would block on acquiring the GIL while a thread that owns the GIL spins forever waiting for the result of the `rayon` thread. Calling `detach` allows the GIL to be released in the thread collecting the results from the worker threads. You should always call `detach` in situations that spawn worker threads, but especially so in cases where worker threads need to acquire the GIL, to prevent deadlocks. [`Python::detach`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.detach [`rayon`]: https://github.com/rayon-rs/rayon ================================================ FILE: guide/src/performance.md ================================================ # Performance To achieve the best possible performance, it is useful to be aware of several tricks and sharp edges concerning PyO3's API. ## `extract` versus `cast` Pythonic API implemented using PyO3 are often polymorphic, i.e. they will accept `&Bound<'_, PyAny>` and try to turn this into multiple more concrete types to which the requested operation is applied. This often leads to chains of calls to `extract`, e.g. ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::{exceptions::PyTypeError, types::PyList}; fn frobnicate_list<'py>(list: &Bound<'_, PyList>) -> PyResult> { todo!() } fn frobnicate_vec<'py>(vec: Vec>) -> PyResult> { todo!() } #[pyfunction] fn frobnicate<'py>(value: &Bound<'py, PyAny>) -> PyResult> { if let Ok(list) = value.extract::>() { frobnicate_list(&list) } else if let Ok(vec) = value.extract::>>() { frobnicate_vec(vec) } else { Err(PyTypeError::new_err("Cannot frobnicate that type.")) } } ``` This suboptimal as the `FromPyObject` trait requires `extract` to have a `Result` return type. For native types like `PyList`, it faster to use `cast` (which `extract` calls internally) when the error value is ignored. This avoids the costly conversion of a `PyDowncastError` to a `PyErr` required to fulfil the `FromPyObject` contract, i.e. ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::{exceptions::PyTypeError, types::PyList}; # fn frobnicate_list<'py>(list: &Bound<'_, PyList>) -> PyResult> { todo!() } # fn frobnicate_vec<'py>(vec: Vec>) -> PyResult> { todo!() } # #[pyfunction] fn frobnicate<'py>(value: &Bound<'py, PyAny>) -> PyResult> { // Use `cast` instead of `extract` as turning `PyDowncastError` into `PyErr` is quite costly. if let Ok(list) = value.cast::() { frobnicate_list(list) } else if let Ok(vec) = value.extract::>>() { frobnicate_vec(vec) } else { Err(PyTypeError::new_err("Cannot frobnicate that type.")) } } ``` ## Access to Bound implies access to Python token Calling `Python::attach` is effectively a no-op when we're already attached to the interpreter, but checking that this is the case still has a cost. If an existing Python token can not be accessed, for example when implementing a pre-existing trait, but a Python-bound reference is available, this cost can be avoided by exploiting that access to Python-bound reference gives zero-cost access to a Python token via `Bound::py`. For example, instead of writing ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::PyList; struct Foo(Py); struct FooBound<'py>(Bound<'py, PyList>); impl PartialEq for FooBound<'_> { fn eq(&self, other: &Foo) -> bool { Python::attach(|py| { let len = other.0.bind(py).len(); self.0.len() == len }) } } ``` use the more efficient ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::PyList; # struct Foo(Py); # struct FooBound<'py>(Bound<'py, PyList>); # impl PartialEq for FooBound<'_> { fn eq(&self, other: &Foo) -> bool { // Access to `&Bound<'py, PyAny>` implies access to `Python<'py>`. let py = self.0.py(); let len = other.0.bind(py).len(); self.0.len() == len } } ``` ## Calling Python callables (`__call__`) CPython support multiple calling protocols: [`tp_call`] and [`vectorcall`]. [`vectorcall`] is a more efficient protocol unlocking faster calls. PyO3 will try to dispatch Python `call`s using the [`vectorcall`] calling convention to archive maximum performance if possible and falling back to [`tp_call`] otherwise. This is implemented using the (internal) `PyCallArgs` trait. It defines how Rust types can be used as Python `call` arguments. This trait is currently implemented for - Rust tuples, where each member implements `IntoPyObject`, - `Bound<'_, PyTuple>` - `Py` Rust tuples may make use of [`vectorcall`] where as `Bound<'_, PyTuple>` and `Py` can only use [`tp_call`]. For maximum performance prefer using Rust tuples as arguments. [`tp_call`]: https://docs.python.org/3/c-api/call.html#the-tp-call-protocol [`vectorcall`]: https://docs.python.org/3/c-api/call.html#the-vectorcall-protocol ## Detach from the interpreter for long-running Rust-only work When executing Rust code which does not need to interact with the Python interpreter, use [`Python::detach`] to allow the Python interpreter to proceed without waiting for the current thread. On the GIL-enabled build, this is crucial for best performance as only a single thread may ever be attached at a time. On the free-threaded build, this is still best practice as there are several "stop the world" events (such as garbage collection) where all threads attached to the Python interpreter are forced to wait. As a rule of thumb, attaching and detaching from the Python interpreter takes less than a millisecond, so any work which is expected to take multiple milliseconds can likely benefit from detaching from the interpreter. [`Python::detach`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.detach ## Disable the global reference pool PyO3 uses global mutable state to keep track of deferred reference count updates implied by `impl Drop for Py` being called without being attached to the interpreter. The necessary synchronization to obtain and apply these reference count updates when PyO3-based code next attaches to the interpreter is somewhat expensive and can become a significant part of the cost of crossing the Python-Rust boundary. This functionality can be avoided by setting the `pyo3_disable_reference_pool` conditional compilation flag. This removes the global reference pool and the associated costs completely. However, it does _not_ remove the `Drop` implementation for `Py` which is necessary to interoperate with existing Rust code written without PyO3-based code in mind. To stay compatible with the wider Rust ecosystem in these cases, we keep the implementation but abort when `Drop` is called without being attached to the interpreter. If `pyo3_leak_on_drop_without_reference_pool` is additionally enabled, objects dropped without being attached to Python will be leaked instead which is always sound but might have determinal effects like resource exhaustion in the long term. This limitation is important to keep in mind when this setting is used, especially when embedding Python code into a Rust application as it is quite easy to accidentally drop a `Py` (or types containing it like `PyErr`, `PyBackedStr` or `PyBackedBytes`) returned from `Python::attach` without making sure to re-attach beforehand. For example, the following code ```rust,ignore # use pyo3::prelude::*; # use pyo3::types::PyList; let numbers: Py = Python::attach(|py| PyList::empty(py).unbind()); Python::attach(|py| { numbers.bind(py).append(23).unwrap(); }); Python::attach(|py| { numbers.bind(py).append(42).unwrap(); }); ``` will abort if the list not explicitly disposed via ```rust # use pyo3::prelude::*; # use pyo3::types::PyList; let numbers: Py = Python::attach(|py| PyList::empty(py).unbind()); Python::attach(|py| { numbers.bind(py).append(23).unwrap(); }); Python::attach(|py| { numbers.bind(py).append(42).unwrap(); }); Python::attach(move |py| { drop(numbers); }); ``` ================================================ FILE: guide/src/python-from-rust/calling-existing-code.md ================================================ # Executing existing Python code If you already have some existing Python code that you need to execute from Rust, the following FAQs can help you select the right PyO3 functionality for your situation: ## Want to access Python APIs? Then use `PyModule::import` [`PyModule::import`] can be used to get handle to a Python module from Rust. You can use this to import and use any Python module available in your environment. ```rust use pyo3::prelude::*; fn main() -> PyResult<()> { Python::attach(|py| { let builtins = PyModule::import(py, "builtins")?; let total: i32 = builtins .getattr("sum")? .call1((vec![1, 2, 3],))? .extract()?; assert_eq!(total, 6); Ok(()) }) } ``` [`PyModule::import`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.import ## Want to run just an expression? Then use `eval` [`Python::eval`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval) is a method to execute a [Python expression](https://docs.python.org/3/reference/expressions.html) and return the evaluated value as a `Bound<'py, PyAny>` object. ```rust use pyo3::prelude::*; # fn main() -> Result<(), ()> { Python::attach(|py| { let result = py .eval(c"[i * 10 for i in range(5)]", None, None) .map_err(|e| { e.print_and_set_sys_last_vars(py); })?; let res: Vec = result.extract().unwrap(); assert_eq!(res, vec![0, 10, 20, 30, 40]); Ok(()) }) # } ``` ## Want to run statements? Then use `run` [`Python::run`] is a method to execute one or more [Python statements](https://docs.python.org/3/reference/simple_stmts.html). This method returns nothing (like any Python statement), but you can get access to manipulated objects via the `locals` dict. You can also use the [`py_run!`] macro, which is a shorthand for [`Python::run`]. Since [`py_run!`] panics on exceptions, we recommend you use this macro only for quickly testing your Python extensions. ```rust use pyo3::prelude::*; use pyo3::py_run; # fn main() { #[pyclass] struct UserData { id: u32, name: String, } #[pymethods] impl UserData { fn as_tuple(&self) -> (u32, String) { (self.id, self.name.clone()) } fn __repr__(&self) -> PyResult { Ok(format!("User {}(id: {})", self.name, self.id)) } } Python::attach(|py| { let userdata = UserData { id: 34, name: "Yu".to_string(), }; let userdata = Py::new(py, userdata).unwrap(); let userdata_as_tuple = (34, "Yu"); py_run!(py, userdata userdata_as_tuple, r#" assert repr(userdata) == "User Yu(id: 34)" assert userdata.as_tuple() == userdata_as_tuple "#); }) # } ``` ## You have a Python file or code snippet? Then use `PyModule::from_code` [`PyModule::from_code`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.from_code) can be used to generate a Python module which can then be used just as if it was imported with `PyModule::import`. **Warning**: This will compile and execute code. **Never** pass untrusted code to this function! ```rust use pyo3::{prelude::*, types::IntoPyDict}; # fn main() -> PyResult<()> { Python::attach(|py| { let activators = PyModule::from_code( py, cr#" def relu(x): """see https://en.wikipedia.org/wiki/Rectifier_(neural_networks)""" return max(0.0, x) def leaky_relu(x, slope=0.01): return x if x >= 0 else x * slope "#, c"activators.py", c"activators", )?; let relu_result: f64 = activators.getattr("relu")?.call1((-1.0,))?.extract()?; assert_eq!(relu_result, 0.0); let kwargs = [("slope", 0.2)].into_py_dict(py)?; let lrelu_result: f64 = activators .getattr("leaky_relu")? .call((-1.0,), Some(&kwargs))? .extract()?; assert_eq!(lrelu_result, -0.2); # Ok(()) }) # } ``` ## Want to embed Python in Rust with additional modules? Python maintains the `sys.modules` dict as a cache of all imported modules. An import in Python will first attempt to lookup the module from this dict, and if not present will use various strategies to attempt to locate and load the module. The [`append_to_inittab`]({{#PYO3_DOCS_URL}}/pyo3/macro.append_to_inittab.html) macro can be used to add additional `#[pymodule]` modules to an embedded Python interpreter. The macro **must** be invoked _before_ initializing Python. As an example, the below adds the module `foo` to the embedded interpreter: ```rust use pyo3::prelude::*; #[pymodule] mod foo { use pyo3::prelude::*; #[pyfunction] fn add_one(x: i64) -> i64 { x + 1 } } fn main() -> PyResult<()> { pyo3::append_to_inittab!(foo); Python::attach(|py| Python::run(py, c"import foo; foo.add_one(6)", None, None)) } ``` If `append_to_inittab` cannot be used due to constraints in the program, an alternative is to create a module using [`PyModule::new`] and insert it manually into `sys.modules`: ```rust use pyo3::prelude::*; use pyo3::types::PyDict; #[pyfunction] pub fn add_one(x: i64) -> i64 { x + 1 } fn main() -> PyResult<()> { Python::attach(|py| { // Create new module let foo_module = PyModule::new(py, "foo")?; foo_module.add_function(wrap_pyfunction!(add_one, &foo_module)?)?; // Import and get sys.modules let sys = PyModule::import(py, "sys")?; let py_modules: Bound<'_, PyDict> = sys.getattr("modules")?.cast_into()?; // Insert foo into sys.modules py_modules.set_item("foo", foo_module)?; // Now we can import + run our python code Python::run(py, c"import foo; foo.add_one(6)", None, None) }) } ``` ## Include multiple Python files You can include a file at compile time by using [`std::include_str`](https://doc.rust-lang.org/std/macro.include_str.html) macro. Or you can load a file at runtime by using [`std::fs::read_to_string`](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) function. Many Python files can be included and loaded as modules. If one file depends on another you must preserve correct order while declaring `PyModule`. Example directory structure: ```text . ├── Cargo.lock ├── Cargo.toml ├── python_app │ ├── app.py │ └── utils │ └── foo.py └── src └── main.rs ``` `python_app/app.py`: ```python from utils.foo import bar def run(): return bar() ``` `python_app/utils/foo.py`: ```python def bar(): return "baz" ``` The example below shows: - how to include content of `app.py` and `utils/foo.py` into your rust binary - how to call function `run()` (declared in `app.py`) that needs function imported from `utils/foo.py` `src/main.rs`: ```rust,ignore use pyo3::prelude::*; use pyo3_ffi::c_str; fn main() -> PyResult<()> { let py_foo = c_str!(include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/python_app/utils/foo.py" ))); let py_app = c_str!(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/python_app/app.py"))); let from_python = Python::attach(|py| -> PyResult> { PyModule::from_code(py, py_foo, c"foo.py", c"utils.foo")?; let app: Py = PyModule::from_code(py, py_app, c"app.py", c"")? .getattr("run")? .into(); app.call0(py) }); println!("py: {}", from_python?); Ok(()) } ``` The example below shows: - how to load content of `app.py` at runtime so that it sees its dependencies automatically - how to call function `run()` (declared in `app.py`) that needs function imported from `utils/foo.py` It is recommended to use absolute paths because then your binary can be run from anywhere as long as your `app.py` is in the expected directory (in this example that directory is `/usr/share/python_app`). `src/main.rs`: ```rust,no_run use pyo3::prelude::*; use pyo3::types::PyList; use std::fs; use std::path::Path; use std::ffi::CString; fn main() -> PyResult<()> { let path = Path::new("/usr/share/python_app"); let py_app = CString::new(fs::read_to_string(path.join("app.py"))?)?; let from_python = Python::attach(|py| -> PyResult> { let syspath = py .import("sys")? .getattr("path")? .cast_into::()?; syspath.insert(0, path)?; let app: Py = PyModule::from_code(py, py_app.as_c_str(), c"app.py", c"")? .getattr("run")? .into(); app.call0(py) }); println!("py: {}", from_python?); Ok(()) } ``` ## Need to use a context manager from Rust? Use context managers by directly invoking `__enter__` and `__exit__`. ```rust use pyo3::prelude::*; fn main() { Python::attach(|py| { let custom_manager = PyModule::from_code( py, cr#" class House(object): def __init__(self, address): self.address = address def __enter__(self): print(f"Welcome to {self.address}!") def __exit__(self, type, value, traceback): if type: print(f"Sorry you had {type} trouble at {self.address}") else: print(f"Thank you for visiting {self.address}, come again soon!") "#, c"house.py", c"house", ) .unwrap(); let house_class = custom_manager.getattr("House").unwrap(); let house = house_class.call1(("123 Main Street",)).unwrap(); house.call_method0("__enter__").unwrap(); let result = py.eval(c"undefined_variable + 1", None, None); // If the eval threw an exception we'll pass it through to the context manager. // Otherwise, __exit__ is called with empty arguments (Python "None"). match result { Ok(_) => { let none = py.None(); house .call_method1("__exit__", (&none, &none, &none)) .unwrap(); } Err(e) => { house .call_method1( "__exit__", ( e.get_type(py), e.value(py), e.traceback(py), ), ) .unwrap(); } } }) } ``` ## Handling system signals/interrupts (Ctrl-C) The best way to handle system signals when running Rust code is to periodically call `Python::check_signals` to handle any signals captured by Python's signal handler. See also [the FAQ entry](../faq.md#ctrl-c-doesnt-do-anything-while-my-rust-code-is-executing). Alternatively, set Python's `signal` module to take the default action for a signal: ```rust use pyo3::prelude::*; # fn main() -> PyResult<()> { Python::attach(|py| -> PyResult<()> { let signal = py.import("signal")?; // Set SIGINT to have the default action signal .getattr("signal")? .call1((signal.getattr("SIGINT")?, signal.getattr("SIG_DFL")?))?; Ok(()) }) # } ``` [`py_run!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.py_run.html [`Python::run`]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.run [`PyModule::new`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyModule.html#method.new ================================================ FILE: guide/src/python-from-rust/function-calls.md ================================================ # Calling Python functions The `Bound<'py, T>` smart pointer (such as `Bound<'py, PyAny>`, `Bound<'py, PyList>`, or `Bound<'py, MyClass>`) can be used to call Python functions. PyO3 offers two APIs to make function calls: - [`call`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call) - call any callable Python object. - [`call_method`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call_method) - call a method on the Python object. Both of these APIs take `args` and `kwargs` arguments (for positional and keyword arguments respectively). There are variants for less complex calls: - [`call1`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call1) and [`call_method1`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call_method1) to call only with positional `args`. - [`call0`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call0) and [`call_method0`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call_method0) to call with no arguments. For convenience the [`Py`](../types.md#pyt) smart pointer also exposes these same six API methods, but needs a `Python` token as an additional first argument to prove the thread is attached to the Python interpreter. The example below calls a Python function behind a `Py` reference: ```rust use pyo3::prelude::*; use pyo3::types::PyTuple; fn main() -> PyResult<()> { let arg1 = "arg1"; let arg2 = "arg2"; let arg3 = "arg3"; Python::attach(|py| { let fun: Py = PyModule::from_code( py, c"def example(*args, **kwargs): if args != (): print('called with args', args) if kwargs != {}: print('called with kwargs', kwargs) if args == () and kwargs == {}: print('called with no arguments')", c"example.py", c"", )? .getattr("example")? .into(); // call object without any arguments fun.call0(py)?; // pass object with Rust tuple of positional arguments let args = (arg1, arg2, arg3); fun.call1(py, args)?; // call object with Python tuple of positional arguments let args = PyTuple::new(py, &[arg1, arg2, arg3])?; fun.call1(py, args)?; Ok(()) }) } ``` ## Creating keyword arguments For the `call` and `call_method` APIs, `kwargs` are `Option<&Bound<'py, PyDict>>`, so can either be `None` or `Some(&dict)`. You can use the [`IntoPyDict`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.IntoPyDict.html) trait to convert other dict-like containers, e.g. `HashMap` or `BTreeMap`, as well as tuples with up to 10 elements and `Vec`s where each element is a two-element tuple. To pass keyword arguments of different types, construct a `PyDict` object. ```rust use pyo3::prelude::*; use pyo3::types::{PyDict, IntoPyDict}; use std::collections::HashMap; fn main() -> PyResult<()> { let key1 = "key1"; let val1 = 1; let key2 = "key2"; let val2 = 2; Python::attach(|py| { let fun: Py = PyModule::from_code( py, c"def example(*args, **kwargs): if args != (): print('called with args', args) if kwargs != {}: print('called with kwargs', kwargs) if args == () and kwargs == {}: print('called with no arguments')", c"example.py", c"", )? .getattr("example")? .into(); // call object with PyDict let kwargs = [(key1, val1)].into_py_dict(py)?; fun.call(py, (), Some(&kwargs))?; // pass arguments as Vec let kwargs = vec![(key1, val1), (key2, val2)]; fun.call(py, (), Some(&kwargs.into_py_dict(py)?))?; // pass arguments as HashMap let mut kwargs = HashMap::<&str, i32>::new(); kwargs.insert(key1, 1); fun.call(py, (), Some(&kwargs.into_py_dict(py)?))?; // pass arguments of different types as PyDict let kwargs = PyDict::new(py); kwargs.set_item(key1, val1)?; kwargs.set_item(key2, "string")?; fun.call(py, (), Some(&kwargs))?; Ok(()) }) } ``` ================================================ FILE: guide/src/python-from-rust.md ================================================ # Calling Python in Rust code This chapter of the guide documents some ways to interact with Python code from Rust. Below is an introduction to the `'py` lifetime and some general remarks about how PyO3's API reasons about Python code. The subchapters also cover the following topics: - Python object types available in PyO3's API - How to work with Python exceptions - How to call Python functions - How to execute existing Python code ## The `'py` lifetime To safely interact with the Python interpreter a Rust thread must be [attached] to the Python interpreter. PyO3 has a `Python<'py>` token that is used to prove that these conditions are met. Its lifetime `'py` is a central part of PyO3's API. The `Python<'py>` token serves three purposes: - It provides global APIs for the Python interpreter, such as [`py.eval()`][eval] and [`py.import()`][import]. - It can be passed to functions that require a proof of attachment, such as [`Py::clone_ref`][clone_ref]. - Its lifetime `'py` is used to bind many of PyO3's types to the Python interpreter, such as [`Bound<'py, T>`][Bound]. PyO3's types that are bound to the `'py` lifetime, for example `Bound<'py, T>`, all contain a `Python<'py>` token. This means they have full access to the Python interpreter and offer a complete API for interacting with Python objects. Consult [PyO3's API documentation][obtaining-py] to learn how to acquire one of these tokens. ### The Global Interpreter Lock Prior to the introduction of free-threaded Python (first available in 3.13, fully supported in 3.14), the Python interpreter was made thread-safe by the [global interpreter lock]. This ensured that only one Python thread can use the Python interpreter and its API at the same time. Historically, Rust code was able to use the GIL as a synchronization guarantee, but the introduction of free-threaded Python removed this possibility. The [`pyo3::sync`] module offers synchronization tools which abstract over both Python builds. To enable any parallelism on the GIL-enabled build, and best throughput on the free-threaded build, non-Python operations (system calls and native Rust code) should consider detaching from the Python interpreter to allow other work to proceed. See [the section on parallelism](parallelism.md) for how to do that using PyO3's API. ## Python's memory model Python's memory model differs from Rust's memory model in two key ways: - There is no concept of ownership; all Python objects are shared and usually implemented via reference counting - There is no concept of exclusive (`&mut`) references; any reference can mutate a Python object PyO3's API reflects this by providing [smart pointer][smart-pointers] types, `Py`, `Bound<'py, T>`, and (the very rarely used) `Borrowed<'a, 'py, T>`. These smart pointers all use Python reference counting. See the [subchapter on types](./types.md) for more detail on these types. Because of the lack of exclusive `&mut` references, PyO3's APIs for Python objects, for example [`PyListMethods::append`], use shared references. This is safe because Python objects have internal mechanisms to prevent data races (as of time of writing, the Python GIL). [attached]: https://docs.python.org/3.14/glossary.html#term-attached-thread-state [global interpreter lock]: https://docs.python.org/3/c-api/threads.html#threads [smart-pointers]: https://doc.rust-lang.org/book/ch15-00-smart-pointers.html [obtaining-py]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#obtaining-a-python-token [`pyo3::sync`]: {{#PYO3_DOCS_URL}}/pyo3/sync/index.html [eval]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval [import]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.import [clone_ref]: {{#PYO3_DOCS_URL}}/pyo3/prelude/struct.Py.html#method.clone_ref [Bound]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html [`PyListMethods::append`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyListMethods.html#tymethod.append ================================================ FILE: guide/src/python-typing-hints.md ================================================ # Typing and IDE hints for your Python package PyO3 provides an easy to use interface to code native Python libraries in Rust. The accompanying Maturin allows you to build and publish them as a package. Yet, for a better user experience, Python libraries should provide typing hints and documentation for all public entities, so that IDEs can show them during development and type analyzing tools such as `mypy` can use them to properly verify the code. Currently the best solution for the problem is to manually maintain `*.pyi` files and ship them along with the package. PyO3 is working on automated their generation. See the [type stub generation](type-stub.md) documentation for a description of the current state of automated generation. ## Introduction to `pyi` files `pyi` files (an abbreviation for `Python Interface`) are called "stub files" in most of the documentation related to them. A very good definition of what it is can be found in [old MyPy documentation](https://github.com/python/mypy/wiki/Creating-Stubs-For-Python-Modules): > A stubs file only contains a description of the public interface of the module without any implementations. There is also [extensive documentation on type stubs on the official Python typing documentation](https://typing.readthedocs.io/en/latest/source/stubs.html). Most Python developers probably already encountered them when trying to use their IDE's "Go to Definition" function on any builtin type. For example, the definitions of a few standard exceptions look like this: ```python class BaseException(object): args: Tuple[Any, ...] __cause__: BaseException | None __context__: BaseException | None __suppress_context__: bool __traceback__: TracebackType | None def __init__(self, *args: object) -> None: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... def with_traceback(self: _TBE, tb: TracebackType | None) -> _TBE: ... class SystemExit(BaseException): code: int class Exception(BaseException): ... class StopIteration(Exception): value: Any ``` As we can see, those are not full definitions containing implementation, but just a description of the interface. It is usually all that the user of the library needs. ### What do the PEPs say? At the time of writing this documentation, the `pyi` files are referenced in four PEPs. [PEP8 - Style Guide for Python Code - #Function Annotations](https://www.python.org/dev/peps/pep-0008/#function-annotations) (last point) recommends all third party library creators to provide stub files as the source of knowledge about the package for type checker tools. > (...) it is expected that users of third party library packages may want to run type checkers over those packages. For this purpose [PEP 484](https://www.python.org/dev/peps/pep-0484) recommends the use of stub files: .pyi files that are read by the type checker in preference of the corresponding .py files. (...) [PEP484 - Type Hints - #Stub Files](https://www.python.org/dev/peps/pep-0484/#stub-files) defines stub files as follows. > Stub files are files containing type hints that are only for use by the type checker, not at runtime. It contains a specification for them (highly recommended reading, since it contains at least one thing that is not used in normal Python code) and also some general information about where to store the stub files. [PEP561 - Distributing and Packaging Type Information](https://www.python.org/dev/peps/pep-0561/) describes in detail how to build packages that will enable type checking. In particular it contains information about how the stub files must be distributed in order for type checkers to use them. [PEP560 - Core support for typing module and generic types](https://www.python.org/dev/peps/pep-0560/) describes the details on how Python's type system internally supports generics, including both runtime behavior and integration with static type checkers. ## How to do it? [PEP561](https://www.python.org/dev/peps/pep-0561/) recognizes three ways of distributing type information: - `inline` - the typing is placed directly in source (`py`) files; - `separate package with stub files` - the typing is placed in `pyi` files distributed in their own, separate package; - `in-package stub files` - the typing is placed in `pyi` files distributed in the same package as source files. The first way is tricky with PyO3 since we do not have `py` files. When it has been investigated and necessary changes are implemented, this document will be updated. The second way is easy to do, and the whole work can be fully separated from the main library code. The example repo for the package with stub files can be found in [PEP561 references section](https://www.python.org/dev/peps/pep-0561/#references): [Stub package repository](https://github.com/ethanhs/stub-package) The third way is described below. ### Including `pyi` files in your PyO3/Maturin build package When source files are in the same package as stub files, they should be placed next to each other. We need a way to do that with Maturin. Also, in order to mark our package as typing-enabled we need to add an empty file named `py.typed` to the package. #### If you do not have other Python files If you do not need to add any other Python files apart from `pyi` to the package, Maturin provides a way to do most of the work for you. As documented in the [Maturin Guide](https://github.com/PyO3/maturin/#mixed-rustpython-projects), the only thing you need to do is to create a stub file for your module named `.pyi` in your project root and Maturin will do the rest. ```text my-rust-project/ ├── Cargo.toml ├── my_project.pyi # <<< add type stubs for Rust functions in the my_project module here ├── pyproject.toml └── src └── lib.rs ``` For an example `pyi` file see the [`my_project.pyi` content](#my_projectpyi-content) section. #### If you need other Python files If you need to add other Python files apart from `pyi` to the package, you can do it also, but that requires some more work. Maturin provides an easy way to add files to a package ([documentation](https://github.com/PyO3/maturin/blob/0dee40510083c03607834c821eea76964140a126/Readme.md#mixed-rustpython-projects)). You just need to create a folder with the name of your module next to the `Cargo.toml` file (for customization see documentation linked above). The folder structure would be: ```text my-project ├── Cargo.toml ├── my_project │ ├── __init__.py │ ├── my_project.pyi │ ├── other_python_file.py │ └── py.typed ├── pyproject.toml ├── Readme.md └── src └── lib.rs ``` Let's go a little bit more into detail regarding the files inside the package folder. ##### `__init__.py` content As we now specify our own package content, we have to provide the `__init__.py` file, so the folder is treated as a package and we can import things from it. We can always use the same content that Maturin creates for us if we do not specify a Python source folder. For PyO3 bindings it would be: ```python from .my_project import * ``` That way everything that is exposed by our native module can be imported directly from the package. ##### `py.typed` requirement As stated in [PEP561](https://www.python.org/dev/peps/pep-0561/): > Package maintainers who wish to support type checking of their code MUST add a marker file named py.typed to their package supporting typing. This marker applies recursively: if a top-level package includes it, all its sub-packages MUST support type checking as well. If we do not include that file, some IDEs might still use our `pyi` files to show hints, but the type checkers might not. MyPy will raise an error in this situation: ```text error: Skipping analyzing "my_project": found module but no type hints or library stubs ``` The file is just a marker file, so it should be empty. ##### `my_project.pyi` content Our module stub file. This document does not aim at describing how to write them, since you can find a lot of documentation on it, starting from the already quoted [PEP484](https://www.python.org/dev/peps/pep-0484/#stub-files). The example can look like this: ```python class Car: """ A class representing a car. :param body_type: the name of body type, e.g. hatchback, sedan :param horsepower: power of the engine in horsepower """ def __init__(self, body_type: str, horsepower: int) -> None: ... @classmethod def from_unique_name(cls, name: str) -> 'Car': """ Creates a Car based on unique name :param name: model name of a car to be created :return: a Car instance with default data """ def best_color(self) -> str: """ Gets the best color for the car. :return: the name of the color our great algorithm thinks is the best for this car """ ``` ### Supporting Generics Type annotations can also be made generic in Python. They are useful for working with different types while maintaining type safety. Usually, generic classes inherit from the `typing.Generic` metaclass. Take for example the following `.pyi` file that specifies a `Car` that can accept multiple types of wheels: ```python from typing import Generic, TypeVar W = TypeVar('W') class Car(Generic[W]): def __init__(self, wheels: list[W]) -> None: ... def get_wheels(self) -> list[W]: ... def change_wheel(self, wheel_number: int, wheel: W) -> None: ... ``` This way, the end-user can specify the type with variables such as `truck: Car[SteelWheel] = ...` and `f1_car: Car[AlloyWheel] = ...`. There is also a special syntax for specifying generic types in Python 3.12+: ```python class Car[W]: def __init__(self, wheels: list[W]) -> None: ... def get_wheels(self) -> list[W]: ... ``` #### Runtime Behaviour Stub files (`pyi`) are only useful for static type checkers and ignored at runtime. Therefore, PyO3 classes do not inherit from `typing.Generic` even if specified in the stub files. This can cause some runtime issues, as annotating a variable like `f1_car: Car[AlloyWheel] = ...` can make Python call magic methods that are not defined. To overcome this limitation, implementers can pass the `generic` parameter to `pyclass` in Rust: ```rust ignore #[pyclass(generic)] ``` #### Advanced Users `#[pyclass(generic)]` implements a very simple runtime behavior that accepts any generic argument. Advanced users can opt to manually implement [`__class_getitem__`](https://docs.python.org/3/reference/datamodel.html#emulating-generic-types) for the generic class to have more control. ```rust ignore impl MyClass { #[classmethod] #[pyo3(signature = (key, /))] pub fn __class_getitem__( cls: &Bound<'_, PyType>, key: &Bound<'_, PyAny>, ) -> PyResult> { /* implementation details */ } } ``` Note that [`pyo3::types::PyGenericAlias`][pygenericalias] can be helpful when implementing `__class_getitem__` as it can create [`types.GenericAlias`][genericalias] objects from Rust. [pygenericalias]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyGenericAlias.html [genericalias]: https://docs.python.org/3/library/types.html#types.GenericAlias ================================================ FILE: guide/src/rust-from-python.md ================================================ # Using Rust from Python This chapter of the guide is dedicated to explaining how to wrap Rust code into Python objects. PyO3 uses Rust's "procedural macros" to provide a powerful yet simple API to denote what Rust code should map into Python objects. PyO3 can create three types of Python objects: - Python modules, via the `#[pymodule]` macro - Python functions, via the `#[pyfunction]` macro - Python classes, via the `#[pyclass]` macro (plus `#[pymethods]` to define methods for those classes) The following subchapters go through each of these in turn. ================================================ FILE: guide/src/trait-bounds.md ================================================ # Using in Python a Rust function with trait bounds PyO3 allows for easy conversion from Rust to Python for certain functions and classes (see the [conversion table](conversions/tables.md)). However, it is not always straightforward to convert Rust code that requires a given trait implementation as an argument. This tutorial explains how to convert a Rust function that takes a trait as argument for use in Python with classes implementing the same methods as the trait. Why is this useful? ## Pros - Make your Rust code available to Python users - Code complex algorithms in Rust with the help of the borrow checker ### Cons - Not as fast as native Rust (type conversion has to be performed and one part of the code runs in Python) - You need to adapt your code to expose it ## Example Let's work with the following basic example of an implementation of a optimization solver operating on a given model. Let's say we have a function `solve` that operates on a model and mutates its state. The argument of the function can be any model that implements the `Model` trait : ```rust,no_run # #![allow(dead_code)] pub trait Model { fn set_variables(&mut self, inputs: &Vec); fn compute(&mut self); fn get_results(&self) -> Vec; } pub fn solve(model: &mut T) { println!("Magic solver that mutates the model into a resolved state"); } ``` Let's assume we have the following constraints: - We cannot change that code as it runs on many Rust models. - We also have many Python models that cannot be solved as this solver is not available in that language. Rewriting it in Python would be cumbersome and error-prone, as everything is already available in Rust. How could we expose this solver to Python thanks to PyO3 ? ## Implementation of the trait bounds for the Python class If a Python class implements the same three methods as the `Model` trait, it seems logical it could be adapted to use the solver. However, it is not possible to pass a `Py` to it as it does not implement the Rust trait (even if the Python model has the required methods). In order to implement the trait, we must write a wrapper around the calls in Rust to the Python model. The method signatures must be the same as the trait, keeping in mind that the Rust trait cannot be changed for the purpose of making the code available in Python. The Python model we want to expose is the following one, which already contains all the required methods: ```python class Model: def set_variables(self, inputs): self.inputs = inputs def compute(self): self.results = [elt**2 - 3 for elt in self.inputs] def get_results(self): return self.results ``` The following wrapper will call the Python model from Rust, using a struct to hold the model as a `PyAny` object: ```rust,no_run # #![allow(dead_code)] use pyo3::prelude::*; use pyo3::types::PyList; # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); # fn compute(&mut self); # fn get_results(&self) -> Vec; # } struct UserModel { model: Py, } impl Model for UserModel { fn set_variables(&mut self, var: &Vec) { println!("Rust calling Python to set the variables"); Python::attach(|py| { self.model .bind(py) .call_method("set_variables", (PyList::new(py, var).unwrap(),), None) .unwrap(); }) } fn get_results(&self) -> Vec { println!("Rust calling Python to get the results"); Python::attach(|py| { self.model .bind(py) .call_method("get_results", (), None) .unwrap() .extract() .unwrap() }) } fn compute(&mut self) { println!("Rust calling Python to perform the computation"); Python::attach(|py| { self.model .bind(py) .call_method("compute", (), None) .unwrap(); }) } } ``` Now that this bit is implemented, let's expose the model wrapper to Python. Let's add the PyO3 annotations and add a constructor: ```rust,no_run # #![allow(dead_code)] # fn main() {} # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); # fn compute(&mut self); # fn get_results(&self) -> Vec; # } # use pyo3::prelude::*; #[pyclass] struct UserModel { model: Py, } #[pymethods] impl UserModel { #[new] pub fn new(model: Py) -> Self { UserModel { model } } } #[pymodule] mod trait_exposure { #[pymodule_export] use super::UserModel; } ``` Now we add the PyO3 annotations to the trait implementation: ```rust,ignore #[pymethods] impl Model for UserModel { // the previous trait implementation } ``` However, the previous code will not compile. The compilation error is the following one: `error: #[pymethods] cannot be used on trait impl blocks` That's a bummer! However, we can write a second wrapper around these functions to call them directly. This wrapper will also perform the type conversions between Python and Rust. ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::PyList; # # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); # fn compute(&mut self); # fn get_results(&self) -> Vec; # } # # #[pyclass] # struct UserModel { # model: Py, # } # # impl Model for UserModel { # fn set_variables(&mut self, var: &Vec) { # println!("Rust calling Python to set the variables"); # Python::attach(|py| { # self.model.bind(py) # .call_method("set_variables", (PyList::new(py, var).unwrap(),), None) # .unwrap(); # }) # } # # fn get_results(&self) -> Vec { # println!("Rust calling Python to get the results"); # Python::attach(|py| { # self.model # .bind(py) # .call_method("get_results", (), None) # .unwrap() # .extract() # .unwrap() # }) # } # # fn compute(&mut self) { # println!("Rust calling Python to perform the computation"); # Python::attach(|py| { # self.model # .bind(py) # .call_method("compute", (), None) # .unwrap(); # }) # # } # } #[pymethods] impl UserModel { pub fn set_variables(&mut self, var: Vec) { println!("Set variables from Python calling Rust"); Model::set_variables(self, &var) } pub fn get_results(&mut self) -> Vec { println!("Get results from Python calling Rust"); Model::get_results(self) } pub fn compute(&mut self) { println!("Compute from Python calling Rust"); Model::compute(self) } } ``` This wrapper handles the type conversion between the PyO3 requirements and the trait. In order to meet PyO3 requirements, this wrapper must: - return an object of type `PyResult` - use only values, not references in the method signatures Let's run the file python file: ```python class Model: def set_variables(self, inputs): self.inputs = inputs def compute(self): self.results = [elt**2 - 3 for elt in self.inputs] def get_results(self): return self.results if __name__=="__main__": import trait_exposure myModel = Model() my_rust_model = trait_exposure.UserModel(myModel) my_rust_model.set_variables([2.0]) print("Print value from Python: ", myModel.inputs) my_rust_model.compute() print("Print value from Python through Rust: ", my_rust_model.get_results()) print("Print value directly from Python: ", myModel.get_results()) ``` This outputs: ```block Set variables from Python calling Rust Set variables from Rust calling Python Print value from Python: [2.0] Compute from Python calling Rust Compute from Rust calling Python Get results from Python calling Rust Get results from Rust calling Python Print value from Python through Rust: [1.0] Print value directly from Python: [1.0] ``` We have now successfully exposed a Rust model that implements the `Model` trait to Python! We will now expose the `solve` function, but before, let's talk about types errors. ## Type errors in Python What happens if you have type errors when using Python and how can you improve the error messages? ### Wrong types in Python function arguments Let's assume in the first case that you will use in your Python file `my_rust_model.set_variables(2.0)` instead of `my_rust_model.set_variables([2.0])`. The Rust signature expects a vector, which corresponds to a list in Python. What happens if instead of a vector, we pass a single value ? At the execution of Python, we get : ```block File "main.py", line 15, in my_rust_model.set_variables(2) TypeError ``` It is a type error and Python points to it, so it's easy to identify and solve. ### Wrong types in Python method signatures Let's assume now that the return type of one of the methods of our Model class is wrong, for example the `get_results` method that is expected to return a `Vec` in Rust, a list in Python. ```python class Model: def set_variables(self, inputs): self.inputs = inputs def compute(self): self.results = [elt**2 -3 for elt in self.inputs] def get_results(self): return self.results[0] #return self.results <-- this is the expected output ``` This call results in the following panic: ```block pyo3_runtime.PanicException: called `Result::unwrap()` on an `Err` value: PyErr { type: Py(0x10dcf79f0, PhantomData) } ``` This error code is not helpful for a Python user that does not know anything about Rust, or someone that does not know PyO3 was used to interface the Rust code. However, as we are responsible for making the Rust code available to Python, we can do something about it. The issue is that we called `unwrap` anywhere we could, and therefore any panic from PyO3 will be directly forwarded to the end user. Let's modify the code performing the type conversion to give a helpful error message to the Python user: We used in our `get_results` method the following call that performs the type conversion: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::PyList; # # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); # fn compute(&mut self); # fn get_results(&self) -> Vec; # } # # #[pyclass] # struct UserModel { # model: Py, # } impl Model for UserModel { fn get_results(&self) -> Vec { println!("Rust calling Python to get the results"); Python::attach(|py| { self.model .bind(py) .call_method("get_results", (), None) .unwrap() .extract() .unwrap() }) } # fn set_variables(&mut self, var: &Vec) { # println!("Rust calling Python to set the variables"); # Python::attach(|py| { # self.model.bind(py) # .call_method("set_variables", (PyList::new(py, var).unwrap(),), None) # .unwrap(); # }) # } # # fn compute(&mut self) { # println!("Rust calling Python to perform the computation"); # Python::attach(|py| { # self.model # .bind(py) # .call_method("compute", (), None) # .unwrap(); # }) # } } ``` Let's break it down in order to perform better error handling: ```rust,no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::PyList; # # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); # fn compute(&mut self); # fn get_results(&self) -> Vec; # } # # #[pyclass] # struct UserModel { # model: Py, # } impl Model for UserModel { fn get_results(&self) -> Vec { println!("Get results from Rust calling Python"); Python::attach(|py| { let py_result: Bound<'_, PyAny> = self .model .bind(py) .call_method("get_results", (), None) .unwrap(); if py_result.get_type().name().unwrap() != "list" { panic!( "Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap() ); } py_result.extract() }) .unwrap() } # fn set_variables(&mut self, var: &Vec) { # println!("Rust calling Python to set the variables"); # Python::attach(|py| { # let py_model = self.model.bind(py) # .call_method("set_variables", (PyList::new(py, var).unwrap(),), None) # .unwrap(); # }) # } # # fn compute(&mut self) { # println!("Rust calling Python to perform the computation"); # Python::attach(|py| { # self.model # .bind(py) # .call_method("compute", (), None) # .unwrap(); # }) # } } ``` By doing so, you catch the result of the Python computation and check its type in order to be able to deliver a better error message before performing the unwrapping. Of course, it does not cover all the possible wrong outputs: the user could return a list of strings instead of a list of floats. In this case, a runtime panic would still occur due to PyO3, but with an error message much more difficult to decipher for non-rust user. It is up to the developer exposing the rust code to decide how much effort to invest into Python type error handling and improved error messages. ## The final code Now let's expose the `solve()` function to make it available from Python. It is not possible to directly expose the `solve` function to Python, as the type conversion cannot be performed. It requires an object implementing the `Model` trait as input. However, the `UserModel` already implements this trait. Because of this, we can write a function wrapper that takes the `UserModel`--which has already been exposed to Python--as an argument in order to call the core function `solve`. It is also required to make the struct public. ```rust,no_run # #![allow(dead_code)] # fn main() {} use pyo3::prelude::*; use pyo3::types::PyList; pub trait Model { fn set_variables(&mut self, var: &Vec); fn get_results(&self) -> Vec; fn compute(&mut self); } pub fn solve(model: &mut T) { println!("Magic solver that mutates the model into a resolved state"); } #[pyfunction] #[pyo3(name = "solve")] pub fn solve_wrapper(model: &mut UserModel) { solve(model); } #[pyclass] pub struct UserModel { model: Py, } #[pymethods] impl UserModel { #[new] pub fn new(model: Py) -> Self { UserModel { model } } pub fn set_variables(&mut self, var: Vec) { println!("Set variables from Python calling Rust"); Model::set_variables(self, &var) } pub fn get_results(&mut self) -> Vec { println!("Get results from Python calling Rust"); Model::get_results(self) } pub fn compute(&mut self) { Model::compute(self) } } #[pymodule] mod trait_exposure { #[pymodule_export] use super::{UserModel, solve_wrapper}; } impl Model for UserModel { fn set_variables(&mut self, var: &Vec) { println!("Rust calling Python to set the variables"); Python::attach(|py| { self.model .bind(py) .call_method("set_variables", (PyList::new(py, var).unwrap(),), None) .unwrap(); }) } fn get_results(&self) -> Vec { println!("Get results from Rust calling Python"); Python::attach(|py| { let py_result: Bound<'_, PyAny> = self .model .bind(py) .call_method("get_results", (), None) .unwrap(); if py_result.get_type().name().unwrap() != "list" { panic!( "Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap() ); } py_result.extract() }) .unwrap() } fn compute(&mut self) { println!("Rust calling Python to perform the computation"); Python::attach(|py| { self.model .bind(py) .call_method("compute", (), None) .unwrap(); }) } } ``` ================================================ FILE: guide/src/type-stub.md ================================================ # Type stub generation (`*.pyi` files) and introspection *This feature is still in active development.* *See [the related issue](https://github.com/PyO3/pyo3/issues/5137).* *For documentation on type stubs and how to use them with stable PyO3, refer to [this page](python-typing-hints.md)* PyO3 has a work in progress support to generate [type stub files](https://typing.python.org/en/latest/spec/distributing.html#stub-files). It works using: 1. PyO3 macros (`#[pyclass]`) that generate constant JSON strings that are then included in the built binaries by rustc if the `experimental-inspect` feature is enabled. 2. The `pyo3-introspection` crate that can parse the generated binaries, extract the JSON strings and build stub files from it. 3. \[Not done yet\] Build tools like `maturin` exposing `pyo3-introspection` features in their CLI API. For example, the following Rust code ```rust #[pymodule] pub mod example { use pyo3::prelude::*; #[pymodule_export] pub const CONSTANT: &str = "FOO"; #[pyclass(eq)] #[derive(Eq)] struct Class { value: usize } #[pymethods] impl Class { #[new] fn new(value: usize) -> Self { Self { value } } #[getter] fn value(&self) -> usize { self.value } } #[pyfunction] #[pyo3(signature = (arg: "list[int]") -> "list[int]")] fn list_of_int_identity(arg: Bound<'_, PyAny>) -> Bound<'_, PyAny> { arg } } ``` will generate the following stub file: ```python import typing CONSTANT: typing.Final = "FOO" class Class: def __init__(self, value: int) -> None: ... @property def value(self) -> int: ... def __eq__(self, other: Class) -> bool: ... def __ne__(self, other: Class) -> bool: ... def list_of_int_identity(arg: list[int]) -> list[int]: ... ``` The only piece of added syntax is that the `#[pyo3(signature = ...)]` attribute can now contain type annotations like `#[pyo3(signature = (arg: "list[int]") -> "list[int]")]` (note the `""` around type annotations). This is useful when PyO3 is not able to derive proper type annotations by itself. ## Constraints and limitations - The `experimental-inspect` feature is required to generate the introspection fragments. - Lots of features are not implemented yet. See [the related issue](https://github.com/PyO3/pyo3/issues/5137) for a list of them. - Introspection only works with Python modules declared with an inline Rust module. Modules declared using a function are not supported. - `FromPyObject::INPUT_TYPE` and `IntoPyObject::OUTPUT_TYPE` must be implemented for PyO3 to get the proper input/output type annotations to use. - PyO3 is not able to introspect the content of `#[pymodule]` and `#[pymodule_init]` functions. If they are present, the module is tagged as incomplete using a fake `def __getattr__(name: str) -> Incomplete: ...` function [following best practices](https://typing.python.org/en/latest/guides/writing_stubs.html#incomplete-stubs). ================================================ FILE: guide/src/types.md ================================================ # Python object types PyO3 offers two main sets of types to interact with Python objects. This section of the guide expands into detail about these types and how to choose which to use. The first set of types are the [smart pointers][smart-pointers] which all Python objects are wrapped in. These are `Py`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`. The [first section below](#pyo3s-smart-pointers) expands on each of these in detail and why there are three of them. The second set of types are types which fill in the generic parameter `T` of the smart pointers. The most common is `PyAny`, which represents any Python object (similar to Python's `typing.Any`). There are also concrete types for many Python built-in types, such as `PyList`, `PyDict`, and `PyTuple`. User defined `#[pyclass]` types also fit this category. The [second section below](#concrete-python-types) expands on how to use these types. ## PyO3's smart pointers PyO3's API offers three generic smart pointers: `Py`, `Bound<'py, T>` and `Borrowed<'a, 'py, T>`. For each of these the type parameter `T` will be filled by a [concrete Python type](#concrete-python-types). For example, a Python list object can be represented by `Py`, `Bound<'py, PyList>`, and `Borrowed<'a, 'py, PyList>`. These smart pointers behave differently due to their lifetime parameters. `Py` has no lifetime parameters, `Bound<'py, T>` has [the `'py` lifetime](./python-from-rust.md#the-py-lifetime) as a parameter, and `Borrowed<'a, 'py, T>` has the `'py` lifetime plus an additional lifetime `'a` to denote the lifetime it is borrowing data for. (You can read more about these lifetimes in the subsections below). Python objects are reference counted, like [`std::sync::Arc`](https://doc.rust-lang.org/stable/std/sync/struct.Arc.html). A major reason for these smart pointers is to bring Python's reference counting to a Rust API. The recommendation of when to use each of these smart pointers is as follows: - Use `Bound<'py, T>` for as much as possible, as it offers the most efficient and complete API. - Use `Py` mostly just for storage inside Rust `struct`s which do not want to or can't add a lifetime parameter for `Bound<'py, T>`. - `Borrowed<'a, 'py, T>` is almost never used. It is occasionally present at the boundary between Rust and the Python interpreter, for example when borrowing data from Python tuples (which is safe because they are immutable). The sections below also explain these smart pointers in a little more detail. ### `Py` [`Py`][Py] is the foundational smart pointer in PyO3's API. The type parameter `T` denotes the type of the Python object. Very frequently this is `PyAny`, meaning any Python object. Because `Py` is not bound to [the `'py` lifetime](./python-from-rust.md#the-py-lifetime), it is the type to use when storing a Python object inside a Rust `struct` or `enum` which do not want to have a lifetime parameter. In particular, [`#[pyclass]`][pyclass] types are not permitted to have a lifetime, so `Py` is the correct type to store Python objects inside them. The lack of binding to the `'py` lifetime also carries drawbacks: - Almost all methods on `Py` require a `Python<'py>` token as the first argument - Other functionality, such as [`Drop`][Drop], needs to check at runtime for attachment to the Python interpreter, at a small performance cost Because of the drawbacks `Bound<'py, T>` is preferred for many of PyO3's APIs. In particular, `Bound<'py, T>` is better for function arguments. To convert a `Py` into a `Bound<'py, T>`, the `Py::bind` and `Py::into_bound` methods are available. `Bound<'py, T>` can be converted back into `Py` using [`Bound::unbind`]. ### `Bound<'py, T>` [`Bound<'py, T>`][Bound] is the counterpart to `Py` which is also bound to the `'py` lifetime. It can be thought of as equivalent to the Rust tuple `(Python<'py>, Py)`. By having the binding to the `'py` lifetime, `Bound<'py, T>` can offer the complete PyO3 API at maximum efficiency. This means that `Bound<'py, T>` should usually be used whenever carrying this lifetime is acceptable, and `Py` otherwise. `Bound<'py, T>` engages in Python reference counting. This means that `Bound<'py, T>` owns a Python object. Rust code which just wants to borrow a Python object should use a shared reference `&Bound<'py, T>`. Just like `std::sync::Arc`, using `.clone()` and `drop()` will cheaply increment and decrement the reference count of the object (just in this case, the reference counting is implemented by the Python interpreter itself). To give an example of how `Bound<'py, T>` is PyO3's primary API type, consider the following Python code: ```python def example(): x = list() # create a Python list x.append(1) # append the integer 1 to it y = x # create a second reference to the list del x # delete the original reference ``` Using PyO3's API, and in particular `Bound<'py, PyList>`, this code translates into the following Rust code: ```rust use pyo3::prelude::*; use pyo3::types::PyList; fn example<'py>(py: Python<'py>) -> PyResult<()> { let x: Bound<'py, PyList> = PyList::empty(py); x.append(1)?; let y: Bound<'py, PyList> = x.clone(); // y is a new reference to the same list drop(x); // release the original reference x Ok(()) } # Python::attach(example).unwrap(); ``` Or, without the type annotations: ```rust use pyo3::prelude::*; use pyo3::types::PyList; fn example(py: Python<'_>) -> PyResult<()> { let x = PyList::empty(py); x.append(1)?; let y = x.clone(); drop(x); Ok(()) } # Python::attach(example).unwrap(); ``` #### Function argument lifetimes Because the `'py` lifetime often appears in many function arguments as part of the `Bound<'py, T>` smart pointer, the Rust compiler will often require annotations of input and output lifetimes. This occurs when the function output has at least one lifetime, and there is more than one lifetime present on the inputs. To demonstrate, consider this function which takes accepts Python objects and applies the [Python `+` operation][PyAnyMethods::add] to them: ```rust,compile_fail # use pyo3::prelude::*; fn add(left: &'_ Bound<'_, PyAny>, right: &'_ Bound<'_, PyAny>) -> PyResult> { left.add(right) } ``` Because the Python `+` operation might raise an exception, this function returns `PyResult>`. It doesn't need ownership of the inputs, so it takes `&Bound<'_, PyAny>` shared references. To demonstrate the point, all lifetimes have used the wildcard `'_` to allow the Rust compiler to attempt to infer them. Because there are four input lifetimes (two lifetimes of the shared references, and two `'py` lifetimes unnamed inside the `Bound<'_, PyAny>` pointers), the compiler cannot reason about which must be connected to the output. The correct way to solve this is to add the `'py` lifetime as a parameter for the function, and name all the `'py` lifetimes inside the `Bound<'py, PyAny>` smart pointers. For the shared references, it's also fine to reduce `&'_` to just `&`. The working end result is below: ```rust # use pyo3::prelude::*; fn add<'py>( left: &Bound<'py, PyAny>, right: &Bound<'py, PyAny>, ) -> PyResult> { left.add(right) } # Python::attach(|py| { # let s = pyo3::types::PyString::new(py, "s"); # assert!(add(&s, &s).unwrap().eq("ss").unwrap()); # }) ``` If naming the `'py` lifetime adds unwanted complexity to the function signature, it is also acceptable to return `Py`, which has no lifetime. The cost is instead paid by a slight increase in implementation complexity, as seen by the introduction of a call to [`Bound::unbind`]: ```rust # use pyo3::prelude::*; fn add(left: &Bound<'_, PyAny>, right: &Bound<'_, PyAny>) -> PyResult> { let output: Bound<'_, PyAny> = left.add(right)?; Ok(output.unbind()) } # Python::attach(|py| { # let s = pyo3::types::PyString::new(py, "s"); # assert!(add(&s, &s).unwrap().bind(py).eq("ss").unwrap()); # }) ``` ### `Borrowed<'a, 'py, T>` [`Borrowed<'a, 'py, T>`][Borrowed] is an advanced type used just occasionally at the edge of interaction with the Python interpreter. It can be thought of as analogous to the shared reference `&'a Bound<'py, T>`. The difference is that `Borrowed<'a, 'py, T>` is just a smart pointer rather than a reference-to-a-smart-pointer, which is a helpful reduction in indirection in specific interactions with the Python interpreter. `Borrowed<'a, 'py, T>` dereferences to `Bound<'py, T>`, so all methods on `Bound<'py, T>` are available on `Borrowed<'a, 'py, T>`. An example where `Borrowed<'a, 'py, T>` is used is in [`PyTupleMethods::get_borrowed_item`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyTupleMethods.html#tymethod.get_item): ```rust use pyo3::prelude::*; use pyo3::types::PyTuple; # fn example<'py>(py: Python<'py>) -> PyResult<()> { // Create a new tuple with the elements (0, 1, 2) let t = PyTuple::new(py, [0, 1, 2])?; for i in 0..=2 { let entry: Borrowed<'_, 'py, PyAny> = t.get_borrowed_item(i)?; // `PyAnyMethods::extract` is available on `Borrowed` // via the dereference to `Bound` let value: usize = entry.extract()?; assert_eq!(i, value); } # Ok(()) # } # Python::attach(example).unwrap(); ``` ### Casting between smart pointer types To convert between `Py` and `Bound<'py, T>` use the `bind()` / `into_bound()` methods. Use the `as_unbound()` / `unbind()` methods to go back from `Bound<'py, T>` to `Py`. ```rust,ignore let obj: Py = ...; let bound: &Bound<'py, PyAny> = obj.bind(py); let bound: Bound<'py, PyAny> = obj.into_bound(py); let obj: &Py = bound.as_unbound(); let obj: Py = bound.unbind(); ``` To convert between `Bound<'py, T>` and `Borrowed<'a, 'py, T>` use the `as_borrowed()` method. `Borrowed<'a, 'py, T>` has a deref coercion to `Bound<'py, T>`. Use the `to_owned()` method to increment the Python reference count and to create a new `Bound<'py, T>` from the `Borrowed<'a, 'py, T>`. ```rust,ignore let bound: Bound<'py, PyAny> = ...; let borrowed: Borrowed<'_, 'py, PyAny> = bound.as_borrowed(); // deref coercion let bound: &Bound<'py, PyAny> = &borrowed; // create a new Bound by increase the Python reference count let bound: Bound<'py, PyAny> = borrowed.to_owned(); ``` To convert between `Py` and `Borrowed<'a, 'py, T>` use the `bind_borrowed()` method. Use either `as_unbound()` or `.to_owned().unbind()` to go back to `Py` from `Borrowed<'a, 'py, T>`, via `Bound<'py, T>`. ```rust,ignore let obj: Py = ...; let borrowed: Borrowed<'_, 'py, PyAny> = bound.as_borrowed(); // via deref coercion to Bound and then using Bound::as_unbound let obj: &Py = borrowed.as_unbound(); // via a new Bound by increasing the Python reference count, and unbind it let obj: Py = borrowed.to_owned().unbind(). ``` ## Concrete Python types In all of `Py`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`, the type parameter `T` denotes the type of the Python object referred to by the smart pointer. This parameter `T` can be filled by: - [`PyAny`][PyAny], which represents any Python object, - Native Python types such as `PyList`, `PyTuple`, and `PyDict`, and - [`#[pyclass]`][pyclass] types defined from Rust The following subsections covers some further detail about how to work with these types: - the APIs that are available for these concrete types, - how to cast `Bound<'py, T>` to a specific concrete type, and - how to get Rust data out of a `Bound<'py, T>`. ### Using APIs for concrete Python types Each concrete Python type such as `PyAny`, `PyTuple` and `PyDict` exposes its API on the corresponding bound smart pointer `Bound<'py, PyAny>`, `Bound<'py, PyTuple>` and `Bound<'py, PyDict>`. Each type's API is exposed as a trait: [`PyAnyMethods`], [`PyTupleMethods`], [`PyDictMethods`], and so on for all concrete types. Using traits rather than associated methods on the `Bound` smart pointer is done for a couple of reasons: - Clarity of documentation: each trait gets its own documentation page in the PyO3 API docs. If all methods were on the `Bound` smart pointer directly, the vast majority of PyO3's API would be on a single, extremely long, documentation page. - Consistency: downstream code implementing Rust APIs for existing Python types can also follow this pattern of using a trait. Downstream code would not be allowed to add new associated methods directly on the `Bound` type. - Future design: it is hoped that a future Rust with [arbitrary self types](https://github.com/rust-lang/rust/issues/44874) will remove the need for these traits in favour of placing the methods directly on `PyAny`, `PyTuple`, `PyDict`, and so on. These traits are all included in the `pyo3::prelude` module, so with the glob import `use pyo3::prelude::*` the full PyO3 API is made available to downstream code. The following function accesses the first item in the input Python list, using the `.get_item()` method from the `PyListMethods` trait: ```rust use pyo3::prelude::*; use pyo3::types::PyList; fn get_first_item<'py>(list: &Bound<'py, PyList>) -> PyResult> { list.get_item(0) } # Python::attach(|py| { # let l = PyList::new(py, ["hello world"]).unwrap(); # assert!(get_first_item(&l).unwrap().eq("hello world").unwrap()); # }) ``` ### Casting between Python object types To cast `Bound<'py, T>` smart pointers to some other type, use the [`.cast()`][Bound::cast] family of functions. This converts `&Bound<'py, T>` to a different `&Bound<'py, U>`, without transferring ownership. There is also [`.cast_into()`][Bound::cast_into] to convert `Bound<'py, T>` to `Bound<'py, U>` with transfer of ownership. These methods are available for all types `T` which implement the [`PyTypeCheck`] trait. Casting to `Bound<'py, PyAny>` can be done with `.as_any()` or `.into_any()`. For example, the following snippet shows how to cast `Bound<'py, PyAny>` to `Bound<'py, PyTuple>`: ```rust # use pyo3::prelude::*; # use pyo3::types::PyTuple; # fn example<'py>(py: Python<'py>) -> PyResult<()> { // create a new Python `tuple`, and use `.into_any()` to erase the type let obj: Bound<'py, PyAny> = PyTuple::empty(py).into_any(); // use `.cast()` to cast to `PyTuple` without transferring ownership let _: &Bound<'py, PyTuple> = obj.cast()?; // use `.cast_into()` to cast to `PyTuple` with transfer of ownership let _: Bound<'py, PyTuple> = obj.cast_into()?; # Ok(()) # } # Python::attach(example).unwrap() ``` Custom [`#[pyclass]`][pyclass] types implement [`PyTypeCheck`], so `.cast()` also works for these types. The snippet below is the same as the snippet above casting instead to a custom type `MyClass`: ```rust use pyo3::prelude::*; #[pyclass] struct MyClass {} # fn example<'py>(py: Python<'py>) -> PyResult<()> { // create a new Python `tuple`, and use `.into_any()` to erase the type let obj: Bound<'py, PyAny> = Bound::new(py, MyClass {})?.into_any(); // use `.cast()` to cast to `MyClass` without transferring ownership let _: &Bound<'py, MyClass> = obj.cast()?; // use `.cast_into()` to cast to `MyClass` with transfer of ownership let _: Bound<'py, MyClass> = obj.cast_into()?; # Ok(()) # } # Python::attach(example).unwrap() ``` ### Extracting Rust data from Python objects To extract Rust data from Python objects, use [`.extract()`][PyAnyMethods::extract] instead of `.cast()`. This method is available for all types which implement the [`FromPyObject`] trait. For example, the following snippet extracts a Rust tuple of integers from a Python tuple: ```rust # use pyo3::prelude::*; # use pyo3::types::PyTuple; # fn example<'py>(py: Python<'py>) -> PyResult<()> { // create a new Python `tuple`, and use `.into_any()` to erase the type let obj: Bound<'py, PyAny> = PyTuple::new(py, [1, 2, 3])?.into_any(); // extracting the Python `tuple` to a rust `(i32, i32, i32)` tuple let (x, y, z) = obj.extract::<(i32, i32, i32)>()?; assert_eq!((x, y, z), (1, 2, 3)); # Ok(()) # } # Python::attach(example).unwrap() ``` To avoid copying data, [`#[pyclass]`][pyclass] types can directly reference Rust data stored within the Python objects without needing to `.extract()`. See the [corresponding documentation in the class section of the guide](./class.md#bound-and-interior-mutability) for more detail. [Bound]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html [`Bound::unbind`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html#method.unbind [Py]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html [PyAnyMethods::add]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.add [PyAnyMethods::extract]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.extract [Bound::cast]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html#method.cast [Bound::cast_into]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html#method.cast_into [`PyTypeCheck`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeCheck.html [`PyAnyMethods`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html [`PyDictMethods`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyDictMethods.html [`PyTupleMethods`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyTupleMethods.html [pyclass]: class.md [Borrowed]: {{#PYO3_DOCS_URL}}/pyo3/struct.Borrowed.html [Drop]: https://doc.rust-lang.org/std/ops/trait.Drop.html [PyAny]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html [smart-pointers]: https://doc.rust-lang.org/book/ch15-00-smart-pointers.html [`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html ================================================ FILE: guide/theme/tabs.css ================================================ .mdbook-tabs { display: flex; } .mdbook-tab { background-color: var(--table-alternate-bg); padding: 0.5rem 1rem; cursor: pointer; border: none; font-size: 1.6rem; line-height: 1.45em; } .mdbook-tab.active { background-color: var(--table-header-bg); font-weight: bold; } .mdbook-tab-content { padding: 1rem 0rem; } .mdbook-tab-content table { margin: unset; } ================================================ FILE: guide/theme/tabs.js ================================================ /** * Change active tab of tabs. * * @param {Element} container * @param {string} name */ const changeTab = (container, name) => { for (const child of container.children) { if (!(child instanceof HTMLElement)) { continue; } if (child.classList.contains('mdbook-tabs')) { for (const tab of child.children) { if (!(tab instanceof HTMLElement)) { continue; } if (tab.dataset.tabname === name) { tab.classList.add('active'); } else { tab.classList.remove('active'); } } } else if (child.classList.contains('mdbook-tab-content')) { if (child.dataset.tabname === name) { child.classList.remove('hidden'); } else { child.classList.add('hidden'); } } } }; document.addEventListener('DOMContentLoaded', () => { const tabs = document.querySelectorAll('.mdbook-tab'); for (const tab of tabs) { tab.addEventListener('click', () => { if (!(tab instanceof HTMLElement)) { return; } if (!tab.parentElement || !tab.parentElement.parentElement) { return; } const container = tab.parentElement.parentElement; const name = tab.dataset.tabname; const global = container.dataset.tabglobal; changeTab(container, name); if (global) { localStorage.setItem(`mdbook-tabs-${global}`, name); const globalContainers = document.querySelectorAll( `.mdbook-tabs-container[data-tabglobal="${global}"]` ); for (const globalContainer of globalContainers) { changeTab(globalContainer, name); } } }); } const containers = document.querySelectorAll('.mdbook-tabs-container[data-tabglobal]'); for (const container of containers) { const global = container.dataset.tabglobal; const name = localStorage.getItem(`mdbook-tabs-${global}`); if (name && document.querySelector(`.mdbook-tab[data-tabname=${name}]`)) { changeTab(container, name); } } }); ================================================ FILE: newsfragments/.gitignore ================================================ ================================================ FILE: newsfragments/5349.added.md ================================================ Added `PyErr::set_traceback` to set the traceback of an exception object ================================================ FILE: newsfragments/5349.changed.md ================================================ Changed exception remapping on argument extraction error to use `add_note` for enhancing the error message instead. ================================================ FILE: newsfragments/5668.added.md ================================================ Added new `PyUnicodeDecodeError::new_err_from_utf8` API to create a `PyErr` from a `str::Utf8Error` ================================================ FILE: newsfragments/5668.fixed.md ================================================ Fixed the implementations of `From` and `From` for `PyErr`. ================================================ FILE: newsfragments/5668.removed.md ================================================ Removed the failing implementations of `From`, `From`, and `From` for `PyErr`. ================================================ FILE: newsfragments/5753.changed.md ================================================ Module initialization uses the PyModExport and PyABIInfo APIs on python 3.15 and newer. ================================================ FILE: newsfragments/5770.added.md ================================================ Introspection: implement `INPUT_TYPE` and `OUTPUT_TYPE` on third party crates conversions ================================================ FILE: newsfragments/5782.added.md ================================================ Introspection: introspect doc comments and emit them in the stubs. ================================================ FILE: newsfragments/5797.changed.md ================================================ Deprecate `Py::get_refcnt` and `PyAnyMethods::get_refcnt` in favor of `pyo3::ffi::Py_REFCNT(obj.as_ptr())` ================================================ FILE: newsfragments/5803.changed.md ================================================ `PyEnvironmentError`, `PyIOError`, and `PyWindowsError` are now type aliases of `PyOSError` (as is the case in Python since 3.3). ================================================ FILE: newsfragments/5809.packaging.md ================================================ `pyo3-macros-backend` no longer depends on `pyo3-build-config`. ================================================ FILE: newsfragments/5824.changed.md ================================================ Deprecate and remove `Py_TRACE_REFS` support (unsupported from Python 3.13). ================================================ FILE: newsfragments/5828.added.md ================================================ Added FFI wrappers for the following PyUnstable APIs available in Python 3.14: `PyUnstable_Object_IsUniquelyReferenced`, `PyUnstable_Object_IsUniquelyReferencedTemporary`, `PyUnstable_EnableTryIncref`, `PyUnstable_TryIncref` ================================================ FILE: newsfragments/5830.changed.md ================================================ Allow non-`ExactSizeIterator` in `PyList::new` ================================================ FILE: newsfragments/5837.fixed.md ================================================ Allow pyclass named `Probe` with `get_all` fields. ================================================ FILE: newsfragments/5839.changed.md ================================================ Stubs: emit `#[classattribute]` as plain Python class attributes and not functions annotated with `@classattribute` and `@property` (not supported since 3.13) ================================================ FILE: newsfragments/5841.changed.md ================================================ Stubs: use `object` as the input annotation type of magic methods returning `NonImplemented` if the input value is not of the correct type ================================================ FILE: newsfragments/5847.fixed.md ================================================ Fix type resolution error when using `_Py_NegativeRefcount` with Python < 3.12. ================================================ FILE: newsfragments/5849.added.md ================================================ Add `Borrowed::get` as an equivalent to `Bound::get` and `Py::get`. ================================================ FILE: newsfragments/5857.added.md ================================================ Add `PyFrame::new`, `PyTraceBack::new`, and `PyFrameMethods::line_number`. ================================================ FILE: newsfragments/5865.packaging.md ================================================ Drop support for Python 3.13t (3.14t and above continue to be supported in alignment with CPython declaring free-threading supported starting with Python 3.14). ================================================ FILE: newsfragments/5866.changed.md ================================================ pyo3-ffi: use raw-dylib for Windows linking, eliminating import libraries. ================================================ FILE: newsfragments/5883.changed.md ================================================ `PyMappingProxy`: allow subclasses during type checking `PyWeakrefReference`: always allow subclasses during type checking ================================================ FILE: newsfragments/5887.added.md ================================================ Add `PyErr::set_context`, `PyErr::context`, `ffi::PyErr_GetHandledException` and `ffi::PyErr_SetHandledException`. ================================================ FILE: newsfragments/5891.added.md ================================================ Add `Py_HASH_SIPHASH13` to Rust binding ================================================ FILE: newsfragments/5893.removed.md ================================================ Remove the `TypeInfo` enum and the `FromPyObject::type_input` and `IntoPyObject::type_output` functions. They are replaced by the `PyStaticExpr` enum and the `FromPyObject::INPUT_TYPE` and the `IntoPyObject::OUTPUT_TYPE` associated constants. ================================================ FILE: newsfragments/5896.changed.md ================================================ `PyDate::from_timestamp` argument is now a `f64` (the Python API expects a float and not an integer) ================================================ FILE: newsfragments/5897.changed.md ================================================ Change `PathBuf::extract` input type hint from `str | os.PathLike` to `str | os.PathLike[str]` ================================================ FILE: noxfile.py ================================================ import io import json import os import re import shutil import subprocess import sys import sysconfig import tarfile import tempfile from contextlib import ExitStack, contextmanager from functools import lru_cache from glob import glob from pathlib import Path from shlex import quote from typing import ( Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple, ) import nox.command try: import tomllib as toml except ImportError: try: import toml except ImportError: toml = None try: import requests except ImportError: requests = None nox.options.sessions = ["test", "clippy", "rustfmt", "ruff", "rumdl", "docs"] PYO3_DIR = Path(__file__).parent PYO3_TARGET = Path(os.environ.get("CARGO_TARGET_DIR", PYO3_DIR / "target")).absolute() PYO3_GUIDE_SRC = PYO3_DIR / "guide" / "src" PYO3_GUIDE_TARGET = PYO3_TARGET / "guide" PYO3_DOCS_TARGET = PYO3_TARGET / "doc" FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) def _get_output(*args: str) -> str: return subprocess.run(args, capture_output=True, text=True, check=True).stdout def _parse_supported_interpreter_version( python_impl: str, # Literal["cpython", "pypy"], TODO update after 3.7 dropped ) -> Tuple[str, str]: output = _get_output("cargo", "metadata", "--format-version=1", "--no-deps") cargo_packages = json.loads(output)["packages"] # Check Python interpreter version support in package metadata package = "pyo3-ffi" metadata = next(pkg["metadata"] for pkg in cargo_packages if pkg["name"] == package) version_info = metadata[python_impl] assert "min-version" in version_info, f"missing min-version for {python_impl}" assert "max-version" in version_info, f"missing max-version for {python_impl}" return version_info["min-version"], version_info["max-version"] def _supported_interpreter_versions( python_impl: str, # Literal["cpython", "pypy"], TODO update after 3.7 dropped ) -> List[str]: min_version, max_version = _parse_supported_interpreter_version(python_impl) major = int(min_version.split(".")[0]) assert major == 3, f"unsupported Python major version {major}" min_minor = int(min_version.split(".")[1]) max_minor = int(max_version.split(".")[1]) versions = [f"{major}.{minor}" for minor in range(min_minor, max_minor + 1)] # Add free-threaded builds for 3.13+ if python_impl == "cpython": versions += [f"{major}.{minor}t" for minor in range(14, max_minor + 1)] return versions PY_VERSIONS = _supported_interpreter_versions("cpython") # We don't yet support abi3-py315 but do support cp315 and cp315t # version-specific builds ABI3_PY_VERSIONS = [p for p in PY_VERSIONS if not p.endswith("t")] ABI3_PY_VERSIONS.remove("3.15") PYPY_VERSIONS = _supported_interpreter_versions("pypy") @nox.session(venv_backend="none") def test(session: nox.Session) -> None: test_rust(session) test_py(session) @nox.session(name="test-rust", venv_backend="none") def test_rust(session: nox.Session): _run_cargo_test(session, package="pyo3-build-config") _run_cargo_test(session, package="pyo3-macros-backend") _run_cargo_test(session, package="pyo3-macros") extra_flags = [] # pypy and graalpy don't have Py_Initialize APIs, so we can only # build the main tests, not run them if sys.implementation.name in ("pypy", "graalpy"): extra_flags.append("--no-run") _run_cargo_test(session, package="pyo3-ffi", extra_flags=extra_flags) extra_flags.append("--no-default-features") for feature_set in _get_feature_sets(): flags = extra_flags.copy() if feature_set is None or "full" not in feature_set: # doctests require at least the macros feature, which is # activated by the full feature set # # using `--all-targets` makes cargo run everything except doctests flags.append("--all-targets") # We need to pass the feature set to the test command # so that it can be used in the test code # (e.g. for `#[cfg(feature = "abi3-py37")]`) if feature_set and "abi3" in feature_set and FREE_THREADED_BUILD: # free-threaded builds don't support abi3 yet continue _run_cargo_test(session, features=feature_set, extra_flags=flags) if ( feature_set and "abi3" in feature_set and "full" in feature_set and sys.version_info >= (3, 7) ): # run abi3-py37 tests to check abi3 forward compatibility _run_cargo_test( session, features=feature_set.replace("abi3", "abi3-py37"), extra_flags=flags, ) @nox.session(name="test-py", venv_backend="none") def test_py(session: nox.Session) -> None: _run(session, "nox", "-f", "pytests/noxfile.py", external=True) for example in glob("examples/*/noxfile.py"): _run(session, "nox", "-f", example, external=True) for example in glob("pyo3-ffi/examples/*/noxfile.py"): _run(session, "nox", "-f", example, external=True) @nox.session(venv_backend="none") def coverage(session: nox.Session) -> None: session.env.update(_get_coverage_env()) _run_cargo(session, "llvm-cov", "clean", "--workspace") test(session) generate_coverage_report(session) @nox.session(name="set-coverage-env", venv_backend="none") def set_coverage_env(session: nox.Session) -> None: """For use in GitHub Actions to set coverage environment variables.""" with open(os.environ["GITHUB_ENV"], "a") as env_file: for k, v in _get_coverage_env().items(): print(f"{k}={v}", file=env_file) @nox.session(name="generate-coverage-report", venv_backend="none") def generate_coverage_report(session: nox.Session) -> None: cov_format = "codecov" output_file = "coverage.json" if "lcov" in session.posargs: cov_format = "lcov" output_file = "lcov.info" _run_cargo( session, "llvm-cov", "--package=pyo3", "--package=pyo3-build-config", "--package=pyo3-macros-backend", "--package=pyo3-macros", "--package=pyo3-ffi", "report", f"--{cov_format}", "--output-path", output_file, ) @nox.session(venv_backend="none") def rustfmt(session: nox.Session): _run_cargo(session, "fmt", "--all", "--check") _run_cargo(session, "fmt", _FFI_CHECK, "--all", "--check") _format_ffi_extern(session, check=True) @nox.session(name="ruff") def ruff(session: nox.Session): session.install("ruff") _run(session, "ruff", "format", ".", "--check") _run(session, "ruff", "check", ".") @nox.session(name="rumdl", venv_backend="none") def rumdl(session: nox.Session): """Run rumdl to check markdown formatting in the guide. Can also run with uv directly, e.g. `uv run rumdl check guide`. """ _run( session, "uv", "run", "rumdl", "check", "guide", *session.posargs, external=True ) @nox.session(name="clippy", venv_backend="none") def clippy(session: nox.Session) -> bool: if not (_clippy(session) and _clippy_additional_workspaces(session)): session.error("one or more jobs failed") def _clippy(session: nox.Session, *, env: Dict[str, str] = None) -> bool: success = True env = env or os.environ for feature_set in _get_feature_sets(): try: _run_cargo( session, "clippy", "--no-default-features", *((f"--features={feature_set}",) if feature_set else ()), "--all-targets", "--workspace", "--", "--deny=warnings", env=env, ) except nox.command.CommandFailed: success = False return success def _clippy_additional_workspaces(session: nox.Session) -> bool: # pyo3-benches and pyo3-ffi-check are in isolated workspaces so that their # dependencies do not interact with MSRV success = True try: _run_cargo(session, "clippy", _BENCHES) except Exception: success = False # Run pyo3-ffi-check only on when not cross-compiling, because it needs to # have Python headers to feed to bindgen which gets messy when cross-compiling. target = os.environ.get("CARGO_BUILD_TARGET") if target is None or _get_rust_default_target() == target: try: _build_docs_for_ffi_check(session) _run_cargo(session, "clippy", _FFI_CHECK, "--workspace", "--all-targets") except Exception: success = False return success @nox.session(venv_backend="none") def bench(session: nox.Session) -> bool: _run_cargo(session, "bench", _BENCHES, *session.posargs) @nox.session() def codspeed(session: nox.Session) -> bool: # rust benchmarks os.chdir(PYO3_DIR / "pyo3-benches") _run_cargo(session, "codspeed", "build") _run_cargo(session, "codspeed", "run") # python benchmarks os.chdir(PYO3_DIR / "pytests") session.install(".[dev]", "pytest-codspeed") _run(session, "pytest", "--codspeed", external=True) @nox.session(name="clippy-all", venv_backend="none") def clippy_all(session: nox.Session) -> None: success = True def _clippy_with_config(env: Dict[str, str]) -> None: nonlocal success success &= _clippy(session, env=env) _for_all_version_configs(session, _clippy_with_config) success &= _clippy_additional_workspaces(session) if not success: session.error("one or more jobs failed") @nox.session(name="check-all", venv_backend="none") def check_all(session: nox.Session) -> None: success = True def _check(env: Dict[str, str]) -> None: nonlocal success for feature_set in _get_feature_sets(): try: _run_cargo( session, "check", "--no-default-features", *((f"--features={feature_set}",) if feature_set else ()), "--all-targets", "--workspace", env=env, ) except Exception: success = False _for_all_version_configs(session, _check) if not success: session.error("one or more jobs failed") @nox.session(venv_backend="none") def publish(session: nox.Session) -> None: _run_cargo_publish(session, package="pyo3-build-config") _run_cargo_publish(session, package="pyo3-macros-backend") _run_cargo_publish(session, package="pyo3-macros") _run_cargo_publish(session, package="pyo3-ffi") _run_cargo_publish(session, package="pyo3") _run_cargo_publish(session, package="pyo3-introspection") @nox.session(venv_backend="none") def contributors(session: nox.Session) -> None: import requests if len(session.posargs) < 1: raise Exception("base commit positional argument missing") base = session.posargs[0] page = 1 head = "HEAD" if len(session.posargs) == 2: head = session.posargs[1] if len(session.posargs) > 2: raise Exception("too many arguments") authors = set() while True: resp = requests.get( f"https://api.github.com/repos/PyO3/pyo3/compare/{base}...{head}", params={"page": page, "per_page": 100}, ) body = resp.json() if resp.status_code != 200: raise Exception( f"failed to retrieve commits: {resp.status_code} {body['message']}" ) for commit in body["commits"]: try: authors.add(commit["author"]["login"]) except Exception: continue if "next" in resp.links: page += 1 else: break authors = sorted(list(authors), key=lambda author: author.lower()) for author in authors: print(f"@{author}") class EmscriptenInfo: def __init__(self): self.emscripten_dir = PYO3_DIR / "emscripten" self.builddir = PYO3_DIR / ".nox/emscripten" self.builddir.mkdir(exist_ok=True, parents=True) self.pyversion = sys.version.split()[0] self.pymajor, self.pyminor, self.pymicro = self.pyversion.split(".") self.pymicro, self.pydev = re.match( "([0-9]*)([^0-9].*)?", self.pymicro ).groups() if self.pydev is None: self.pydev = "" self.pymajorminor = f"{self.pymajor}.{self.pyminor}" self.pymajorminormicro = f"{self.pymajorminor}.{self.pymicro}" @nox.session(name="build-emscripten", venv_backend="none") def build_emscripten(session: nox.Session): info = EmscriptenInfo() _run( session, "make", "-C", str(info.emscripten_dir), f"PYTHON={sys.executable}", f"BUILDROOT={info.builddir}", f"PYMAJORMINORMICRO={info.pymajorminormicro}", f"PYPRERELEASE={info.pydev}", external=True, ) @nox.session(name="test-emscripten", venv_backend="none") def test_emscripten(session: nox.Session): info = EmscriptenInfo() libdir = info.builddir / f"install/Python-{info.pyversion}/lib" pythonlibdir = libdir / f"python{info.pymajorminor}" target = "wasm32-unknown-emscripten" session.env["CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER"] = "python " + str( info.emscripten_dir / "runner.py" ) session.env["RUSTFLAGS"] = " ".join( [ f"-L native={libdir}", "-C link-arg=--preload-file", f"-C link-arg={pythonlibdir}@/lib/python{info.pymajorminor}", f"-C link-arg=-lpython{info.pymajorminor}", "-C link-arg=-lexpat", "-C link-arg=-lffi", "-C link-arg=-lmpdec", "-C link-arg=-lhacl", "-C link-arg=-sUSE_SQLITE3", "-C link-arg=-sUSE_ZLIB", "-C link-arg=-sUSE_BZIP2", "-C link-arg=-sEXPORTED_FUNCTIONS=_main,__PyRuntime", "-C link-arg=-sALLOW_MEMORY_GROWTH=1", "-C link-arg=-sSTACK_SIZE=262144", ] ) session.env["RUSTDOCFLAGS"] = session.env["RUSTFLAGS"] session.env["CARGO_BUILD_TARGET"] = target session.env["PYO3_CROSS_LIB_DIR"] = pythonlibdir _run(session, "rustup", "target", "add", target, "--toolchain", "stable") emsdk_env = next(info.builddir.glob("**/emsdk-cache/**/emsdk_env.sh")) _run( session, "bash", "-c", f"source {emsdk_env} && cargo test {' '.join(quote(arg) for arg in session.posargs)}", ) @nox.session(name="test-cross-compilation-windows") def test_cross_compilation_windows(session: nox.Session): session.install("cargo-xwin") env = os.environ.copy() env["XWIN_ARCH"] = "x86_64" # abi3 _run_cargo( session, "build", "--manifest-path", "examples/maturin-starter/Cargo.toml", "--features", "abi3", "--target", "x86_64-pc-windows-gnu", env=env, ) _run_cargo( session, "xwin", "build", "--cross-compiler", "clang", "--manifest-path", "examples/maturin-starter/Cargo.toml", "--features", "abi3", "--target", "x86_64-pc-windows-msvc", env=env, ) # non-abi3 env["PYO3_CROSS_PYTHON_VERSION"] = "3.13" _run_cargo( session, "build", "--manifest-path", "examples/maturin-starter/Cargo.toml", "--features", "pyo3/generate-import-lib", "--target", "x86_64-pc-windows-gnu", env=env, ) _run_cargo( session, "xwin", "build", "--cross-compiler", "clang", "--manifest-path", "examples/maturin-starter/Cargo.toml", "--features", "pyo3/generate-import-lib", "--target", "x86_64-pc-windows-msvc", env=env, ) @nox.session(venv_backend="none") def docs(session: nox.Session, nightly: bool = False, internal: bool = False) -> None: rustdoc_flags = ["-Dwarnings"] toolchain_flags = [] cargo_flags = [] nightly = nightly or ("nightly" in session.posargs) internal = internal or ("internal" in session.posargs) if "open" in session.posargs: cargo_flags.append("--open") if nightly: rustdoc_flags.append("--cfg docsrs") toolchain_flags.append("+nightly") cargo_flags.extend(["-Z", "unstable-options", "-Z", "rustdoc-scrape-examples"]) if internal: rustdoc_flags.append("--Z unstable-options") rustdoc_flags.append("--document-hidden-items") rustdoc_flags.extend(("--html-after-content", ".netlify/internal_banner.html")) cargo_flags.append("--document-private-items") else: cargo_flags.extend(["--exclude=pyo3-macros", "--exclude=pyo3-macros-backend"]) rustdoc_flags.append(session.env.get("RUSTDOCFLAGS", "")) session.env["RUSTDOCFLAGS"] = " ".join(rustdoc_flags) features = "full" shutil.rmtree(PYO3_DOCS_TARGET, ignore_errors=True) _run_cargo( session, *toolchain_flags, "doc", "--lib", "--no-default-features", f"--features={features}", "--no-deps", "--workspace", *cargo_flags, ) @nox.session(name="build-guide", venv_backend="none") def build_guide(session: nox.Session): shutil.rmtree(PYO3_GUIDE_TARGET, ignore_errors=True) _run( session, "mdbook", "build", "-d", str(PYO3_GUIDE_TARGET), "guide", *session.posargs, external=True, ) for license in ("LICENSE-APACHE", "LICENSE-MIT"): target_file = PYO3_GUIDE_TARGET / license target_file.unlink(missing_ok=True) shutil.copy(PYO3_DIR / license, target_file) @nox.session(name="build-netlify-site") def build_netlify_site(session: nox.Session): # Remove netlify_build directory if it exists netlify_build = Path("netlify_build") if netlify_build.exists(): shutil.rmtree(netlify_build) url = "https://github.com/PyO3/pyo3/archive/gh-pages.tar.gz" response = requests.get(url, stream=True) response.raise_for_status() with tarfile.open(fileobj=io.BytesIO(response.content), mode="r:gz") as tar: tar.extractall() shutil.move("pyo3-gh-pages", "netlify_build") preview = "--preview" in session.posargs if preview: session.posargs.remove("--preview") session.install("towncrier") # Save a copy of the changelog to restore later changelog = (PYO3_DIR / "CHANGELOG.md").read_text() # Build the changelog session.run( "towncrier", "build", "--keep", "--version", "Unreleased", "--date", "TBC" ) # Build the guide build_guide(session) PYO3_GUIDE_TARGET.rename("netlify_build/main") # Restore the original changelog (PYO3_DIR / "CHANGELOG.md").write_text(changelog) session.run("git", "restore", "--staged", "CHANGELOG.md", external=True) # Build the main branch docs docs(session) PYO3_DOCS_TARGET.rename("netlify_build/main/doc") Path("netlify_build/main/doc/index.html").write_text( "" ) # Build the internal docs docs(session, nightly=True, internal=True) PYO3_DOCS_TARGET.rename("netlify_build/internal") _build_netlify_redirects(preview) def _build_netlify_redirects(preview: bool) -> None: current_version = os.environ.get("PYO3_VERSION") with ExitStack() as stack: redirects_file = stack.enter_context(open("netlify_build/_redirects", "w")) headers_file = stack.enter_context(open("netlify_build/_headers", "w")) for d in glob("netlify_build/v*"): version = d.removeprefix("netlify_build/v") redirects_file.write( f"/v{version}/doc/* https://docs.rs/pyo3/{version}/:splat\n" ) # for versions other than the current version, set noindex if version != current_version: headers_file.write(f"/v{version}/*\n X-Robots-Tag: noindex\n") continue # for the current version, index all files and set canonical links where possible for file in glob(f"{d}/**", recursive=True): file_path = file.removeprefix("netlify_build") url_path = _url_path_from_file_path(file_path) for path in _url_and_file_paths(url_path, file_path): headers_file.write( f'{path}\n Link: ; rel="canonical"\n' ) # main files should be indexed and canonical for file in glob("netlify_build/main/**", recursive=True): file_path = file.removeprefix("netlify_build") url_path = _url_path_from_file_path(file_path) for path in _url_and_file_paths(url_path, file_path): headers_file.write( f'{path}\n Link: ; rel="canonical"\n' ) # for internal docs, set noindex for all files headers_file.write("/internal/*\n X-Robots-Tag: noindex\n") # Add latest redirect if current_version is not None: redirects_file.write(f"/latest/* /v{current_version}/:splat 302\n") # some backwards compatbiility redirects redirects_file.write( """\ /latest/building_and_distribution/* /latest/building-and-distribution/:splat 302 /latest/building_and_distribution/multiple_python_versions/* /latest/building-and-distribution/multiple-python-versions:splat 302 /latest/function/error_handling/* /latest/function/error-handling/:splat 302 /latest/getting_started/* /latest/getting-started/:splat 302 /latest/python_from_rust/* /latest/python-from-rust/:splat 302 /latest/python_typing_hints/* /latest/python-typing-hints/:splat 302 /latest/trait_bounds/* /latest/trait-bounds/:splat 302 """ ) # Add landing page redirect if preview: redirects_file.write("/ /main/ 302\n") else: redirects_file.write(f"/ /v{current_version}/ 302\n") def _url_path_from_file_path(file_path: str) -> str: """Removes index.html and/or .html suffix to match the page URL on the final netlify site""" url_path = file_path if url_path.endswith("index.html"): url_path = url_path[: -len("index.html")] elif url_path.endswith(".html"): url_path = url_path[: -len(".html")] return url_path def _url_and_file_paths(url_path: str, file_path: str) -> Tuple[str, str]: """Returns all combinations of url and file paths with and without index.html suffix""" if url_path == file_path: return (url_path,) else: return (url_path, file_path) @nox.session(name="check-guide") def check_guide(session: nox.Session): # reuse other sessions, but with default args posargs = [*session.posargs] del session.posargs[:] build_guide(session) docs(session) session.posargs.extend(posargs) if toml is None: session.error("requires Python 3.11 or `toml` to be installed") pyo3_version = toml.loads((PYO3_DIR / "Cargo.toml").read_text())["package"][ "version" ] remaps = { f"file://{PYO3_GUIDE_SRC}/([^/]*/)*?%7B%7B#PYO3_DOCS_URL}}}}": f"file://{PYO3_DOCS_TARGET}", f"https://pyo3.rs/v{pyo3_version}": f"file://{PYO3_GUIDE_TARGET}", "https://pyo3.rs/main/": f"file://{PYO3_GUIDE_TARGET}/", "https://pyo3.rs/latest/": f"file://{PYO3_GUIDE_TARGET}/", "%7B%7B#PYO3_DOCS_VERSION}}": "latest", # bypass fragments for edge cases # blob links "(https://github.com/[^/]+/[^/]+/blob/[^#]+)#[a-zA-Z0-9._-]*": "$1", # issue comments "(https://github.com/[^/]+/[^/]+/issues/[0-9]+)#issuecomment-[0-9]*": "$1", # rust docs "(https://docs.rs/[^#]+)#[a-zA-Z0-9._-]*": "$1", } remap_args = [] for key, value in remaps.items(): remap_args.extend(("--remap", f"{key} {value}")) # check all links in the guide _run( session, "lychee", "--include-fragments", str(PYO3_GUIDE_SRC), *remap_args, "--accept=200,429", "--cache", "--max-cache-age=7d", *session.posargs, external=True, ) # check external links in the docs # (intra-doc links are checked by rustdoc) _run( session, "lychee", str(PYO3_DOCS_TARGET), *remap_args, f"--exclude=file://{PYO3_DOCS_TARGET}", # exclude some old http links from copyright notices, known to fail "--exclude=http://www.adobe.com/", "--exclude=http://www.nhncorp.com/", "--accept=200,429", # reduce the concurrency to avoid rate-limit from `pyo3.rs` "--max-concurrency=32", "--cache", "--max-cache-age=7d", *session.posargs, external=True, ) @nox.session(name="format-guide", venv_backend="none") def format_guide(session: nox.Session): fence_line = "//! ```\n" for path in Path("guide").glob("**/*.md"): session.log("Working on %s", path) lines = iter(path.read_text().splitlines(True)) new_lines = [] for line in lines: new_lines.append(line) if not re.search("```rust(,.*)?$", line): continue # Found a code block fence, gobble up its lines and write to temp. file prefix = line[: line.index("```")] with tempfile.NamedTemporaryFile("w", delete=False) as file: tempname = file.name file.write(fence_line) for line in lines: if line == prefix + "```\n": break file.write(("//! " + line[len(prefix) :]).rstrip() + "\n") file.write(fence_line) # Format it (needs nightly rustfmt for `format_code_in_doc_comments`) _run( session, "rustfmt", "+nightly", "--config", "format_code_in_doc_comments=true", "--config", "reorder_imports=false", tempname, ) # Re-read the formatted file, add its lines, and delete it with open(tempname, "r") as file: for line in file: if line == fence_line: continue new_lines.append((prefix + line[4:]).rstrip() + "\n") os.unlink(tempname) new_lines.append(prefix + "```\n") path.write_text("".join(new_lines)) def _format_ffi_extern(session: nox.Session, *, check: bool = False): """Format extern blocks inside extern_libpython! macros in pyo3-ffi. rustfmt cannot format inside macro invocations, so this temporarily replaces `extern_libpython!` with plain `extern "C"` blocks, runs rustfmt, and then restores the macro invocations. When check=True, errors out if any file would change (CI mode). """ ffi_src = PYO3_DIR / "pyo3-ffi" / "src" # Pattern for default ABI: `extern_libpython! {` default_re = re.compile(r"^(\s*)extern_libpython!\s*\{", re.MULTILINE) # Pattern for explicit ABI: `extern_libpython! { "C-unwind" {` explicit_re = re.compile( r'^(\s*)extern_libpython!\s*\{\s*"([^"]+)"\s*\{', re.MULTILINE ) # Use #[doc] attributes as sentinels instead of /* */ comments to avoid # rustfmt re-indenting them (rustfmt aligns block comments with nearby # trailing comments, but leaves #[doc] attributes in place). SENTINEL_DEFAULT = '#[doc = "__extern_libpython_default__"]' SENTINEL_EXPLICIT = '#[doc = "__extern_libpython_explicit__:' SENTINEL_EXPLICIT_CLOSE = "/* __extern_libpython_explicit_close__ */" def replace_explicit(m): indent = m.group(1) abi = m.group(2) return f'{indent}{SENTINEL_EXPLICIT}{abi}__"]\n{indent}extern "{abi}" {{' def replace_default(m): indent = m.group(1) return f'{indent}{SENTINEL_DEFAULT}\n{indent}extern "C" {{' # Pattern for the double closing brace of explicit ABI blocks: # `extern_libpython! { "abi" { ... }}` has two closing braces, but after # replacing the opening we only have one opening brace, so we need to # remove the extra closing brace before running rustfmt. explicit_close_re = re.compile(r"\}\}", re.MULTILINE) originals = {} files_to_format = [] for path in sorted(ffi_src.rglob("*.rs")): if path.name == "macros.rs": continue content = path.read_text() if "extern_libpython!" not in content: continue # Replace explicit ABI first (more specific pattern) new_content = explicit_re.sub(replace_explicit, content) # Fix double closing braces for explicit ABI blocks: the explicit # pattern `extern_libpython! { "abi" { ... }}` has an outer `}` for # the macro invocation that must be removed after we replaced the # opening with a plain `extern "abi" {`. if SENTINEL_EXPLICIT in new_content: new_content = explicit_close_re.sub( f"}} {SENTINEL_EXPLICIT_CLOSE}", new_content ) # Replace default ABI new_content = default_re.sub(replace_default, new_content) if new_content != content: originals[path] = content path.write_text(new_content) files_to_format.append(path) if not files_to_format: session.log("No extern_libpython! blocks found to format") return # Run rustfmt on the modified files try: _run( session, "rustfmt", "--edition", "2021", *[str(f) for f in files_to_format] ) except Exception: # Restore originals on failure for path, content in originals.items(): path.write_text(content) raise # Restore the macro invocations sentinel_default_re = re.compile( r'^(\s*)#\[doc = "__extern_libpython_default__"\]\n\s*extern "C" \{', re.MULTILINE, ) sentinel_explicit_re = re.compile( r'^(\s*)#\[doc = "__extern_libpython_explicit__:([^_]+)__"\]\n\s*extern "[^"]*" \{', re.MULTILINE, ) changed = [] for path in files_to_format: content = path.read_text() content = sentinel_explicit_re.sub( lambda m: f'{m.group(1)}extern_libpython! {{ "{m.group(2)}" {{', content ) # Restore the double closing brace for explicit ABI blocks content = content.replace(f"}} {SENTINEL_EXPLICIT_CLOSE}", "}}") content = sentinel_default_re.sub( lambda m: f"{m.group(1)}extern_libpython! {{", content ) if check and content != originals[path]: changed.append(path) # Restore original so we don't leave dirty files in CI path.write_text(originals[path]) else: path.write_text(content) if check and changed: session.error( "extern_libpython! blocks are not formatted:\n" + "\n".join(f" {p}" for p in changed) + "\n\nRun `nox -s format-ffi-extern` to fix." ) session.log(f"Formatted extern_libpython! blocks in {len(files_to_format)} files ✓") @nox.session(name="format-ffi-extern", venv_backend="none") def format_ffi_extern(session: nox.Session): _format_ffi_extern(session) @nox.session(name="address-sanitizer", venv_backend="none") def address_sanitizer(session: nox.Session): _run_cargo( session, "+nightly", "test", "--release", "-Zbuild-std", f"--target={_get_rust_default_target()}", "--", "--test-threads=1", env={ "RUSTFLAGS": "-Zsanitizer=address", "RUSTDOCFLAGS": "-Zsanitizer=address", "ASAN_OPTIONS": "detect_leaks=0", }, ) _IGNORE_CHANGELOG_PR_CATEGORIES = ( "release", "docs", "ci", ) @nox.session(name="check-changelog") def check_changelog(session: nox.Session): if not _is_github_actions(): session.error("Can only check changelog on github actions") event_path = os.environ["GITHUB_EVENT_PATH"] with open(event_path) as event_file: event = json.load(event_file) for category in _IGNORE_CHANGELOG_PR_CATEGORIES: if event["pull_request"]["title"].startswith(f"{category}:"): session.skip(f"PR title starts with {category}") for label in event["pull_request"]["labels"]: if label["name"] == "CI-skip-changelog": session.skip("CI-skip-changelog label applied") issue_number = event["pull_request"]["number"] newsfragments = PYO3_DIR / "newsfragments" fragments = tuple( filter( Path.exists, ( newsfragments / f"{issue_number}.{change_type}.md" for change_type in ("packaging", "added", "changed", "removed", "fixed") ), ) ) if not fragments: session.error( "Changelog entry not found, please add one (or more) to the `newsfragments` directory.\n" "Alternatively, start the PR title with `docs:` if this PR is a docs-only PR.\n" "See https://github.com/PyO3/pyo3/blob/main/Contributing.md#documenting-changes for more information." ) print("Found newsfragments:") for fragment in fragments: print(fragment.name) @nox.session(name="set-msrv-package-versions", venv_backend="none") def set_msrv_package_versions(session: nox.Session): from collections import defaultdict projects = ( PYO3_DIR, *(Path(p).parent for p in glob("examples/*/Cargo.toml")), *(Path(p).parent for p in glob("pyo3-ffi/examples/*/Cargo.toml")), ) min_pkg_versions = {} # run cargo update first to ensure that everything is at highest # possible version, so that this matches what CI will resolve to. for project in projects: _run_cargo( session, "+stable", "update", f"--manifest-path={project}/Cargo.toml", env=os.environ | {"CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS": "fallback"}, ) lock_file = project / "Cargo.lock" def load_pkg_versions(): cargo_lock = toml.loads(lock_file.read_text()) # Cargo allows to depends on multiple versions of the same package pkg_versions = defaultdict(list) for pkg in cargo_lock["package"]: name = pkg["name"] if name not in min_pkg_versions: continue pkg_versions[name].append(pkg["version"]) return pkg_versions pkg_versions = load_pkg_versions() for pkg_name, min_version in min_pkg_versions.items(): versions = pkg_versions.get(pkg_name, []) for version in versions: if version != min_version: pkg_id = pkg_name + ":" + version _run_cargo_set_package_version( session, pkg_id, min_version, project=project ) # assume `_run_cargo_set_package_version` has changed something # and re-read `Cargo.lock` pkg_versions = load_pkg_versions() # As a smoke test, cargo metadata solves all dependencies, so # will break if any crates rely on cargo features not # supported on MSRV _run_cargo( session, "metadata", f"--manifest-path={project}/Cargo.toml", silent=True, ) @nox.session(name="ffi-check") def ffi_check(session: nox.Session): _build_docs_for_ffi_check(session) _run_cargo(session, "run", _FFI_CHECK) _check_raw_dylib_macro(session) @nox.session(name="test-version-limits") def test_version_limits(session: nox.Session): env = os.environ.copy() with _config_file() as config_file: env["PYO3_CONFIG_FILE"] = config_file.name assert "3.6" not in PY_VERSIONS config_file.set("CPython", "3.6") _run_cargo(session, "check", env=env, expect_error=True) assert "3.16" not in PY_VERSIONS config_file.set("CPython", "3.16") _run_cargo(session, "check", env=env, expect_error=True) # 3.16 CPython should build if abi3 is explicitly requested _run_cargo(session, "check", "--features=pyo3/abi3", env=env) # 3.15 CPython should build with forward compatibility # TODO: check on 3.16 when adding abi3-py315 support config_file.set("CPython", "3.15") env["PYO3_USE_ABI3_FORWARD_COMPATIBILITY"] = "1" _run_cargo(session, "check", env=env) assert "3.10" not in PYPY_VERSIONS config_file.set("PyPy", "3.10") _run_cargo(session, "check", env=env, expect_error=True) # 3.13t is no longer supported config_file.set("CPython", "3.13t") _run_cargo(session, "check", env=env, expect_error=True) # 3.14t is PyO3's minimum version of free-threaded Python config_file.set("CPython", "3.14t") _run_cargo(session, "check", env=env) # attempt to build with latest version and check that abi3 version # configured matches the feature max_minor_version = max(int(v.split(".")[1]) for v in ABI3_PY_VERSIONS) with tempfile.TemporaryFile() as stderr: env = os.environ.copy() env["PYO3_PRINT_CONFIG"] = "1" # get diagnostics from the build env["PYO3_NO_PYTHON"] = "1" # isolate the build from local Python _run_cargo( session, "check", f"--features=pyo3/abi3-py3{max_minor_version}", env=env, stderr=stderr, expect_error=True, ) stderr.seek(0) stderr = stderr.read().decode() # NB if this assertion fails with something like # "An abi3-py3* feature must be specified when compiling without a Python # interpreter." # # then `ABI3_MAX_MINOR` in `pyo3-build-config/src/impl_.rs` is probably outdated. assert f"version=3.{max_minor_version}" in stderr, ( f"Expected to see version=3.{max_minor_version}, got: \n\n{stderr}" ) def _check_raw_dylib_macro(session: nox.Session): """Check that extern_libpython! macro covers all supported Python DLL names.""" min_version, max_version = _parse_supported_interpreter_version("cpython") min_minor = int(min_version.split(".")[1]) max_minor = int(max_version.split(".")[1]) # Build the set of DLL names that default_lib_name_windows can produce expected_dlls = {"python3", "python3_d"} for minor in range(min_minor, max_minor + 1): expected_dlls.add(f"python3{minor}") expected_dlls.add(f"python3{minor}_d") if minor >= 13: expected_dlls.add(f"python3{minor}t") expected_dlls.add(f"python3{minor}t_d") # PyPy DLL names (libpypy3.X-c.dll) pypy_min, pypy_max = _parse_supported_interpreter_version("pypy") pypy_min_minor = int(pypy_min.split(".")[1]) pypy_max_minor = int(pypy_max.split(".")[1]) for minor in range(pypy_min_minor, pypy_max_minor + 1): expected_dlls.add(f"libpypy3.{minor}-c") # Parse the DLL name list in the extern_libpython!(@impl ...) invocation lib_rs = (PYO3_DIR / "pyo3-ffi" / "src" / "impl_" / "macros.rs").read_text() found_dlls = set(re.findall(r'"((?:python|libpypy)[^"]+)"', lib_rs)) missing = expected_dlls - found_dlls extra = found_dlls - expected_dlls errors = [] if missing: errors.append( f"Missing DLL names in extern_libpython! macro: {sorted(missing)}" ) if extra: errors.append(f"Extra DLL names in extern_libpython! macro: {sorted(extra)}") if errors: session.error( "\n".join(errors) + "\n\nUpdate the extern_libpython! macro in pyo3-ffi/src/impl_/macros.rs" + " to match supported Python versions in pyo3-ffi/Cargo.toml" ) session.log( f"extern_libpython! macro covers all {len(expected_dlls)} expected DLL names ✓" ) private_fn_allowlist = set(re.findall(r"\[\s*(_Py[A-Za-z0-9_]*)\s*\]", lib_rs)) required_private_fns = _raw_dylib_x86_private_functions() missing = required_private_fns - private_fn_allowlist extra = private_fn_allowlist - required_private_fns errors = [] if missing: errors.append( "Missing x86 raw-dylib workaround entries for CPython private functions: " f"{sorted(missing)}" ) if extra: errors.append( "Unexpected x86 raw-dylib workaround entries for non-CPython/private functions: " f"{sorted(extra)}" ) if errors: session.error( "\n".join(errors) + "\n\nUpdate extern_libpython_maybe_private_fn! in pyo3-ffi/src/impl_/macros.rs" + " to match the CPython `_Py*` function imports declared via extern_libpython!." ) session.log( "extern_libpython_maybe_private_fn! covers all required x86 CPython" f" private function imports ({len(required_private_fns)}) ✓" ) def _raw_dylib_x86_private_functions() -> Set[str]: ffi_src = PYO3_DIR / "pyo3-ffi" / "src" private_fns = set() for path in ffi_src.rglob("*.rs"): for block in _iter_extern_libpython_blocks(path.read_text()): attrs: List[str] = [] for line in block.splitlines(): stripped = line.strip() if stripped.startswith("#["): attrs.append(stripped) continue match = re.search(r"\bfn\s+(_Py[A-Za-z0-9_]*)\b", stripped) if match: if not any( _cfg_attr_is_non_cpython_only(attr) for attr in attrs if attr.startswith("#[cfg(") ): private_fns.add(match.group(1)) attrs = [] continue if stripped and not stripped.startswith("//"): attrs = [] return private_fns def _iter_extern_libpython_blocks(source: str) -> Iterator[str]: cursor = 0 while True: start = source.find("extern_libpython!", cursor) if start == -1: return block_start = source.find("{", start) if block_start == -1: return depth = 0 for idx in range(block_start, len(source)): if source[idx] == "{": depth += 1 elif source[idx] == "}": depth -= 1 if depth == 0: yield source[block_start + 1 : idx] cursor = idx + 1 break else: return def _cfg_attr_is_non_cpython_only(attr: str) -> bool: """Check if a #[cfg()] attribute targets only non-CPython implementations. Functions behind #[cfg(PyPy)] or #[cfg(GraalPy)] are linked against the PyPy/GraalPy runtime, not the CPython DLL, so they don't need the x86 raw-dylib underscore workaround. """ match = re.fullmatch(r"#\[cfg\((.*)\)\]", attr) if match is None: return False return bool( re.fullmatch( r"\s*(?:any\()?\s*(?:PyPy|GraalPy)\s*(?:,\s*(?:PyPy|GraalPy)\s*)*\)?\s*", match.group(1), ) ) @nox.session(name="check-feature-powerset", venv_backend="none") def check_feature_powerset(session: nox.Session): if toml is None: session.error("requires Python 3.11 or `toml` to be installed") cargo_toml = toml.loads((PYO3_DIR / "Cargo.toml").read_text()) # free-threaded builds do not support ABI3 (yet) EXPECTED_ABI3_FEATURES = { f"abi3-py3{ver.split('.')[1]}" for ver in ABI3_PY_VERSIONS } EXCLUDED_FROM_FULL = { "nightly", "extension-module", "full", "default", "auto-initialize", "generate-import-lib", "multiple-pymethods", # Because it's not supported on wasm } features = cargo_toml["features"] full_feature = set(features["full"]) abi3_features = {feature for feature in features if feature.startswith("abi3")} abi3_version_features = abi3_features - {"abi3"} unexpected_abi3_features = abi3_version_features - EXPECTED_ABI3_FEATURES if unexpected_abi3_features: session.error( f"unexpected `abi3` features found in Cargo.toml: {unexpected_abi3_features}" ) missing_abi3_features = EXPECTED_ABI3_FEATURES - abi3_version_features if missing_abi3_features: session.error(f"missing `abi3` features in Cargo.toml: {missing_abi3_features}") expected_full_feature = features.keys() - EXCLUDED_FROM_FULL - abi3_features uncovered_features = expected_full_feature - full_feature if uncovered_features: session.error( f"some features missing from `full` meta feature: {uncovered_features}" ) experimental_features = { feature for feature in features if feature.startswith("experimental-") } full_without_experimental = full_feature - experimental_features if len(experimental_features) >= 2: # justification: we always assume that feature within these groups are # mutually exclusive to simplify CI features_to_group = [ full_without_experimental, experimental_features, ] elif len(experimental_features) == 1: # no need to make an experimental features group features_to_group = [full_without_experimental] else: session.error("no experimental features exist; please simplify the noxfile") features_to_skip = [ *(EXCLUDED_FROM_FULL), *abi3_version_features, ] # deny warnings env = os.environ.copy() rust_flags = env.get("RUSTFLAGS", "") env["RUSTFLAGS"] = f"{rust_flags} -Dwarnings" subcommand = "hack" if "minimal-versions" in session.posargs: subcommand = "minimal-versions" comma_join = ",".join _run_cargo( session, subcommand, "--feature-powerset", '--optional-deps=""', f'--skip="{comma_join(features_to_skip)}"', *(f"--group-features={comma_join(group)}" for group in features_to_group), "check", "--all-targets", env=env, ) @nox.session(name="update-ui-tests", venv_backend="none") def update_ui_tests(session: nox.Session): env = os.environ.copy() env["TRYBUILD"] = "overwrite" command = ["test", "--test", "test_compile_error"] _run_cargo(session, *command, env=env) _run_cargo(session, *command, "--features=full", env=env) _run_cargo(session, *command, "--features=abi3,full", env=env) @nox.session(name="test-introspection") def test_introspection(session: nox.Session): session.install("maturin") session.install("ruff") options = [] target = os.environ.get("CARGO_BUILD_TARGET") if target is not None: options += ("--target", target) profile = os.environ.get("CARGO_BUILD_PROFILE") if profile == "release": options.append("--release") session.run_always( "maturin", "develop", "-m", "./pytests/Cargo.toml", "--features", "experimental-async,experimental-inspect", *options, ) lib_file = session.run( "python", "-c", "import pyo3_pytests; print(pyo3_pytests.pyo3_pytests.__file__)", silent=True, ).strip() _run_cargo_test( session, package="pyo3-introspection", env={"PYO3_PYTEST_LIB_PATH": lib_file}, ) def _build_docs_for_ffi_check(session: nox.Session) -> None: # pyo3-ffi-check needs to scrape docs of pyo3-ffi env = os.environ.copy() env["PYO3_PYTHON"] = sys.executable _run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps", env=env) @lru_cache() def _get_rust_info() -> Tuple[str, ...]: output = _get_output("rustc", "-vV") return tuple(output.splitlines()) def get_rust_version() -> Tuple[int, int, int, List[str]]: for line in _get_rust_info(): if line.startswith(_RELEASE_LINE_START): version = line[len(_RELEASE_LINE_START) :].strip() # e.g. 1.67.0-beta.2 (version_number, *extra) = version.split("-", maxsplit=1) return (*map(int, version_number.split(".")), extra) def is_rust_nightly() -> bool: for line in _get_rust_info(): if line.startswith(_RELEASE_LINE_START): return line.strip().endswith("-nightly") return False def _get_rust_default_target() -> str: for line in _get_rust_info(): if line.startswith(_HOST_LINE_START): return line[len(_HOST_LINE_START) :].strip() @lru_cache() def _get_feature_sets() -> Tuple[Optional[str], ...]: """Returns feature sets to use for Rust jobs""" cargo_target = os.getenv("CARGO_BUILD_TARGET", "") features = "full" if "wasm32-wasip1" not in cargo_target: # multiple-pymethods not supported on wasm features += ",multiple-pymethods" if is_rust_nightly(): features += ",nightly" return (None, "abi3", features, f"abi3,{features}") _RELEASE_LINE_START = "release: " _HOST_LINE_START = "host: " def _get_coverage_env() -> Dict[str, str]: env = {} output = _get_output("cargo", "llvm-cov", "show-env") for line in output.strip().splitlines(): (key, value) = line.split("=", maxsplit=1) # Strip single or double quotes from the variable value # - quote used by llvm-cov differs between Windows and Linux if value and value[0] in ("'", '"'): value = value[1:-1] env[key] = value # Ensure that examples/ and pytests/ all build to the correct target directory to collect # coverage artifacts. env["CARGO_TARGET_DIR"] = env["CARGO_LLVM_COV_TARGET_DIR"] return env def _run(session: nox.Session, *args: str, **kwargs: Any) -> None: """Wrapper for _run(session, which creates nice groups on GitHub Actions.""" is_github_actions = _is_github_actions() failed = False if is_github_actions: # Insert ::group:: at the start of nox's command line output print("::group::", end="", flush=True, file=sys.stderr) try: session.run(*args, **kwargs) except nox.command.CommandFailed: failed = True raise finally: if is_github_actions: print("::endgroup::", file=sys.stderr) # Defer the error message until after the group to make them easier # to find in the log if failed: command = " ".join(args) print(f"::error::`{command}` failed", file=sys.stderr) def _run_cargo( session: nox.Session, *args: str, expect_error: bool = False, **kwargs: Any ) -> None: if expect_error: if "success_codes" in kwargs: raise ValueError("expect_error overrides success_codes") kwargs["success_codes"] = [101] _run(session, "cargo", *args, **kwargs, external=True) def _run_cargo_test( session: nox.Session, *, package: Optional[str] = None, features: Optional[str] = None, env: Optional[Dict[str, str]] = None, extra_flags: Optional[List[str]] = None, ) -> None: command = ["cargo"] if "careful" in session.posargs: # do explicit setup so failures in setup can be seen _run_cargo(session, "careful", "setup") command.append("careful") command.extend(("test", "--no-fail-fast")) if "release" in session.posargs: command.append("--release") if package: command.append(f"--package={package}") if features: command.append(f"--features={features}") if extra_flags: command.extend(extra_flags) _run(session, *command, external=True, env=env or {}) def _run_cargo_publish(session: nox.Session, *, package: str) -> None: _run_cargo(session, "publish", f"--package={package}") def _run_cargo_set_package_version( session: nox.Session, pkg_id: str, version: str, *, project: Optional[str] = None, ) -> None: command = ["cargo", "update", "-p", pkg_id, "--precise", version, "--workspace"] if project: command.append(f"--manifest-path={project}/Cargo.toml") _run(session, *command, external=True) def _for_all_version_configs( session: nox.Session, job: Callable[[Dict[str, str]], None] ) -> None: env = os.environ.copy() with _config_file() as config_file: env["PYO3_CONFIG_FILE"] = config_file.name def _job_with_config(implementation, version): session.log(f"{implementation} {version}") config_file.set(implementation, version) job(env) for version in PY_VERSIONS: _job_with_config("CPython", version) for version in PYPY_VERSIONS: _job_with_config("PyPy", version) class _ConfigFile: def __init__(self, config_file) -> None: self._config_file = config_file def set( self, implementation: str, version: str, build_flags: Iterable[str] = () ) -> None: """Set the contents of this config file to the given implementation and version.""" if version.endswith("t"): # Free threaded versions pass the support in config file through a flag version = version[:-1] build_flags = (*build_flags, "Py_GIL_DISABLED") self._config_file.seek(0) self._config_file.truncate(0) self._config_file.write( f"""\ implementation={implementation} version={version} build_flags={",".join(build_flags)} suppress_build_script_link_lines=true """ ) self._config_file.flush() @property def name(self) -> str: return self._config_file.name @contextmanager def _config_file() -> Iterator[_ConfigFile]: """Creates a temporary config file which can be repeatedly set to different values.""" with tempfile.NamedTemporaryFile("r+") as config: yield _ConfigFile(config) def _is_github_actions() -> bool: return "GITHUB_ACTIONS" in os.environ _BENCHES = "--manifest-path=pyo3-benches/Cargo.toml" _FFI_CHECK = "--manifest-path=pyo3-ffi-check/Cargo.toml" ================================================ FILE: pyo3-benches/Cargo.toml ================================================ [package] name = "pyo3-benches" version = "0.1.0" description = "In-tree benchmarks for the PyO3 project" authors = ["PyO3 Project and Contributors "] edition = "2021" publish = false [dependencies] pyo3 = { path = "../", features = ["auto-initialize", "full"] } [build-dependencies] pyo3-build-config = { path = "../pyo3-build-config" } [dev-dependencies] codspeed-criterion-compat = "4.0" criterion = "0.8.0" num-bigint = "0.4.3" rust_decimal = { version = "1.0.0", default-features = false } hashbrown = "0.16" [features] abi3 = ["pyo3-build-config/abi3"] [[bench]] name = "bench_any" harness = false [[bench]] name = "bench_attach" harness = false [[bench]] name = "bench_call" harness = false [[bench]] name = "bench_comparisons" harness = false [[bench]] name = "bench_critical_sections" harness = false [[bench]] name = "bench_err" harness = false [[bench]] name = "bench_decimal" harness = false [[bench]] name = "bench_dict" harness = false [[bench]] name = "bench_frompyobject" harness = false [[bench]] name = "bench_intopyobject" harness = false [[bench]] name = "bench_list" harness = false [[bench]] name = "bench_py" harness = false [[bench]] name = "bench_pyclass" harness = false [[bench]] name = "bench_set" harness = false [[bench]] name = "bench_tuple" harness = false [[bench]] name = "bench_intern" harness = false [[bench]] name = "bench_extract" harness = false [[bench]] name = "bench_bigint" harness = false [[bench]] name = "bench_pystring_from_fmt" harness = false [workspace] ================================================ FILE: pyo3-benches/benches/bench_any.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::{ prelude::*, types::{ PyBool, PyByteArray, PyBytes, PyDict, PyFloat, PyFrozenSet, PyInt, PyList, PyMapping, PySequence, PySet, PyString, PyTuple, }, }; #[derive(PartialEq, Eq, Debug)] enum ObjectType { None, Bool, ByteArray, Bytes, Dict, Float, FrozenSet, Int, List, Set, Str, Tuple, Sequence, Mapping, Unknown, } fn find_object_type(obj: &Bound<'_, PyAny>) -> ObjectType { if obj.is_none() { ObjectType::None } else if obj.is_instance_of::() { ObjectType::Bool } else if obj.is_instance_of::() { ObjectType::ByteArray } else if obj.is_instance_of::() { ObjectType::Bytes } else if obj.is_instance_of::() { ObjectType::Dict } else if obj.is_instance_of::() { ObjectType::Float } else if obj.is_instance_of::() { ObjectType::FrozenSet } else if obj.is_instance_of::() { ObjectType::Int } else if obj.is_instance_of::() { ObjectType::List } else if obj.is_instance_of::() { ObjectType::Set } else if obj.is_instance_of::() { ObjectType::Str } else if obj.is_instance_of::() { ObjectType::Tuple } else if obj.cast::().is_ok() { ObjectType::Sequence } else if obj.cast::().is_ok() { ObjectType::Mapping } else { ObjectType::Unknown } } fn bench_identify_object_type(b: &mut Bencher<'_>) { Python::attach(|py| { let obj = py.eval(c"object()", None, None).unwrap(); b.iter(|| find_object_type(&obj)); assert_eq!(find_object_type(&obj), ObjectType::Unknown); }); } fn bench_collect_generic_iterator(b: &mut Bencher<'_>) { Python::attach(|py| { let collection = py.eval(c"list(range(1 << 20))", None, None).unwrap(); b.iter(|| { collection .try_iter() .unwrap() .collect::>>() .unwrap() }); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("identify_object_type", bench_identify_object_type); c.bench_function("collect_generic_iterator", bench_collect_generic_iterator); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_attach.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::prelude::*; fn bench_clean_attach(b: &mut Bencher<'_>) { // Acquiring first GIL will also create a "clean" GILPool, so this measures the Python overhead. b.iter(|| Python::attach(|_| {})); } fn bench_dirty_attach(b: &mut Bencher<'_>) { let obj = Python::attach(|py| py.None()); // Drop the returned clone of the object so that the reference pool has work to do. b.iter(|| Python::attach(|py| obj.clone_ref(py))); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("clean_attach", bench_clean_attach); c.bench_function("dirty_attach", bench_dirty_attach); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_bigint.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use num_bigint::BigInt; use pyo3::prelude::*; use pyo3::types::PyDict; fn extract_bigint_extract_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { let d = PyDict::new(py).into_any(); bench.iter(|| match black_box(&d).extract::() { Ok(v) => panic!("should err {}", v), Err(e) => e, }); }); } fn extract_bigint_small(bench: &mut Bencher<'_>) { Python::attach(|py| { let int = py.eval(c"-42", None, None).unwrap(); bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); }); } fn extract_bigint_big_negative(bench: &mut Bencher<'_>) { Python::attach(|py| { let int = py.eval(c"-10**300", None, None).unwrap(); bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); }); } fn extract_bigint_big_positive(bench: &mut Bencher<'_>) { Python::attach(|py| { let int = py.eval(c"10**300", None, None).unwrap(); bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); }); } fn extract_bigint_huge_negative(bench: &mut Bencher<'_>) { Python::attach(|py| { let int = py.eval(c"-10**3000", None, None).unwrap(); bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); }); } fn extract_bigint_huge_positive(bench: &mut Bencher<'_>) { Python::attach(|py| { let int = py.eval(c"10**3000", None, None).unwrap(); bench.iter_with_large_drop(|| black_box(&int).extract::().unwrap()); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("extract_bigint_extract_fail", extract_bigint_extract_fail); c.bench_function("extract_bigint_small", extract_bigint_small); c.bench_function("extract_bigint_big_negative", extract_bigint_big_negative); c.bench_function("extract_bigint_big_positive", extract_bigint_big_positive); c.bench_function("extract_bigint_huge_negative", extract_bigint_huge_negative); c.bench_function("extract_bigint_huge_positive", extract_bigint_huge_positive); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_call.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::ffi::c_str; use pyo3::prelude::*; use pyo3::types::IntoPyDict; macro_rules! test_module { ($py:ident, $code:literal) => { PyModule::from_code($py, c_str!($code), c_str!(file!()), c"test_module") .expect("module creation failed") }; } fn bench_call_0(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!(py, "def foo(): pass"); let foo_module = &module.getattr("foo").unwrap(); b.iter(|| { for _ in 0..1000 { black_box(foo_module).call0().unwrap(); } }); }) } fn bench_call_1(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!(py, "def foo(a, b, c): pass"); let foo_module = &module.getattr("foo").unwrap(); let args = ( 1.into_pyobject(py).unwrap(), "s".into_pyobject(py).unwrap(), 1.23.into_pyobject(py).unwrap(), ); b.iter(|| { for _ in 0..1000 { black_box(foo_module).call1(args.clone()).unwrap(); } }); }) } fn bench_call(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!(py, "def foo(a, b, c, d, e): pass"); let foo_module = &module.getattr("foo").unwrap(); let args = ( 1.into_pyobject(py).unwrap(), "s".into_pyobject(py).unwrap(), 1.23.into_pyobject(py).unwrap(), ); let kwargs = [("d", 1), ("e", 42)].into_py_dict(py).unwrap(); b.iter(|| { for _ in 0..1000 { black_box(foo_module) .call(args.clone(), Some(&kwargs)) .unwrap(); } }); }) } fn bench_call_one_arg(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!(py, "def foo(a): pass"); let foo_module = &module.getattr("foo").unwrap(); let arg = 1i32.into_pyobject(py).unwrap(); b.iter(|| { for _ in 0..1000 { black_box(foo_module).call1((arg.clone(),)).unwrap(); } }); }) } fn bench_call_method_0(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!( py, " class Foo: def foo(self): pass " ); let foo_module = &module.getattr("Foo").unwrap().call0().unwrap(); b.iter(|| { for _ in 0..1000 { black_box(foo_module).call_method0("foo").unwrap(); } }); }) } fn bench_call_method_1(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!( py, " class Foo: def foo(self, a, b, c): pass " ); let foo_module = &module.getattr("Foo").unwrap().call0().unwrap(); let args = ( 1.into_pyobject(py).unwrap(), "s".into_pyobject(py).unwrap(), 1.23.into_pyobject(py).unwrap(), ); b.iter(|| { for _ in 0..1000 { black_box(foo_module) .call_method1("foo", args.clone()) .unwrap(); } }); }) } fn bench_call_method(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!( py, " class Foo: def foo(self, a, b, c, d, e): pass " ); let foo_module = &module.getattr("Foo").unwrap().call0().unwrap(); let args = ( 1.into_pyobject(py).unwrap(), "s".into_pyobject(py).unwrap(), 1.23.into_pyobject(py).unwrap(), ); let kwargs = [("d", 1), ("e", 42)].into_py_dict(py).unwrap(); b.iter(|| { for _ in 0..1000 { black_box(foo_module) .call_method("foo", args.clone(), Some(&kwargs)) .unwrap(); } }); }) } fn bench_call_method_one_arg(b: &mut Bencher<'_>) { Python::attach(|py| { let module = test_module!( py, " class Foo: def foo(self, a): pass " ); let foo_module = &module.getattr("Foo").unwrap().call0().unwrap(); let arg = 1i32.into_pyobject(py).unwrap(); b.iter(|| { for _ in 0..1000 { black_box(foo_module) .call_method1("foo", (arg.clone(),)) .unwrap(); } }); }) } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("call_0", bench_call_0); c.bench_function("call_1", bench_call_1); c.bench_function("call", bench_call); c.bench_function("call_one_arg", bench_call_one_arg); c.bench_function("call_method_0", bench_call_method_0); c.bench_function("call_method_1", bench_call_method_1); c.bench_function("call_method", bench_call_method); c.bench_function("call_method_one_arg", bench_call_method_one_arg); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_comparisons.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::{prelude::*, pyclass::CompareOp, Python}; #[pyclass] struct OrderedDunderMethods(i64); #[pymethods] impl OrderedDunderMethods { fn __lt__(&self, other: &Self) -> bool { self.0 < other.0 } fn __le__(&self, other: &Self) -> bool { self.0 <= other.0 } fn __eq__(&self, other: &Self) -> bool { self.0 == other.0 } fn __ne__(&self, other: &Self) -> bool { self.0 != other.0 } fn __gt__(&self, other: &Self) -> bool { self.0 > other.0 } fn __ge__(&self, other: &Self) -> bool { self.0 >= other.0 } } #[pyclass] #[derive(PartialEq, Eq, PartialOrd, Ord)] struct OrderedRichcmp(i64); #[pymethods] impl OrderedRichcmp { fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { op.matches(self.cmp(other)) } } fn bench_ordered_dunder_methods(b: &mut Bencher<'_>) { Python::attach(|py| { let obj1 = &Bound::new(py, OrderedDunderMethods(0)).unwrap().into_any(); let obj2 = &Bound::new(py, OrderedDunderMethods(1)).unwrap().into_any(); b.iter(|| obj2.gt(obj1).unwrap()); }); } fn bench_ordered_richcmp(b: &mut Bencher<'_>) { Python::attach(|py| { let obj1 = &Bound::new(py, OrderedRichcmp(0)).unwrap().into_any(); let obj2 = &Bound::new(py, OrderedRichcmp(1)).unwrap().into_any(); b.iter(|| obj2.gt(obj1).unwrap()); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("ordered_dunder_methods", bench_ordered_dunder_methods); c.bench_function("ordered_richcmp", bench_ordered_richcmp); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_critical_sections.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::prelude::*; use pyo3::sync::critical_section::{with_critical_section, with_critical_section2}; use pyo3::types::PyList; fn create_cs(b: &mut Bencher<'_>) { Python::attach(|py| { let lis = PyList::new(py, 0..3).unwrap(); b.iter(|| { with_critical_section(&lis, || {}); }) }); } fn create_cs2(b: &mut Bencher<'_>) { Python::attach(|py| { let lis1 = PyList::new(py, 0..3).unwrap(); let lis2 = PyList::new(py, 4..6).unwrap(); b.iter(|| { with_critical_section2(&lis1, &lis2, || {}); }) }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("critical_section_creation", create_cs); c.bench_function("critical_section_creation2", create_cs2); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_decimal.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use rust_decimal::Decimal; use pyo3::prelude::*; use pyo3::types::PyDict; fn decimal_via_extract(b: &mut Bencher<'_>) { Python::attach(|py| { let locals = PyDict::new(py); py.run( cr#" import decimal py_dec = decimal.Decimal("0.0") "#, None, Some(&locals), ) .unwrap(); let py_dec = locals.get_item("py_dec").unwrap().unwrap(); b.iter(|| black_box(&py_dec).extract::().unwrap()); }) } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("decimal_via_extract", decimal_via_extract); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_dict.rs ================================================ use std::collections::{BTreeMap, HashMap}; use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::types::IntoPyDict; use pyo3::{prelude::*, types::PyMapping}; fn iter_dict(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) .unwrap(); let mut sum = 0; b.iter(|| { for (k, _v) in &dict { let i: u64 = k.extract().unwrap(); sum += i; } }); }) } fn dict_new(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; b.iter_with_large_drop(|| { (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) .unwrap() }); }); } fn dict_get_item(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) .unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += dict .get_item(i) .unwrap() .unwrap() .extract::() .unwrap(); } }); }); } fn extract_hashmap(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) .unwrap() .into_any(); b.iter(|| HashMap::::extract(dict.as_borrowed())); }); } fn extract_btreemap(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) .unwrap() .into_any(); b.iter(|| BTreeMap::::extract(dict.as_borrowed())); }); } fn extract_hashbrown_map(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) .unwrap() .into_any(); b.iter(|| hashbrown::HashMap::::extract(dict.as_borrowed())); }); } fn mapping_from_dict(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let dict = &(0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) .unwrap(); b.iter(|| black_box(dict).cast::().unwrap()); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("iter_dict", iter_dict); c.bench_function("dict_new", dict_new); c.bench_function("dict_get_item", dict_get_item); c.bench_function("extract_hashmap", extract_hashmap); c.bench_function("extract_btreemap", extract_btreemap); c.bench_function("mapping_from_dict", mapping_from_dict); c.bench_function("extract_hashbrown_map", extract_hashbrown_map); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_err.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::{exceptions::PyValueError, prelude::*}; fn err_new_restore_and_fetch(b: &mut Bencher<'_>) { Python::attach(|py| { b.iter(|| { PyValueError::new_err("some exception message").restore(py); PyErr::fetch(py) }) }) } fn err_new_without_gil(b: &mut Bencher<'_>) { b.iter(|| PyValueError::new_err("some exception message")) } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("err_new_restore_and_fetch", err_new_restore_and_fetch); c.bench_function("err_new_without_gil", err_new_without_gil); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_extract.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::{ prelude::*, types::{PyDict, PyFloat, PyInt, PyString}, }; fn extract_str_extract_success(bench: &mut Bencher<'_>) { Python::attach(|py| { let s = PyString::new(py, "Hello, World!").into_any(); bench.iter(|| black_box(&s).extract::<&str>().unwrap()); }); } fn extract_str_extract_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { let d = PyDict::new(py).into_any(); bench.iter(|| match black_box(&d).extract::<&str>() { Ok(v) => panic!("should err {}", v), Err(e) => e, }); }); } #[cfg(Py_3_10)] fn extract_str_cast_success(bench: &mut Bencher<'_>) { Python::attach(|py| { let s = PyString::new(py, "Hello, World!").into_any(); bench.iter(|| { let py_str = black_box(&s).cast::().unwrap(); py_str.to_str().unwrap() }); }); } fn extract_str_cast_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { let d = PyDict::new(py).into_any(); bench.iter(|| match black_box(&d).cast::() { Ok(v) => panic!("should err {}", v), Err(e) => e, }); }); } fn extract_int_extract_success(bench: &mut Bencher<'_>) { Python::attach(|py| { let int = 123i32.into_pyobject(py).unwrap(); bench.iter(|| black_box(&int).extract::().unwrap()); }); } fn extract_int_extract_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { let d = PyDict::new(py).into_any(); bench.iter(|| match black_box(&d).extract::() { Ok(v) => panic!("should err {}", v), Err(e) => e, }); }); } fn extract_int_cast_success(bench: &mut Bencher<'_>) { Python::attach(|py| { let int = 123i32.into_pyobject(py).unwrap(); bench.iter(|| { let py_int = black_box(&int).cast::().unwrap(); py_int.extract::().unwrap() }); }); } fn extract_int_cast_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { let d = PyDict::new(py).into_any(); bench.iter(|| match black_box(&d).cast::() { Ok(v) => panic!("should err {}", v), Err(e) => black_box(e), }); }); } fn extract_float_extract_success(bench: &mut Bencher<'_>) { Python::attach(|py| { let float = 23.42f64.into_pyobject(py).unwrap(); bench.iter(|| black_box(&float).extract::().unwrap()); }); } fn extract_float_extract_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { let d = PyDict::new(py).into_any(); bench.iter(|| match black_box(&d).extract::() { Ok(v) => panic!("should err {}", v), Err(e) => e, }); }); } fn extract_float_cast_success(bench: &mut Bencher<'_>) { Python::attach(|py| { let float = 23.42f64.into_pyobject(py).unwrap(); bench.iter(|| { let py_float = black_box(&float).cast::().unwrap(); py_float.value() }); }); } fn extract_float_cast_fail(bench: &mut Bencher<'_>) { Python::attach(|py| { let d = PyDict::new(py).into_any(); bench.iter(|| match black_box(&d).cast::() { Ok(v) => panic!("should err {}", v), Err(e) => e, }); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("extract_str_extract_success", extract_str_extract_success); c.bench_function("extract_str_extract_fail", extract_str_extract_fail); #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] c.bench_function("extract_str_cast_success", extract_str_cast_success); c.bench_function("extract_str_cast_fail", extract_str_cast_fail); c.bench_function("extract_int_extract_success", extract_int_extract_success); c.bench_function("extract_int_extract_fail", extract_int_extract_fail); c.bench_function("extract_int_cast_success", extract_int_cast_success); c.bench_function("extract_int_cast_fail", extract_int_cast_fail); c.bench_function( "extract_float_extract_success", extract_float_extract_success, ); c.bench_function("extract_float_extract_fail", extract_float_extract_fail); c.bench_function( "extract_float_cast_success", extract_float_cast_success, ); c.bench_function("extract_float_cast_fail", extract_float_cast_fail); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_frompyobject.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::{ prelude::*, types::{PyByteArray, PyBytes, PyList, PyString}, }; #[derive(FromPyObject)] #[expect(dead_code)] enum ManyTypes { Int(i32), Bytes(Vec), String(String), } fn enum_from_pyobject(b: &mut Bencher<'_>) { Python::attach(|py| { let any = PyString::new(py, "hello world").into_any(); b.iter(|| black_box(&any).extract::().unwrap()); }) } fn list_via_cast(b: &mut Bencher<'_>) { Python::attach(|py| { let any = PyList::empty(py).into_any(); b.iter(|| black_box(&any).cast::().unwrap()); }) } fn list_via_extract(b: &mut Bencher<'_>) { Python::attach(|py| { let any = PyList::empty(py).into_any(); b.iter(|| black_box(&any).extract::>().unwrap()); }) } fn not_a_list_via_cast(b: &mut Bencher<'_>) { Python::attach(|py| { let any = PyString::new(py, "foobar").into_any(); b.iter(|| black_box(&any).cast::().unwrap_err()); }) } fn not_a_list_via_extract(b: &mut Bencher<'_>) { Python::attach(|py| { let any = PyString::new(py, "foobar").into_any(); b.iter(|| black_box(&any).extract::>().unwrap_err()); }) } #[derive(FromPyObject)] enum ListOrNotList<'a> { List(Bound<'a, PyList>), NotList(Bound<'a, PyAny>), } fn not_a_list_via_extract_enum(b: &mut Bencher<'_>) { Python::attach(|py| { let any = PyString::new(py, "foobar").into_any(); b.iter(|| match black_box(&any).extract::>() { Ok(ListOrNotList::List(_list)) => panic!(), Ok(ListOrNotList::NotList(any)) => any, Err(_) => panic!(), }); }) } fn bench_vec_from_py_bytes(b: &mut Bencher<'_>, data: &[u8]) { Python::attach(|py| { let any = PyBytes::new(py, data).into_any(); b.iter(|| black_box(&any).extract::>().unwrap()); }) } fn vec_bytes_from_py_bytes_small(b: &mut Bencher<'_>) { bench_vec_from_py_bytes(b, &[]); } fn vec_bytes_from_py_bytes_medium(b: &mut Bencher<'_>) { let data = (0..u8::MAX).collect::>(); bench_vec_from_py_bytes(b, &data); } fn vec_bytes_from_py_bytes_large(b: &mut Bencher<'_>) { let data = vec![10u8; 100_000]; bench_vec_from_py_bytes(b, &data); } fn bench_vec_from_py_bytearray(b: &mut Bencher<'_>, data: &[u8]) { Python::attach(|py| { let any = PyByteArray::new(py, data).into_any(); b.iter(|| black_box(&any).extract::>().unwrap()); }) } fn vec_bytes_from_py_bytearray_small(b: &mut Bencher<'_>) { bench_vec_from_py_bytearray(b, &[]); } fn vec_bytes_from_py_bytearray_medium(b: &mut Bencher<'_>) { let data = (0..u8::MAX).collect::>(); bench_vec_from_py_bytearray(b, &data); } fn vec_bytes_from_py_bytearray_large(b: &mut Bencher<'_>) { let data = vec![10u8; 100_000]; bench_vec_from_py_bytearray(b, &data); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("enum_from_pyobject", enum_from_pyobject); c.bench_function("list_via_cast", list_via_cast); c.bench_function("list_via_extract", list_via_extract); c.bench_function("not_a_list_via_cast", not_a_list_via_cast); c.bench_function("not_a_list_via_extract", not_a_list_via_extract); c.bench_function("not_a_list_via_extract_enum", not_a_list_via_extract_enum); c.bench_function( "vec_bytes_from_py_bytes_small", vec_bytes_from_py_bytes_small, ); c.bench_function( "vec_bytes_from_py_bytes_medium", vec_bytes_from_py_bytes_medium, ); c.bench_function( "vec_bytes_from_py_bytes_large", vec_bytes_from_py_bytes_large, ); c.bench_function( "vec_bytes_from_py_bytearray_small", vec_bytes_from_py_bytearray_small, ); c.bench_function( "vec_bytes_from_py_bytearray_medium", vec_bytes_from_py_bytearray_medium, ); c.bench_function( "vec_bytes_from_py_bytearray_large", vec_bytes_from_py_bytearray_large, ); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_intern.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::prelude::*; use pyo3::intern; fn getattr_direct(b: &mut Bencher<'_>) { Python::attach(|py| { let sys = &py.import("sys").unwrap(); b.iter(|| black_box(sys).getattr("version").unwrap()); }); } fn getattr_intern(b: &mut Bencher<'_>) { Python::attach(|py| { let sys = &py.import("sys").unwrap(); b.iter(|| black_box(sys).getattr(intern!(py, "version")).unwrap()); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("getattr_direct", getattr_direct); c.bench_function("getattr_intern", getattr_intern); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_intopyobject.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::conversion::IntoPyObject; use pyo3::prelude::*; use pyo3::types::PyBytes; fn bench_bytes_new(b: &mut Bencher<'_>, data: &[u8]) { Python::attach(|py| { b.iter_with_large_drop(|| PyBytes::new(py, black_box(data))); }); } fn bytes_new_small(b: &mut Bencher<'_>) { bench_bytes_new(b, &[]); } fn bytes_new_medium(b: &mut Bencher<'_>) { let data = (0..u8::MAX).collect::>(); bench_bytes_new(b, &data); } fn bytes_new_large(b: &mut Bencher<'_>) { let data = vec![10u8; 100_000]; bench_bytes_new(b, &data); } fn bench_bytes_into_pyobject(b: &mut Bencher<'_>, data: &[u8]) { Python::attach(|py| { b.iter_with_large_drop(|| black_box(data).into_pyobject(py)); }); } fn byte_slice_into_pyobject_small(b: &mut Bencher<'_>) { bench_bytes_into_pyobject(b, &[]); } fn byte_slice_into_pyobject_medium(b: &mut Bencher<'_>) { let data = (0..u8::MAX).collect::>(); bench_bytes_into_pyobject(b, &data); } fn byte_slice_into_pyobject_large(b: &mut Bencher<'_>) { let data = vec![10u8; 100_000]; bench_bytes_into_pyobject(b, &data); } fn vec_into_pyobject(b: &mut Bencher<'_>) { Python::attach(|py| { let bytes = (0..u8::MAX).collect::>(); b.iter_with_large_drop(|| black_box(&bytes).clone().into_pyobject(py)); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("bytes_new_small", bytes_new_small); c.bench_function("bytes_new_medium", bytes_new_medium); c.bench_function("bytes_new_large", bytes_new_large); c.bench_function( "byte_slice_into_pyobject_small", byte_slice_into_pyobject_small, ); c.bench_function( "byte_slice_into_pyobject_medium", byte_slice_into_pyobject_medium, ); c.bench_function( "byte_slice_into_pyobject_large", byte_slice_into_pyobject_large, ); c.bench_function("vec_into_pyobject", vec_into_pyobject); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_list.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::prelude::*; use pyo3::types::{PyList, PySequence}; fn iter_list(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let list = PyList::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for x in &list { let i: u64 = x.extract().unwrap(); sum += i; } }); }); } fn list_new(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; b.iter_with_large_drop(|| PyList::new(py, 0..LEN)); }); } fn list_get_item(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let list = PyList::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += list.get_item(i).unwrap().extract::().unwrap(); } }); }); } fn list_nth(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50; let list = PyList::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += list.iter().nth(i).unwrap().extract::().unwrap(); } }); }); } fn list_nth_back(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50; let list = PyList::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += list.iter().nth_back(i).unwrap().extract::().unwrap(); } }); }); } #[cfg(not(Py_LIMITED_API))] fn list_get_item_unchecked(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let list = PyList::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { unsafe { sum += list.get_item_unchecked(i).extract::().unwrap(); } } }); }); } fn sequence_from_list(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let list = &PyList::new(py, 0..LEN).unwrap(); b.iter(|| black_box(list).cast::().unwrap()); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("iter_list", iter_list); c.bench_function("list_new", list_new); c.bench_function("list_nth", list_nth); c.bench_function("list_nth_back", list_nth_back); c.bench_function("list_get_item", list_get_item); #[cfg(not(Py_LIMITED_API))] c.bench_function("list_get_item_unchecked", list_get_item_unchecked); c.bench_function("sequence_from_list", sequence_from_list); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_py.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Bencher, Criterion}; use std::sync::{ atomic::{AtomicUsize, Ordering}, mpsc::channel, Arc, Barrier, }; use std::thread::spawn; use std::time::{Duration, Instant}; use pyo3::prelude::*; fn drop_many_objects(b: &mut Bencher<'_>) { Python::attach(|py| { b.iter(|| { for _ in 0..1000 { drop(py.None()); } }); }); } fn drop_many_objects_without_gil(b: &mut Bencher<'_>) { b.iter_batched( || Python::attach(|py| (0..1000).map(|_| py.None()).collect::>>()), |objs| { drop(objs); Python::attach(|_py| ()); }, BatchSize::SmallInput, ); } fn drop_many_objects_multiple_threads(b: &mut Bencher<'_>) { const THREADS: usize = 5; let barrier = Arc::new(Barrier::new(1 + THREADS)); let done = Arc::new(AtomicUsize::new(0)); let sender = (0..THREADS) .map(|_| { let (sender, receiver) = channel(); let barrier = barrier.clone(); let done = done.clone(); spawn(move || { for objs in receiver { barrier.wait(); drop(objs); done.fetch_add(1, Ordering::AcqRel); } }); sender }) .collect::>(); b.iter_custom(|iters| { let mut duration = Duration::ZERO; let mut last_done = done.load(Ordering::Acquire); for _ in 0..iters { for sender in &sender { let objs = Python::attach(|py| { (0..1000 / THREADS) .map(|_| py.None()) .collect::>>() }); sender.send(objs).unwrap(); } barrier.wait(); let start = Instant::now(); loop { Python::attach(|_py| ()); let done = done.load(Ordering::Acquire); if done - last_done == THREADS { last_done = done; break; } } Python::attach(|_py| ()); duration += start.elapsed(); } duration }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("drop_many_objects", drop_many_objects); c.bench_function( "drop_many_objects_without_gil", drop_many_objects_without_gil, ); c.bench_function( "drop_many_objects_multiple_threads", drop_many_objects_multiple_threads, ); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_pyclass.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Bencher, Criterion}; use pyo3::conversion::IntoPyObjectExt; use pyo3::types::PyInt; use pyo3::{impl_::pyclass::LazyTypeObject, prelude::*}; /// This is a feature-rich class instance used to benchmark various parts of the pyclass lifecycle. #[pyclass] struct MyClass { #[pyo3(get, set)] elements: Vec, } #[pymethods] impl MyClass { #[new] fn new(elements: Vec) -> Self { Self { elements } } fn __call__(&mut self, new_element: i32) -> usize { self.elements.push(new_element); self.elements.len() } /// A basic __str__ implementation. fn __str__(&self) -> &'static str { "MyClass" } } pub fn first_time_init(b: &mut Bencher<'_>) { Python::attach(|py| { b.iter(|| { // This is using an undocumented internal PyO3 API to measure pyclass performance; please // don't use this in your own code! let ty = LazyTypeObject::::new(); ty.get_or_try_init(py).unwrap(); }); }); } pub fn bench_pyclass(c: &mut Criterion) { c.bench_function("bench_pyclass_create", |b| { Python::attach(|py| { b.iter_batched( || vec![1, 2, 3], |elements| { MyClass::new(elements).into_py_any(py).unwrap(); }, BatchSize::SmallInput, ); }); }); c.bench_function("bench_call", |b| { Python::attach(|py| { b.iter_batched( || { ( MyClass::new(vec![1, 2, 3]).into_py_any(py).unwrap(), PyInt::new(py, 4), ) }, |(inst, arg)| { inst.call1(py, (arg,)).unwrap(); }, BatchSize::SmallInput, ); }); }); c.bench_function("bench_str", |b| { Python::attach(|py| { let inst = MyClass::new(vec![1, 2, 3]).into_py_any(py).unwrap(); let bound = inst.bind(py); b.iter(|| bound.str()); }); }); } fn bench_first_time_init(c: &mut Criterion) { c.bench_function("first_time_init", first_time_init); } criterion_group!(benches, bench_first_time_init, bench_pyclass); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_pystring_from_fmt.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::{py_format, Python}; use std::hint::black_box; fn format_simple(b: &mut Bencher<'_>) { Python::attach(|py| { b.iter(|| { py_format!(py, "Hello {}!", "world").unwrap() }); }); } fn format_complex(b: &mut Bencher<'_>) { Python::attach(|py| { b.iter(|| { let value = (black_box(42), black_box("foo"), [0; 0]); py_format!(py, "This is some complex value: {value:?}").unwrap() }); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("format_simple", format_simple); c.bench_function("format_complex", format_complex); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_set.rs ================================================ use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::types::PySet; use pyo3::{prelude::*, IntoPyObjectExt}; use std::{ collections::{BTreeSet, HashSet}, hint::black_box, }; fn set_new(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; // Create Python objects up-front, so that the benchmark doesn't need to include // the cost of allocating LEN Python integers let elements: Vec> = (0..LEN).map(|i| i.into_py_any(py).unwrap()).collect(); b.iter_with_large_drop(|| PySet::new(py, &elements).unwrap()); }); } fn iter_set(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let set = PySet::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for x in &set { let i: u64 = x.extract().unwrap(); sum += i; } }); }); } fn extract_hashset(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let any = PySet::new(py, 0..LEN).unwrap().into_any(); b.iter_with_large_drop(|| black_box(&any).extract::>()); }); } fn extract_btreeset(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let any = PySet::new(py, 0..LEN).unwrap().into_any(); b.iter_with_large_drop(|| black_box(&any).extract::>()); }); } fn extract_hashbrown_set(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let any = PySet::new(py, 0..LEN).unwrap().into_any(); b.iter_with_large_drop(|| black_box(&any).extract::>()); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("set_new", set_new); c.bench_function("iter_set", iter_set); c.bench_function("extract_hashset", extract_hashset); c.bench_function("extract_btreeset", extract_btreeset); c.bench_function("extract_hashbrown_set", extract_hashbrown_set); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/benches/bench_tuple.rs ================================================ use std::hint::black_box; use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion}; use pyo3::prelude::*; use pyo3::types::{PyList, PySequence, PyTuple}; fn iter_tuple(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 100_000; let tuple = PyTuple::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for x in tuple.iter_borrowed() { let i: u64 = x.extract().unwrap(); sum += i; } }); }); } fn tuple_new(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; b.iter_with_large_drop(|| PyTuple::new(py, 0..LEN).unwrap()); }); } fn tuple_get_item(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let tuple = PyTuple::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += tuple.get_item(i).unwrap().extract::().unwrap(); } }); }); } #[cfg(not(any(Py_LIMITED_API, PyPy)))] fn tuple_get_item_unchecked(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let tuple = PyTuple::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { unsafe { sum += tuple.get_item_unchecked(i).extract::().unwrap(); } } }); }); } fn tuple_get_borrowed_item(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let tuple = PyTuple::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += tuple .get_borrowed_item(i) .unwrap() .extract::() .unwrap(); } }); }); } #[cfg(not(any(Py_LIMITED_API, PyPy)))] fn tuple_get_borrowed_item_unchecked(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let tuple = PyTuple::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { unsafe { sum += tuple .get_borrowed_item_unchecked(i) .extract::() .unwrap(); } } }); }); } fn sequence_from_tuple(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let tuple = PyTuple::new(py, 0..LEN).unwrap().into_any(); b.iter(|| black_box(&tuple).cast::().unwrap()); }); } fn tuple_new_list(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let tuple = PyTuple::new(py, 0..LEN).unwrap(); b.iter_with_large_drop(|| PyList::new(py, tuple.iter_borrowed())); }); } fn tuple_to_list(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50_000; let tuple = PyTuple::new(py, 0..LEN).unwrap(); b.iter_with_large_drop(|| tuple.to_list()); }); } fn tuple_into_pyobject(b: &mut Bencher<'_>) { Python::attach(|py| { b.iter(|| { (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) .into_pyobject(py) .unwrap() }); }); } fn tuple_nth(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50; let list = PyTuple::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += list.iter().nth(i).unwrap().extract::().unwrap(); } }); }); } fn tuple_nth_back(b: &mut Bencher<'_>) { Python::attach(|py| { const LEN: usize = 50; let list = PyTuple::new(py, 0..LEN).unwrap(); let mut sum = 0; b.iter(|| { for i in 0..LEN { sum += list.iter().nth_back(i).unwrap().extract::().unwrap(); } }); }); } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("iter_tuple", iter_tuple); c.bench_function("tuple_new", tuple_new); c.bench_function("tuple_get_item", tuple_get_item); c.bench_function("tuple_nth", tuple_nth); c.bench_function("tuple_nth_back", tuple_nth_back); #[cfg(not(any(Py_LIMITED_API, PyPy)))] c.bench_function("tuple_get_item_unchecked", tuple_get_item_unchecked); c.bench_function("tuple_get_borrowed_item", tuple_get_borrowed_item); #[cfg(not(any(Py_LIMITED_API, PyPy)))] c.bench_function( "tuple_get_borrowed_item_unchecked", tuple_get_borrowed_item_unchecked, ); c.bench_function("sequence_from_tuple", sequence_from_tuple); c.bench_function("tuple_new_list", tuple_new_list); c.bench_function("tuple_to_list", tuple_to_list); c.bench_function("tuple_into_pyobject", tuple_into_pyobject); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: pyo3-benches/build.rs ================================================ fn main() { pyo3_build_config::use_pyo3_cfgs(); pyo3_build_config::add_libpython_rpath_link_args(); } ================================================ FILE: pyo3-build-config/Cargo.toml ================================================ [package] name = "pyo3-build-config" version = "0.28.2" description = "Build configuration for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] homepage = "https://github.com/pyo3/pyo3" repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" rust-version.workspace = true [dependencies] target-lexicon = "0.13.3" [build-dependencies] target-lexicon = "0.13.3" [features] default = [] # Attempt to resolve a Python interpreter config for building in the build # script. If this feature isn't enabled, the build script no-ops. resolve-config = [] # deprecated extension-module = [] # deprecated: no longer needed, raw-dylib is used instead generate-import-lib = [] # These features are enabled by pyo3 when building Stable ABI extension modules. abi3 = [] abi3-py37 = ["abi3-py38"] abi3-py38 = ["abi3-py39"] abi3-py39 = ["abi3-py310"] abi3-py310 = ["abi3-py311"] abi3-py311 = ["abi3-py312"] abi3-py312 = ["abi3-py313"] abi3-py313 = ["abi3-py314"] abi3-py314 = ["abi3"] [package.metadata.docs.rs] features = ["resolve-config"] ================================================ FILE: pyo3-build-config/build.rs ================================================ // Import some modules from this crate inline to generate the build config. // Allow dead code because not all code in the modules is used in this build script. #[path = "src/impl_.rs"] #[allow(dead_code, reason = "not all code is used in build.rs")] mod impl_; #[path = "src/errors.rs"] #[allow(dead_code, reason = "not all code is used in build.rs")] mod errors; use std::{env, path::Path}; use errors::{Context, Result}; use impl_::{make_interpreter_config, InterpreterConfig}; fn configure(interpreter_config: Option, name: &str) -> Result { let target = Path::new(&env::var_os("OUT_DIR").unwrap()).join(name); if let Some(config) = interpreter_config { config .to_writer(&mut std::fs::File::create(&target).with_context(|| { format!("failed to write config file at {}", target.display()) })?)?; Ok(true) } else { std::fs::File::create(&target) .with_context(|| format!("failed to create new file at {}", target.display()))?; Ok(false) } } fn generate_build_configs() -> Result<()> { // If PYO3_CONFIG_FILE is set, copy it into the crate. let configured = configure( InterpreterConfig::from_pyo3_config_file_env().transpose()?, "pyo3-build-config-file.txt", )?; if configured { // Don't bother trying to find an interpreter on the host system // if the user-provided config file is present. configure(None, "pyo3-build-config.txt")?; } else { configure(Some(make_interpreter_config()?), "pyo3-build-config.txt")?; } Ok(()) } fn main() { if std::env::var("CARGO_FEATURE_RESOLVE_CONFIG").is_ok() { if let Err(e) = generate_build_configs() { eprintln!("error: {}", e.report()); std::process::exit(1) } } else { eprintln!("resolve-config feature not enabled; build script in no-op mode"); } } ================================================ FILE: pyo3-build-config/src/errors.rs ================================================ /// A simple macro for returning an error. Resembles anyhow::bail. #[macro_export] #[doc(hidden)] macro_rules! bail { ($($args: tt)+) => { return Err(format!($($args)+).into()) }; } /// A simple macro for checking a condition. Resembles anyhow::ensure. #[macro_export] #[doc(hidden)] macro_rules! ensure { ($condition:expr, $($args: tt)+) => { if !($condition) { bail!($($args)+) } }; } /// Show warning. #[macro_export] #[doc(hidden)] macro_rules! warn { ($($args: tt)+) => { println!("{}", $crate::format_warn!($($args)+)) }; } /// Format warning into string. #[macro_export] #[doc(hidden)] macro_rules! format_warn { ($($args: tt)+) => { format!("cargo:warning={}", format_args!($($args)+)) }; } /// A simple error implementation which allows chaining of errors, inspired somewhat by anyhow. #[derive(Debug)] pub struct Error { value: String, source: Option>, } /// Error report inspired by /// pub struct ErrorReport<'a>(&'a Error); impl Error { pub fn report(&self) -> ErrorReport<'_> { ErrorReport(self) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.value) } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.source.as_deref() } } impl std::fmt::Display for ErrorReport<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use std::error::Error; self.0.fmt(f)?; let mut source = self.0.source(); if source.is_some() { writeln!(f, "\ncaused by:")?; let mut index = 0; while let Some(some_source) = source { writeln!(f, " - {index}: {some_source}")?; source = some_source.source(); index += 1; } } Ok(()) } } impl From for Error { fn from(value: String) -> Self { Self { value, source: None, } } } impl From<&'_ str> for Error { fn from(value: &str) -> Self { value.to_string().into() } } impl From for Error { fn from(value: std::convert::Infallible) -> Self { match value {} } } pub type Result = std::result::Result; pub trait Context { fn context(self, message: impl Into) -> Result; fn with_context(self, message: impl FnOnce() -> String) -> Result; } impl Context for Result where E: std::error::Error + 'static, { fn context(self, message: impl Into) -> Result { self.map_err(|error| Error { value: message.into(), source: Some(Box::new(error)), }) } fn with_context(self, message: impl FnOnce() -> String) -> Result { self.map_err(|error| Error { value: message(), source: Some(Box::new(error)), }) } } #[cfg(test)] mod tests { use super::*; #[test] fn error_report() { let error: Result<()> = Err(Error::from("there was an internal error")) .with_context(|| format!("failed to do {}", "something difficult")) .context("things went wrong"); assert_eq!( error .unwrap_err() .report() .to_string() .split('\n') .collect::>(), vec![ "things went wrong", "caused by:", " - 0: failed to do something difficult", " - 1: there was an internal error", "" ] ); } } ================================================ FILE: pyo3-build-config/src/impl_.rs ================================================ //! Main implementation module included in both the `pyo3-build-config` library crate //! and its build script. #[cfg(test)] use std::cell::RefCell; use std::{ collections::{HashMap, HashSet}, env, ffi::{OsStr, OsString}, fmt::Display, fs::{self, DirEntry}, io::{BufRead, BufReader, Read, Write}, path::{Path, PathBuf}, process::{Command, Stdio}, str::{self, FromStr}, }; pub use target_lexicon::Triple; use target_lexicon::{Architecture, Environment, OperatingSystem, Vendor}; use crate::{ bail, ensure, errors::{Context, Error, Result}, warn, }; /// Minimum Python version PyO3 supports. pub(crate) const MINIMUM_SUPPORTED_VERSION: PythonVersion = PythonVersion { major: 3, minor: 7 }; pub(crate) const MINIMUM_SUPPORTED_VERSION_PYPY: PythonVersion = PythonVersion { major: 3, minor: 11, }; pub(crate) const MAXIMUM_SUPPORTED_VERSION_PYPY: PythonVersion = PythonVersion { major: 3, minor: 11, }; /// GraalPy may implement the same CPython version over multiple releases. const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion { major: 25, minor: 0, }; /// Maximum Python version that can be used as minimum required Python version with abi3. pub(crate) const ABI3_MAX_MINOR: u8 = 14; #[cfg(test)] thread_local! { static READ_ENV_VARS: RefCell> = const { RefCell::new(Vec::new()) }; } /// Gets an environment variable owned by cargo. /// /// Environment variables set by cargo are expected to be valid UTF8. pub fn cargo_env_var(var: &str) -> Option { env::var_os(var).map(|os_string| os_string.to_str().unwrap().into()) } /// Gets an external environment variable, and registers the build script to rerun if /// the variable changes. pub fn env_var(var: &str) -> Option { if cfg!(feature = "resolve-config") { println!("cargo:rerun-if-env-changed={var}"); } #[cfg(test)] { READ_ENV_VARS.with(|env_vars| { env_vars.borrow_mut().push(var.to_owned()); }); } env::var_os(var) } /// Gets the compilation target triple from environment variables set by Cargo. /// /// Must be called from a crate build script. pub fn target_triple_from_env() -> Triple { env::var("TARGET") .expect("target_triple_from_env() must be called from a build script") .parse() .expect("Unrecognized TARGET environment variable value") } /// Configuration needed by PyO3 to build for the correct Python implementation. /// /// Usually this is queried directly from the Python interpreter, or overridden using the /// `PYO3_CONFIG_FILE` environment variable. /// /// When the `PYO3_NO_PYTHON` variable is set, or during cross compile situations, then alternative /// strategies are used to populate this type. #[cfg_attr(test, derive(Debug, PartialEq, Eq))] pub struct InterpreterConfig { /// The Python implementation flavor. /// /// Serialized to `implementation`. pub implementation: PythonImplementation, /// Python `X.Y` version. e.g. `3.9`. /// /// Serialized to `version`. pub version: PythonVersion, /// Whether link library is shared. /// /// Serialized to `shared`. pub shared: bool, /// Whether linking against the stable/limited Python 3 API. /// /// Serialized to `abi3`. pub abi3: bool, /// The name of the link library defining Python. /// /// This effectively controls the `cargo:rustc-link-lib=` value to /// control how libpython is linked. Values should not contain the `lib` /// prefix. /// /// Serialized to `lib_name`. pub lib_name: Option, /// The directory containing the Python library to link against. /// /// The effectively controls the `cargo:rustc-link-search=native=` value /// to add an additional library search path for the linker. /// /// Serialized to `lib_dir`. pub lib_dir: Option, /// Path of host `python` executable. /// /// This is a valid executable capable of running on the host/building machine. /// For configurations derived by invoking a Python interpreter, it was the /// executable invoked. /// /// Serialized to `executable`. pub executable: Option, /// Width in bits of pointers on the target machine. /// /// Serialized to `pointer_width`. pub pointer_width: Option, /// Additional relevant Python build flags / configuration settings. /// /// Serialized to `build_flags`. pub build_flags: BuildFlags, /// Whether to suppress emitting of `cargo:rustc-link-*` lines from the build script. /// /// Typically, `pyo3`'s build script will emit `cargo:rustc-link-lib=` and /// `cargo:rustc-link-search=` lines derived from other fields in this struct. In /// advanced building configurations, the default logic to derive these lines may not /// be sufficient. This field can be set to `Some(true)` to suppress the emission /// of these lines. /// /// If suppression is enabled, `extra_build_script_lines` should contain equivalent /// functionality or else a build failure is likely. pub suppress_build_script_link_lines: bool, /// Additional lines to `println!()` from Cargo build scripts. /// /// This field can be populated to enable the `pyo3` crate to emit additional lines from its /// its Cargo build script. /// /// This crate doesn't populate this field itself. Rather, it is intended to be used with /// externally provided config files to give them significant control over how the crate /// is build/configured. /// /// Serialized to multiple `extra_build_script_line` values. pub extra_build_script_lines: Vec, /// macOS Python3.framework requires special rpath handling pub python_framework_prefix: Option, } impl InterpreterConfig { #[doc(hidden)] pub fn build_script_outputs(&self) -> Vec { // This should have been checked during pyo3-build-config build time. assert!(self.version >= MINIMUM_SUPPORTED_VERSION); let mut out = vec![]; for i in MINIMUM_SUPPORTED_VERSION.minor..=self.version.minor { out.push(format!("cargo:rustc-cfg=Py_3_{i}")); } match self.implementation { PythonImplementation::CPython => {} PythonImplementation::PyPy => out.push("cargo:rustc-cfg=PyPy".to_owned()), PythonImplementation::GraalPy => out.push("cargo:rustc-cfg=GraalPy".to_owned()), } // If Py_GIL_DISABLED is set, do not build with limited API support if self.abi3 && !self.is_free_threaded() { out.push("cargo:rustc-cfg=Py_LIMITED_API".to_owned()); } for flag in &self.build_flags.0 { match flag { BuildFlag::Py_GIL_DISABLED => { out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned()) } flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{flag}\"")), } } out } #[doc(hidden)] pub fn from_interpreter(interpreter: impl AsRef) -> Result { const SCRIPT: &str = r#" # Allow the script to run on Python 2, so that nicer error can be printed later. from __future__ import print_function import os.path import platform import struct import sys from sysconfig import get_config_var, get_platform PYPY = platform.python_implementation() == "PyPy" GRAALPY = platform.python_implementation() == "GraalVM" if GRAALPY: graalpy_ver = map(int, __graalpython__.get_graalvm_version().split('.')); print("graalpy_major", next(graalpy_ver)) print("graalpy_minor", next(graalpy_ver)) # sys.base_prefix is missing on Python versions older than 3.3; this allows the script to continue # so that the version mismatch can be reported in a nicer way later. base_prefix = getattr(sys, "base_prefix", None) if base_prefix: # Anaconda based python distributions have a static python executable, but include # the shared library. Use the shared library for embedding to avoid rust trying to # LTO the static library (and failing with newer gcc's, because it is old). ANACONDA = os.path.exists(os.path.join(base_prefix, "conda-meta")) else: ANACONDA = False def print_if_set(varname, value): if value is not None: print(varname, value) # Windows always uses shared linking WINDOWS = platform.system() == "Windows" # macOS framework packages use shared linking FRAMEWORK = bool(get_config_var("PYTHONFRAMEWORK")) FRAMEWORK_PREFIX = get_config_var("PYTHONFRAMEWORKPREFIX") # unix-style shared library enabled SHARED = bool(get_config_var("Py_ENABLE_SHARED")) print("implementation", platform.python_implementation()) print("version_major", sys.version_info[0]) print("version_minor", sys.version_info[1]) print("shared", PYPY or GRAALPY or ANACONDA or WINDOWS or FRAMEWORK or SHARED) print("python_framework_prefix", FRAMEWORK_PREFIX) print_if_set("ld_version", get_config_var("LDVERSION")) print_if_set("libdir", get_config_var("LIBDIR")) print_if_set("base_prefix", base_prefix) print("executable", sys.executable) print("calcsize_pointer", struct.calcsize("P")) print("mingw", get_platform().startswith("mingw")) print("cygwin", get_platform().startswith("cygwin")) print("ext_suffix", get_config_var("EXT_SUFFIX")) print("gil_disabled", get_config_var("Py_GIL_DISABLED")) "#; let output = run_python_script(interpreter.as_ref(), SCRIPT)?; let map: HashMap = parse_script_output(&output); ensure!( !map.is_empty(), "broken Python interpreter: {}", interpreter.as_ref().display() ); if let Some(value) = map.get("graalpy_major") { let graalpy_version = PythonVersion { major: value .parse() .context("failed to parse GraalPy major version")?, minor: map["graalpy_minor"] .parse() .context("failed to parse GraalPy minor version")?, }; ensure!( graalpy_version >= MINIMUM_SUPPORTED_VERSION_GRAALPY, "At least GraalPy version {} needed, got {}", MINIMUM_SUPPORTED_VERSION_GRAALPY, graalpy_version ); }; let shared = map["shared"].as_str() == "True"; let python_framework_prefix = map.get("python_framework_prefix").cloned(); let version = PythonVersion { major: map["version_major"] .parse() .context("failed to parse major version")?, minor: map["version_minor"] .parse() .context("failed to parse minor version")?, }; let abi3 = is_abi3(); let implementation = map["implementation"].parse()?; let gil_disabled = match map["gil_disabled"].as_str() { "1" => true, "0" => false, "None" => false, _ => panic!("Unknown Py_GIL_DISABLED value"), }; let cygwin = map["cygwin"].as_str() == "True"; let lib_name = if cfg!(windows) { default_lib_name_windows( version, implementation, abi3, map["mingw"].as_str() == "True", // This is the best heuristic currently available to detect debug build // on Windows from sysconfig - e.g. ext_suffix may be // `_d.cp312-win_amd64.pyd` for 3.12 debug build map["ext_suffix"].starts_with("_d."), gil_disabled, )? } else { default_lib_name_unix( version, implementation, abi3, cygwin, map.get("ld_version").map(String::as_str), gil_disabled, )? }; let lib_dir = if cfg!(windows) { map.get("base_prefix") .map(|base_prefix| format!("{base_prefix}\\libs")) } else { map.get("libdir").cloned() }; // The reason we don't use platform.architecture() here is that it's not // reliable on macOS. See https://stackoverflow.com/a/1405971/823869. // Similarly, sys.maxsize is not reliable on Windows. See // https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971 // and https://stackoverflow.com/a/3411134/823869. let calcsize_pointer: u32 = map["calcsize_pointer"] .parse() .context("failed to parse calcsize_pointer")?; Ok(InterpreterConfig { version, implementation, shared, abi3, lib_name: Some(lib_name), lib_dir, executable: map.get("executable").cloned(), pointer_width: Some(calcsize_pointer * 8), build_flags: BuildFlags::from_interpreter(interpreter)?, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix, }) } /// Generate from parsed sysconfigdata file /// /// Use [`parse_sysconfigdata`] to generate a hash map of configuration values which may be /// used to build an [`InterpreterConfig`]. pub fn from_sysconfigdata(sysconfigdata: &Sysconfigdata) -> Result { macro_rules! get_key { ($sysconfigdata:expr, $key:literal) => { $sysconfigdata .get_value($key) .ok_or(concat!($key, " not found in sysconfigdata file")) }; } macro_rules! parse_key { ($sysconfigdata:expr, $key:literal) => { get_key!($sysconfigdata, $key)? .parse() .context(concat!("could not parse value of ", $key)) }; } let soabi = get_key!(sysconfigdata, "SOABI")?; let implementation = PythonImplementation::from_soabi(soabi)?; let version = parse_key!(sysconfigdata, "VERSION")?; let shared = match sysconfigdata.get_value("Py_ENABLE_SHARED") { Some("1") | Some("true") | Some("True") => true, Some("0") | Some("false") | Some("False") => false, _ => bail!("expected a bool (1/true/True or 0/false/False) for Py_ENABLE_SHARED"), }; // macOS framework packages use shared linking (PYTHONFRAMEWORK is the framework name, hence the empty check) let framework = match sysconfigdata.get_value("PYTHONFRAMEWORK") { Some(s) => !s.is_empty(), _ => false, }; let python_framework_prefix = sysconfigdata .get_value("PYTHONFRAMEWORKPREFIX") .map(str::to_string); let lib_dir = get_key!(sysconfigdata, "LIBDIR").ok().map(str::to_string); let gil_disabled = match sysconfigdata.get_value("Py_GIL_DISABLED") { Some(value) => value == "1", None => false, }; let cygwin = soabi.ends_with("cygwin"); let abi3 = is_abi3(); let lib_name = Some(default_lib_name_unix( version, implementation, abi3, cygwin, sysconfigdata.get_value("LDVERSION"), gil_disabled, )?); let pointer_width = parse_key!(sysconfigdata, "SIZEOF_VOID_P") .map(|bytes_width: u32| bytes_width * 8) .ok(); let build_flags = BuildFlags::from_sysconfigdata(sysconfigdata); Ok(InterpreterConfig { implementation, version, shared: shared || framework, abi3, lib_dir, lib_name, executable: None, pointer_width, build_flags, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix, }) } /// Import an externally-provided config file. /// /// The `abi3` features, if set, may apply an `abi3` constraint to the Python version. #[allow(dead_code)] // only used in build.rs pub(super) fn from_pyo3_config_file_env() -> Option> { env_var("PYO3_CONFIG_FILE").map(|path| { let path = Path::new(&path); println!("cargo:rerun-if-changed={}", path.display()); // Absolute path is necessary because this build script is run with a cwd different to the // original `cargo build` instruction. ensure!( path.is_absolute(), "PYO3_CONFIG_FILE must be an absolute path" ); let mut config = InterpreterConfig::from_path(path) .context("failed to parse contents of PYO3_CONFIG_FILE")?; // If the abi3 feature is enabled, the minimum Python version is constrained by the abi3 // feature. // // TODO: abi3 is a property of the build mode, not the interpreter. Should this be // removed from `InterpreterConfig`? config.abi3 |= is_abi3(); config.fixup_for_abi3_version(get_abi3_version())?; Ok(config) }) } #[doc(hidden)] pub fn from_path(path: impl AsRef) -> Result { let path = path.as_ref(); let config_file = std::fs::File::open(path) .with_context(|| format!("failed to open PyO3 config file at {}", path.display()))?; let reader = std::io::BufReader::new(config_file); InterpreterConfig::from_reader(reader) } #[doc(hidden)] pub fn from_cargo_dep_env() -> Option> { cargo_env_var("DEP_PYTHON_PYO3_CONFIG") .map(|buf| InterpreterConfig::from_reader(&*unescape(&buf))) } #[doc(hidden)] pub fn from_reader(reader: impl Read) -> Result { let reader = BufReader::new(reader); let lines = reader.lines(); macro_rules! parse_value { ($variable:ident, $value:ident) => { $variable = Some($value.trim().parse().context(format!( concat!( "failed to parse ", stringify!($variable), " from config value '{}'" ), $value ))?) }; } let mut implementation = None; let mut version = None; let mut shared = None; let mut abi3 = None; let mut lib_name = None; let mut lib_dir = None; let mut executable = None; let mut pointer_width = None; let mut build_flags: Option = None; let mut suppress_build_script_link_lines = None; let mut extra_build_script_lines = vec![]; let mut python_framework_prefix = None; for (i, line) in lines.enumerate() { let line = line.context("failed to read line from config")?; let mut split = line.splitn(2, '='); let (key, value) = ( split .next() .expect("first splitn value should always be present"), split .next() .ok_or_else(|| format!("expected key=value pair on line {}", i + 1))?, ); match key { "implementation" => parse_value!(implementation, value), "version" => parse_value!(version, value), "shared" => parse_value!(shared, value), "abi3" => parse_value!(abi3, value), "lib_name" => parse_value!(lib_name, value), "lib_dir" => parse_value!(lib_dir, value), "executable" => parse_value!(executable, value), "pointer_width" => parse_value!(pointer_width, value), "build_flags" => parse_value!(build_flags, value), "suppress_build_script_link_lines" => { parse_value!(suppress_build_script_link_lines, value) } "extra_build_script_line" => { extra_build_script_lines.push(value.to_string()); } "python_framework_prefix" => parse_value!(python_framework_prefix, value), unknown => warn!("unknown config key `{}`", unknown), } } let version = version.ok_or("missing value for version")?; let implementation = implementation.unwrap_or(PythonImplementation::CPython); let abi3 = abi3.unwrap_or(false); let build_flags = build_flags.unwrap_or_default(); Ok(InterpreterConfig { implementation, version, shared: shared.unwrap_or(true), abi3, lib_name, lib_dir, executable, pointer_width, build_flags, suppress_build_script_link_lines: suppress_build_script_link_lines.unwrap_or(false), extra_build_script_lines, python_framework_prefix, }) } /// Helper function to apply a default lib_name if none is set in `PYO3_CONFIG_FILE`. /// /// This requires knowledge of the final target, so cannot be done when the config file is /// inlined into `pyo3-build-config` at build time and instead needs to be done when /// resolving the build config for linking. #[cfg(any(test, feature = "resolve-config"))] pub(crate) fn apply_default_lib_name_to_config_file(&mut self, target: &Triple) { if self.lib_name.is_none() { self.lib_name = Some(default_lib_name_for_target( self.version, self.implementation, self.abi3, self.is_free_threaded(), target, )); } } #[doc(hidden)] /// Serialize the `InterpreterConfig` and print it to the environment for Cargo to pass along /// to dependent packages during build time. /// /// NB: writing to the cargo environment requires the /// [`links`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key) /// manifest key to be set. In this case that means this is called by the `pyo3-ffi` crate and /// available for dependent package build scripts in `DEP_PYTHON_PYO3_CONFIG`. See /// documentation for the /// [`DEP__`](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) /// environment variable. pub fn to_cargo_dep_env(&self) -> Result<()> { let mut buf = Vec::new(); self.to_writer(&mut buf)?; // escape newlines in env var println!("cargo:PYO3_CONFIG={}", escape(&buf)); Ok(()) } #[doc(hidden)] pub fn to_writer(&self, mut writer: impl Write) -> Result<()> { macro_rules! write_line { ($value:ident) => { writeln!(writer, "{}={}", stringify!($value), self.$value).context(concat!( "failed to write ", stringify!($value), " to config" )) }; } macro_rules! write_option_line { ($value:ident) => { if let Some(value) = &self.$value { writeln!(writer, "{}={}", stringify!($value), value).context(concat!( "failed to write ", stringify!($value), " to config" )) } else { Ok(()) } }; } write_line!(implementation)?; write_line!(version)?; write_line!(shared)?; write_line!(abi3)?; write_option_line!(lib_name)?; write_option_line!(lib_dir)?; write_option_line!(executable)?; write_option_line!(pointer_width)?; write_line!(build_flags)?; write_option_line!(python_framework_prefix)?; write_line!(suppress_build_script_link_lines)?; for line in &self.extra_build_script_lines { writeln!(writer, "extra_build_script_line={line}") .context("failed to write extra_build_script_line")?; } Ok(()) } /// Run a python script using the [`InterpreterConfig::executable`]. /// /// # Panics /// /// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`. pub fn run_python_script(&self, script: &str) -> Result { run_python_script_with_envs( Path::new(self.executable.as_ref().expect("no interpreter executable")), script, std::iter::empty::<(&str, &str)>(), ) } /// Run a python script using the [`InterpreterConfig::executable`] with additional /// environment variables (e.g. PYTHONPATH) set. /// /// # Panics /// /// This function will panic if the [`executable`](InterpreterConfig::executable) is `None`. pub fn run_python_script_with_envs(&self, script: &str, envs: I) -> Result where I: IntoIterator, K: AsRef, V: AsRef, { run_python_script_with_envs( Path::new(self.executable.as_ref().expect("no interpreter executable")), script, envs, ) } pub fn is_free_threaded(&self) -> bool { self.build_flags.0.contains(&BuildFlag::Py_GIL_DISABLED) } /// Updates configured ABI to build for to the requested abi3 version /// This is a no-op for platforms where abi3 is not supported fn fixup_for_abi3_version(&mut self, abi3_version: Option) -> Result<()> { // PyPy, GraalPy, and the free-threaded build don't support abi3; don't adjust the version if self.implementation.is_pypy() || self.implementation.is_graalpy() || self.is_free_threaded() { return Ok(()); } if let Some(version) = abi3_version { ensure!( version <= self.version, "cannot set a minimum Python version {} higher than the interpreter version {} \ (the minimum Python version is implied by the abi3-py3{} feature)", version, self.version, version.minor, ); self.version = version; } else if is_abi3() && self.version.minor > ABI3_MAX_MINOR { warn!("Automatically falling back to abi3-py3{ABI3_MAX_MINOR} because current Python is higher than the maximum supported"); self.version.minor = ABI3_MAX_MINOR; } Ok(()) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct PythonVersion { pub major: u8, pub minor: u8, } impl PythonVersion { pub const PY315: Self = PythonVersion { major: 3, minor: 15, }; pub const PY313: Self = PythonVersion { major: 3, minor: 13, }; pub const PY312: Self = PythonVersion { major: 3, minor: 12, }; const PY310: Self = PythonVersion { major: 3, minor: 10, }; const PY37: Self = PythonVersion { major: 3, minor: 7 }; } impl Display for PythonVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}.{}", self.major, self.minor) } } impl FromStr for PythonVersion { type Err = crate::errors::Error; fn from_str(value: &str) -> Result { let mut split = value.splitn(2, '.'); let (major, minor) = ( split .next() .expect("first splitn value should always be present"), split.next().ok_or("expected major.minor version")?, ); Ok(Self { major: major.parse().context("failed to parse major version")?, minor: minor.parse().context("failed to parse minor version")?, }) } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PythonImplementation { CPython, PyPy, GraalPy, } impl PythonImplementation { #[doc(hidden)] pub fn is_pypy(self) -> bool { self == PythonImplementation::PyPy } #[doc(hidden)] pub fn is_graalpy(self) -> bool { self == PythonImplementation::GraalPy } #[doc(hidden)] pub fn from_soabi(soabi: &str) -> Result { if soabi.starts_with("pypy") { Ok(PythonImplementation::PyPy) } else if soabi.starts_with("cpython") { Ok(PythonImplementation::CPython) } else if soabi.starts_with("graalpy") { Ok(PythonImplementation::GraalPy) } else { bail!("unsupported Python interpreter"); } } } impl Display for PythonImplementation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PythonImplementation::CPython => write!(f, "CPython"), PythonImplementation::PyPy => write!(f, "PyPy"), PythonImplementation::GraalPy => write!(f, "GraalVM"), } } } impl FromStr for PythonImplementation { type Err = Error; fn from_str(s: &str) -> Result { match s { "CPython" => Ok(PythonImplementation::CPython), "PyPy" => Ok(PythonImplementation::PyPy), "GraalVM" => Ok(PythonImplementation::GraalPy), _ => bail!("unknown interpreter: {}", s), } } } /// Checks if we should look for a Python interpreter installation /// to get the target interpreter configuration. /// /// Returns `false` if `PYO3_NO_PYTHON` environment variable is set. fn have_python_interpreter() -> bool { env_var("PYO3_NO_PYTHON").is_none() } /// Checks if `abi3` or any of the `abi3-py3*` features is enabled for the PyO3 crate. /// /// Must be called from a PyO3 crate build script. fn is_abi3() -> bool { cargo_env_var("CARGO_FEATURE_ABI3").is_some() || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").is_some_and(|os_str| os_str == "1") } /// Gets the minimum supported Python version from PyO3 `abi3-py*` features. /// /// Must be called from a PyO3 crate build script. pub fn get_abi3_version() -> Option { let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR) .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some()); minor_version.map(|minor| PythonVersion { major: 3, minor }) } /// Checks if the `extension-module` feature is enabled for the PyO3 crate. /// /// This can be triggered either by: /// - The `extension-module` Cargo feature (deprecated) /// - Setting the `PYO3_BUILD_EXTENSION_MODULE` environment variable /// /// Must be called from a PyO3 crate build script. pub fn is_extension_module() -> bool { cargo_env_var("CARGO_FEATURE_EXTENSION_MODULE").is_some() || env_var("PYO3_BUILD_EXTENSION_MODULE").is_some() } /// Checks if we need to link to `libpython` for the target. /// /// Must be called from a PyO3 crate build script. pub fn is_linking_libpython_for_target(target: &Triple) -> bool { target.operating_system == OperatingSystem::Windows // See https://github.com/PyO3/pyo3/issues/4068#issuecomment-2051159852 || target.operating_system == OperatingSystem::Aix || target.environment == Environment::Android || target.environment == Environment::Androideabi || target.operating_system == OperatingSystem::Cygwin || matches!(target.operating_system, OperatingSystem::IOS(_)) || !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 { // With raw-dylib, Windows targets never need a lib dir — the compiler generates // import entries directly from `#[link(kind = "raw-dylib")]` attributes. if target.operating_system == OperatingSystem::Windows { return false; } is_linking_libpython_for_target(target) } /// 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_*`) /// when a cross-compilation configuration is detected. #[derive(Debug, PartialEq, Eq)] pub struct CrossCompileConfig { /// The directory containing the Python library to link against. pub lib_dir: Option, /// The version of the Python library to link against. version: Option, /// The target Python implementation hint (CPython, PyPy, GraalPy, ...) implementation: Option, /// The compile target triple (e.g. aarch64-unknown-linux-gnu) target: Triple, /// Python ABI flags, used to detect free-threaded Python builds. abiflags: Option, } impl CrossCompileConfig { /// Creates a new cross compile config struct from PyO3 environment variables /// and the build environment when cross compilation mode is detected. /// /// Returns `None` when not cross compiling. fn try_from_env_vars_host_target( env_vars: CrossCompileEnvVars, host: &Triple, target: &Triple, ) -> Result> { if env_vars.any() || Self::is_cross_compiling_from_to(host, target) { let lib_dir = env_vars.lib_dir_path()?; let (version, abiflags) = env_vars.parse_version()?; let implementation = env_vars.parse_implementation()?; let target = target.clone(); Ok(Some(CrossCompileConfig { lib_dir, version, implementation, target, abiflags, })) } else { Ok(None) } } /// Checks if compiling on `host` for `target` required "real" cross compilation. /// /// Returns `false` if the target Python interpreter can run on the host. fn is_cross_compiling_from_to(host: &Triple, target: &Triple) -> bool { // Not cross-compiling if arch-vendor-os is all the same // e.g. x86_64-unknown-linux-musl on x86_64-unknown-linux-gnu host // x86_64-pc-windows-gnu on x86_64-pc-windows-msvc host let mut compatible = host.architecture == target.architecture && (host.vendor == target.vendor // Don't treat `-pc-` to `-win7-` as cross-compiling || (host.vendor == Vendor::Pc && target.vendor.as_str() == "win7")) && host.operating_system == target.operating_system; // Not cross-compiling to compile for 32-bit Python from windows 64-bit compatible |= target.operating_system == OperatingSystem::Windows && host.operating_system == OperatingSystem::Windows && matches!(target.architecture, Architecture::X86_32(_)) && host.architecture == Architecture::X86_64; // Not cross-compiling to compile for x86-64 Python from macOS arm64 and vice versa compatible |= matches!(target.operating_system, OperatingSystem::Darwin(_)) && matches!(host.operating_system, OperatingSystem::Darwin(_)); compatible |= matches!(target.operating_system, OperatingSystem::IOS(_)); !compatible } /// Converts `lib_dir` member field to an UTF-8 string. /// /// The conversion can not fail because `PYO3_CROSS_LIB_DIR` variable /// is ensured contain a valid UTF-8 string. #[allow(dead_code)] fn lib_dir_string(&self) -> Option { self.lib_dir .as_ref() .map(|s| s.to_str().unwrap().to_owned()) } } /// PyO3-specific cross compile environment variable values struct CrossCompileEnvVars { /// `PYO3_CROSS` pyo3_cross: Option, /// `PYO3_CROSS_LIB_DIR` pyo3_cross_lib_dir: Option, /// `PYO3_CROSS_PYTHON_VERSION` pyo3_cross_python_version: Option, /// `PYO3_CROSS_PYTHON_IMPLEMENTATION` pyo3_cross_python_implementation: Option, } impl CrossCompileEnvVars { /// Grabs the PyO3 cross-compile variables from the environment. /// /// Registers the build script to rerun if any of the variables changes. fn from_env() -> Self { CrossCompileEnvVars { pyo3_cross: env_var("PYO3_CROSS"), pyo3_cross_lib_dir: env_var("PYO3_CROSS_LIB_DIR"), pyo3_cross_python_version: env_var("PYO3_CROSS_PYTHON_VERSION"), pyo3_cross_python_implementation: env_var("PYO3_CROSS_PYTHON_IMPLEMENTATION"), } } /// Checks if any of the variables is set. fn any(&self) -> bool { self.pyo3_cross.is_some() || self.pyo3_cross_lib_dir.is_some() || self.pyo3_cross_python_version.is_some() || self.pyo3_cross_python_implementation.is_some() } /// Parses `PYO3_CROSS_PYTHON_VERSION` environment variable value /// into `PythonVersion` and ABI flags. fn parse_version(&self) -> Result<(Option, Option)> { match self.pyo3_cross_python_version.as_ref() { Some(os_string) => { let utf8_str = os_string .to_str() .ok_or("PYO3_CROSS_PYTHON_VERSION is not valid a UTF-8 string")?; let (utf8_str, abiflags) = if let Some(version) = utf8_str.strip_suffix('t') { (version, Some("t".to_string())) } else { (utf8_str, None) }; let version = utf8_str .parse() .context("failed to parse PYO3_CROSS_PYTHON_VERSION")?; Ok((Some(version), abiflags)) } None => Ok((None, None)), } } /// Parses `PYO3_CROSS_PYTHON_IMPLEMENTATION` environment variable value /// into `PythonImplementation`. fn parse_implementation(&self) -> Result> { let implementation = self .pyo3_cross_python_implementation .as_ref() .map(|os_string| { let utf8_str = os_string .to_str() .ok_or("PYO3_CROSS_PYTHON_IMPLEMENTATION is not valid a UTF-8 string")?; utf8_str .parse() .context("failed to parse PYO3_CROSS_PYTHON_IMPLEMENTATION") }) .transpose()?; Ok(implementation) } /// Converts the stored `PYO3_CROSS_LIB_DIR` variable value (if any) /// into a `PathBuf` instance. /// /// Ensures that the path is a valid UTF-8 string. fn lib_dir_path(&self) -> Result> { let lib_dir = self.pyo3_cross_lib_dir.as_ref().map(PathBuf::from); if let Some(dir) = lib_dir.as_ref() { ensure!( dir.to_str().is_some(), "PYO3_CROSS_LIB_DIR variable value is not a valid UTF-8 string" ); } Ok(lib_dir) } } /// Detect whether we are cross compiling and return an assembled CrossCompileConfig if so. /// /// This function relies on PyO3 cross-compiling environment variables: /// /// * `PYO3_CROSS`: If present, forces PyO3 to configure as a cross-compilation. /// * `PYO3_CROSS_LIB_DIR`: If present, must be set to the directory containing /// the target's libpython DSO and the associated `_sysconfigdata*.py` file for /// Unix-like targets, or the Python DLL import libraries for the Windows target. /// * `PYO3_CROSS_PYTHON_VERSION`: Major and minor version (e.g. 3.9) of the target Python /// installation. This variable is only needed if PyO3 cannot determine the version to target /// from `abi3-py3*` features, or if there are multiple versions of Python present in /// `PYO3_CROSS_LIB_DIR`. /// /// See the [PyO3 User Guide](https://pyo3.rs/) for more info on cross-compiling. pub fn cross_compiling_from_to( host: &Triple, target: &Triple, ) -> Result> { let env_vars = CrossCompileEnvVars::from_env(); CrossCompileConfig::try_from_env_vars_host_target(env_vars, host, target) } /// Detect whether we are cross compiling from Cargo and `PYO3_CROSS_*` environment /// variables and return an assembled `CrossCompileConfig` if so. /// /// This must be called from PyO3's build script, because it relies on environment /// variables such as `CARGO_CFG_TARGET_OS` which aren't available at any other time. #[allow(dead_code)] pub fn cross_compiling_from_cargo_env() -> Result> { let env_vars = CrossCompileEnvVars::from_env(); let host = Triple::host(); let target = target_triple_from_env(); CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) } #[allow(non_camel_case_types)] #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum BuildFlag { Py_DEBUG, Py_REF_DEBUG, #[deprecated(since = "0.29.0", note = "no longer supported by PyO3")] Py_TRACE_REFS, Py_GIL_DISABLED, COUNT_ALLOCS, Other(String), } impl Display for BuildFlag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BuildFlag::Other(flag) => write!(f, "{flag}"), _ => write!(f, "{self:?}"), } } } impl FromStr for BuildFlag { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { match s { "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG), "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG), "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED), "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS), other => Ok(BuildFlag::Other(other.to_owned())), } } } /// A list of python interpreter compile-time preprocessor defines. /// /// PyO3 will pick these up and pass to rustc via `--cfg=py_sys_config={varname}`; /// this allows using them conditional cfg attributes in the .rs files, so /// /// ```rust,no_run /// #[cfg(py_sys_config="{varname}")] /// # struct Foo; /// ``` /// /// is the equivalent of `#ifdef {varname}` in C. /// /// see Misc/SpecialBuilds.txt in the python source for what these mean. #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Clone, Default)] pub struct BuildFlags(pub HashSet); impl BuildFlags { const ALL: [BuildFlag; 4] = [ BuildFlag::Py_DEBUG, BuildFlag::Py_REF_DEBUG, BuildFlag::Py_GIL_DISABLED, BuildFlag::COUNT_ALLOCS, ]; pub fn new() -> Self { BuildFlags(HashSet::new()) } fn from_sysconfigdata(config_map: &Sysconfigdata) -> Self { Self( BuildFlags::ALL .iter() .filter(|flag| config_map.get_value(flag.to_string()) == Some("1")) .cloned() .collect(), ) .fixup() } /// Examine python's compile flags to pass to cfg by launching /// the interpreter and printing variables of interest from /// sysconfig.get_config_vars. fn from_interpreter(interpreter: impl AsRef) -> Result { // sysconfig is missing all the flags on windows for Python 3.12 and // older, so we can't actually query the interpreter directly for its // build flags on those versions. if cfg!(windows) { let script = String::from("import sys;print(sys.version_info < (3, 13))"); let stdout = run_python_script(interpreter.as_ref(), &script)?; if stdout.trim_end() == "True" { return Ok(Self::new()); } } let mut script = String::from("import sysconfig\n"); script.push_str("config = sysconfig.get_config_vars()\n"); for k in &BuildFlags::ALL { use std::fmt::Write; writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap(); } let stdout = run_python_script(interpreter.as_ref(), &script)?; let split_stdout: Vec<&str> = stdout.trim_end().lines().collect(); ensure!( split_stdout.len() == BuildFlags::ALL.len(), "Python stdout len didn't return expected number of lines: {}", split_stdout.len() ); let flags = BuildFlags::ALL .iter() .zip(split_stdout) .filter(|(_, flag_value)| *flag_value == "1") .map(|(flag, _)| flag.clone()) .collect(); Ok(Self(flags).fixup()) } fn fixup(mut self) -> Self { if self.0.contains(&BuildFlag::Py_DEBUG) { self.0.insert(BuildFlag::Py_REF_DEBUG); } self } } impl Display for BuildFlags { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut first = true; for flag in &self.0 { if first { first = false; } else { write!(f, ",")?; } write!(f, "{flag}")?; } Ok(()) } } impl FromStr for BuildFlags { type Err = std::convert::Infallible; fn from_str(value: &str) -> Result { let mut flags = HashSet::new(); for flag in value.split_terminator(',') { flags.insert(flag.parse().unwrap()); } Ok(BuildFlags(flags)) } } fn parse_script_output(output: &str) -> HashMap { output .lines() .filter_map(|line| { let mut i = line.splitn(2, ' '); Some((i.next()?.into(), i.next()?.into())) }) .collect() } /// Parsed data from Python sysconfigdata file /// /// A hash map of all values from a sysconfigdata file. pub struct Sysconfigdata(HashMap); impl Sysconfigdata { pub fn get_value>(&self, k: S) -> Option<&str> { self.0.get(k.as_ref()).map(String::as_str) } #[allow(dead_code)] fn new() -> Self { Sysconfigdata(HashMap::new()) } #[allow(dead_code)] fn insert>(&mut self, k: S, v: S) { self.0.insert(k.into(), v.into()); } } /// Parse sysconfigdata file /// /// The sysconfigdata is simply a dictionary containing all the build time variables used for the /// python executable and library. This function necessitates a python interpreter on the host /// machine to work. Here it is read into a `Sysconfigdata` (hash map), which can be turned into an /// [`InterpreterConfig`] using /// [`from_sysconfigdata`](InterpreterConfig::from_sysconfigdata). pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef) -> Result { let sysconfigdata_path = sysconfigdata_path.as_ref(); let mut script = fs::read_to_string(sysconfigdata_path).with_context(|| { format!( "failed to read config from {}", sysconfigdata_path.display() ) })?; script += r#" for key, val in build_time_vars.items(): # (ana)conda(-forge) built Pythons are statically linked but ship the shared library with them. # We detect them based on the magic prefix directory they have encoded in their builds. if key == "Py_ENABLE_SHARED" and "_h_env_placehold" in build_time_vars.get("prefix"): val = 1 print(key, val) "#; let output = run_python_script(&find_interpreter()?, &script)?; Ok(Sysconfigdata(parse_script_output(&output))) } fn starts_with(entry: &DirEntry, pat: &str) -> bool { let name = entry.file_name(); name.to_string_lossy().starts_with(pat) } fn ends_with(entry: &DirEntry, pat: &str) -> bool { let name = entry.file_name(); name.to_string_lossy().ends_with(pat) } /// Finds the sysconfigdata file when the target Python library directory is set. /// /// Returns `None` if the library directory is not available, and a runtime error /// when no or multiple sysconfigdata files are found. #[allow(dead_code)] fn find_sysconfigdata(cross: &CrossCompileConfig) -> Result> { let mut sysconfig_paths = find_all_sysconfigdata(cross)?; if sysconfig_paths.is_empty() { if let Some(lib_dir) = cross.lib_dir.as_ref() { bail!("Could not find _sysconfigdata*.py in {}", lib_dir.display()); } else { // Continue with the default configuration when PYO3_CROSS_LIB_DIR is not set. return Ok(None); } } else if sysconfig_paths.len() > 1 { let mut error_msg = String::from( "Detected multiple possible Python versions. Please set either the \ PYO3_CROSS_PYTHON_VERSION variable to the wanted version or the \ _PYTHON_SYSCONFIGDATA_NAME variable to the wanted sysconfigdata file name.\n\n\ sysconfigdata files found:", ); for path in sysconfig_paths { use std::fmt::Write; write!(&mut error_msg, "\n\t{}", path.display()).unwrap(); } bail!("{}\n", error_msg); } Ok(Some(sysconfig_paths.remove(0))) } /// Finds `_sysconfigdata*.py` files for detected Python interpreters. /// /// From the python source for `_sysconfigdata*.py` is always going to be located at /// `build/lib.{PLATFORM}-{PY_MINOR_VERSION}` when built from source. The [exact line][1] is defined as: /// /// ```py /// pybuilddir = 'build/lib.%s-%s' % (get_platform(), sys.version_info[:2]) /// ``` /// /// Where get_platform returns a kebab-case formatted string containing the os, the architecture and /// possibly the os' kernel version (not the case on linux). However, when installed using a package /// manager, the `_sysconfigdata*.py` file is installed in the `${PREFIX}/lib/python3.Y/` directory. /// The `_sysconfigdata*.py` is generally in a sub-directory of the location of `libpython3.Y.so`. /// So we must find the file in the following possible locations: /// /// ```sh /// # distribution from package manager, (lib_dir may or may not include lib/) /// ${INSTALL_PREFIX}/lib/python3.Y/_sysconfigdata*.py /// ${INSTALL_PREFIX}/lib/libpython3.Y.so /// ${INSTALL_PREFIX}/lib/python3.Y/config-3.Y-${HOST_TRIPLE}/libpython3.Y.so /// /// # Built from source from host /// ${CROSS_COMPILED_LOCATION}/build/lib.linux-x86_64-Y/_sysconfigdata*.py /// ${CROSS_COMPILED_LOCATION}/libpython3.Y.so /// /// # if cross compiled, kernel release is only present on certain OS targets. /// ${CROSS_COMPILED_LOCATION}/build/lib.{OS}(-{OS-KERNEL-RELEASE})?-{ARCH}-Y/_sysconfigdata*.py /// ${CROSS_COMPILED_LOCATION}/libpython3.Y.so /// /// # PyPy includes a similar file since v73 /// ${INSTALL_PREFIX}/lib/pypy3.Y/_sysconfigdata.py /// ${INSTALL_PREFIX}/lib_pypy/_sysconfigdata.py /// ``` /// /// [1]: https://github.com/python/cpython/blob/3.5/Lib/sysconfig.py#L389 /// /// Returns an empty vector when the target Python library directory /// is not set via `PYO3_CROSS_LIB_DIR`. pub fn find_all_sysconfigdata(cross: &CrossCompileConfig) -> Result> { let sysconfig_paths = if let Some(lib_dir) = cross.lib_dir.as_ref() { search_lib_dir(lib_dir, cross).with_context(|| { format!( "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR={}'", lib_dir.display() ) })? } else { return Ok(Vec::new()); }; let sysconfig_name = env_var("_PYTHON_SYSCONFIGDATA_NAME"); let mut sysconfig_paths = sysconfig_paths .iter() .filter_map(|p| { let canonical = fs::canonicalize(p).ok(); match &sysconfig_name { Some(_) => canonical.filter(|p| p.file_stem() == sysconfig_name.as_deref()), None => canonical, } }) .collect::>(); sysconfig_paths.sort(); sysconfig_paths.dedup(); Ok(sysconfig_paths) } fn is_pypy_lib_dir(path: &str, v: &Option) -> bool { let pypy_version_pat = if let Some(v) = v { format!("pypy{v}") } else { "pypy3.".into() }; path == "lib_pypy" || path.starts_with(&pypy_version_pat) } fn is_graalpy_lib_dir(path: &str, v: &Option) -> bool { let graalpy_version_pat = if let Some(v) = v { format!("graalpy{v}") } else { "graalpy2".into() }; path == "lib_graalpython" || path.starts_with(&graalpy_version_pat) } fn is_cpython_lib_dir(path: &str, v: &Option) -> bool { let cpython_version_pat = if let Some(v) = v { format!("python{v}") } else { "python3.".into() }; path.starts_with(&cpython_version_pat) } /// recursive search for _sysconfigdata, returns all possibilities of sysconfigdata paths fn search_lib_dir(path: impl AsRef, cross: &CrossCompileConfig) -> Result> { let mut sysconfig_paths = vec![]; for f in fs::read_dir(path.as_ref()).with_context(|| { format!( "failed to list the entries in '{}'", path.as_ref().display() ) })? { sysconfig_paths.extend(match &f { // Python 3.7+ sysconfigdata with platform specifics Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()], Ok(f) if f.metadata().is_ok_and(|metadata| metadata.is_dir()) => { let file_name = f.file_name(); let file_name = file_name.to_string_lossy(); if file_name == "build" || file_name == "lib" { search_lib_dir(f.path(), cross)? } else if file_name.starts_with("lib.") { // check if right target os if !file_name.contains(&cross.target.operating_system.to_string()) { continue; } // Check if right arch if !file_name.contains(&cross.target.architecture.to_string()) { continue; } search_lib_dir(f.path(), cross)? } else if is_cpython_lib_dir(&file_name, &cross.version) || is_pypy_lib_dir(&file_name, &cross.version) || is_graalpy_lib_dir(&file_name, &cross.version) { search_lib_dir(f.path(), cross)? } else { continue; } } _ => continue, }); } // If we got more than one file, only take those that contain the arch name. // For ubuntu 20.04 with host architecture x86_64 and a foreign architecture of armhf // this reduces the number of candidates to 1: // // $ find /usr/lib/python3.8/ -name '_sysconfigdata*.py' -not -lname '*' // /usr/lib/python3.8/_sysconfigdata__x86_64-linux-gnu.py // /usr/lib/python3.8/_sysconfigdata__arm-linux-gnueabihf.py if sysconfig_paths.len() > 1 { let temp = sysconfig_paths .iter() .filter(|p| { p.to_string_lossy() .contains(&cross.target.architecture.to_string()) }) .cloned() .collect::>(); if !temp.is_empty() { sysconfig_paths = temp; } } Ok(sysconfig_paths) } /// Find cross compilation information from sysconfigdata file /// /// first find sysconfigdata file which follows the pattern [`_sysconfigdata_{abi}_{platform}_{multiarch}`][1] /// /// [1]: https://github.com/python/cpython/blob/3.8/Lib/sysconfig.py#L348 /// /// Returns `None` when the target Python library directory is not set. #[allow(dead_code)] fn cross_compile_from_sysconfigdata( cross_compile_config: &CrossCompileConfig, ) -> Result> { if let Some(path) = find_sysconfigdata(cross_compile_config)? { let data = parse_sysconfigdata(path)?; let mut config = InterpreterConfig::from_sysconfigdata(&data)?; if let Some(cross_lib_dir) = cross_compile_config.lib_dir_string() { config.lib_dir = Some(cross_lib_dir) } Ok(Some(config)) } else { Ok(None) } } /// Generates "default" cross compilation information for the target. /// /// This should work for most CPython extension modules when targeting /// Windows, macOS and Linux. /// /// Must be called from a PyO3 crate build script. #[allow(unused_mut, dead_code)] fn default_cross_compile(cross_compile_config: &CrossCompileConfig) -> Result { let version = cross_compile_config .version .or_else(get_abi3_version) .ok_or_else(|| format!( "PYO3_CROSS_PYTHON_VERSION or an abi3-py3* feature must be specified \ when cross-compiling and PYO3_CROSS_LIB_DIR is not set.\n\ = help: see the PyO3 user guide for more information: https://pyo3.rs/v{}/building-and-distribution.html#cross-compiling", env!("CARGO_PKG_VERSION") ) )?; let abi3 = is_abi3(); let implementation = cross_compile_config .implementation .unwrap_or(PythonImplementation::CPython); let gil_disabled: bool = cross_compile_config.abiflags.as_deref() == Some("t"); let lib_name = default_lib_name_for_target( version, implementation, abi3, gil_disabled, &cross_compile_config.target, ); let mut lib_dir = cross_compile_config.lib_dir_string(); Ok(InterpreterConfig { implementation, version, shared: true, abi3, lib_name: Some(lib_name), lib_dir, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }) } /// Generates "default" interpreter configuration when compiling "abi3" extensions /// without a working Python interpreter. /// /// `version` specifies the minimum supported Stable ABI CPython version. /// /// This should work for most CPython extension modules when compiling on /// Windows, macOS and Linux. /// /// Must be called from a PyO3 crate build script. fn default_abi3_config(host: &Triple, version: PythonVersion) -> Result { // FIXME: PyPy & GraalPy do not support the Stable ABI. let implementation = PythonImplementation::CPython; let abi3 = true; let lib_name = if host.operating_system == OperatingSystem::Windows { Some(default_lib_name_windows( version, implementation, abi3, false, false, false, )?) } else { None }; Ok(InterpreterConfig { implementation, version, shared: true, abi3, lib_name, lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }) } /// Detects the cross compilation target interpreter configuration from all /// available sources (PyO3 environment variables, Python sysconfigdata, etc.). /// /// Returns the "default" target interpreter configuration for Windows and /// when no target Python interpreter is found. /// /// Must be called from a PyO3 crate build script. #[allow(dead_code)] fn load_cross_compile_config( cross_compile_config: CrossCompileConfig, ) -> Result { let windows = cross_compile_config.target.operating_system == OperatingSystem::Windows; let config = if windows || !have_python_interpreter() { // Load the defaults for Windows even when `PYO3_CROSS_LIB_DIR` is set // since it has no sysconfigdata files in it. // Also, do not try to look for sysconfigdata when `PYO3_NO_PYTHON` variable is set. default_cross_compile(&cross_compile_config)? } else if let Some(config) = cross_compile_from_sysconfigdata(&cross_compile_config)? { // Try to find and parse sysconfigdata files on other targets. config } else { // Fall back to the defaults when nothing else can be done. default_cross_compile(&cross_compile_config)? }; Ok(config) } // These contains only the limited ABI symbols. const WINDOWS_ABI3_LIB_NAME: &str = "python3"; const WINDOWS_ABI3_DEBUG_LIB_NAME: &str = "python3_d"; /// Generates the default library name for the target platform. #[allow(dead_code)] fn default_lib_name_for_target( version: PythonVersion, implementation: PythonImplementation, abi3: bool, gil_disabled: bool, target: &Triple, ) -> String { if target.operating_system == OperatingSystem::Windows { default_lib_name_windows(version, implementation, abi3, false, false, gil_disabled).unwrap() } else { default_lib_name_unix( version, implementation, abi3, target.operating_system == OperatingSystem::Cygwin, None, gil_disabled, ) .unwrap() } } fn default_lib_name_windows( version: PythonVersion, implementation: PythonImplementation, abi3: bool, mingw: bool, debug: bool, gil_disabled: bool, ) -> Result { if implementation.is_pypy() { // PyPy on Windows ships `libpypy3.X-c.dll` (e.g. `libpypy3.11-c.dll`), // not CPython's `pythonXY.dll`. With raw-dylib linking we need the real // DLL name rather than the import-library alias. Ok(format!("libpypy{}.{}-c", version.major, version.minor)) } else if debug && version < PythonVersion::PY310 { // CPython bug: linking against python3_d.dll raises error // https://github.com/python/cpython/issues/101614 Ok(format!("python{}{}_d", version.major, version.minor)) } else if abi3 && !(gil_disabled || implementation.is_pypy() || implementation.is_graalpy()) { if debug { Ok(WINDOWS_ABI3_DEBUG_LIB_NAME.to_owned()) } else { Ok(WINDOWS_ABI3_LIB_NAME.to_owned()) } } else if mingw { ensure!( !gil_disabled, "MinGW free-threaded builds are not currently tested or supported" ); // https://packages.msys2.org/base/mingw-w64-python Ok(format!("python{}.{}", version.major, version.minor)) } else if gil_disabled { ensure!(version >= PythonVersion::PY313, "Cannot compile C extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", version.major, version.minor); if debug { Ok(format!("python{}{}t_d", version.major, version.minor)) } else { Ok(format!("python{}{}t", version.major, version.minor)) } } else if debug { Ok(format!("python{}{}_d", version.major, version.minor)) } else { Ok(format!("python{}{}", version.major, version.minor)) } } fn default_lib_name_unix( version: PythonVersion, implementation: PythonImplementation, abi3: bool, cygwin: bool, ld_version: Option<&str>, gil_disabled: bool, ) -> Result { match implementation { PythonImplementation::CPython => match ld_version { Some(ld_version) => Ok(format!("python{ld_version}")), None => { if cygwin && abi3 { Ok("python3".to_string()) } else if version > PythonVersion::PY37 { // PEP 3149 ABI version tags are finally gone if gil_disabled { ensure!(version >= PythonVersion::PY313, "Cannot compile C extensions for the free-threaded build on Python versions earlier than 3.13, found {}.{}", version.major, version.minor); Ok(format!("python{}.{}t", version.major, version.minor)) } else { Ok(format!("python{}.{}", version.major, version.minor)) } } else { // Work around https://bugs.python.org/issue36707 Ok(format!("python{}.{}m", version.major, version.minor)) } } }, PythonImplementation::PyPy => match ld_version { Some(ld_version) => Ok(format!("pypy{ld_version}-c")), None => Ok(format!("pypy{}.{}-c", version.major, version.minor)), }, PythonImplementation::GraalPy => Ok("python-native".to_string()), } } /// Run a python script using the specified interpreter binary. fn run_python_script(interpreter: &Path, script: &str) -> Result { run_python_script_with_envs(interpreter, script, std::iter::empty::<(&str, &str)>()) } /// Run a python script using the specified interpreter binary with additional environment /// variables (e.g. PYTHONPATH) set. fn run_python_script_with_envs(interpreter: &Path, script: &str, envs: I) -> Result where I: IntoIterator, K: AsRef, V: AsRef, { let out = Command::new(interpreter) .env("PYTHONIOENCODING", "utf-8") .envs(envs) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .spawn() .and_then(|mut child| { child .stdin .as_mut() .expect("piped stdin") .write_all(script.as_bytes())?; child.wait_with_output() }); match out { Err(err) => bail!( "failed to run the Python interpreter at {}: {}", interpreter.display(), err ), Ok(ok) if !ok.status.success() => bail!("Python script failed"), Ok(ok) => Ok(String::from_utf8(ok.stdout) .context("failed to parse Python script output as utf-8")?), } } fn venv_interpreter(virtual_env: &OsStr, windows: bool) -> PathBuf { if windows { Path::new(virtual_env).join("Scripts").join("python.exe") } else { Path::new(virtual_env).join("bin").join("python") } } fn conda_env_interpreter(conda_prefix: &OsStr, windows: bool) -> PathBuf { if windows { Path::new(conda_prefix).join("python.exe") } else { Path::new(conda_prefix).join("bin").join("python") } } fn get_env_interpreter() -> Option { match (env_var("VIRTUAL_ENV"), env_var("CONDA_PREFIX")) { // Use cfg rather than CARGO_CFG_TARGET_OS because this affects where files are located on the // build host (Some(dir), None) => Some(venv_interpreter(&dir, cfg!(windows))), (None, Some(dir)) => Some(conda_env_interpreter(&dir, cfg!(windows))), (Some(_), Some(_)) => { warn!( "Both VIRTUAL_ENV and CONDA_PREFIX are set. PyO3 will ignore both of these for \ locating the Python interpreter until you unset one of them." ); None } (None, None) => None, } } /// Attempts to locate a python interpreter. /// /// Locations are checked in the order listed: /// 1. If `PYO3_PYTHON` is set, this interpreter is used. /// 2. If in a virtualenv, that environment's interpreter is used. /// 3. `python`, if this is functional a Python 3.x interpreter /// 4. `python3`, as above pub fn find_interpreter() -> Result { // Trigger rebuilds when `PYO3_ENVIRONMENT_SIGNATURE` env var value changes // See https://github.com/PyO3/pyo3/issues/2724 println!("cargo:rerun-if-env-changed=PYO3_ENVIRONMENT_SIGNATURE"); if let Some(exe) = env_var("PYO3_PYTHON") { Ok(exe.into()) } else if let Some(env_interpreter) = get_env_interpreter() { Ok(env_interpreter) } else { println!("cargo:rerun-if-env-changed=PATH"); ["python", "python3"] .iter() .find(|bin| { if let Ok(out) = Command::new(bin).arg("--version").output() { // begin with `Python 3.X.X :: additional info` out.stdout.starts_with(b"Python 3") || out.stderr.starts_with(b"Python 3") || out.stdout.starts_with(b"GraalPy 3") } else { false } }) .map(PathBuf::from) .ok_or_else(|| "no Python 3.x interpreter found".into()) } } /// 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) -> Result { 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. /// /// This must be called from PyO3's build script, because it relies on environment variables such as /// CARGO_CFG_TARGET_OS which aren't available at any other time. #[allow(dead_code)] pub fn make_cross_compile_config() -> Result> { let interpreter_config = if let Some(cross_config) = cross_compiling_from_cargo_env()? { let mut interpreter_config = load_cross_compile_config(cross_config)?; interpreter_config.fixup_for_abi3_version(get_abi3_version())?; Some(interpreter_config) } else { None }; Ok(interpreter_config) } /// Generates an interpreter config which will be hard-coded into the pyo3-build-config crate. /// Only used by `pyo3-build-config` build script. #[allow(dead_code, unused_mut)] pub fn make_interpreter_config() -> Result { let host = Triple::host(); let abi3_version = get_abi3_version(); // See if we can safely skip the Python interpreter configuration detection. // Unix "abi3" extension modules can usually be built without any interpreter. let need_interpreter = abi3_version.is_none() || require_libdir_for_target(&host); if have_python_interpreter() { match get_host_interpreter(abi3_version) { Ok(interpreter_config) => return Ok(interpreter_config), // 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 interpreter_config = default_abi3_config(&host, abi3_version.unwrap())?; 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 { assert_eq!(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"), } } bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1])); } bytes } #[cfg(test)] mod tests { use target_lexicon::triple; use super::*; #[test] fn test_config_file_roundtrip() { 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".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()], python_framework_prefix: None, }; let mut buf: Vec = Vec::new(); config.to_writer(&mut buf).unwrap(); assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap()); // And some different options, for variety let config = InterpreterConfig { abi3: false, build_flags: { let mut flags = HashSet::new(); flags.insert(BuildFlag::Py_DEBUG); flags.insert(BuildFlag::Other(String::from("Py_SOME_FLAG"))); BuildFlags(flags) }, pointer_width: None, executable: None, implementation: PythonImplementation::PyPy, lib_dir: None, lib_name: None, shared: true, version: PythonVersion { major: 3, minor: 10, }, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; let mut buf: Vec = Vec::new(); config.to_writer(&mut buf).unwrap(); assert_eq!(config, InterpreterConfig::from_reader(&*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()], python_framework_prefix: None, }; let mut buf: Vec = Vec::new(); config.to_writer(&mut buf).unwrap(); let buf = unescape(&escape(&buf)); assert_eq!(config, InterpreterConfig::from_reader(&*buf).unwrap()); } #[test] fn test_config_file_defaults() { // Only version is required assert_eq!( InterpreterConfig::from_reader("version=3.7".as_bytes()).unwrap(), InterpreterConfig { version: PythonVersion { major: 3, minor: 7 }, implementation: PythonImplementation::CPython, shared: true, abi3: false, lib_name: None, lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ) } #[test] fn test_config_file_unknown_keys() { // ext_suffix is unknown to pyo3-build-config, but it shouldn't error assert_eq!( InterpreterConfig::from_reader("version=3.7\next_suffix=.python37.so".as_bytes()) .unwrap(), InterpreterConfig { version: PythonVersion { major: 3, minor: 7 }, implementation: PythonImplementation::CPython, shared: true, abi3: false, lib_name: None, lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ) } #[test] fn build_flags_default() { assert_eq!(BuildFlags::default(), BuildFlags::new()); } #[test] fn build_flags_from_sysconfigdata() { let mut sysconfigdata = Sysconfigdata::new(); assert_eq!( BuildFlags::from_sysconfigdata(&sysconfigdata).0, HashSet::new() ); for flag in &BuildFlags::ALL { sysconfigdata.insert(flag.to_string(), "0".into()); } assert_eq!( BuildFlags::from_sysconfigdata(&sysconfigdata).0, HashSet::new() ); let mut expected_flags = HashSet::new(); for flag in &BuildFlags::ALL { sysconfigdata.insert(flag.to_string(), "1".into()); expected_flags.insert(flag.clone()); } assert_eq!( BuildFlags::from_sysconfigdata(&sysconfigdata).0, expected_flags ); } #[test] fn build_flags_fixup() { let mut build_flags = BuildFlags::new(); build_flags = build_flags.fixup(); assert!(build_flags.0.is_empty()); build_flags.0.insert(BuildFlag::Py_DEBUG); build_flags = build_flags.fixup(); // Py_DEBUG implies Py_REF_DEBUG assert!(build_flags.0.contains(&BuildFlag::Py_REF_DEBUG)); } #[test] fn parse_script_output() { let output = "foo bar\nbar foobar\n\n"; let map = super::parse_script_output(output); assert_eq!(map.len(), 2); assert_eq!(map["foo"], "bar"); assert_eq!(map["bar"], "foobar"); } #[test] fn config_from_interpreter() { // Smoke test to just see whether this works // // PyO3's CI is dependent on Python being installed, so this should be reliable. assert!(make_interpreter_config().is_ok()) } #[test] fn config_from_empty_sysconfigdata() { let sysconfigdata = Sysconfigdata::new(); assert!(InterpreterConfig::from_sysconfigdata(&sysconfigdata).is_err()); } #[test] fn config_from_sysconfigdata() { let mut sysconfigdata = Sysconfigdata::new(); // these are the minimal values required such that InterpreterConfig::from_sysconfigdata // does not error sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu"); sysconfigdata.insert("VERSION", "3.7"); sysconfigdata.insert("Py_ENABLE_SHARED", "1"); sysconfigdata.insert("LIBDIR", "/usr/lib"); sysconfigdata.insert("LDVERSION", "3.7m"); sysconfigdata.insert("SIZEOF_VOID_P", "8"); assert_eq!( InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(), InterpreterConfig { abi3: false, build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata), pointer_width: Some(64), executable: None, implementation: PythonImplementation::CPython, lib_dir: Some("/usr/lib".into()), lib_name: Some("python3.7m".into()), shared: true, version: PythonVersion::PY37, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn config_from_sysconfigdata_framework() { let mut sysconfigdata = Sysconfigdata::new(); sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu"); sysconfigdata.insert("VERSION", "3.7"); // PYTHONFRAMEWORK should override Py_ENABLE_SHARED sysconfigdata.insert("Py_ENABLE_SHARED", "0"); sysconfigdata.insert("PYTHONFRAMEWORK", "Python"); sysconfigdata.insert("LIBDIR", "/usr/lib"); sysconfigdata.insert("LDVERSION", "3.7m"); sysconfigdata.insert("SIZEOF_VOID_P", "8"); assert_eq!( InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(), InterpreterConfig { abi3: false, build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata), pointer_width: Some(64), executable: None, implementation: PythonImplementation::CPython, lib_dir: Some("/usr/lib".into()), lib_name: Some("python3.7m".into()), shared: true, version: PythonVersion::PY37, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); sysconfigdata = Sysconfigdata::new(); sysconfigdata.insert("SOABI", "cpython-37m-x86_64-linux-gnu"); sysconfigdata.insert("VERSION", "3.7"); // An empty PYTHONFRAMEWORK means it is not a framework sysconfigdata.insert("Py_ENABLE_SHARED", "0"); sysconfigdata.insert("PYTHONFRAMEWORK", ""); sysconfigdata.insert("LIBDIR", "/usr/lib"); sysconfigdata.insert("LDVERSION", "3.7m"); sysconfigdata.insert("SIZEOF_VOID_P", "8"); assert_eq!( InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(), InterpreterConfig { abi3: false, build_flags: BuildFlags::from_sysconfigdata(&sysconfigdata), pointer_width: Some(64), executable: None, implementation: PythonImplementation::CPython, lib_dir: Some("/usr/lib".into()), lib_name: Some("python3.7m".into()), shared: false, version: PythonVersion::PY37, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn windows_hardcoded_abi3_compile() { let host = triple!("x86_64-pc-windows-msvc"); let min_version = "3.7".parse().unwrap(); assert_eq!( default_abi3_config(&host, min_version).unwrap(), InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 7 }, shared: true, abi3: true, lib_name: Some("python3".into()), lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn unix_hardcoded_abi3_compile() { let host = triple!("x86_64-unknown-linux-gnu"); let min_version = "3.9".parse().unwrap(); assert_eq!( default_abi3_config(&host, min_version).unwrap(), InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 9 }, shared: true, abi3: true, lib_name: None, lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn windows_hardcoded_cross_compile() { let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: Some("C:\\some\\path".into()), pyo3_cross_python_implementation: None, pyo3_cross_python_version: Some("3.7".into()), }; let host = triple!("x86_64-unknown-linux-gnu"); let target = triple!("i686-pc-windows-msvc"); let cross_config = CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) .unwrap() .unwrap(); assert_eq!( default_cross_compile(&cross_config).unwrap(), InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 7 }, shared: true, abi3: false, lib_name: Some("python37".into()), lib_dir: Some("C:\\some\\path".into()), executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn mingw_hardcoded_cross_compile() { let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: Some("/usr/lib/mingw".into()), pyo3_cross_python_implementation: None, pyo3_cross_python_version: Some("3.8".into()), }; let host = triple!("x86_64-unknown-linux-gnu"); let target = triple!("i686-pc-windows-gnu"); let cross_config = CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) .unwrap() .unwrap(); assert_eq!( default_cross_compile(&cross_config).unwrap(), InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 8 }, shared: true, abi3: false, lib_name: Some("python38".into()), lib_dir: Some("/usr/lib/mingw".into()), executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn unix_hardcoded_cross_compile() { let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: Some("/usr/arm64/lib".into()), pyo3_cross_python_implementation: None, pyo3_cross_python_version: Some("3.9".into()), }; let host = triple!("x86_64-unknown-linux-gnu"); let target = triple!("aarch64-unknown-linux-gnu"); let cross_config = CrossCompileConfig::try_from_env_vars_host_target(env_vars, &host, &target) .unwrap() .unwrap(); assert_eq!( default_cross_compile(&cross_config).unwrap(), InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 9 }, shared: true, abi3: false, lib_name: Some("python3.9".into()), lib_dir: Some("/usr/arm64/lib".into()), executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn pypy_hardcoded_cross_compile() { let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: None, pyo3_cross_python_implementation: Some("PyPy".into()), pyo3_cross_python_version: Some("3.11".into()), }; let triple = triple!("x86_64-unknown-linux-gnu"); let cross_config = CrossCompileConfig::try_from_env_vars_host_target(env_vars, &triple, &triple) .unwrap() .unwrap(); assert_eq!( default_cross_compile(&cross_config).unwrap(), InterpreterConfig { implementation: PythonImplementation::PyPy, version: PythonVersion { major: 3, minor: 11 }, shared: true, abi3: false, lib_name: Some("pypy3.11-c".into()), lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ); } #[test] fn default_lib_name_windows() { use PythonImplementation::*; assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, CPython, false, false, false, false, ) .unwrap(), "python39", ); assert!(super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, CPython, false, false, false, true, ) .is_err()); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, CPython, true, false, false, false, ) .unwrap(), "python3", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, CPython, false, true, false, false, ) .unwrap(), "python3.9", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, CPython, true, true, false, false, ) .unwrap(), "python3", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, PyPy, true, false, false, false, ) .unwrap(), "libpypy3.9-c", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 11 }, PyPy, false, false, false, false, ) .unwrap(), "libpypy3.11-c", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, CPython, false, false, true, false, ) .unwrap(), "python39_d", ); // abi3 debug builds on windows use version-specific lib on 3.9 and older // to workaround https://github.com/python/cpython/issues/101614 assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 9 }, CPython, true, false, true, false, ) .unwrap(), "python39_d", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 10 }, CPython, true, false, true, false, ) .unwrap(), "python3_d", ); // Python versions older than 3.13 don't support gil_disabled assert!(super::default_lib_name_windows( PythonVersion { major: 3, minor: 12, }, CPython, false, false, false, true, ) .is_err()); // mingw and free-threading are incompatible (until someone adds support) assert!(super::default_lib_name_windows( PythonVersion { major: 3, minor: 12, }, CPython, false, true, false, true, ) .is_err()); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 13 }, CPython, false, false, false, true, ) .unwrap(), "python313t", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 13 }, CPython, true, // abi3 true should not affect the free-threaded lib name false, false, true, ) .unwrap(), "python313t", ); assert_eq!( super::default_lib_name_windows( PythonVersion { major: 3, minor: 13 }, CPython, false, false, true, true, ) .unwrap(), "python313t_d", ); } #[test] fn default_lib_name_unix() { use PythonImplementation::*; // Defaults to python3.7m for CPython 3.7 assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 7 }, CPython, false, false, None, false ) .unwrap(), "python3.7m", ); // Defaults to pythonX.Y for CPython 3.8+ assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 8 }, CPython, false, false, None, false ) .unwrap(), "python3.8", ); assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 9 }, CPython, false, false, None, false ) .unwrap(), "python3.9", ); // Can use ldversion to override for CPython assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 9 }, CPython, false, false, Some("3.7md"), false ) .unwrap(), "python3.7md", ); // PyPy 3.11 includes ldversion assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 11 }, PyPy, false, false, None, false ) .unwrap(), "pypy3.11-c", ); assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 9 }, PyPy, false, false, Some("3.11d"), false ) .unwrap(), "pypy3.11d-c", ); // free-threading adds a t suffix assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 13 }, CPython, false, false, None, true ) .unwrap(), "python3.13t", ); // 3.12 and older are incompatible with gil_disabled assert!(super::default_lib_name_unix( PythonVersion { major: 3, minor: 12, }, CPython, false, false, None, true, ) .is_err()); // cygwin abi3 links to unversioned libpython assert_eq!( super::default_lib_name_unix( PythonVersion { major: 3, minor: 13 }, CPython, true, true, None, false ) .unwrap(), "python3", ); } #[test] fn parse_cross_python_version() { let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: None, pyo3_cross_python_version: Some("3.9".into()), pyo3_cross_python_implementation: None, }; assert_eq!( env_vars.parse_version().unwrap(), (Some(PythonVersion { major: 3, minor: 9 }), None), ); let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: None, pyo3_cross_python_version: None, pyo3_cross_python_implementation: None, }; assert_eq!(env_vars.parse_version().unwrap(), (None, None)); let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: None, pyo3_cross_python_version: Some("3.13t".into()), pyo3_cross_python_implementation: None, }; assert_eq!( env_vars.parse_version().unwrap(), ( Some(PythonVersion { major: 3, minor: 13 }), Some("t".into()) ), ); let env_vars = CrossCompileEnvVars { pyo3_cross: None, pyo3_cross_lib_dir: None, pyo3_cross_python_version: Some("100".into()), pyo3_cross_python_implementation: None, }; assert!(env_vars.parse_version().is_err()); } #[test] fn interpreter_version_reduced_to_abi3() { let mut config = InterpreterConfig { abi3: true, build_flags: BuildFlags::default(), pointer_width: None, executable: None, implementation: PythonImplementation::CPython, lib_dir: None, lib_name: None, shared: true, version: PythonVersion { major: 3, minor: 7 }, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; config .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 7 })) .unwrap(); assert_eq!(config.version, PythonVersion { major: 3, minor: 7 }); } #[test] fn abi3_version_cannot_be_higher_than_interpreter() { let mut config = InterpreterConfig { abi3: true, build_flags: BuildFlags::new(), pointer_width: None, executable: None, implementation: PythonImplementation::CPython, lib_dir: None, lib_name: None, shared: true, version: PythonVersion { major: 3, minor: 7 }, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; assert!(config .fixup_for_abi3_version(Some(PythonVersion { major: 3, minor: 8 })) .unwrap_err() .to_string() .contains( "cannot set a minimum Python version 3.8 higher than the interpreter version 3.7" )); } #[test] #[cfg(all( target_os = "linux", target_arch = "x86_64", feature = "resolve-config" ))] fn parse_sysconfigdata() { // A best effort attempt to get test coverage for the sysconfigdata parsing. // Might not complete successfully depending on host installation; that's ok as long as // CI demonstrates this path is covered! let interpreter_config = crate::get(); let lib_dir = match &interpreter_config.lib_dir { Some(lib_dir) => Path::new(lib_dir), // Don't know where to search for sysconfigdata; never mind. None => return, }; let cross = CrossCompileConfig { lib_dir: Some(lib_dir.into()), version: Some(interpreter_config.version), implementation: Some(interpreter_config.implementation), target: triple!("x86_64-unknown-linux-gnu"), abiflags: if interpreter_config.is_free_threaded() { Some("t".into()) } else { None }, }; let sysconfigdata_path = match find_sysconfigdata(&cross) { Ok(Some(path)) => path, // Couldn't find a matching sysconfigdata; never mind! _ => return, }; let sysconfigdata = super::parse_sysconfigdata(sysconfigdata_path).unwrap(); let parsed_config = InterpreterConfig::from_sysconfigdata(&sysconfigdata).unwrap(); assert_eq!( parsed_config, InterpreterConfig { abi3: false, build_flags: BuildFlags(interpreter_config.build_flags.0.clone()), pointer_width: Some(64), executable: None, implementation: PythonImplementation::CPython, lib_dir: interpreter_config.lib_dir.to_owned(), lib_name: interpreter_config.lib_name.to_owned(), shared: true, version: interpreter_config.version, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, } ) } #[test] fn test_venv_interpreter() { let base = OsStr::new("base"); assert_eq!( venv_interpreter(base, true), PathBuf::from_iter(&["base", "Scripts", "python.exe"]) ); assert_eq!( venv_interpreter(base, false), PathBuf::from_iter(&["base", "bin", "python"]) ); } #[test] fn test_conda_env_interpreter() { let base = OsStr::new("base"); assert_eq!( conda_env_interpreter(base, true), PathBuf::from_iter(&["base", "python.exe"]) ); assert_eq!( conda_env_interpreter(base, false), PathBuf::from_iter(&["base", "bin", "python"]) ); } #[test] fn test_not_cross_compiling_from_to() { assert!(cross_compiling_from_to( &triple!("x86_64-unknown-linux-gnu"), &triple!("x86_64-unknown-linux-gnu"), ) .unwrap() .is_none()); assert!(cross_compiling_from_to( &triple!("x86_64-apple-darwin"), &triple!("x86_64-apple-darwin") ) .unwrap() .is_none()); assert!(cross_compiling_from_to( &triple!("aarch64-apple-darwin"), &triple!("x86_64-apple-darwin") ) .unwrap() .is_none()); assert!(cross_compiling_from_to( &triple!("x86_64-apple-darwin"), &triple!("aarch64-apple-darwin") ) .unwrap() .is_none()); assert!(cross_compiling_from_to( &triple!("x86_64-pc-windows-msvc"), &triple!("i686-pc-windows-msvc") ) .unwrap() .is_none()); assert!(cross_compiling_from_to( &triple!("x86_64-unknown-linux-gnu"), &triple!("x86_64-unknown-linux-musl") ) .unwrap() .is_none()); assert!(cross_compiling_from_to( &triple!("x86_64-pc-windows-msvc"), &triple!("x86_64-win7-windows-msvc"), ) .unwrap() .is_none()); } #[test] fn test_is_cross_compiling_from_to() { assert!(cross_compiling_from_to( &triple!("x86_64-pc-windows-msvc"), &triple!("aarch64-pc-windows-msvc") ) .unwrap() .is_some()); } #[test] fn test_run_python_script() { // as above, this should be okay in CI where Python is presumed installed let interpreter = make_interpreter_config() .expect("could not get InterpreterConfig from installed interpreter"); let out = interpreter .run_python_script("print(2 + 2)") .expect("failed to run Python script"); assert_eq!(out.trim_end(), "4"); } #[test] fn test_run_python_script_with_envs() { // as above, this should be okay in CI where Python is presumed installed let interpreter = make_interpreter_config() .expect("could not get InterpreterConfig from installed interpreter"); let out = interpreter .run_python_script_with_envs( "import os; print(os.getenv('PYO3_TEST'))", vec![("PYO3_TEST", "42")], ) .expect("failed to run Python script"); assert_eq!(out.trim_end(), "42"); } #[test] fn test_build_script_outputs_base() { let interpreter_config = InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 11, }, shared: true, abi3: false, lib_name: Some("python3".into()), lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; assert_eq!( interpreter_config.build_script_outputs(), [ "cargo:rustc-cfg=Py_3_7".to_owned(), "cargo:rustc-cfg=Py_3_8".to_owned(), "cargo:rustc-cfg=Py_3_9".to_owned(), "cargo:rustc-cfg=Py_3_10".to_owned(), "cargo:rustc-cfg=Py_3_11".to_owned(), ] ); let interpreter_config = InterpreterConfig { implementation: PythonImplementation::PyPy, ..interpreter_config }; assert_eq!( interpreter_config.build_script_outputs(), [ "cargo:rustc-cfg=Py_3_7".to_owned(), "cargo:rustc-cfg=Py_3_8".to_owned(), "cargo:rustc-cfg=Py_3_9".to_owned(), "cargo:rustc-cfg=Py_3_10".to_owned(), "cargo:rustc-cfg=Py_3_11".to_owned(), "cargo:rustc-cfg=PyPy".to_owned(), ] ); } #[test] fn test_build_script_outputs_abi3() { let interpreter_config = InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 9 }, shared: true, abi3: true, lib_name: Some("python3".into()), lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; assert_eq!( interpreter_config.build_script_outputs(), [ "cargo:rustc-cfg=Py_3_7".to_owned(), "cargo:rustc-cfg=Py_3_8".to_owned(), "cargo:rustc-cfg=Py_3_9".to_owned(), "cargo:rustc-cfg=Py_LIMITED_API".to_owned(), ] ); let interpreter_config = InterpreterConfig { implementation: PythonImplementation::PyPy, ..interpreter_config }; assert_eq!( interpreter_config.build_script_outputs(), [ "cargo:rustc-cfg=Py_3_7".to_owned(), "cargo:rustc-cfg=Py_3_8".to_owned(), "cargo:rustc-cfg=Py_3_9".to_owned(), "cargo:rustc-cfg=PyPy".to_owned(), "cargo:rustc-cfg=Py_LIMITED_API".to_owned(), ] ); } #[test] fn test_build_script_outputs_gil_disabled() { let mut build_flags = BuildFlags::default(); build_flags.0.insert(BuildFlag::Py_GIL_DISABLED); let interpreter_config = InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 13, }, shared: true, abi3: false, lib_name: Some("python3".into()), lib_dir: None, executable: None, pointer_width: None, build_flags, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; assert_eq!( interpreter_config.build_script_outputs(), [ "cargo:rustc-cfg=Py_3_7".to_owned(), "cargo:rustc-cfg=Py_3_8".to_owned(), "cargo:rustc-cfg=Py_3_9".to_owned(), "cargo:rustc-cfg=Py_3_10".to_owned(), "cargo:rustc-cfg=Py_3_11".to_owned(), "cargo:rustc-cfg=Py_3_12".to_owned(), "cargo:rustc-cfg=Py_3_13".to_owned(), "cargo:rustc-cfg=Py_GIL_DISABLED".to_owned(), ] ); } #[test] fn test_build_script_outputs_debug() { let mut build_flags = BuildFlags::default(); build_flags.0.insert(BuildFlag::Py_DEBUG); let interpreter_config = InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 7 }, shared: true, abi3: false, lib_name: Some("python3".into()), lib_dir: None, executable: None, pointer_width: None, build_flags, suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; assert_eq!( interpreter_config.build_script_outputs(), [ "cargo:rustc-cfg=Py_3_7".to_owned(), "cargo:rustc-cfg=py_sys_config=\"Py_DEBUG\"".to_owned(), ] ); } #[test] fn test_find_sysconfigdata_in_invalid_lib_dir() { let e = find_all_sysconfigdata(&CrossCompileConfig { lib_dir: Some(PathBuf::from("/abc/123/not/a/real/path")), version: None, implementation: None, target: triple!("x86_64-unknown-linux-gnu"), abiflags: None, }) .unwrap_err(); // actual error message is platform-dependent, so just check the context we add assert!(e.report().to_string().starts_with( "failed to search the lib dir at 'PYO3_CROSS_LIB_DIR=/abc/123/not/a/real/path'\n\ caused by:\n \ - 0: failed to list the entries in '/abc/123/not/a/real/path'\n \ - 1: \ " )); } #[test] fn test_from_pyo3_config_file_env_rebuild() { READ_ENV_VARS.with(|vars| vars.borrow_mut().clear()); let _ = InterpreterConfig::from_pyo3_config_file_env(); // it's possible that other env vars were also read, hence just checking for contains READ_ENV_VARS.with(|vars| assert!(vars.borrow().contains(&"PYO3_CONFIG_FILE".to_string()))); } #[test] fn test_apply_default_lib_name_to_config_file() { let mut config = InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 9 }, shared: true, abi3: false, lib_name: None, lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; let unix = Triple::from_str("x86_64-unknown-linux-gnu").unwrap(); let win_x64 = Triple::from_str("x86_64-pc-windows-msvc").unwrap(); let win_arm64 = Triple::from_str("aarch64-pc-windows-msvc").unwrap(); config.apply_default_lib_name_to_config_file(&unix); assert_eq!(config.lib_name, Some("python3.9".into())); config.lib_name = None; config.apply_default_lib_name_to_config_file(&win_x64); assert_eq!(config.lib_name, Some("python39".into())); config.lib_name = None; config.apply_default_lib_name_to_config_file(&win_arm64); assert_eq!(config.lib_name, Some("python39".into())); // PyPy config.implementation = PythonImplementation::PyPy; config.version = PythonVersion { major: 3, minor: 11, }; config.lib_name = None; config.apply_default_lib_name_to_config_file(&unix); assert_eq!(config.lib_name, Some("pypy3.11-c".into())); config.lib_name = None; config.apply_default_lib_name_to_config_file(&win_x64); assert_eq!(config.lib_name, Some("libpypy3.11-c".into())); config.implementation = PythonImplementation::CPython; // Free-threaded config.build_flags.0.insert(BuildFlag::Py_GIL_DISABLED); config.version = PythonVersion { major: 3, minor: 13, }; config.lib_name = None; config.apply_default_lib_name_to_config_file(&unix); assert_eq!(config.lib_name, Some("python3.13t".into())); config.lib_name = None; config.apply_default_lib_name_to_config_file(&win_x64); assert_eq!(config.lib_name, Some("python313t".into())); config.lib_name = None; config.apply_default_lib_name_to_config_file(&win_arm64); assert_eq!(config.lib_name, Some("python313t".into())); config.build_flags.0.remove(&BuildFlag::Py_GIL_DISABLED); // abi3 config.abi3 = true; config.lib_name = None; config.apply_default_lib_name_to_config_file(&unix); assert_eq!(config.lib_name, Some("python3.13".into())); config.lib_name = None; config.apply_default_lib_name_to_config_file(&win_x64); assert_eq!(config.lib_name, Some("python3".into())); config.lib_name = None; config.apply_default_lib_name_to_config_file(&win_arm64); assert_eq!(config.lib_name, Some("python3".into())); } } ================================================ FILE: pyo3-build-config/src/lib.rs ================================================ //! Configuration used by PyO3 for conditional support of varying Python versions. //! //! This crate exposes functionality to be called from build scripts to simplify building crates //! which depend on PyO3. //! //! It used internally by the PyO3 crate's build script to apply the same configuration. #![warn(elided_lifetimes_in_paths, unused_lifetimes)] mod errors; mod impl_; #[cfg(feature = "resolve-config")] use std::{ io::Cursor, path::{Path, PathBuf}, }; use std::{env, process::Command, str::FromStr, sync::OnceLock}; pub use impl_::{ cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags, CrossCompileConfig, InterpreterConfig, PythonImplementation, PythonVersion, Triple, }; use target_lexicon::OperatingSystem; /// Adds all the [`#[cfg]` flags](index.html) to the current compilation. /// /// This should be called from a build script. /// /// The full list of attributes added are the following: /// /// | Flag | Description | /// | ---- | ----------- | /// | `#[cfg(Py_3_7)]`, `#[cfg(Py_3_8)]`, `#[cfg(Py_3_9)]`, `#[cfg(Py_3_10)]` | These attributes mark code only for a given Python version and up. For example, `#[cfg(Py_3_7)]` marks code which can run on Python 3.7 **and newer**. | /// | `#[cfg(Py_LIMITED_API)]` | This marks code which is run when compiling with PyO3's `abi3` feature enabled. | /// | `#[cfg(Py_GIL_DISABLED)]` | This marks code which is run on the free-threaded interpreter. | /// | `#[cfg(PyPy)]` | This marks code which is run when compiling for PyPy. | /// | `#[cfg(GraalPy)]` | This marks code which is run when compiling for GraalPy. | /// /// For examples of how to use these attributes, #[doc = concat!("[see PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution/multiple_python_versions.html)")] /// . #[cfg(feature = "resolve-config")] pub fn use_pyo3_cfgs() { print_expected_cfgs(); for cargo_command in get().build_script_outputs() { println!("{cargo_command}") } } /// Adds linker arguments suitable for linking an extension module. /// /// This should be called from a build script. /// /// The following link flags are added: /// - macOS: `-undefined dynamic_lookup` /// - wasm32-unknown-emscripten: for Rust <= 1.95, `-sSIDE_MODULE=2 -sWASM_BIGINT` /// /// All other platforms currently are no-ops, however this may change as necessary /// in future. pub fn add_extension_module_link_args() { _add_extension_module_link_args( &impl_::target_triple_from_env(), std::io::stdout(), rustc_minor_version(), ) } fn _add_extension_module_link_args( triple: &Triple, mut writer: impl std::io::Write, rustc_minor_version: Option, ) { if matches!(triple.operating_system, OperatingSystem::Darwin(_)) { writeln!(writer, "cargo:rustc-cdylib-link-arg=-undefined").unwrap(); writeln!(writer, "cargo:rustc-cdylib-link-arg=dynamic_lookup").unwrap(); } else if triple == &Triple::from_str("wasm32-unknown-emscripten").unwrap() && rustc_minor_version.is_some_and(|version| version < 95) { writeln!(writer, "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2").unwrap(); writeln!(writer, "cargo:rustc-cdylib-link-arg=-sWASM_BIGINT").unwrap(); } } /// Adds linker arguments to set rpath when embedding Python within a Rust binary. /// /// When running tests or binaries built with PyO3, the Python dynamic library needs /// to be found at runtime. /// /// This can be done by setting environment variables like `DYLD_LIBRARY_PATH` on macOS, /// `LD_LIBRARY_PATH` on Linux, or `PATH` on Windows. /// /// Altrnatively (as per this function) rpath can be set at link time to point to the /// directory containing the Python dynamic library. This avoids the need to set environment /// variables, so can be convenient, however may not be appropriate for binaries packaged /// for distribution. /// #[doc = concat!("[See PyO3's guide](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution#dynamically-embedding-the-python-interpreter)")] /// for more details. #[cfg(feature = "resolve-config")] pub fn add_libpython_rpath_link_args() { let target = impl_::target_triple_from_env(); _add_libpython_rpath_link_args( get(), impl_::is_linking_libpython_for_target(&target), std::io::stdout(), ) } #[cfg(feature = "resolve-config")] fn _add_libpython_rpath_link_args( interpreter_config: &InterpreterConfig, is_linking_libpython: bool, mut writer: impl std::io::Write, ) { if is_linking_libpython { if let Some(lib_dir) = interpreter_config.lib_dir.as_ref() { writeln!(writer, "cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}").unwrap(); } } } /// Adds linker arguments suitable for linking against the Python framework on macOS. /// /// This should be called from a build script. /// /// The following link flags are added: /// - macOS: `-Wl,-rpath,` /// /// All other platforms currently are no-ops. #[cfg(feature = "resolve-config")] pub fn add_python_framework_link_args() { let target = impl_::target_triple_from_env(); _add_python_framework_link_args( get(), &target, impl_::is_linking_libpython_for_target(&target), std::io::stdout(), ) } #[cfg(feature = "resolve-config")] fn _add_python_framework_link_args( interpreter_config: &InterpreterConfig, triple: &Triple, link_libpython: bool, mut writer: impl std::io::Write, ) { if matches!(triple.operating_system, OperatingSystem::Darwin(_)) && link_libpython { if let Some(framework_prefix) = interpreter_config.python_framework_prefix.as_ref() { writeln!(writer, "cargo:rustc-link-arg=-Wl,-rpath,{framework_prefix}").unwrap(); } } } /// Loads the configuration determined from the build environment. /// /// Because this will never change in a given compilation run, this is cached in a `OnceLock`. #[cfg(feature = "resolve-config")] pub fn get() -> &'static InterpreterConfig { static CONFIG: OnceLock = OnceLock::new(); CONFIG.get_or_init(|| { // Check if we are in a build script and cross compiling to a different target. let cross_compile_config_path = resolve_cross_compile_config_path(); let cross_compiling = cross_compile_config_path .as_ref() .map(|path| path.exists()) .unwrap_or(false); #[allow( clippy::const_is_empty, reason = "CONFIG_FILE is generated in build.rs, content can vary" )] if let Some(interpreter_config) = InterpreterConfig::from_cargo_dep_env() { interpreter_config } else if let Some(interpreter_config) = config_from_pyo3_config_file_env() { Ok(interpreter_config) } else if cross_compiling { InterpreterConfig::from_path(cross_compile_config_path.as_ref().unwrap()) } else { InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG)) } .expect("failed to parse PyO3 config") }) } /// Build configuration provided by `PYO3_CONFIG_FILE`, inlined into the `pyo3-build-config` binary. #[cfg(feature = "resolve-config")] fn config_from_pyo3_config_file_env() -> Option { #[doc(hidden)] const CONFIG_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config-file.txt")); #[allow( clippy::const_is_empty, reason = "CONFIG_FILE is generated in build.rs, content can vary" )] if !CONFIG_FILE.is_empty() { let config = InterpreterConfig::from_reader(Cursor::new(CONFIG_FILE)) .expect("contents of CONFIG_FILE should always be valid (generated by pyo3-build-config's build.rs)"); Some(config) } else { None } } /// Build configuration discovered by `pyo3-build-config` build script. Not aware of /// cross-compilation settings. Not generated if `PYO3_CONFIG_FILE` is set. #[doc(hidden)] #[cfg(feature = "resolve-config")] const HOST_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/pyo3-build-config.txt")); /// Returns the path where PyO3's build.rs writes its cross compile configuration. /// /// The config file will be named `$OUT_DIR//pyo3-build-config.txt`. /// /// Must be called from a build script, returns `None` if not. #[doc(hidden)] #[cfg(feature = "resolve-config")] fn resolve_cross_compile_config_path() -> Option { env::var_os("TARGET").map(|target| { let mut path = PathBuf::from(env!("OUT_DIR")); path.push(Path::new(&target)); path.push("pyo3-build-config.txt"); path }) } /// Helper to print a feature cfg with a minimum rust version required. fn print_feature_cfg(minor_version_required: u32, cfg: &str) { let minor_version = rustc_minor_version().unwrap_or(0); if minor_version >= minor_version_required { println!("cargo:rustc-cfg={cfg}"); } // rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before if minor_version >= 80 { println!("cargo:rustc-check-cfg=cfg({cfg})"); } } /// Use certain features if we detect the compiler being used supports them. /// /// Features may be removed or added as MSRV gets bumped or new features become available, /// so this function is unstable. #[doc(hidden)] pub fn print_feature_cfgs() { print_feature_cfg(85, "fn_ptr_eq"); print_feature_cfg(86, "from_bytes_with_nul_error"); } /// Registers `pyo3`s config names as reachable cfg expressions /// /// - /// - #[doc(hidden)] pub fn print_expected_cfgs() { if rustc_minor_version().is_some_and(|version| version < 80) { // rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before return; } println!("cargo:rustc-check-cfg=cfg(Py_LIMITED_API)"); println!("cargo:rustc-check-cfg=cfg(Py_GIL_DISABLED)"); println!("cargo:rustc-check-cfg=cfg(PyPy)"); println!("cargo:rustc-check-cfg=cfg(GraalPy)"); println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))"); println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)"); println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)"); // allow `Py_3_*` cfgs from the minimum supported version up to the // maximum minor version (+1 for development for the next) for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::ABI3_MAX_MINOR + 1 { println!("cargo:rustc-check-cfg=cfg(Py_3_{i})"); } // pyo3_dll cfg for raw-dylib linking on Windows let mut dll_names = vec!["python3".to_string(), "python3_d".to_string()]; for i in impl_::MINIMUM_SUPPORTED_VERSION.minor..=impl_::ABI3_MAX_MINOR + 1 { dll_names.push(format!("python3{i}")); dll_names.push(format!("python3{i}_d")); if i >= 13 { dll_names.push(format!("python3{i}t")); dll_names.push(format!("python3{i}t_d")); } } // PyPy DLL names (libpypy3.X-c.dll) for i in impl_::MINIMUM_SUPPORTED_VERSION_PYPY.minor..=impl_::MAXIMUM_SUPPORTED_VERSION_PYPY.minor { dll_names.push(format!("libpypy3.{i}-c")); } let values = dll_names .iter() .map(|n| format!("\"{n}\"")) .collect::>() .join(", "); println!("cargo:rustc-check-cfg=cfg(pyo3_dll, values({values}))"); } /// Private exports used in PyO3's build.rs /// /// Please don't use these - they could change at any time. #[doc(hidden)] #[cfg(feature = "resolve-config")] pub mod pyo3_build_script_impl { use crate::errors::{Context, Result}; use super::*; pub mod errors { pub use crate::errors::*; } pub use crate::impl_::{ cargo_env_var, env_var, is_linking_libpython_for_target, make_cross_compile_config, target_triple_from_env, InterpreterConfig, PythonVersion, }; pub enum BuildConfigSource { /// Config was provided by `PYO3_CONFIG_FILE`. ConfigFile, /// Config was found by an interpreter on the host system. Host, /// Config was configured by cross-compilation settings. CrossCompile, } pub struct BuildConfig { pub interpreter_config: InterpreterConfig, pub source: BuildConfigSource, } /// Gets the configuration for use from `pyo3-ffi`'s build script. /// /// Differs from `.get()` in three ways: /// 1. The cargo_dep_env config is not yet available (exported by `pyo3-ffi`'s build script). /// 1. If `PYO3_CONFIG_FILE` is set, lib name is fixed up and the windows import libs might be generated. /// 2. The cross-compile config file is generated if necessary. /// /// Steps 2 and 3 are necessary because `pyo3-ffi`'s build script is the first code run which knows /// the correct target triple. pub fn resolve_build_config(target: &Triple) -> Result { #[allow( clippy::const_is_empty, reason = "CONFIG_FILE is generated in build.rs, content can vary" )] if let Some(mut interpreter_config) = config_from_pyo3_config_file_env() { interpreter_config.apply_default_lib_name_to_config_file(target); Ok(BuildConfig { interpreter_config, source: BuildConfigSource::ConfigFile, }) } else if let Some(interpreter_config) = make_cross_compile_config()? { // This is a cross compile and need to write the config file. let path = resolve_cross_compile_config_path() .expect("resolve_build_config() must be called from a build script"); let parent_dir = path.parent().ok_or_else(|| { format!( "failed to resolve parent directory of config file {}", path.display() ) })?; std::fs::create_dir_all(parent_dir).with_context(|| { format!( "failed to create config file directory {}", parent_dir.display() ) })?; interpreter_config.to_writer(&mut std::fs::File::create(&path).with_context( || format!("failed to create config file at {}", path.display()), )?)?; Ok(BuildConfig { interpreter_config, source: BuildConfigSource::CrossCompile, }) } else { let interpreter_config = InterpreterConfig::from_reader(Cursor::new(HOST_CONFIG))?; Ok(BuildConfig { interpreter_config, source: BuildConfigSource::Host, }) } } /// Helper to generate an error message when the configured Python version is newer /// than PyO3's current supported version. pub struct MaximumVersionExceeded { message: String, } impl MaximumVersionExceeded { pub fn new( interpreter_config: &InterpreterConfig, supported_version: PythonVersion, ) -> Self { let implementation = match interpreter_config.implementation { PythonImplementation::CPython => "Python", PythonImplementation::PyPy => "PyPy", PythonImplementation::GraalPy => "GraalPy", }; let version = &interpreter_config.version; let message = format!( "the configured {implementation} version ({version}) is newer than PyO3's maximum supported version ({supported_version})\n\ = help: this package is being built with PyO3 version {current_version}\n\ = help: check https://crates.io/crates/pyo3 for the latest PyO3 version available\n\ = help: updating this package to the latest version of PyO3 may provide compatibility with this {implementation} version", current_version = env!("CARGO_PKG_VERSION") ); Self { message } } pub fn add_help(&mut self, help: &str) { self.message.push_str("\n= help: "); self.message.push_str(help); } pub fn finish(self) -> String { self.message } } } fn rustc_minor_version() -> Option { static RUSTC_MINOR_VERSION: OnceLock> = OnceLock::new(); *RUSTC_MINOR_VERSION.get_or_init(|| { let rustc = env::var_os("RUSTC")?; let output = Command::new(rustc).arg("--version").output().ok()?; let version = core::str::from_utf8(&output.stdout).ok()?; let mut pieces = version.split('.'); if pieces.next() != Some("rustc 1") { return None; } pieces.next()?.parse().ok() }) } #[cfg(test)] mod tests { use super::*; #[test] fn extension_module_link_args() { let mut buf = Vec::new(); // Does nothing on non-mac _add_extension_module_link_args( &Triple::from_str("x86_64-pc-windows-msvc").unwrap(), &mut buf, None, ); assert_eq!(buf, Vec::new()); _add_extension_module_link_args( &Triple::from_str("x86_64-apple-darwin").unwrap(), &mut buf, None, ); assert_eq!( std::str::from_utf8(&buf).unwrap(), "cargo:rustc-cdylib-link-arg=-undefined\n\ cargo:rustc-cdylib-link-arg=dynamic_lookup\n" ); buf.clear(); _add_extension_module_link_args( &Triple::from_str("wasm32-unknown-emscripten").unwrap(), &mut buf, Some(94), ); assert_eq!( std::str::from_utf8(&buf).unwrap(), "cargo:rustc-cdylib-link-arg=-sSIDE_MODULE=2\n\ cargo:rustc-cdylib-link-arg=-sWASM_BIGINT\n" ); buf.clear(); _add_extension_module_link_args( &Triple::from_str("wasm32-unknown-emscripten").unwrap(), &mut buf, Some(95), ); assert_eq!(std::str::from_utf8(&buf).unwrap(), ""); } #[cfg(feature = "resolve-config")] #[test] fn python_framework_link_args() { let mut buf = Vec::new(); let interpreter_config = InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 13, }, shared: true, abi3: false, lib_name: None, lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: Some( "/Applications/Xcode.app/Contents/Developer/Library/Frameworks".to_string(), ), }; // Does nothing on non-mac _add_python_framework_link_args( &interpreter_config, &Triple::from_str("x86_64-pc-windows-msvc").unwrap(), true, &mut buf, ); assert_eq!(buf, Vec::new()); _add_python_framework_link_args( &interpreter_config, &Triple::from_str("x86_64-apple-darwin").unwrap(), true, &mut buf, ); assert_eq!( std::str::from_utf8(&buf).unwrap(), "cargo:rustc-link-arg=-Wl,-rpath,/Applications/Xcode.app/Contents/Developer/Library/Frameworks\n" ); } #[test] #[cfg(feature = "resolve-config")] fn test_maximum_version_exceeded_formatting() { let interpreter_config = InterpreterConfig { implementation: PythonImplementation::CPython, version: PythonVersion { major: 3, minor: 13, }, shared: true, abi3: false, lib_name: None, lib_dir: None, executable: None, pointer_width: None, build_flags: BuildFlags::default(), suppress_build_script_link_lines: false, extra_build_script_lines: vec![], python_framework_prefix: None, }; let mut error = pyo3_build_script_impl::MaximumVersionExceeded::new( &interpreter_config, PythonVersion { major: 3, minor: 12, }, ); error.add_help("this is a help message"); let error = error.finish(); let expected = concat!("\ the configured Python version (3.13) is newer than PyO3's maximum supported version (3.12)\n\ = help: this package is being built with PyO3 version ", env!("CARGO_PKG_VERSION"), "\n\ = help: check https://crates.io/crates/pyo3 for the latest PyO3 version available\n\ = help: updating this package to the latest version of PyO3 may provide compatibility with this Python version\n\ = help: this is a help message" ); assert_eq!(error, expected); } } ================================================ FILE: pyo3-ffi/ACKNOWLEDGEMENTS ================================================ This is a Rust reimplementation of the CPython public header files as necessary for binary compatibility, with additional metadata to support PyPy. For original implementations please see: - https://github.com/python/cpython - https://github.com/pypy/pypy ================================================ FILE: pyo3-ffi/Cargo.toml ================================================ [package] name = "pyo3-ffi" version = "0.28.2" description = "Python-API bindings for the PyO3 ecosystem" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] homepage = "https://github.com/pyo3/pyo3" repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" links = "python" rust-version.workspace = true [dependencies] libc = "0.2.62" [features] default = [] # deprecated extension-module = ["pyo3-build-config/extension-module"] # Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more. abi3 = ["pyo3-build-config/abi3"] # With abi3, we can manually set the minimum Python version. abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37"] abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"] abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"] abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310"] abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311"] abi3-py312 = ["abi3-py313", "pyo3-build-config/abi3-py312"] abi3-py313 = ["abi3-py314", "pyo3-build-config/abi3-py313"] abi3-py314 = ["abi3", "pyo3-build-config/abi3-py314"] # deprecated: no longer needed, raw-dylib is used instead generate-import-lib = ["pyo3-build-config/generate-import-lib"] [dev-dependencies] paste = "1" [build-dependencies] pyo3-build-config = { path = "../pyo3-build-config", version = "=0.28.2", features = ["resolve-config"] } [lints] workspace = true [package.metadata.cpython] min-version = "3.7" max-version = "3.15" # inclusive [package.metadata.pypy] min-version = "3.11" max-version = "3.11" # inclusive ================================================ FILE: pyo3-ffi/README.md ================================================ # pyo3-ffi This crate provides [Rust](https://www.rust-lang.org/) FFI declarations for Python 3. It supports both the stable and the unstable component of the ABI through the use of cfg flags. Python Versions 3.7+ are supported. It is meant for advanced users only - regular PyO3 users shouldn't need to interact with this crate at all. The contents of this crate are not documented here, as it would entail basically copying the documentation from CPython. Consult the [Python/C API Reference Manual][capi] for up-to-date documentation. # Minimum supported Rust and Python versions Requires Rust 1.63 or greater. `pyo3-ffi` supports the following Python distributions: - CPython 3.7 or greater - PyPy 7.3 (Python 3.9+) - GraalPy 24.0 or greater (Python 3.10+) # Example: Building Python Native modules PyO3 can be used to generate a native Python module. The easiest way to try this out for the first time is to use [`maturin`]. `maturin` is a tool for building and publishing Rust-based Python packages with minimal configuration. The following steps set up some files for an example Python module, install `maturin`, and then show how to build and import the Python module. First, create a new folder (let's call it `string_sum`) containing the following two files: **`Cargo.toml`** ```toml [lib] name = "string_sum" # "cdylib" is necessary to produce a shared library for Python to import from. # # Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able # to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.: # crate-type = ["cdylib", "rlib"] crate-type = ["cdylib"] [dependencies] pyo3-ffi = "0.28.2" [build-dependencies] # This is only necessary if you need to configure your build based on # the Python version or the compile-time configuration for the interpreter. pyo3_build_config = "0.28.2" ``` If you need to use conditional compilation based on Python version or how Python was compiled, you need to add `pyo3-build-config` as a `build-dependency` in your `Cargo.toml` as in the example above and either create a new `build.rs` file or modify an existing one so that `pyo3_build_config::use_pyo3_cfgs()` gets called at build time: **`build.rs`** ```rust,ignore fn main() { pyo3_build_config::use_pyo3_cfgs() } ``` **`src/lib.rs`** ```rust,no_run #[cfg(Py_3_15)] use std::ffi::c_void; use std::ffi::{c_char, c_long}; use std::ptr; use pyo3_ffi::*; #[cfg(not(Py_3_15))] static mut MODULE_DEF: PyModuleDef = PyModuleDef { m_base: PyModuleDef_HEAD_INIT, m_name: c"string_sum".as_ptr(), m_doc: c"A Python module written in Rust.".as_ptr(), m_size: 0, m_methods: (&raw mut METHODS).cast(), m_slots: (&raw mut SLOTS).cast(), m_traverse: None, m_clear: None, m_free: None, }; static mut METHODS: [PyMethodDef; 2] = [ PyMethodDef { ml_name: c"sum_as_string".as_ptr(), ml_meth: PyMethodDefPointer { PyCFunctionFast: sum_as_string, }, ml_flags: METH_FASTCALL, ml_doc: c"returns the sum of two integers as a string".as_ptr(), }, // A zeroed PyMethodDef to mark the end of the array. PyMethodDef::zeroed(), ]; #[cfg(Py_3_15)] PyABIInfo_VAR!(ABI_INFO); const SLOTS_LEN: usize = 1 + cfg!(Py_3_12) as usize + cfg!(Py_GIL_DISABLED) as usize + 4 * (cfg!(Py_3_15) as usize); static mut SLOTS: [PyModuleDef_Slot; SLOTS_LEN] = [ #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_abi, value: (&raw mut ABI_INFO).cast(), }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_name, // safety: Python does not write to this field value: c"string_sum".as_ptr() as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_doc, // safety: Python does not write to this field value: c"A Python module written in Rust.".as_ptr() as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_methods, value: (&raw mut METHODS).cast(), }, #[cfg(Py_3_12)] PyModuleDef_Slot { slot: Py_mod_multiple_interpreters, value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED, }, #[cfg(Py_GIL_DISABLED)] PyModuleDef_Slot { slot: Py_mod_gil, value: Py_MOD_GIL_NOT_USED, }, PyModuleDef_Slot { slot: 0, value: ptr::null_mut(), }, ]; // The module initialization function #[cfg(not(Py_3_15))] #[allow(non_snake_case, reason = "must be named `PyInit_`")] #[no_mangle] pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject { PyModuleDef_Init(&raw mut MODULE_DEF) } #[cfg(Py_3_15)] #[allow(non_snake_case, reason = "must be named `PyModExport_`")] #[no_mangle] pub unsafe extern "C" fn PyModExport_string_sum() -> *mut PyModuleDef_Slot { (&raw mut SLOTS).cast() } /// A helper to parse function arguments /// If we used PyO3's proc macros they'd handle all of this boilerplate for us :) unsafe fn parse_arg_as_i32(obj: *mut PyObject, n_arg: usize) -> Option { if PyLong_Check(obj) == 0 { let msg = format!( "sum_as_string expected an int for positional argument {}\0", n_arg ); PyErr_SetString(PyExc_TypeError, msg.as_ptr().cast::()); return None; } // Let's keep the behaviour consistent on platforms where `c_long` is bigger than 32 bits. // In particular, it is an i32 on Windows but i64 on most Linux systems let mut overflow = 0; let i_long: c_long = PyLong_AsLongAndOverflow(obj, &mut overflow); #[allow( irrefutable_let_patterns, reason = "some platforms have c_long equal to i32" )] if overflow != 0 { raise_overflowerror(obj); None } else if let Ok(i) = i_long.try_into() { Some(i) } else { raise_overflowerror(obj); None } } unsafe fn raise_overflowerror(obj: *mut PyObject) { let obj_repr = PyObject_Str(obj); if !obj_repr.is_null() { let mut size = 0; let p = PyUnicode_AsUTF8AndSize(obj_repr, &mut size); if !p.is_null() { let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts( p.cast::(), size as usize, )); let msg = format!("cannot fit {} in 32 bits\0", s); PyErr_SetString(PyExc_OverflowError, msg.as_ptr().cast::()); } Py_DECREF(obj_repr); } } pub unsafe extern "C" fn sum_as_string( _self: *mut PyObject, args: *mut *mut PyObject, nargs: Py_ssize_t, ) -> *mut PyObject { if nargs != 2 { PyErr_SetString( PyExc_TypeError, c"sum_as_string expected 2 positional arguments".as_ptr(), ); return std::ptr::null_mut(); } let (first, second) = (*args, *args.add(1)); let first = match parse_arg_as_i32(first, 1) { Some(x) => x, None => return std::ptr::null_mut(), }; let second = match parse_arg_as_i32(second, 2) { Some(x) => x, None => return std::ptr::null_mut(), }; match first.checked_add(second) { Some(sum) => { let string = sum.to_string(); PyUnicode_FromStringAndSize(string.as_ptr().cast::(), string.len() as isize) } None => { PyErr_SetString(PyExc_OverflowError, c"arguments too large to add".as_ptr()); std::ptr::null_mut() } } } ``` With those two files in place, now `maturin` needs to be installed. This can be done using Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin` into it: ```bash $ cd string_sum $ python -m venv .env $ source .env/bin/activate $ pip install maturin ``` Now build and execute the module: ```bash $ maturin develop # lots of progress output as maturin runs the compilation... $ python >>> import string_sum >>> string_sum.sum_as_string(5, 20) '25' ``` As well as with `maturin`, it is possible to build using [setuptools-rust] or [manually][manual_builds]. Both offer more flexibility than `maturin` but require further configuration. While most projects use the safe wrapper provided by PyO3, you can take a look at the [`orjson`] library as an example on how to use `pyo3-ffi` directly. For those well versed in C and Rust the [tutorials] from the CPython documentation can be easily converted to rust as well. [tutorials]: https://docs.python.org/3/extending/ [`orjson`]: https://github.com/ijl/orjson [capi]: https://docs.python.org/3/c-api/index.html [`maturin`]: https://github.com/PyO3/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" [`pyo3-build-config`]: https://docs.rs/pyo3-build-config [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book" [manual_builds]: https://pyo3.rs/latest/building-and-distribution.html#manual-builds "Manual builds - Building and Distribution - PyO3 user guide" [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions" [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI" [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide" ================================================ FILE: pyo3-ffi/build.rs ================================================ use pyo3_build_config::{ bail, ensure, print_feature_cfgs, pyo3_build_script_impl::{ cargo_env_var, env_var, errors::Result, is_linking_libpython_for_target, resolve_build_config, target_triple_from_env, BuildConfig, BuildConfigSource, InterpreterConfig, MaximumVersionExceeded, PythonVersion, }, warn, PythonImplementation, }; /// Minimum Python version PyO3 supports. struct SupportedVersions { min: PythonVersion, max: PythonVersion, } const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 7 }, max: PythonVersion { major: 3, minor: 14, }, }; const SUPPORTED_VERSIONS_PYPY: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 11, }, max: SUPPORTED_VERSIONS_CPYTHON.max, }; const SUPPORTED_VERSIONS_GRAALPY: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 10, }, max: SUPPORTED_VERSIONS_CPYTHON.max, }; fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { // This is an undocumented env var which is only really intended to be used in CI / for testing // and development. if std::env::var("UNSAFE_PYO3_SKIP_VERSION_CHECK").as_deref() == Ok("1") { return Ok(()); } match interpreter_config.implementation { PythonImplementation::CPython => { let versions = SUPPORTED_VERSIONS_CPYTHON; ensure!( interpreter_config.version >= versions.min, "the configured Python interpreter version ({}) is lower than PyO3's minimum supported version ({})", interpreter_config.version, versions.min, ); let v_plus_1 = PythonVersion { major: versions.max.major, minor: versions.max.minor + 1, }; if interpreter_config.version == v_plus_1 { warn!( "Using experimental support for the Python {}.{} ABI. \ Build artifacts may not be compatible with the final release of CPython, \ so do not distribute them.", v_plus_1.major, v_plus_1.minor, ); } else if interpreter_config.version > v_plus_1 { let mut error = MaximumVersionExceeded::new(interpreter_config, versions.max); if interpreter_config.is_free_threaded() { error.add_help( "the free-threaded build of CPython does not support the limited API so this check cannot be suppressed.", ); return Err(error.finish().into()); } if env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").is_none_or(|os_str| os_str != "1") { error.add_help("set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI"); return Err(error.finish().into()); } } if interpreter_config.is_free_threaded() { let min_free_threaded_version = PythonVersion { major: 3, minor: 14, }; ensure!( interpreter_config.version >= min_free_threaded_version, "PyO3 does not support the free-threaded build of CPython versions below {}, the selected Python version is {}", min_free_threaded_version, interpreter_config.version, ); } } PythonImplementation::PyPy => { let versions = SUPPORTED_VERSIONS_PYPY; ensure!( interpreter_config.version >= versions.min, "the configured PyPy interpreter version ({}) is lower than PyO3's minimum supported version ({})", interpreter_config.version, versions.min, ); // PyO3 does not support abi3, so we cannot offer forward compatibility if interpreter_config.version > versions.max { let error = MaximumVersionExceeded::new(interpreter_config, versions.max); return Err(error.finish().into()); } } PythonImplementation::GraalPy => { let versions = SUPPORTED_VERSIONS_GRAALPY; ensure!( interpreter_config.version >= versions.min, "the configured GraalPy interpreter version ({}) is lower than PyO3's minimum supported version ({})", interpreter_config.version, versions.min, ); // GraalPy does not support abi3, so we cannot offer forward compatibility if interpreter_config.version > versions.max { let error = MaximumVersionExceeded::new(interpreter_config, versions.max); return Err(error.finish().into()); } } } if interpreter_config.abi3 { match interpreter_config.implementation { PythonImplementation::CPython => { if interpreter_config.is_free_threaded() { warn!( "The free-threaded build of CPython does not yet support abi3 so the build artifacts will be version-specific." ) } } PythonImplementation::PyPy => warn!( "PyPy does not yet support abi3 so the build artifacts will be version-specific. \ See https://github.com/pypy/pypy/issues/3397 for more information." ), PythonImplementation::GraalPy => warn!( "GraalPy does not support abi3 so the build artifacts will be version-specific." ), } } Ok(()) } fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> { if let Some(pointer_width) = interpreter_config.pointer_width { // Try to check whether the target architecture matches the python library let rust_target = match cargo_env_var("CARGO_CFG_TARGET_POINTER_WIDTH") .unwrap() .as_str() { "64" => 64, "32" => 32, x => bail!("unexpected Rust target pointer width: {}", x), }; ensure!( rust_target == pointer_width, "your Rust target architecture ({}-bit) does not match your python interpreter ({}-bit)", rust_target, pointer_width ); } Ok(()) } fn emit_link_config(build_config: &BuildConfig) -> Result<()> { let interpreter_config = &build_config.interpreter_config; let target_os = cargo_env_var("CARGO_CFG_TARGET_OS").unwrap(); let lib_name = interpreter_config .lib_name .as_ref() .ok_or("attempted to link to Python shared library but config does not contain lib_name")?; if target_os == "windows" { // Use raw-dylib linking: emit a cfg so that `extern_libpython!` picks the // right `#[link(name = "...", kind = "raw-dylib")]` attribute at compile time. // This eliminates the need for import libraries (.lib files) entirely. // // Note: raw-dylib is inherently dynamic linking. Static embedding of the // Python interpreter on Windows is not supported by this path (and is not // officially supported by CPython on Windows). println!("cargo:rustc-cfg=pyo3_dll=\"{lib_name}\""); } else { println!( "cargo:rustc-link-lib={link_model}{lib_name}", link_model = if interpreter_config.shared { "" } else { "static=" }, ); if let Some(lib_dir) = &interpreter_config.lib_dir { println!("cargo:rustc-link-search=native={lib_dir}"); } else if matches!(build_config.source, BuildConfigSource::CrossCompile) { warn!( "The output binary will link to libpython, \ but PYO3_CROSS_LIB_DIR environment variable is not set. \ Ensure that the target Python library directory is \ in the rustc native library search path." ); } } Ok(()) } /// Prepares the PyO3 crate for compilation. /// /// This loads the config from pyo3-build-config and then makes some additional checks to improve UX /// for users. /// /// Emits the cargo configuration based on this config as well as a few checks of the Rust compiler /// version to enable features which aren't supported on MSRV. fn configure_pyo3() -> Result<()> { let target = target_triple_from_env(); let build_config = resolve_build_config(&target)?; let interpreter_config = &build_config.interpreter_config; if env_var("PYO3_PRINT_CONFIG").is_some_and(|os_str| os_str == "1") { print_config_and_exit(interpreter_config); } ensure_python_version(interpreter_config)?; ensure_target_pointer_width(interpreter_config)?; // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var. interpreter_config.to_cargo_dep_env()?; if is_linking_libpython_for_target(&target) && !interpreter_config.suppress_build_script_link_lines { emit_link_config(&build_config)?; } for cfg in interpreter_config.build_script_outputs() { println!("{cfg}") } // Extra lines come last, to support last write wins. for line in &interpreter_config.extra_build_script_lines { println!("{line}"); } print_feature_cfgs(); Ok(()) } fn print_config_and_exit(config: &InterpreterConfig) { println!("\n-- PYO3_PRINT_CONFIG=1 is set, printing configuration and halting compile --"); config .to_writer(std::io::stdout()) .expect("failed to print config to stdout"); println!("\nnote: unset the PYO3_PRINT_CONFIG environment variable and retry to compile with the above config"); std::process::exit(101); } fn main() { pyo3_build_config::print_expected_cfgs(); if let Err(e) = configure_pyo3() { eprintln!("error: {}", e.report()); std::process::exit(1) } } ================================================ FILE: pyo3-ffi/examples/README.md ================================================ # `pyo3-ffi` Examples These example crates are a collection of toy extension modules built with `pyo3-ffi`. They are all tested using `nox` in PyO3's CI. Below is a brief description of each of these: | Example | Description | | `word-count` | Illustrates how to use pyo3-ffi to write a static rust extension | | `sequential` | Illustrates how to use pyo3-ffi to write subinterpreter-safe modules using multi-phase module initialization | ## Creating new projects from these examples To copy an example, use [`cargo-generate`](https://crates.io/crates/cargo-generate). Follow the commands below, replacing `` with the example to start from: ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/ ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: pyo3-ffi/examples/sequential/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [lib] name = "sequential" crate-type = ["cdylib", "lib"] [dependencies] pyo3-ffi = { version = "{{PYO3_VERSION}}" } ================================================ FILE: pyo3-ffi/examples/sequential/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.19.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); ================================================ FILE: pyo3-ffi/examples/sequential/.template/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "{{project-name}}" version = "0.1.0" ================================================ FILE: pyo3-ffi/examples/sequential/Cargo.toml ================================================ [package] name = "sequential" version = "0.1.0" edition = "2021" rust-version = "1.83" [lib] name = "sequential" crate-type = ["cdylib", "lib"] [dependencies] pyo3-ffi = { path = "../../" } [build-dependencies] pyo3-build-config = { path = "../../../pyo3-build-config" } [workspace] ================================================ FILE: pyo3-ffi/examples/sequential/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: pyo3-ffi/examples/sequential/README.md ================================================ # sequential A project built using only `pyo3_ffi`, without any of PyO3's safe api. It supports both subinterpreters and free-threaded Python. ## Building and Testing To build this package, first install `maturin`: ```shell pip install maturin ``` To build and test use `maturin develop`: ```shell pip install -r requirements-dev.txt maturin develop pytest ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/sequential ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: pyo3-ffi/examples/sequential/build.rs ================================================ fn main() { pyo3_build_config::use_pyo3_cfgs(); } ================================================ FILE: pyo3-ffi/examples/sequential/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: pyo3-ffi/examples/sequential/noxfile.py ================================================ import sys import nox @nox.session def python(session): if sys.version_info < (3, 12): session.skip("Python 3.12+ is required") session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install(".[dev]") session.run("pytest") ================================================ FILE: pyo3-ffi/examples/sequential/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "sequential" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] requires-python = ">=3.12" [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: pyo3-ffi/examples/sequential/src/id.rs ================================================ use core::sync::atomic::{AtomicU64, Ordering}; use core::{mem, ptr}; use std::ffi::CString; use std::ffi::{c_char, c_int, c_uint, c_ulonglong, c_void}; use pyo3_ffi::*; #[repr(C)] pub struct PyId { _ob_base: PyObject, id: Id, } static COUNT: AtomicU64 = AtomicU64::new(0); #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] pub struct Id(u64); impl Id { fn new() -> Self { Id(COUNT.fetch_add(1, Ordering::Relaxed)) } } unsafe extern "C" fn id_new( subtype: *mut PyTypeObject, args: *mut PyObject, kwds: *mut PyObject, ) -> *mut PyObject { if PyTuple_Size(args) != 0 || !kwds.is_null() { // We use pyo3-ffi's `c_str!` macro to create null-terminated literals because // Rust's string literals are not null-terminated // On Rust 1.77 or newer you can use `c"text"` instead. PyErr_SetString(PyExc_TypeError, c"Id() takes no arguments".as_ptr()); return ptr::null_mut(); } let f: allocfunc = (*subtype).tp_alloc.unwrap_or(PyType_GenericAlloc); let slf = f(subtype, 0); if slf.is_null() { return ptr::null_mut(); } else { let id = Id::new(); let slf = slf.cast::(); (&raw mut (*slf).id).write(id); } slf } unsafe extern "C" fn id_repr(slf: *mut PyObject) -> *mut PyObject { let slf = slf.cast::(); let id = (*slf).id.0; let string = format!("Id({})", id); PyUnicode_FromStringAndSize(string.as_ptr().cast::(), string.len() as Py_ssize_t) } unsafe extern "C" fn id_int(slf: *mut PyObject) -> *mut PyObject { let slf = slf.cast::(); let id = (*slf).id.0; PyLong_FromUnsignedLongLong(id as c_ulonglong) } unsafe extern "C" fn id_richcompare( slf: *mut PyObject, other: *mut PyObject, op: c_int, ) -> *mut PyObject { let pytype = Py_TYPE(slf); // guaranteed to be `sequential.Id` if Py_TYPE(other) != pytype { return Py_NewRef(Py_NotImplemented()); } let slf = (*slf.cast::()).id; let other = (*other.cast::()).id; let cmp = match op { pyo3_ffi::Py_LT => slf < other, pyo3_ffi::Py_LE => slf <= other, pyo3_ffi::Py_EQ => slf == other, pyo3_ffi::Py_NE => slf != other, pyo3_ffi::Py_GT => slf > other, pyo3_ffi::Py_GE => slf >= other, unrecognized => { let msg = CString::new(&*format!( "unrecognized richcompare opcode {}", unrecognized )) .unwrap(); PyErr_SetString(PyExc_SystemError, msg.as_ptr()); return ptr::null_mut(); } }; if cmp { Py_NewRef(Py_True()) } else { Py_NewRef(Py_False()) } } static mut SLOTS: [PyType_Slot; 6] = [ PyType_Slot { slot: Py_tp_new, pfunc: id_new as *mut c_void, }, PyType_Slot { slot: Py_tp_doc, pfunc: c"An id that is increased every time an instance is created".as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_repr, pfunc: id_repr as *mut c_void, }, PyType_Slot { slot: Py_nb_int, pfunc: id_int as *mut c_void, }, PyType_Slot { slot: Py_tp_richcompare, pfunc: id_richcompare as *mut c_void, }, PyType_Slot { slot: 0, pfunc: ptr::null_mut(), }, ]; pub static mut ID_SPEC: PyType_Spec = PyType_Spec { name: c"sequential.Id".as_ptr(), basicsize: mem::size_of::() as c_int, itemsize: 0, flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint, slots: (&raw mut SLOTS).cast(), }; ================================================ FILE: pyo3-ffi/examples/sequential/src/lib.rs ================================================ use pyo3_ffi::*; mod id; mod module; #[cfg(not(Py_3_15))] use crate::module::MODULE_DEF; #[cfg(Py_3_15)] use crate::module::SEQUENTIAL_SLOTS; #[cfg(not(Py_3_15))] #[allow(non_snake_case, reason = "must be named `PyInit_`")] #[no_mangle] pub unsafe extern "C" fn PyInit_sequential() -> *mut PyObject { PyModuleDef_Init(&raw mut MODULE_DEF) } #[cfg(Py_3_15)] #[allow(non_snake_case, reason = "must be named `PyModExport_`")] #[no_mangle] pub unsafe extern "C" fn PyModExport_sequential() -> *mut PyModuleDef_Slot { (&raw mut SEQUENTIAL_SLOTS).cast() } ================================================ FILE: pyo3-ffi/examples/sequential/src/module.rs ================================================ use core::{mem, ptr}; use pyo3_ffi::*; use std::ffi::{c_int, c_void}; #[cfg(not(Py_3_15))] pub static mut MODULE_DEF: PyModuleDef = PyModuleDef { m_base: PyModuleDef_HEAD_INIT, m_name: c"sequential".as_ptr(), m_doc: c"A library for generating sequential ids, written in Rust.".as_ptr(), m_size: mem::size_of::() as Py_ssize_t, m_methods: std::ptr::null_mut(), m_slots: (&raw mut SEQUENTIAL_SLOTS).cast(), m_traverse: Some(sequential_traverse), m_clear: Some(sequential_clear), m_free: Some(sequential_free), }; #[cfg(Py_3_15)] PyABIInfo_VAR!(ABI_INFO); const SEQUENTIAL_SLOTS_LEN: usize = 2 + cfg!(Py_3_12) as usize + cfg!(Py_GIL_DISABLED) as usize + 7 * (cfg!(Py_3_15) as usize); pub static mut SEQUENTIAL_SLOTS: [PyModuleDef_Slot; SEQUENTIAL_SLOTS_LEN] = [ #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_abi, value: (&raw mut ABI_INFO).cast(), }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_name, // safety: Python does not write to this field value: c"sequential".as_ptr() as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_doc, // safety: Python does not write to this field value: c"A library for generating sequential ids, written in Rust.".as_ptr() as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_state_size, value: mem::size_of::() as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_state_traverse, value: sequential_traverse as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_state_clear, value: sequential_clear as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_state_free, value: sequential_free as *mut c_void, }, PyModuleDef_Slot { slot: Py_mod_exec, value: sequential_exec as *mut c_void, }, #[cfg(Py_3_12)] PyModuleDef_Slot { slot: Py_mod_multiple_interpreters, value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED, }, #[cfg(Py_GIL_DISABLED)] PyModuleDef_Slot { slot: Py_mod_gil, value: Py_MOD_GIL_NOT_USED, }, PyModuleDef_Slot { slot: 0, value: ptr::null_mut(), }, ]; unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int { let state: *mut sequential_state = PyModule_GetState(module).cast(); let id_type = PyType_FromModuleAndSpec(module, &raw mut crate::id::ID_SPEC, ptr::null_mut()); if id_type.is_null() { PyErr_SetString(PyExc_SystemError, c"cannot locate type object".as_ptr()); return -1; } (*state).id_type = id_type.cast::(); PyModule_AddObjectRef(module, c"Id".as_ptr(), id_type) } unsafe extern "C" fn sequential_traverse( module: *mut PyObject, visit: visitproc, arg: *mut c_void, ) -> c_int { let state: *mut sequential_state = PyModule_GetState(module.cast()).cast(); let id_type: *mut PyObject = (*state).id_type.cast(); if id_type.is_null() { 0 } else { (visit)(id_type, arg) } } unsafe extern "C" fn sequential_clear(module: *mut PyObject) -> c_int { let state: *mut sequential_state = PyModule_GetState(module.cast()).cast(); Py_CLEAR((&raw mut (*state).id_type).cast()); 0 } unsafe extern "C" fn sequential_free(module: *mut c_void) { sequential_clear(module.cast()); } #[repr(C)] struct sequential_state { id_type: *mut PyTypeObject, } ================================================ FILE: pyo3-ffi/examples/sequential/tests/test.rs ================================================ use core::ffi::{c_char, CStr}; use core::ptr; use std::thread; use pyo3_ffi::*; use sequential::PyInit_sequential; static COMMAND: &'static CStr= c" from sequential import Id s = sum(int(Id()) for _ in range(12)) "; // Newtype to be able to pass it to another thread. struct State(*mut PyThreadState); unsafe impl Sync for State {} unsafe impl Send for State {} #[test] fn lets_go_fast() -> Result<(), String> { unsafe { let ret = PyImport_AppendInittab(c"sequential".as_ptr(), Some(PyInit_sequential)); if ret == -1 { return Err("could not add module to inittab".into()); } Py_Initialize(); let main_state = PyThreadState_Swap(ptr::null_mut()); const NULL: State = State(ptr::null_mut()); let mut subs = [NULL; 12]; let config = PyInterpreterConfig { use_main_obmalloc: 0, allow_fork: 0, allow_exec: 0, allow_threads: 1, allow_daemon_threads: 0, check_multi_interp_extensions: 1, gil: PyInterpreterConfig_OWN_GIL, }; for State(state) in &mut subs { let status = Py_NewInterpreterFromConfig(state, &config); if PyStatus_IsError(status) == 1 { let msg = if status.err_msg.is_null() { "no error message".into() } else { CStr::from_ptr(status.err_msg).to_string_lossy() }; PyThreadState_Swap(main_state); Py_FinalizeEx(); return Err(format!("could not create new subinterpreter: {msg}")); } } PyThreadState_Swap(main_state); let main_state = PyEval_SaveThread(); // a PyInterpreterConfig with shared gil would deadlock otherwise let ints: Vec<_> = thread::scope(move |s| { let mut handles = vec![]; for state in subs { let handle = s.spawn(move || { let state = state; PyEval_RestoreThread(state.0); let ret = run_code(); Py_EndInterpreter(state.0); ret }); handles.push(handle); } handles.into_iter().map(|h| h.join().unwrap()).collect() }); PyEval_RestoreThread(main_state); let ret = Py_FinalizeEx(); if ret == -1 { return Err("could not finalize interpreter".into()); } let mut sum: u64 = 0; for i in ints { let i = i?; sum += i; } assert_eq!(sum, (0..).take(12 * 12).sum()); } Ok(()) } unsafe fn fetch() -> String { let err = PyErr_GetRaisedException(); let err_repr = PyObject_Str(err); if !err_repr.is_null() { let mut size = 0; let p = PyUnicode_AsUTF8AndSize(err_repr, &mut size); if !p.is_null() { let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts( p.cast::(), size as usize, )); let s = String::from(s); Py_DECREF(err_repr); return s; } } String::from("could not get error") } fn run_code() -> Result { unsafe { let code_obj = Py_CompileString(COMMAND.as_ptr(), c"program".as_ptr(), Py_file_input); if code_obj.is_null() { return Err(fetch()); } let globals = PyDict_New(); let res_ptr = PyEval_EvalCode(code_obj, globals, ptr::null_mut()); Py_DECREF(code_obj); if res_ptr.is_null() { return Err(fetch()); } else { Py_DECREF(res_ptr); } let sum = PyDict_GetItemString(globals, c"s".as_ptr()); /* borrowed reference */ if sum.is_null() { Py_DECREF(globals); return Err("globals did not have `s`".into()); } let int = PyLong_AsUnsignedLongLong(sum) as u64; Py_DECREF(globals); Ok(int) } } ================================================ FILE: pyo3-ffi/examples/sequential/tests/test_.py ================================================ import pytest from sequential import Id def test_make_some(): for x in range(12): i = Id() assert x == int(i) def test_args(): with pytest.raises(TypeError, match="Id\\(\\) takes no arguments"): Id(3, 4) def test_cmp(): a = Id() b = Id() assert a <= b assert a < b assert a == a ================================================ FILE: pyo3-ffi/examples/string-sum/.template/Cargo.toml ================================================ [package] authors = ["{{authors}}"] name = "{{project-name}}" version = "0.1.0" edition = "2021" [lib] name = "string_sum" crate-type = ["cdylib"] [dependencies] pyo3-ffi = { version = "{{PYO3_VERSION}}" } ================================================ FILE: pyo3-ffi/examples/string-sum/.template/pre-script.rhai ================================================ variable::set("PYO3_VERSION", "0.19.2"); file::rename(".template/Cargo.toml", "Cargo.toml"); file::rename(".template/pyproject.toml", "pyproject.toml"); file::delete(".template"); ================================================ FILE: pyo3-ffi/examples/string-sum/.template/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "{{project-name}}" version = "0.1.0" [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: pyo3-ffi/examples/string-sum/Cargo.toml ================================================ [package] name = "string_sum" version = "0.1.0" edition = "2021" rust-version = "1.83" [lib] name = "string_sum" crate-type = ["cdylib"] [dependencies] pyo3-ffi = { path = "../../" } [build-dependencies] pyo3-build-config = { path = "../../../pyo3-build-config" } [workspace] ================================================ FILE: pyo3-ffi/examples/string-sum/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: pyo3-ffi/examples/string-sum/README.md ================================================ # string_sum A project built using only `pyo3_ffi`, without any of PyO3's safe api. ## Building and Testing To build this package, first install `maturin`: ```shell pip install maturin ``` To build and test use `maturin develop`: ```shell pip install -r requirements-dev.txt maturin develop pytest ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Copying this example Use [`cargo-generate`](https://crates.io/crates/cargo-generate): ```bash $ cargo install cargo-generate $ cargo generate --git https://github.com/PyO3/pyo3 examples/string_sum ``` (`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.) ================================================ FILE: pyo3-ffi/examples/string-sum/build.rs ================================================ fn main() { pyo3_build_config::use_pyo3_cfgs(); } ================================================ FILE: pyo3-ffi/examples/string-sum/cargo-generate.toml ================================================ [template] ignore = [".nox"] [hooks] pre = [".template/pre-script.rhai"] ================================================ FILE: pyo3-ffi/examples/string-sum/noxfile.py ================================================ import nox @nox.session def python(session: nox.Session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install(".[dev]") session.run("pytest") ================================================ FILE: pyo3-ffi/examples/string-sum/pyproject.toml ================================================ [build-system] requires = ["maturin>=1,<2"] build-backend = "maturin" [project] name = "string_sum" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] [project.optional-dependencies] dev = ["pytest"] ================================================ FILE: pyo3-ffi/examples/string-sum/src/lib.rs ================================================ #[cfg(Py_3_15)] use std::ffi::c_void; use std::ffi::{c_char, c_long}; use std::ptr; use pyo3_ffi::*; #[cfg(not(Py_3_15))] static mut MODULE_DEF: PyModuleDef = PyModuleDef { m_base: PyModuleDef_HEAD_INIT, m_name: c"string_sum".as_ptr(), m_doc: c"A Python module written in Rust.".as_ptr(), m_size: 0, m_methods: (&raw mut METHODS).cast(), m_slots: (&raw mut SLOTS).cast(), m_traverse: None, m_clear: None, m_free: None, }; static mut METHODS: [PyMethodDef; 2] = [ PyMethodDef { ml_name: c"sum_as_string".as_ptr(), ml_meth: PyMethodDefPointer { PyCFunctionFast: sum_as_string, }, ml_flags: METH_FASTCALL, ml_doc: c"returns the sum of two integers as a string".as_ptr(), }, // A zeroed PyMethodDef to mark the end of the array. PyMethodDef::zeroed(), ]; #[cfg(Py_3_15)] PyABIInfo_VAR!(ABI_INFO); const SLOTS_LEN: usize = 1 + cfg!(Py_3_12) as usize + cfg!(Py_GIL_DISABLED) as usize + 4 * (cfg!(Py_3_15) as usize); static mut SLOTS: [PyModuleDef_Slot; SLOTS_LEN] = [ #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_abi, value: (&raw mut ABI_INFO).cast(), }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_name, // safety: Python does not write to this field value: c"string_sum".as_ptr() as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_doc, // safety: Python does not write to this field value: c"A Python module written in Rust.".as_ptr() as *mut c_void, }, #[cfg(Py_3_15)] PyModuleDef_Slot { slot: Py_mod_methods, value: (&raw mut METHODS).cast(), }, #[cfg(Py_3_12)] PyModuleDef_Slot { slot: Py_mod_multiple_interpreters, value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED, }, #[cfg(Py_GIL_DISABLED)] PyModuleDef_Slot { slot: Py_mod_gil, value: Py_MOD_GIL_NOT_USED, }, PyModuleDef_Slot { slot: 0, value: ptr::null_mut(), }, ]; // The module initialization function #[cfg(not(Py_3_15))] #[allow(non_snake_case, reason = "must be named `PyInit_`")] #[no_mangle] pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject { PyModuleDef_Init(&raw mut MODULE_DEF) } #[cfg(Py_3_15)] #[allow(non_snake_case, reason = "must be named `PyModExport_`")] #[no_mangle] pub unsafe extern "C" fn PyModExport_string_sum() -> *mut PyModuleDef_Slot { (&raw mut SLOTS).cast() } /// A helper to parse function arguments /// If we used PyO3's proc macros they'd handle all of this boilerplate for us :) unsafe fn parse_arg_as_i32(obj: *mut PyObject, n_arg: usize) -> Option { if PyLong_Check(obj) == 0 { let msg = format!( "sum_as_string expected an int for positional argument {}\0", n_arg ); PyErr_SetString(PyExc_TypeError, msg.as_ptr().cast::()); return None; } // Let's keep the behaviour consistent on platforms where `c_long` is bigger than 32 bits. // In particular, it is an i32 on Windows but i64 on most Linux systems let mut overflow = 0; let i_long: c_long = PyLong_AsLongAndOverflow(obj, &mut overflow); #[allow( irrefutable_let_patterns, reason = "some platforms have c_long equal to i32" )] if overflow != 0 { raise_overflowerror(obj); None } else if let Ok(i) = i_long.try_into() { Some(i) } else { raise_overflowerror(obj); None } } unsafe fn raise_overflowerror(obj: *mut PyObject) { let obj_repr = PyObject_Str(obj); if !obj_repr.is_null() { let mut size = 0; let p = PyUnicode_AsUTF8AndSize(obj_repr, &mut size); if !p.is_null() { let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts( p.cast::(), size as usize, )); let msg = format!("cannot fit {} in 32 bits\0", s); PyErr_SetString(PyExc_OverflowError, msg.as_ptr().cast::()); } Py_DECREF(obj_repr); } } pub unsafe extern "C" fn sum_as_string( _self: *mut PyObject, args: *mut *mut PyObject, nargs: Py_ssize_t, ) -> *mut PyObject { if nargs != 2 { PyErr_SetString( PyExc_TypeError, c"sum_as_string expected 2 positional arguments".as_ptr(), ); return std::ptr::null_mut(); } let (first, second) = (*args, *args.add(1)); let first = match parse_arg_as_i32(first, 1) { Some(x) => x, None => return std::ptr::null_mut(), }; let second = match parse_arg_as_i32(second, 2) { Some(x) => x, None => return std::ptr::null_mut(), }; match first.checked_add(second) { Some(sum) => { let string = sum.to_string(); PyUnicode_FromStringAndSize(string.as_ptr().cast::(), string.len() as isize) } None => { PyErr_SetString(PyExc_OverflowError, c"arguments too large to add".as_ptr()); std::ptr::null_mut() } } } ================================================ FILE: pyo3-ffi/examples/string-sum/tests/test_.py ================================================ import pytest from string_sum import sum_as_string def test_sum(): a, b = 12, 42 added = sum_as_string(a, b) assert added == "54" def test_err1(): a, b = "abc", 42 with pytest.raises( TypeError, match="sum_as_string expected an int for positional argument 1" ): sum_as_string(a, b) def test_err2(): a, b = 0, {} with pytest.raises( TypeError, match="sum_as_string expected an int for positional argument 2" ): sum_as_string(a, b) def test_overflow1(): a, b = 0, 1 << 43 with pytest.raises(OverflowError, match="cannot fit 8796093022208 in 32 bits"): sum_as_string(a, b) def test_overflow2(): a, b = 1 << 30, 1 << 30 with pytest.raises(OverflowError, match="arguments too large to add"): sum_as_string(a, b) ================================================ FILE: pyo3-ffi/src/abstract_.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; #[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] use libc::size_t; use std::ffi::{c_char, c_int}; #[inline] #[cfg(all( not(Py_3_13), // CPython exposed as a function in 3.13, in object.h not(all(PyPy, not(Py_3_11))) // PyPy exposed as a function until PyPy 3.10, macro in 3.11+ ))] pub unsafe fn PyObject_DelAttrString(o: *mut PyObject, attr_name: *const c_char) -> c_int { PyObject_SetAttrString(o, attr_name, std::ptr::null_mut()) } #[inline] #[cfg(all( not(Py_3_13), // CPython exposed as a function in 3.13, in object.h not(all(PyPy, not(Py_3_11))) // PyPy exposed as a function until PyPy 3.10, macro in 3.11+ ))] pub unsafe fn PyObject_DelAttr(o: *mut PyObject, attr_name: *mut PyObject) -> c_int { PyObject_SetAttr(o, attr_name, std::ptr::null_mut()) } extern_libpython! { #[cfg(all( not(PyPy), any(Py_3_10, all(not(Py_LIMITED_API), Py_3_9)) // Added to python in 3.9 but to limited API in 3.10 ))] #[cfg_attr(PyPy, link_name = "PyPyObject_CallNoArgs")] pub fn PyObject_CallNoArgs(func: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Call")] pub fn PyObject_Call( callable_object: *mut PyObject, args: *mut PyObject, kw: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_CallObject")] pub fn PyObject_CallObject( callable_object: *mut PyObject, args: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_CallFunction")] pub fn PyObject_CallFunction( callable_object: *mut PyObject, format: *const c_char, ... ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_CallMethod")] pub fn PyObject_CallMethod( o: *mut PyObject, method: *const c_char, format: *const c_char, ... ) -> *mut PyObject; #[cfg(not(Py_3_13))] #[cfg_attr(PyPy, link_name = "_PyPyObject_CallFunction_SizeT")] pub fn _PyObject_CallFunction_SizeT( callable_object: *mut PyObject, format: *const c_char, ... ) -> *mut PyObject; #[cfg(not(Py_3_13))] #[cfg_attr(PyPy, link_name = "_PyPyObject_CallMethod_SizeT")] pub fn _PyObject_CallMethod_SizeT( o: *mut PyObject, method: *const c_char, format: *const c_char, ... ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_CallFunctionObjArgs")] pub fn PyObject_CallFunctionObjArgs(callable: *mut PyObject, ...) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_CallMethodObjArgs")] pub fn PyObject_CallMethodObjArgs( o: *mut PyObject, method: *mut PyObject, ... ) -> *mut PyObject; } #[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = 1 << (8 * std::mem::size_of::() as size_t - 1); extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyObject_Vectorcall")] #[cfg(any(Py_3_12, all(Py_3_11, not(Py_LIMITED_API))))] pub fn PyObject_Vectorcall( callable: *mut PyObject, args: *const *mut PyObject, nargsf: size_t, kwnames: *mut PyObject, ) -> *mut PyObject; #[cfg(any(Py_3_12, all(Py_3_9, not(any(Py_LIMITED_API, PyPy)))))] pub fn PyObject_VectorcallMethod( name: *mut PyObject, args: *const *mut PyObject, nargsf: size_t, kwnames: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Type")] pub fn PyObject_Type(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Size")] pub fn PyObject_Size(o: *mut PyObject) -> Py_ssize_t; } #[inline] pub unsafe fn PyObject_Length(o: *mut PyObject) -> Py_ssize_t { PyObject_Size(o) } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyObject_GetItem")] pub fn PyObject_GetItem(o: *mut PyObject, key: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_SetItem")] pub fn PyObject_SetItem(o: *mut PyObject, key: *mut PyObject, v: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_DelItemString")] pub fn PyObject_DelItemString(o: *mut PyObject, key: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_DelItem")] pub fn PyObject_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int; } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyObject_Format")] pub fn PyObject_Format(obj: *mut PyObject, format_spec: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_GetIter")] pub fn PyObject_GetIter(arg1: *mut PyObject) -> *mut PyObject; } // Before 3.8 PyIter_Check was defined in CPython as a macro, // but the implementation of that in PyO3 did not work, see // https://github.com/PyO3/pyo3/pull/2914 // // This is a slow implementation which should function equivalently. #[cfg(not(any(Py_3_8, PyPy)))] #[inline] pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int { crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), c"__next__".as_ptr()) } extern_libpython! { #[cfg(any(Py_3_8, PyPy))] #[cfg_attr(PyPy, link_name = "PyPyIter_Check")] pub fn PyIter_Check(obj: *mut PyObject) -> c_int; #[cfg(Py_3_14)] #[cfg_attr(PyPy, link_name = "PyPyIter_NextItem")] pub fn PyIter_NextItem(iter: *mut PyObject, item: *mut *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyIter_Next")] pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject; #[cfg(all(not(PyPy), Py_3_10))] #[cfg_attr(PyPy, link_name = "PyPyIter_Send")] pub fn PyIter_Send( iter: *mut PyObject, arg: *mut PyObject, presult: *mut *mut PyObject, ) -> PySendResult; #[cfg_attr(PyPy, link_name = "PyPyNumber_Check")] pub fn PyNumber_Check(o: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyNumber_Add")] pub fn PyNumber_Add(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Subtract")] pub fn PyNumber_Subtract(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Multiply")] pub fn PyNumber_Multiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_MatrixMultiply")] pub fn PyNumber_MatrixMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_FloorDivide")] pub fn PyNumber_FloorDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_TrueDivide")] pub fn PyNumber_TrueDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Remainder")] pub fn PyNumber_Remainder(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Divmod")] pub fn PyNumber_Divmod(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Power")] pub fn PyNumber_Power(o1: *mut PyObject, o2: *mut PyObject, o3: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Negative")] pub fn PyNumber_Negative(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Positive")] pub fn PyNumber_Positive(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Absolute")] pub fn PyNumber_Absolute(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Invert")] pub fn PyNumber_Invert(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Lshift")] pub fn PyNumber_Lshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Rshift")] pub fn PyNumber_Rshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_And")] pub fn PyNumber_And(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Xor")] pub fn PyNumber_Xor(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Or")] pub fn PyNumber_Or(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; } // Defined as this macro in Python limited API, but relies on // non-limited PyTypeObject. Don't expose this since it cannot be used. #[cfg(not(any(Py_LIMITED_API, PyPy)))] #[inline] pub unsafe fn PyIndex_Check(o: *mut PyObject) -> c_int { let tp_as_number = (*Py_TYPE(o)).tp_as_number; (!tp_as_number.is_null() && (*tp_as_number).nb_index.is_some()) as c_int } extern_libpython! { #[cfg(any(all(Py_3_8, Py_LIMITED_API), PyPy))] #[link_name = "PyPyIndex_Check"] pub fn PyIndex_Check(o: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyNumber_Index")] pub fn PyNumber_Index(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_AsSsize_t")] pub fn PyNumber_AsSsize_t(o: *mut PyObject, exc: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyNumber_Long")] pub fn PyNumber_Long(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_Float")] pub fn PyNumber_Float(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceAdd")] pub fn PyNumber_InPlaceAdd(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceSubtract")] pub fn PyNumber_InPlaceSubtract(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceMultiply")] pub fn PyNumber_InPlaceMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceMatrixMultiply")] pub fn PyNumber_InPlaceMatrixMultiply(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceFloorDivide")] pub fn PyNumber_InPlaceFloorDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceTrueDivide")] pub fn PyNumber_InPlaceTrueDivide(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceRemainder")] pub fn PyNumber_InPlaceRemainder(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlacePower")] pub fn PyNumber_InPlacePower( o1: *mut PyObject, o2: *mut PyObject, o3: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceLshift")] pub fn PyNumber_InPlaceLshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceRshift")] pub fn PyNumber_InPlaceRshift(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceAnd")] pub fn PyNumber_InPlaceAnd(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceXor")] pub fn PyNumber_InPlaceXor(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyNumber_InPlaceOr")] pub fn PyNumber_InPlaceOr(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; pub fn PyNumber_ToBase(n: *mut PyObject, base: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_Check")] pub fn PySequence_Check(o: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySequence_Size")] pub fn PySequence_Size(o: *mut PyObject) -> Py_ssize_t; #[cfg(PyPy)] #[link_name = "PyPySequence_Length"] pub fn PySequence_Length(o: *mut PyObject) -> Py_ssize_t; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PySequence_Length(o: *mut PyObject) -> Py_ssize_t { PySequence_Size(o) } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySequence_Concat")] pub fn PySequence_Concat(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_Repeat")] pub fn PySequence_Repeat(o: *mut PyObject, count: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_GetItem")] pub fn PySequence_GetItem(o: *mut PyObject, i: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_GetSlice")] pub fn PySequence_GetSlice(o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_SetItem")] pub fn PySequence_SetItem(o: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySequence_DelItem")] pub fn PySequence_DelItem(o: *mut PyObject, i: Py_ssize_t) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySequence_SetSlice")] pub fn PySequence_SetSlice( o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t, v: *mut PyObject, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySequence_DelSlice")] pub fn PySequence_DelSlice(o: *mut PyObject, i1: Py_ssize_t, i2: Py_ssize_t) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySequence_Tuple")] pub fn PySequence_Tuple(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_List")] pub fn PySequence_List(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_Fast")] pub fn PySequence_Fast(o: *mut PyObject, m: *const c_char) -> *mut PyObject; // skipped PySequence_Fast_GET_SIZE // skipped PySequence_Fast_GET_ITEM // skipped PySequence_Fast_GET_ITEMS pub fn PySequence_Count(o: *mut PyObject, value: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPySequence_Contains")] pub fn PySequence_Contains(seq: *mut PyObject, ob: *mut PyObject) -> c_int; } #[inline] pub unsafe fn PySequence_In(o: *mut PyObject, value: *mut PyObject) -> c_int { PySequence_Contains(o, value) } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySequence_Index")] pub fn PySequence_Index(o: *mut PyObject, value: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPySequence_InPlaceConcat")] pub fn PySequence_InPlaceConcat(o1: *mut PyObject, o2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySequence_InPlaceRepeat")] pub fn PySequence_InPlaceRepeat(o: *mut PyObject, count: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyMapping_Check")] pub fn PyMapping_Check(o: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyMapping_Size")] pub fn PyMapping_Size(o: *mut PyObject) -> Py_ssize_t; #[cfg(PyPy)] #[link_name = "PyPyMapping_Length"] pub fn PyMapping_Length(o: *mut PyObject) -> Py_ssize_t; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyMapping_Length(o: *mut PyObject) -> Py_ssize_t { PyMapping_Size(o) } #[inline] pub unsafe fn PyMapping_DelItemString(o: *mut PyObject, key: *mut c_char) -> c_int { PyObject_DelItemString(o, key) } #[inline] pub unsafe fn PyMapping_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int { PyObject_DelItem(o, key) } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKeyString")] pub fn PyMapping_HasKeyString(o: *mut PyObject, key: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyMapping_HasKey")] pub fn PyMapping_HasKey(o: *mut PyObject, key: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyMapping_Keys")] pub fn PyMapping_Keys(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyMapping_Values")] pub fn PyMapping_Values(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyMapping_Items")] pub fn PyMapping_Items(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyMapping_GetItemString")] pub fn PyMapping_GetItemString(o: *mut PyObject, key: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyMapping_SetItemString")] pub fn PyMapping_SetItemString( o: *mut PyObject, key: *const c_char, value: *mut PyObject, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_IsInstance")] pub fn PyObject_IsInstance(object: *mut PyObject, typeorclass: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_IsSubclass")] pub fn PyObject_IsSubclass(object: *mut PyObject, typeorclass: *mut PyObject) -> c_int; } ================================================ FILE: pyo3-ffi/src/bltinmodule.rs ================================================ use crate::object::PyTypeObject; extern_libpython! { pub static mut PyFilter_Type: PyTypeObject; pub static mut PyMap_Type: PyTypeObject; pub static mut PyZip_Type: PyTypeObject; } ================================================ FILE: pyo3-ffi/src/boolobject.rs ================================================ #[cfg(not(GraalPy))] use crate::longobject::PyLongObject; use crate::object::*; use std::ffi::{c_int, c_long}; #[inline] pub unsafe fn PyBool_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyBool_Type) as c_int } extern_libpython! { #[cfg(not(GraalPy))] #[cfg_attr(PyPy, link_name = "_PyPy_FalseStruct")] static mut _Py_FalseStruct: PyLongObject; #[cfg(not(GraalPy))] #[cfg_attr(PyPy, link_name = "_PyPy_TrueStruct")] static mut _Py_TrueStruct: PyLongObject; #[cfg(GraalPy)] static mut _Py_FalseStructReference: *mut PyObject; #[cfg(GraalPy)] static mut _Py_TrueStructReference: *mut PyObject; } #[inline] pub unsafe fn Py_False() -> *mut PyObject { #[cfg(not(GraalPy))] return (&raw mut _Py_FalseStruct).cast(); #[cfg(GraalPy)] return _Py_FalseStructReference; } #[inline] pub unsafe fn Py_True() -> *mut PyObject { #[cfg(not(GraalPy))] return (&raw mut _Py_TrueStruct).cast(); #[cfg(GraalPy)] return _Py_TrueStructReference; } #[inline] pub unsafe fn Py_IsTrue(x: *mut PyObject) -> c_int { Py_Is(x, Py_True()) } #[inline] pub unsafe fn Py_IsFalse(x: *mut PyObject) -> c_int { Py_Is(x, Py_False()) } // skipped Py_RETURN_TRUE // skipped Py_RETURN_FALSE extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyBool_FromLong")] pub fn PyBool_FromLong(arg1: c_long) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/bytearrayobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int}; #[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))] #[repr(C)] pub struct PyByteArrayObject { pub ob_base: PyVarObject, pub ob_alloc: Py_ssize_t, pub ob_bytes: *mut c_char, pub ob_start: *mut c_char, #[cfg(Py_3_9)] pub ob_exports: Py_ssize_t, #[cfg(not(Py_3_9))] pub ob_exports: c_int, #[cfg(Py_3_15)] pub ob_bytes_object: *mut PyObject, } #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] opaque_struct!(pub PyByteArrayObject); extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyByteArray_Type")] pub static mut PyByteArray_Type: PyTypeObject; pub static mut PyByteArrayIter_Type: PyTypeObject; } #[inline] pub unsafe fn PyByteArray_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyByteArray_Type) } #[inline] pub unsafe fn PyByteArray_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyByteArray_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyByteArray_FromObject")] pub fn PyByteArray_FromObject(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyByteArray_Concat")] pub fn PyByteArray_Concat(a: *mut PyObject, b: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyByteArray_FromStringAndSize")] pub fn PyByteArray_FromStringAndSize(string: *const c_char, len: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyByteArray_Size")] pub fn PyByteArray_Size(bytearray: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyByteArray_AsString")] pub fn PyByteArray_AsString(bytearray: *mut PyObject) -> *mut c_char; #[cfg_attr(PyPy, link_name = "PyPyByteArray_Resize")] pub fn PyByteArray_Resize(bytearray: *mut PyObject, len: Py_ssize_t) -> c_int; } ================================================ FILE: pyo3-ffi/src/bytesobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyBytes_Type")] pub static mut PyBytes_Type: PyTypeObject; pub static mut PyBytesIter_Type: PyTypeObject; } #[inline] pub unsafe fn PyBytes_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_BYTES_SUBCLASS) } #[inline] pub unsafe fn PyBytes_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyBytes_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyBytes_FromStringAndSize")] pub fn PyBytes_FromStringAndSize(arg1: *const c_char, arg2: Py_ssize_t) -> *mut PyObject; pub fn PyBytes_FromString(arg1: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyBytes_FromObject")] pub fn PyBytes_FromObject(arg1: *mut PyObject) -> *mut PyObject; // skipped PyBytes_FromFormatV //#[cfg_attr(PyPy, link_name = "PyPyBytes_FromFormatV")] //pub fn PyBytes_FromFormatV(arg1: *const c_char, arg2: va_list) // -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyBytes_FromFormat")] pub fn PyBytes_FromFormat(arg1: *const c_char, ...) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyBytes_Size")] pub fn PyBytes_Size(arg1: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyBytes_AsString")] pub fn PyBytes_AsString(arg1: *mut PyObject) -> *mut c_char; pub fn PyBytes_Repr(arg1: *mut PyObject, arg2: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyBytes_Concat")] pub fn PyBytes_Concat(arg1: *mut *mut PyObject, arg2: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyBytes_ConcatAndDel")] pub fn PyBytes_ConcatAndDel(arg1: *mut *mut PyObject, arg2: *mut PyObject); pub fn PyBytes_DecodeEscape( arg1: *const c_char, arg2: Py_ssize_t, arg3: *const c_char, arg4: Py_ssize_t, arg5: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyBytes_AsStringAndSize")] pub fn PyBytes_AsStringAndSize( obj: *mut PyObject, s: *mut *mut c_char, len: *mut Py_ssize_t, ) -> c_int; } // skipped F_LJUST // skipped F_SIGN // skipped F_BLANK // skipped F_ALT // skipped F_ZERO ================================================ FILE: pyo3-ffi/src/ceval.rs ================================================ use crate::object::PyObject; use crate::pytypedefs::PyThreadState; use std::ffi::{c_char, c_int, c_void}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyEval_EvalCode")] pub fn PyEval_EvalCode( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject, ) -> *mut PyObject; pub fn PyEval_EvalCodeEx( co: *mut PyObject, globals: *mut PyObject, locals: *mut PyObject, args: *const *mut PyObject, argc: c_int, kwds: *const *mut PyObject, kwdc: c_int, defs: *const *mut PyObject, defc: c_int, kwdefs: *mut PyObject, closure: *mut PyObject, ) -> *mut PyObject; #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] #[cfg_attr(PyPy, link_name = "PyPyEval_CallObjectWithKeywords")] pub fn PyEval_CallObjectWithKeywords( func: *mut PyObject, obj: *mut PyObject, kwargs: *mut PyObject, ) -> *mut PyObject; } #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] #[inline] pub unsafe fn PyEval_CallObject(func: *mut PyObject, arg: *mut PyObject) -> *mut PyObject { #[allow(deprecated)] PyEval_CallObjectWithKeywords(func, arg, std::ptr::null_mut()) } extern_libpython! { #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] #[cfg_attr(PyPy, link_name = "PyPyEval_CallFunction")] pub fn PyEval_CallFunction(obj: *mut PyObject, format: *const c_char, ...) -> *mut PyObject; #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] #[cfg_attr(PyPy, link_name = "PyPyEval_CallMethod")] pub fn PyEval_CallMethod( obj: *mut PyObject, methodname: *const c_char, format: *const c_char, ... ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_GetBuiltins")] pub fn PyEval_GetBuiltins() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_GetGlobals")] pub fn PyEval_GetGlobals() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_GetLocals")] pub fn PyEval_GetLocals() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_GetFrame")] pub fn PyEval_GetFrame() -> *mut crate::PyFrameObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyEval_GetFrameBuiltins")] pub fn PyEval_GetFrameBuiltins() -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyEval_GetFrameGlobals")] pub fn PyEval_GetFrameGlobals() -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyEval_GetFrameLocals")] pub fn PyEval_GetFrameLocals() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPy_AddPendingCall")] pub fn Py_AddPendingCall( func: Option c_int>, arg: *mut c_void, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPy_MakePendingCalls")] pub fn Py_MakePendingCalls() -> c_int; #[cfg_attr(PyPy, link_name = "PyPy_SetRecursionLimit")] pub fn Py_SetRecursionLimit(arg1: c_int); #[cfg_attr(PyPy, link_name = "PyPy_GetRecursionLimit")] pub fn Py_GetRecursionLimit() -> c_int; #[cfg(Py_3_9)] #[cfg_attr(PyPy, link_name = "PyPy_EnterRecursiveCall")] pub fn Py_EnterRecursiveCall(arg1: *const c_char) -> c_int; #[cfg(Py_3_9)] #[cfg_attr(PyPy, link_name = "PyPy_LeaveRecursiveCall")] pub fn Py_LeaveRecursiveCall(); #[cfg_attr(PyPy, link_name = "PyPyEval_GetFuncName")] pub fn PyEval_GetFuncName(arg1: *mut PyObject) -> *const c_char; #[cfg_attr(PyPy, link_name = "PyPyEval_GetFuncDesc")] pub fn PyEval_GetFuncDesc(arg1: *mut PyObject) -> *const c_char; #[cfg_attr(PyPy, link_name = "PyPyEval_EvalFrame")] pub fn PyEval_EvalFrame(arg1: *mut crate::PyFrameObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_EvalFrameEx")] pub fn PyEval_EvalFrameEx(f: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_SaveThread")] pub fn PyEval_SaveThread() -> *mut PyThreadState; #[cfg_attr(PyPy, link_name = "PyPyEval_RestoreThread")] pub fn PyEval_RestoreThread(arg1: *mut PyThreadState); #[cfg(not(Py_3_13))] #[cfg_attr(PyPy, link_name = "PyPyEval_ThreadsInitialized")] #[cfg_attr( Py_3_9, deprecated( note = "Deprecated in Python 3.9, this function always returns true in Python 3.7 or newer." ) )] pub fn PyEval_ThreadsInitialized() -> c_int; #[cfg_attr(PyPy, link_name = "PyPyEval_InitThreads")] #[cfg_attr( Py_3_9, deprecated( note = "Deprecated in Python 3.9, this function does nothing in Python 3.7 or newer." ) )] pub fn PyEval_InitThreads(); #[cfg(not(Py_3_13))] #[deprecated(note = "Deprecated in Python 3.2")] pub fn PyEval_AcquireLock(); #[cfg(not(Py_3_13))] #[deprecated(note = "Deprecated in Python 3.2")] pub fn PyEval_ReleaseLock(); #[cfg_attr(PyPy, link_name = "PyPyEval_AcquireThread")] pub fn PyEval_AcquireThread(tstate: *mut PyThreadState); #[cfg_attr(PyPy, link_name = "PyPyEval_ReleaseThread")] pub fn PyEval_ReleaseThread(tstate: *mut PyThreadState); #[cfg(not(Py_3_8))] pub fn PyEval_ReInitThreads(); } // skipped Py_BEGIN_ALLOW_THREADS // skipped Py_BLOCK_THREADS // skipped Py_UNBLOCK_THREADS // skipped Py_END_ALLOW_THREADS // skipped FVC_MASK // skipped FVC_NONE // skipped FVC_STR // skipped FVC_REPR // skipped FVC_ASCII // skipped FVS_MASK // skipped FVS_HAVE_SPEC ================================================ FILE: pyo3-ffi/src/codecs.rs ================================================ use crate::object::PyObject; use std::ffi::{c_char, c_int}; extern_libpython! { pub fn PyCodec_Register(search_function: *mut PyObject) -> c_int; #[cfg(Py_3_10)] #[cfg(not(PyPy))] pub fn PyCodec_Unregister(search_function: *mut PyObject) -> c_int; // skipped non-limited _PyCodec_Lookup from Include/codecs.h // skipped non-limited _PyCodec_Forget from Include/codecs.h pub fn PyCodec_KnownEncoding(encoding: *const c_char) -> c_int; pub fn PyCodec_Encode( object: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; pub fn PyCodec_Decode( object: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; // skipped non-limited _PyCodec_LookupTextEncoding from Include/codecs.h // skipped non-limited _PyCodec_EncodeText from Include/codecs.h // skipped non-limited _PyCodec_DecodeText from Include/codecs.h // skipped non-limited _PyCodecInfo_GetIncrementalDecoder from Include/codecs.h // skipped non-limited _PyCodecInfo_GetIncrementalEncoder from Include/codecs.h pub fn PyCodec_Encoder(encoding: *const c_char) -> *mut PyObject; pub fn PyCodec_Decoder(encoding: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyCodec_IncrementalEncoder")] pub fn PyCodec_IncrementalEncoder( encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyCodec_IncrementalDecoder")] pub fn PyCodec_IncrementalDecoder( encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; pub fn PyCodec_StreamReader( encoding: *const c_char, stream: *mut PyObject, errors: *const c_char, ) -> *mut PyObject; pub fn PyCodec_StreamWriter( encoding: *const c_char, stream: *mut PyObject, errors: *const c_char, ) -> *mut PyObject; pub fn PyCodec_RegisterError(name: *const c_char, error: *mut PyObject) -> c_int; pub fn PyCodec_LookupError(name: *const c_char) -> *mut PyObject; pub fn PyCodec_StrictErrors(exc: *mut PyObject) -> *mut PyObject; pub fn PyCodec_IgnoreErrors(exc: *mut PyObject) -> *mut PyObject; pub fn PyCodec_ReplaceErrors(exc: *mut PyObject) -> *mut PyObject; pub fn PyCodec_XMLCharRefReplaceErrors(exc: *mut PyObject) -> *mut PyObject; pub fn PyCodec_BackslashReplaceErrors(exc: *mut PyObject) -> *mut PyObject; // skipped non-limited PyCodec_NameReplaceErrors from Include/codecs.h // skipped non-limited Py_hexdigits from Include/codecs.h } ================================================ FILE: pyo3-ffi/src/compat/mod.rs ================================================ //! C API Compatibility Shims //! //! Some CPython C API functions added in recent versions of Python are //! inherently safer to use than older C API constructs. This module //! exposes functions available on all Python versions that wrap the //! old C API on old Python versions and wrap the function directly //! on newer Python versions. // Unless otherwise noted, the compatibility shims are adapted from // the pythoncapi-compat project: https://github.com/python/pythoncapi-compat /// Internal helper macro which defines compatibility shims for C API functions, deferring to a /// re-export when that's available. macro_rules! compat_function { ( originally_defined_for($cfg:meta); $(#[$attrs:meta])* pub unsafe fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty $body:block ) => { // Define as a standalone function under docsrs cfg so that this shows as a unique function in the docs, // not a re-export (the re-export has the wrong visibility) #[cfg(any(docsrs, not($cfg)))] #[cfg_attr(docsrs, doc(cfg(all())))] $(#[$attrs])* pub unsafe fn $name( $($arg_names: $arg_types,)* ) -> $ret $body #[cfg(all($cfg, not(docsrs)))] pub use $crate::$name; #[cfg(test)] paste::paste! { // Test that the compat function does not overlap with the original function. If the // cfgs line up, then the the two glob imports will resolve to the same item via the // re-export. If the cfgs mismatch, then the use of $name will be ambiguous in cases // where the function is defined twice, and the test will fail to compile. #[allow(unused_imports, reason = "imports exist to try to trigger name conflicts")] mod [] { use $crate::*; use $crate::compat::*; #[test] fn test_export() { let _ = $name; } } } }; } mod py_3_10; mod py_3_13; mod py_3_14; mod py_3_15; mod py_3_9; pub use self::py_3_10::*; pub use self::py_3_13::*; pub use self::py_3_14::*; #[allow(unused_imports)] pub use self::py_3_15::*; pub use self::py_3_9::*; ================================================ FILE: pyo3-ffi/src/compat/py_3_10.rs ================================================ compat_function!( originally_defined_for(Py_3_10); #[inline] pub unsafe fn Py_NewRef(obj: *mut crate::PyObject) -> *mut crate::PyObject { crate::Py_INCREF(obj); obj } ); compat_function!( originally_defined_for(Py_3_10); #[inline] pub unsafe fn Py_XNewRef(obj: *mut crate::PyObject) -> *mut crate::PyObject { crate::Py_XINCREF(obj); obj } ); compat_function!( originally_defined_for(Py_3_10); #[inline] pub unsafe fn PyModule_AddObjectRef( module: *mut crate::PyObject, name: *const std::ffi::c_char, value: *mut crate::PyObject, ) -> std::ffi::c_int { if value.is_null() && crate::PyErr_Occurred().is_null() { crate::PyErr_SetString( crate::PyExc_SystemError, c"PyModule_AddObjectRef() must be called with an exception raised if value is NULL".as_ptr(), ); return -1; } crate::Py_XINCREF(value); let result = crate::PyModule_AddObject(module, name, value); if result < 0 { crate::Py_XDECREF(value); } result } ); ================================================ FILE: pyo3-ffi/src/compat/py_3_13.rs ================================================ compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyDict_GetItemRef( dp: *mut crate::PyObject, key: *mut crate::PyObject, result: *mut *mut crate::PyObject, ) -> std::ffi::c_int { use crate::{compat::Py_NewRef, PyDict_GetItemWithError, PyErr_Occurred}; let item = PyDict_GetItemWithError(dp, key); if !item.is_null() { *result = Py_NewRef(item); return 1; // found } *result = std::ptr::null_mut(); if PyErr_Occurred().is_null() { return 0; // not found } -1 } ); compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyList_GetItemRef( arg1: *mut crate::PyObject, arg2: crate::Py_ssize_t, ) -> *mut crate::PyObject { use crate::{PyList_GetItem, Py_XINCREF}; let item = PyList_GetItem(arg1, arg2); Py_XINCREF(item); item } ); compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyImport_AddModuleRef( name: *const std::ffi::c_char, ) -> *mut crate::PyObject { use crate::{compat::Py_XNewRef, PyImport_AddModule}; Py_XNewRef(PyImport_AddModule(name)) } ); compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyWeakref_GetRef( reference: *mut crate::PyObject, pobj: *mut *mut crate::PyObject, ) -> std::ffi::c_int { use crate::{ compat::Py_NewRef, PyErr_SetString, PyExc_TypeError, PyWeakref_Check, PyWeakref_GetObject, Py_None, }; if !reference.is_null() && PyWeakref_Check(reference) == 0 { *pobj = std::ptr::null_mut(); PyErr_SetString(PyExc_TypeError, c"expected a weakref".as_ptr()); return -1; } let obj = PyWeakref_GetObject(reference); if obj.is_null() { // SystemError if reference is NULL *pobj = std::ptr::null_mut(); return -1; } if obj == Py_None() { *pobj = std::ptr::null_mut(); return 0; } *pobj = Py_NewRef(obj); 1 } ); compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyList_Extend( list: *mut crate::PyObject, iterable: *mut crate::PyObject, ) -> std::ffi::c_int { crate::PyList_SetSlice(list, crate::PY_SSIZE_T_MAX, crate::PY_SSIZE_T_MAX, iterable) } ); compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyList_Clear(list: *mut crate::PyObject) -> std::ffi::c_int { crate::PyList_SetSlice(list, 0, crate::PY_SSIZE_T_MAX, std::ptr::null_mut()) } ); compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyModule_Add( module: *mut crate::PyObject, name: *const std::ffi::c_char, value: *mut crate::PyObject, ) -> std::ffi::c_int { let result = crate::compat::PyModule_AddObjectRef(module, name, value); crate::Py_XDECREF(value); result } ); #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(Py_3_13); #[inline] pub unsafe fn PyThreadState_GetUnchecked( ) -> *mut crate::PyThreadState { crate::_PyThreadState_UncheckedGet() } ); ================================================ FILE: pyo3-ffi/src/compat/py_3_14.rs ================================================ compat_function!( originally_defined_for(all(Py_3_14, not(Py_LIMITED_API))); #[inline] pub unsafe fn Py_HashBuffer( ptr: *const std::ffi::c_void, len: crate::Py_ssize_t, ) -> crate::Py_hash_t { #[cfg(not(any(Py_LIMITED_API, PyPy)))] { crate::_Py_HashBytes(ptr, len) } #[cfg(any(Py_LIMITED_API, PyPy))] { let bytes = crate::PyBytes_FromStringAndSize(ptr as *const std::ffi::c_char, len); if bytes.is_null() { -1 } else { let result = crate::PyObject_Hash(bytes); crate::Py_DECREF(bytes); result } } } ); compat_function!( originally_defined_for(Py_3_14); #[inline] pub unsafe fn PyIter_NextItem( iter: *mut crate::PyObject, item: *mut *mut crate::PyObject, ) -> std::ffi::c_int { *item = crate::PyIter_Next(iter); if !(*item).is_null() { 1 } else if crate::PyErr_Occurred().is_null() { 0 } else { -1 } } ); ================================================ FILE: pyo3-ffi/src/compat/py_3_15.rs ================================================ #[cfg(all(Py_3_15, not(Py_LIMITED_API)))] pub use crate::PyBytesWriter; #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(all(Py_3_15, not(Py_LIMITED_API))); #[inline] pub unsafe fn PyBytesWriter_Create( size: crate::Py_ssize_t, ) -> *mut PyBytesWriter { if size < 0 { crate::PyErr_SetString(crate::PyExc_ValueError, c"size must be >= 0".as_ptr() as *const _); return std::ptr::null_mut(); } let writer: *mut PyBytesWriter = crate::PyMem_Malloc(std::mem::size_of::()).cast(); if writer.is_null() { crate::PyErr_NoMemory(); return std::ptr::null_mut(); } (*writer).obj = std::ptr::null_mut(); (*writer).size = 0; if size >=1 { if _PyBytesWriter_Resize_impl(writer, size, 0) < 0 { PyBytesWriter_Discard(writer); return std::ptr::null_mut(); } (*writer).size = size; } writer } ); #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(all(Py_3_15, not(Py_LIMITED_API))); #[inline] pub unsafe fn PyBytesWriter_Discard(writer: *mut PyBytesWriter) -> () { if writer.is_null() { return; } crate::Py_XDECREF((*writer).obj); crate::PyMem_Free(writer.cast()); } ); #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(all(Py_3_15, not(Py_LIMITED_API))); #[inline] pub unsafe fn PyBytesWriter_Finish(writer: *mut PyBytesWriter) -> *mut crate::PyObject { PyBytesWriter_FinishWithSize(writer, (*writer).size) } ); #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(all(Py_3_15, not(Py_LIMITED_API))); #[inline] pub unsafe fn PyBytesWriter_FinishWithSize(writer: *mut PyBytesWriter, size: crate::Py_ssize_t) -> *mut crate::PyObject { let result = if size == 0 { crate::PyBytes_FromStringAndSize(c"".as_ptr(), 0) } else if (*writer).obj.is_null() { crate::PyBytes_FromStringAndSize((*writer).small_buffer.as_ptr(), size) } else { if size != crate::PyBytes_Size((*writer).obj) && crate::_PyBytes_Resize(&mut (*writer).obj, size) < 0 { PyBytesWriter_Discard(writer); return std::ptr::null_mut(); } std::mem::replace(&mut (*writer).obj, std::ptr::null_mut()) }; PyBytesWriter_Discard(writer); result } ); #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(all(Py_3_15, not(Py_LIMITED_API))); #[inline] pub unsafe fn PyBytesWriter_GetData(writer: *mut PyBytesWriter) -> *mut std::ffi::c_void { if (*writer).obj.is_null() { (*writer).small_buffer.as_ptr() as *mut _ } else { crate::PyBytes_AS_STRING((*writer).obj) as *mut _ } } ); #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(all(Py_3_15, not(Py_LIMITED_API))); #[inline] pub unsafe fn PyBytesWriter_GetSize(writer: *mut PyBytesWriter) -> crate::Py_ssize_t { (*writer).size } ); #[cfg(not(Py_LIMITED_API))] compat_function!( originally_defined_for(all(Py_3_15, not(Py_LIMITED_API))); #[inline] pub unsafe fn PyBytesWriter_Resize(writer: *mut PyBytesWriter, size: crate::Py_ssize_t) -> std::ffi::c_int { if size < 0 { crate::PyErr_SetString(crate::PyExc_ValueError, c"size must be >= 0".as_ptr()); return -1; } if _PyBytesWriter_Resize_impl(writer, size, 1) < 0 { return -1; } (*writer).size = size; 0 } ); #[repr(C)] #[cfg(not(any(Py_3_15, Py_LIMITED_API)))] pub struct PyBytesWriter { small_buffer: [std::ffi::c_char; 256], obj: *mut crate::PyObject, size: crate::Py_ssize_t, } #[inline] #[cfg(not(any(Py_3_15, Py_LIMITED_API)))] unsafe fn _PyBytesWriter_Resize_impl( writer: *mut PyBytesWriter, mut size: crate::Py_ssize_t, resize: std::ffi::c_int, ) -> std::ffi::c_int { let overallocate = resize; assert!(size >= 0); let allocated = if (*writer).obj.is_null() { std::mem::size_of_val(&(*writer).small_buffer) as _ } else { crate::PyBytes_Size((*writer).obj) }; if size <= allocated { return 0; } if overallocate > 0 { #[cfg(windows)] if size <= (crate::PY_SSIZE_T_MAX - size / 2) { size += size / 2; } #[cfg(not(windows))] if size <= (crate::PY_SSIZE_T_MAX - size / 4) { size += size / 4; } } if !(*writer).obj.is_null() { if crate::_PyBytes_Resize(&mut (*writer).obj, size) > 0 { return -1; } assert!(!(*writer).obj.is_null()) } else { (*writer).obj = crate::PyBytes_FromStringAndSize(std::ptr::null_mut(), size); if (*writer).obj.is_null() { return -1; } if resize > 0 { assert!((size as usize) > std::mem::size_of_val(&(*writer).small_buffer)); std::ptr::copy_nonoverlapping( (*writer).small_buffer.as_ptr(), crate::PyBytes_AS_STRING((*writer).obj) as *mut _, std::mem::size_of_val(&(*writer).small_buffer), ); } } 0 } ================================================ FILE: pyo3-ffi/src/compat/py_3_9.rs ================================================ compat_function!( originally_defined_for(all( not(PyPy), any(Py_3_10, all(not(Py_LIMITED_API), Py_3_9)) // Added to python in 3.9 but to limited API in 3.10 )); #[inline] pub unsafe fn PyObject_CallNoArgs(obj: *mut crate::PyObject) -> *mut crate::PyObject { crate::PyObject_CallObject(obj, std::ptr::null_mut()) } ); compat_function!( originally_defined_for(all(Py_3_9, not(any(Py_LIMITED_API, PyPy)))); #[inline] pub unsafe fn PyObject_CallMethodNoArgs(obj: *mut crate::PyObject, name: *mut crate::PyObject) -> *mut crate::PyObject { crate::PyObject_CallMethodObjArgs(obj, name, std::ptr::null_mut::()) } ); ================================================ FILE: pyo3-ffi/src/compile.rs ================================================ use std::ffi::c_int; pub const Py_single_input: c_int = 256; pub const Py_file_input: c_int = 257; pub const Py_eval_input: c_int = 258; #[cfg(Py_3_8)] pub const Py_func_type_input: c_int = 345; #[cfg(Py_3_9)] pub const Py_fstring_input: c_int = 800; ================================================ FILE: pyo3-ffi/src/complexobject.rs ================================================ use crate::object::*; use std::ffi::{c_double, c_int}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyComplex_Type")] pub static mut PyComplex_Type: PyTypeObject; } #[inline] pub unsafe fn PyComplex_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyComplex_Type) } #[inline] pub unsafe fn PyComplex_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyComplex_Type) } extern_libpython! { // skipped non-limited PyComplex_FromCComplex #[cfg_attr(PyPy, link_name = "PyPyComplex_FromDoubles")] pub fn PyComplex_FromDoubles(real: c_double, imag: c_double) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyComplex_RealAsDouble")] pub fn PyComplex_RealAsDouble(op: *mut PyObject) -> c_double; #[cfg_attr(PyPy, link_name = "PyPyComplex_ImagAsDouble")] pub fn PyComplex_ImagAsDouble(op: *mut PyObject) -> c_double; } ================================================ FILE: pyo3-ffi/src/context.rs ================================================ use crate::object::{PyObject, PyTypeObject, Py_TYPE}; use std::ffi::{c_char, c_int}; extern_libpython! { pub static mut PyContext_Type: PyTypeObject; // skipped non-limited opaque PyContext pub static mut PyContextVar_Type: PyTypeObject; // skipped non-limited opaque PyContextVar pub static mut PyContextToken_Type: PyTypeObject; // skipped non-limited opaque PyContextToken } #[inline] pub unsafe fn PyContext_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyContext_Type) as c_int } #[inline] pub unsafe fn PyContextVar_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyContextVar_Type) as c_int } #[inline] pub unsafe fn PyContextToken_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyContextToken_Type) as c_int } extern_libpython! { pub fn PyContext_New() -> *mut PyObject; pub fn PyContext_Copy(ctx: *mut PyObject) -> *mut PyObject; pub fn PyContext_CopyCurrent() -> *mut PyObject; pub fn PyContext_Enter(ctx: *mut PyObject) -> c_int; pub fn PyContext_Exit(ctx: *mut PyObject) -> c_int; pub fn PyContextVar_New(name: *const c_char, def: *mut PyObject) -> *mut PyObject; pub fn PyContextVar_Get( var: *mut PyObject, default_value: *mut PyObject, value: *mut *mut PyObject, ) -> c_int; pub fn PyContextVar_Set(var: *mut PyObject, value: *mut PyObject) -> *mut PyObject; pub fn PyContextVar_Reset(var: *mut PyObject, token: *mut PyObject) -> c_int; // skipped non-limited _PyContext_NewHamtForTests } ================================================ FILE: pyo3-ffi/src/cpython/abstract_.rs ================================================ use crate::{PyObject, Py_ssize_t}; #[cfg(any(all(Py_3_8, not(PyPy)), not(Py_3_11)))] use std::ffi::c_char; use std::ffi::c_int; #[cfg(not(Py_3_11))] use crate::Py_buffer; #[cfg(all(Py_3_8, not(PyPy)))] use crate::{ vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL, }; #[cfg(Py_3_8)] use libc::size_t; extern_libpython! { #[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] pub fn _PyStack_AsDict(values: *const *mut PyObject, kwnames: *mut PyObject) -> *mut PyObject; } #[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] const _PY_FASTCALL_SMALL_STACK: size_t = 5; extern_libpython! { #[cfg(all(Py_3_8, not(PyPy)))] pub fn _Py_CheckFunctionResult( tstate: *mut PyThreadState, callable: *mut PyObject, result: *mut PyObject, where_: *const c_char, ) -> *mut PyObject; #[cfg(all(Py_3_8, not(PyPy)))] pub fn _PyObject_MakeTpCall( tstate: *mut PyThreadState, callable: *mut PyObject, args: *const *mut PyObject, nargs: Py_ssize_t, keywords: *mut PyObject, ) -> *mut PyObject; } #[cfg(Py_3_8)] // NB exported as public in abstract.rs from 3.12 const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = 1 << (8 * std::mem::size_of::() as size_t - 1); #[cfg(Py_3_8)] #[inline(always)] pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t { let n = n & !PY_VECTORCALL_ARGUMENTS_OFFSET; n.try_into().expect("cannot fail due to mask") } #[cfg(all(Py_3_8, not(PyPy)))] #[inline(always)] pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option { assert!(!callable.is_null()); let tp = crate::Py_TYPE(callable); if PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL) == 0 { return None; } assert!(PyCallable_Check(callable) > 0); let offset = (*tp).tp_vectorcall_offset; assert!(offset > 0); let ptr = callable.cast::().offset(offset).cast(); *ptr } #[cfg(all(Py_3_8, not(PyPy)))] #[inline(always)] pub unsafe fn _PyObject_VectorcallTstate( tstate: *mut PyThreadState, callable: *mut PyObject, args: *const *mut PyObject, nargsf: size_t, kwnames: *mut PyObject, ) -> *mut PyObject { assert!(kwnames.is_null() || PyTuple_Check(kwnames) > 0); assert!(!args.is_null() || PyVectorcall_NARGS(nargsf) == 0); match PyVectorcall_Function(callable) { None => { let nargs = PyVectorcall_NARGS(nargsf); _PyObject_MakeTpCall(tstate, callable, args, nargs, kwnames) } Some(func) => { let res = func(callable, args, nargsf, kwnames); _Py_CheckFunctionResult(tstate, callable, res, std::ptr::null_mut()) } } } #[cfg(all(Py_3_8, not(any(PyPy, GraalPy, Py_3_11))))] // exported as a function from 3.11, see abstract.rs #[inline(always)] pub unsafe fn PyObject_Vectorcall( callable: *mut PyObject, args: *const *mut PyObject, nargsf: size_t, kwnames: *mut PyObject, ) -> *mut PyObject { _PyObject_VectorcallTstate(PyThreadState_GET(), callable, args, nargsf, kwnames) } extern_libpython! { #[cfg(Py_3_8)] #[cfg_attr( all(not(any(PyPy, GraalPy)), not(Py_3_9)), link_name = "_PyObject_VectorcallDict" )] #[cfg_attr(all(PyPy, not(Py_3_9)), link_name = "_PyPyObject_VectorcallDict")] #[cfg_attr(all(PyPy, Py_3_9), link_name = "PyPyObject_VectorcallDict")] pub fn PyObject_VectorcallDict( callable: *mut PyObject, args: *const *mut PyObject, nargsf: size_t, kwdict: *mut PyObject, ) -> *mut PyObject; #[cfg(Py_3_8)] #[cfg_attr(not(any(Py_3_9, PyPy)), link_name = "_PyVectorcall_Call")] #[cfg_attr(PyPy, link_name = "PyPyVectorcall_Call")] pub fn PyVectorcall_Call( callable: *mut PyObject, tuple: *mut PyObject, dict: *mut PyObject, ) -> *mut PyObject; } #[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] #[inline(always)] pub unsafe fn _PyObject_FastCallTstate( tstate: *mut PyThreadState, func: *mut PyObject, args: *const *mut PyObject, nargs: Py_ssize_t, ) -> *mut PyObject { _PyObject_VectorcallTstate(tstate, func, args, nargs as size_t, std::ptr::null_mut()) } #[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] #[inline(always)] pub unsafe fn _PyObject_FastCall( func: *mut PyObject, args: *const *mut PyObject, nargs: Py_ssize_t, ) -> *mut PyObject { _PyObject_FastCallTstate(PyThreadState_GET(), func, args, nargs) } #[cfg(all(Py_3_8, not(PyPy)))] #[inline(always)] pub unsafe fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject { _PyObject_VectorcallTstate( PyThreadState_GET(), func, std::ptr::null_mut(), 0, std::ptr::null_mut(), ) } extern_libpython! { #[cfg(PyPy)] #[link_name = "_PyPyObject_CallNoArg"] pub fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject; } #[cfg(all(Py_3_8, not(PyPy)))] #[inline(always)] pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *mut PyObject { assert!(!arg.is_null()); let args_array = [std::ptr::null_mut(), arg]; let args = args_array.as_ptr().offset(1); // For PY_VECTORCALL_ARGUMENTS_OFFSET let tstate = PyThreadState_GET(); let nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET; _PyObject_VectorcallTstate(tstate, func, args, nargsf, std::ptr::null_mut()) } #[cfg(all(Py_3_9, not(PyPy)))] #[inline(always)] pub unsafe fn PyObject_CallMethodNoArgs( self_: *mut PyObject, name: *mut PyObject, ) -> *mut PyObject { crate::PyObject_VectorcallMethod( name, &self_, 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, std::ptr::null_mut(), ) } #[cfg(all(Py_3_9, not(PyPy)))] #[inline(always)] pub unsafe fn PyObject_CallMethodOneArg( self_: *mut PyObject, name: *mut PyObject, arg: *mut PyObject, ) -> *mut PyObject { let args = [self_, arg]; assert!(!arg.is_null()); crate::PyObject_VectorcallMethod( name, args.as_ptr(), 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, std::ptr::null_mut(), ) } // skipped _PyObject_VectorcallMethodId // skipped _PyObject_CallMethodIdNoArgs // skipped _PyObject_CallMethodIdOneArg // skipped _PyObject_HasLen extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyObject_LengthHint")] pub fn PyObject_LengthHint(o: *mut PyObject, arg1: Py_ssize_t) -> Py_ssize_t; #[cfg(not(Py_3_11))] // moved to src/buffer.rs from 3.11 #[cfg(all(Py_3_9, not(PyPy)))] pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int; } #[cfg(not(any(Py_3_9, PyPy)))] #[inline] pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int { let tp_as_buffer = (*crate::Py_TYPE(o)).tp_as_buffer; (!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int } #[cfg(not(Py_3_11))] // moved to src/buffer.rs from 3.11 extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")] pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")] pub fn PyBuffer_GetPointer( view: *mut Py_buffer, indices: *mut Py_ssize_t, ) -> *mut std::ffi::c_void; #[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")] pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")] pub fn PyBuffer_ToContiguous( buf: *mut std::ffi::c_void, view: *mut Py_buffer, len: Py_ssize_t, order: c_char, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")] pub fn PyBuffer_FromContiguous( view: *mut Py_buffer, buf: *mut std::ffi::c_void, len: Py_ssize_t, order: c_char, ) -> c_int; pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")] pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int; pub fn PyBuffer_FillContiguousStrides( ndims: c_int, shape: *mut Py_ssize_t, strides: *mut Py_ssize_t, itemsize: c_int, fort: c_char, ); #[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")] pub fn PyBuffer_FillInfo( view: *mut Py_buffer, o: *mut PyObject, buf: *mut std::ffi::c_void, len: Py_ssize_t, readonly: c_int, flags: c_int, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")] pub fn PyBuffer_Release(view: *mut Py_buffer); } // PyIter_Check defined in ffi/abstract_.rs // PyIndex_Check defined in ffi/abstract_.rs // Not defined here because this file is not compiled under the // limited API, but the macros need to be defined for 3.6, 3.7 which // predate the limited API changes. // skipped PySequence_ITEM pub const PY_ITERSEARCH_COUNT: c_int = 1; pub const PY_ITERSEARCH_INDEX: c_int = 2; pub const PY_ITERSEARCH_CONTAINS: c_int = 3; extern_libpython! { #[cfg(not(any(PyPy, GraalPy)))] pub fn _PySequence_IterSearch( seq: *mut PyObject, obj: *mut PyObject, operation: c_int, ) -> Py_ssize_t; } // skipped _PyObject_RealIsInstance // skipped _PyObject_RealIsSubclass // skipped _PySequence_BytesToCharpArray // skipped _Py_FreeCharPArray // skipped _Py_add_one_to_index_F // skipped _Py_add_one_to_index_C // skipped _Py_convert_optional_to_ssize_t // skipped _PyNumber_Index(*mut PyObject o) ================================================ FILE: pyo3-ffi/src/cpython/bytesobject.rs ================================================ use crate::object::*; use crate::Py_ssize_t; #[cfg(not(Py_LIMITED_API))] use std::ffi::c_char; use std::ffi::c_int; #[cfg(Py_3_15)] use std::ffi::c_void; #[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))] #[repr(C)] pub struct PyBytesObject { pub ob_base: PyVarObject, #[cfg_attr( Py_3_11, deprecated(note = "Deprecated in Python 3.11 and will be removed in a future version.") )] pub ob_shash: crate::Py_hash_t, pub ob_sval: [c_char; 1], } #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] opaque_struct!(pub PyBytesObject); extern_libpython! { #[cfg_attr(PyPy, link_name = "_PyPyBytes_Resize")] pub fn _PyBytes_Resize(bytes: *mut *mut PyObject, newsize: Py_ssize_t) -> c_int; } #[cfg(not(Py_LIMITED_API))] #[inline] pub unsafe fn PyBytes_AS_STRING(op: *mut PyObject) -> *const c_char { #[cfg(not(any(PyPy, GraalPy)))] return &(*op.cast::()).ob_sval as *const c_char; #[cfg(any(PyPy, GraalPy))] return crate::PyBytes_AsString(op); } #[cfg(Py_3_15)] opaque_struct!(pub PyBytesWriter); #[cfg(Py_3_15)] extern_libpython! { pub fn PyBytesWriter_Create(size: Py_ssize_t) -> *mut PyBytesWriter; pub fn PyBytesWriter_Discard(writer: *mut PyBytesWriter); pub fn PyBytesWriter_Finish(writer: *mut PyBytesWriter) -> *mut PyObject; pub fn PyBytesWriter_FinishWithSize( writer: *mut PyBytesWriter, size: Py_ssize_t, ) -> *mut PyObject; pub fn PyBytesWriter_GetData(writer: *mut PyBytesWriter) -> *mut c_void; pub fn PyBytesWriter_GetSize(writer: *mut PyBytesWriter) -> Py_ssize_t; pub fn PyBytesWriter_Resize(writer: *mut PyBytesWriter, size: Py_ssize_t) -> c_int; pub fn PyBytesWriter_Grow(writer: *mut PyBytesWriter, size: Py_ssize_t) -> c_int; } ================================================ FILE: pyo3-ffi/src/cpython/ceval.rs ================================================ use crate::cpython::pystate::Py_tracefunc; use crate::object::{freefunc, PyObject}; use std::ffi::c_int; extern_libpython! { // skipped non-limited _PyEval_CallTracing #[cfg(not(Py_3_11))] pub fn _PyEval_EvalFrameDefault(arg1: *mut crate::PyFrameObject, exc: c_int) -> *mut PyObject; #[cfg(Py_3_11)] pub fn _PyEval_EvalFrameDefault( tstate: *mut crate::PyThreadState, frame: *mut crate::_PyInterpreterFrame, exc: c_int, ) -> *mut crate::PyObject; pub fn _PyEval_RequestCodeExtraIndex(func: freefunc) -> c_int; pub fn PyEval_SetProfile(trace_func: Option, arg1: *mut PyObject); pub fn PyEval_SetTrace(trace_func: Option, arg1: *mut PyObject); } ================================================ FILE: pyo3-ffi/src/cpython/code.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; #[cfg(not(GraalPy))] use crate::PyCodeObject; #[cfg(not(GraalPy))] use std::ffi::c_char; use std::ffi::{c_int, c_void}; // skipped private _PY_MONITORING_LOCAL_EVENTS // skipped private _PY_MONITORING_UNGROUPED_EVENTS // skipped private _PY_MONITORING_EVENTS // skipped private _PyLocalMonitors // skipped private _Py_GlobalMonitors // skipped private _Py_CODEUNIT // skipped private _Py_OPCODE // skipped private _Py_OPARG // skipped private _py_make_codeunit // skipped private _py_set_opcode // skipped private _Py_MAKE_CODEUNIT // skipped private _Py_SET_OPCODE // skipped private _PyCoCached // skipped private _PyCoLineInstrumentationData // skipped private _PyCoMonitoringData // skipped private _PyExecutorArray /* Masks for co_flags */ pub const CO_OPTIMIZED: c_int = 0x0001; pub const CO_NEWLOCALS: c_int = 0x0002; pub const CO_VARARGS: c_int = 0x0004; pub const CO_VARKEYWORDS: c_int = 0x0008; pub const CO_NESTED: c_int = 0x0010; pub const CO_GENERATOR: c_int = 0x0020; /* The CO_NOFREE flag is set if there are no free or cell variables. This information is redundant, but it allows a single flag test to determine whether there is any extra work to be done when the call frame it setup. */ pub const CO_NOFREE: c_int = 0x0040; /* The CO_COROUTINE flag is set for coroutine functions (defined with ``async def`` keywords) */ pub const CO_COROUTINE: c_int = 0x0080; pub const CO_ITERABLE_COROUTINE: c_int = 0x0100; pub const CO_ASYNC_GENERATOR: c_int = 0x0200; pub const CO_FUTURE_DIVISION: c_int = 0x2000; pub const CO_FUTURE_ABSOLUTE_IMPORT: c_int = 0x4000; /* do absolute imports by default */ pub const CO_FUTURE_WITH_STATEMENT: c_int = 0x8000; pub const CO_FUTURE_PRINT_FUNCTION: c_int = 0x1_0000; pub const CO_FUTURE_UNICODE_LITERALS: c_int = 0x2_0000; pub const CO_FUTURE_BARRY_AS_BDFL: c_int = 0x4_0000; pub const CO_FUTURE_GENERATOR_STOP: c_int = 0x8_0000; // skipped CO_FUTURE_ANNOTATIONS // skipped CO_CELL_NOT_AN_ARG pub const CO_MAXBLOCKS: usize = 20; #[cfg(not(PyPy))] extern_libpython! { pub static mut PyCode_Type: PyTypeObject; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyCode_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyCode_Type) as c_int } extern_libpython! { #[cfg(PyPy)] #[link_name = "PyPyCode_Check"] pub fn PyCode_Check(op: *mut PyObject) -> c_int; } // skipped PyCode_GetNumFree (requires knowledge of code object layout) extern_libpython! { #[cfg(not(GraalPy))] #[cfg_attr(PyPy, link_name = "PyPyCode_New")] pub fn PyCode_New( argcount: c_int, kwonlyargcount: c_int, nlocals: c_int, stacksize: c_int, flags: c_int, code: *mut PyObject, consts: *mut PyObject, names: *mut PyObject, varnames: *mut PyObject, freevars: *mut PyObject, cellvars: *mut PyObject, filename: *mut PyObject, name: *mut PyObject, firstlineno: c_int, lnotab: *mut PyObject, ) -> *mut PyCodeObject; #[cfg(not(GraalPy))] #[cfg(Py_3_8)] pub fn PyCode_NewWithPosOnlyArgs( argcount: c_int, posonlyargcount: c_int, kwonlyargcount: c_int, nlocals: c_int, stacksize: c_int, flags: c_int, code: *mut PyObject, consts: *mut PyObject, names: *mut PyObject, varnames: *mut PyObject, freevars: *mut PyObject, cellvars: *mut PyObject, filename: *mut PyObject, name: *mut PyObject, firstlineno: c_int, lnotab: *mut PyObject, ) -> *mut PyCodeObject; #[cfg(not(GraalPy))] #[cfg_attr(PyPy, link_name = "PyPyCode_NewEmpty")] pub fn PyCode_NewEmpty( filename: *const c_char, funcname: *const c_char, firstlineno: c_int, ) -> *mut PyCodeObject; #[cfg(not(GraalPy))] pub fn PyCode_Addr2Line(arg1: *mut PyCodeObject, arg2: c_int) -> c_int; // skipped PyCodeAddressRange "for internal use only" // skipped _PyCode_CheckLineNumber // skipped _PyCode_ConstantKey pub fn PyCode_Optimize( code: *mut PyObject, consts: *mut PyObject, names: *mut PyObject, lnotab: *mut PyObject, ) -> *mut PyObject; pub fn _PyCode_GetExtra( code: *mut PyObject, index: Py_ssize_t, extra: *const *mut c_void, ) -> c_int; pub fn _PyCode_SetExtra(code: *mut PyObject, index: Py_ssize_t, extra: *mut c_void) -> c_int; } ================================================ FILE: pyo3-ffi/src/cpython/compile.rs ================================================ #[cfg(not(any(PyPy, Py_3_10)))] use crate::object::PyObject; #[cfg(not(any(PyPy, Py_3_10)))] use crate::pyarena::*; #[cfg(not(any(PyPy, Py_3_10)))] use crate::pythonrun::*; #[cfg(not(any(PyPy, Py_3_10)))] use crate::PyCodeObject; use crate::INT_MAX; #[cfg(not(any(PyPy, Py_3_10)))] use std::ffi::c_char; use std::ffi::c_int; // skipped PyCF_MASK // skipped PyCF_MASK_OBSOLETE // skipped PyCF_SOURCE_IS_UTF8 // skipped PyCF_DONT_IMPLY_DEDENT // skipped PyCF_ONLY_AST // skipped PyCF_IGNORE_COOKIE // skipped PyCF_TYPE_COMMENTS // skipped PyCF_ALLOW_TOP_LEVEL_AWAIT // skipped PyCF_OPTIMIZED_AST // skipped PyCF_COMPILE_MASK #[repr(C)] #[derive(Copy, Clone)] pub struct PyCompilerFlags { pub cf_flags: c_int, #[cfg(Py_3_8)] pub cf_feature_version: c_int, } // skipped _PyCompilerFlags_INIT // NB this type technically existed in the header until 3.13, when it was // moved to the internal CPython headers. // // We choose not to expose it in the public API past 3.10, as it is // not used in the public API past that point. #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyFutureFeatures { pub ff_features: c_int, pub ff_lineno: c_int, } // FIXME: these constants should probably be &CStr, if they are used at all pub const FUTURE_NESTED_SCOPES: &str = "nested_scopes"; pub const FUTURE_GENERATORS: &str = "generators"; pub const FUTURE_DIVISION: &str = "division"; pub const FUTURE_ABSOLUTE_IMPORT: &str = "absolute_import"; pub const FUTURE_WITH_STATEMENT: &str = "with_statement"; pub const FUTURE_PRINT_FUNCTION: &str = "print_function"; pub const FUTURE_UNICODE_LITERALS: &str = "unicode_literals"; pub const FUTURE_BARRY_AS_BDFL: &str = "barry_as_FLUFL"; pub const FUTURE_GENERATOR_STOP: &str = "generator_stop"; pub const FUTURE_ANNOTATIONS: &str = "annotations"; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] extern_libpython! { pub fn PyNode_Compile(arg1: *mut _node, arg2: *const c_char) -> *mut PyCodeObject; pub fn PyAST_CompileEx( _mod: *mut _mod, filename: *const c_char, flags: *mut PyCompilerFlags, optimize: c_int, arena: *mut PyArena, ) -> *mut PyCodeObject; pub fn PyAST_CompileObject( _mod: *mut _mod, filename: *mut PyObject, flags: *mut PyCompilerFlags, optimize: c_int, arena: *mut PyArena, ) -> *mut PyCodeObject; pub fn PyFuture_FromAST(_mod: *mut _mod, filename: *const c_char) -> *mut PyFutureFeatures; pub fn PyFuture_FromASTObject( _mod: *mut _mod, filename: *mut PyObject, ) -> *mut PyFutureFeatures; } pub const PY_INVALID_STACK_EFFECT: c_int = INT_MAX; extern_libpython! { pub fn PyCompile_OpcodeStackEffect(opcode: c_int, oparg: c_int) -> c_int; #[cfg(Py_3_8)] pub fn PyCompile_OpcodeStackEffectWithJump(opcode: c_int, oparg: c_int, jump: c_int) -> c_int; } ================================================ FILE: pyo3-ffi/src/cpython/complexobject.rs ================================================ use crate::PyObject; use std::ffi::c_double; #[repr(C)] #[derive(Copy, Clone)] pub struct Py_complex { pub real: c_double, pub imag: c_double, } // skipped private function _Py_c_sum // skipped private function _Py_c_diff // skipped private function _Py_c_neg // skipped private function _Py_c_prod // skipped private function _Py_c_quot // skipped private function _Py_c_pow // skipped private function _Py_c_abs #[repr(C)] pub struct PyComplexObject { pub ob_base: PyObject, pub cval: Py_complex, } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyComplex_FromCComplex")] pub fn PyComplex_FromCComplex(v: Py_complex) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyComplex_AsCComplex")] pub fn PyComplex_AsCComplex(op: *mut PyObject) -> Py_complex; } ================================================ FILE: pyo3-ffi/src/cpython/critical_section.rs ================================================ #[cfg(any(Py_3_14, Py_GIL_DISABLED))] use crate::PyMutex; use crate::PyObject; #[repr(C)] #[cfg(Py_GIL_DISABLED)] pub struct PyCriticalSection { _cs_prev: usize, _cs_mutex: *mut PyMutex, } #[repr(C)] #[cfg(Py_GIL_DISABLED)] pub struct PyCriticalSection2 { _cs_base: PyCriticalSection, _cs_mutex2: *mut PyMutex, } #[cfg(not(Py_GIL_DISABLED))] opaque_struct!(pub PyCriticalSection); #[cfg(not(Py_GIL_DISABLED))] opaque_struct!(pub PyCriticalSection2); extern_libpython! { pub fn PyCriticalSection_Begin(c: *mut PyCriticalSection, op: *mut PyObject); #[cfg(Py_3_14)] pub fn PyCriticalSection_BeginMutex(c: *mut PyCriticalSection, m: *mut PyMutex); pub fn PyCriticalSection_End(c: *mut PyCriticalSection); pub fn PyCriticalSection2_Begin(c: *mut PyCriticalSection2, a: *mut PyObject, b: *mut PyObject); #[cfg(Py_3_14)] pub fn PyCriticalSection2_BeginMutex( c: *mut PyCriticalSection2, m1: *mut PyMutex, m2: *mut PyMutex, ); pub fn PyCriticalSection2_End(c: *mut PyCriticalSection2); } ================================================ FILE: pyo3-ffi/src/cpython/descrobject.rs ================================================ use crate::{PyGetSetDef, PyMethodDef, PyObject, PyTypeObject}; use std::ffi::{c_char, c_int, c_void}; #[cfg(Py_3_11)] use crate::PyMemberDef; pub type wrapperfunc = Option< unsafe extern "C" fn( slf: *mut PyObject, args: *mut PyObject, wrapped: *mut c_void, ) -> *mut PyObject, >; pub type wrapperfunc_kwds = Option< unsafe extern "C" fn( slf: *mut PyObject, args: *mut PyObject, wrapped: *mut c_void, kwds: *mut PyObject, ) -> *mut PyObject, >; #[repr(C)] pub struct wrapperbase { pub name: *const c_char, pub offset: c_int, pub function: *mut c_void, pub wrapper: wrapperfunc, pub doc: *const c_char, pub flags: c_int, pub name_strobj: *mut PyObject, } pub const PyWrapperFlag_KEYWORDS: c_int = 1; #[repr(C)] pub struct PyDescrObject { pub ob_base: PyObject, pub d_type: *mut PyTypeObject, pub d_name: *mut PyObject, pub d_qualname: *mut PyObject, } // skipped non-limited PyDescr_TYPE // skipped non-limited PyDescr_NAME #[repr(C)] pub struct PyMethodDescrObject { pub d_common: PyDescrObject, pub d_method: *mut PyMethodDef, #[cfg(all(not(PyPy), Py_3_8))] pub vectorcall: Option, } #[repr(C)] pub struct PyMemberDescrObject { pub d_common: PyDescrObject, #[cfg(not(Py_3_11))] pub d_member: *mut PyGetSetDef, #[cfg(Py_3_11)] pub d_member: *mut PyMemberDef, } #[repr(C)] pub struct PyGetSetDescrObject { pub d_common: PyDescrObject, pub d_getset: *mut PyGetSetDef, } #[repr(C)] pub struct PyWrapperDescrObject { pub d_common: PyDescrObject, pub d_base: *mut wrapperbase, pub d_wrapped: *mut c_void, } // skipped _PyMethodWrapper_Type // skipped non-limited PyDescr_NewWrapper // skipped non-limited PyDescr_IsData ================================================ FILE: pyo3-ffi/src/cpython/dictobject.rs ================================================ #[cfg(not(GraalPy))] use crate::object::*; #[cfg(not(any(PyPy, GraalPy)))] use crate::pyport::Py_ssize_t; #[cfg(not(PyPy))] opaque_struct!(pub PyDictKeysObject); #[cfg(Py_3_11)] #[cfg(not(PyPy))] opaque_struct!(pub PyDictValues); #[cfg(not(any(GraalPy, PyPy)))] #[repr(C)] #[derive(Debug)] pub struct PyDictObject { pub ob_base: PyObject, pub ma_used: Py_ssize_t, #[cfg_attr( Py_3_12, deprecated(note = "Deprecated in Python 3.12 and will be removed in the future.") )] #[cfg(not(Py_3_14))] pub ma_version_tag: u64, #[cfg(Py_3_14)] _ma_watcher_tag: u64, pub ma_keys: *mut PyDictKeysObject, #[cfg(not(Py_3_11))] pub ma_values: *mut *mut PyObject, #[cfg(Py_3_11)] pub ma_values: *mut PyDictValues, } #[cfg(PyPy)] #[repr(C)] #[derive(Debug)] pub struct PyDictObject { pub ob_base: PyObject, _tmpkeys: *mut PyObject, } // skipped private _PyDict_GetItem_KnownHash // skipped private _PyDict_GetItemStringWithError // skipped PyDict_SetDefault // skipped PyDict_SetDefaultRef // skipped PyDict_GET_SIZE // skipped PyDict_ContainsString // skipped private _PyDict_NewPresized // skipped PyDict_Pop // skipped PyDict_PopString // skipped private _PyDict_Pop // skipped PY_FOREACH_DICT_EVENT // skipped PyDict_WatchEvent // skipped PyDict_WatchCallback // skipped PyDict_AddWatcher // skipped PyDict_ClearWatcher // skipped PyDict_Watch // skipped PyDict_Unwatch ================================================ FILE: pyo3-ffi/src/cpython/floatobject.rs ================================================ #[cfg(GraalPy)] use crate::PyFloat_AsDouble; use crate::{PyFloat_Check, PyObject}; use std::ffi::c_double; #[repr(C)] pub struct PyFloatObject { pub ob_base: PyObject, pub ob_fval: c_double, } #[inline] pub unsafe fn _PyFloat_CAST(op: *mut PyObject) -> *mut PyFloatObject { debug_assert_eq!(PyFloat_Check(op), 1); op.cast() } #[inline] pub unsafe fn PyFloat_AS_DOUBLE(op: *mut PyObject) -> c_double { #[cfg(not(GraalPy))] return (*_PyFloat_CAST(op)).ob_fval; #[cfg(GraalPy)] return PyFloat_AsDouble(op); } // skipped PyFloat_Pack2 // skipped PyFloat_Pack4 // skipped PyFloat_Pack8 // skipped PyFloat_Unpack2 // skipped PyFloat_Unpack4 // skipped PyFloat_Unpack8 ================================================ FILE: pyo3-ffi/src/cpython/frameobject.rs ================================================ #[cfg(not(GraalPy))] use crate::object::*; #[cfg(not(GraalPy))] use crate::PyCodeObject; use crate::PyFrameObject; #[cfg(not(GraalPy))] use crate::PyThreadState; #[cfg(not(any(PyPy, GraalPy, Py_3_11)))] use std::ffi::c_char; use std::ffi::c_int; #[cfg(not(any(PyPy, GraalPy, Py_3_11)))] pub type PyFrameState = c_char; #[repr(C)] #[derive(Copy, Clone)] #[cfg(not(any(PyPy, GraalPy, Py_3_11)))] pub struct PyTryBlock { pub b_type: c_int, pub b_handler: c_int, pub b_level: c_int, } // skipped _PyFrame_IsRunnable // skipped _PyFrame_IsExecuting // skipped _PyFrameHasCompleted extern_libpython! { #[cfg(not(GraalPy))] #[cfg_attr(PyPy, link_name = "PyPyFrame_New")] pub fn PyFrame_New( tstate: *mut PyThreadState, code: *mut PyCodeObject, globals: *mut PyObject, locals: *mut PyObject, ) -> *mut PyFrameObject; // skipped _PyFrame_New_NoTrack pub fn PyFrame_BlockSetup(f: *mut PyFrameObject, _type: c_int, handler: c_int, level: c_int); #[cfg(not(any(PyPy, GraalPy, Py_3_11)))] pub fn PyFrame_BlockPop(f: *mut PyFrameObject) -> *mut PyTryBlock; pub fn PyFrame_LocalsToFast(f: *mut PyFrameObject, clear: c_int); pub fn PyFrame_FastToLocalsWithError(f: *mut PyFrameObject) -> c_int; pub fn PyFrame_FastToLocals(f: *mut PyFrameObject); // skipped _PyFrame_DebugMallocStats #[cfg(not(Py_3_9))] pub fn PyFrame_ClearFreeList() -> c_int; } ================================================ FILE: pyo3-ffi/src/cpython/funcobject.rs ================================================ use crate::PyObject; use std::ffi::c_int; #[cfg(all(not(any(PyPy, GraalPy)), not(Py_3_10)))] #[repr(C)] pub struct PyFunctionObject { pub ob_base: PyObject, pub func_code: *mut PyObject, pub func_globals: *mut PyObject, pub func_defaults: *mut PyObject, pub func_kwdefaults: *mut PyObject, pub func_closure: *mut PyObject, pub func_doc: *mut PyObject, pub func_name: *mut PyObject, pub func_dict: *mut PyObject, pub func_weakreflist: *mut PyObject, pub func_module: *mut PyObject, pub func_annotations: *mut PyObject, pub func_qualname: *mut PyObject, #[cfg(Py_3_8)] pub vectorcall: Option, } #[cfg(all(not(any(PyPy, GraalPy)), Py_3_10))] #[repr(C)] pub struct PyFunctionObject { pub ob_base: PyObject, pub func_globals: *mut PyObject, pub func_builtins: *mut PyObject, pub func_name: *mut PyObject, pub func_qualname: *mut PyObject, pub func_code: *mut PyObject, pub func_defaults: *mut PyObject, pub func_kwdefaults: *mut PyObject, pub func_closure: *mut PyObject, pub func_doc: *mut PyObject, pub func_dict: *mut PyObject, pub func_weakreflist: *mut PyObject, pub func_module: *mut PyObject, pub func_annotations: *mut PyObject, #[cfg(Py_3_14)] pub func_annotate: *mut PyObject, #[cfg(Py_3_12)] pub func_typeparams: *mut PyObject, pub vectorcall: Option, #[cfg(Py_3_11)] pub func_version: u32, } #[cfg(PyPy)] #[repr(C)] pub struct PyFunctionObject { pub ob_base: PyObject, pub func_name: *mut PyObject, } #[cfg(GraalPy)] pub struct PyFunctionObject { pub ob_base: PyObject, } extern_libpython! { #[cfg(not(all(PyPy, not(Py_3_8))))] #[cfg_attr(PyPy, link_name = "PyPyFunction_Type")] pub static mut PyFunction_Type: crate::PyTypeObject; } #[cfg(not(all(PyPy, not(Py_3_8))))] #[inline] pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int { (crate::Py_TYPE(op) == &raw mut PyFunction_Type) as c_int } extern_libpython! { pub fn PyFunction_New(code: *mut PyObject, globals: *mut PyObject) -> *mut PyObject; pub fn PyFunction_NewWithQualName( code: *mut PyObject, globals: *mut PyObject, qualname: *mut PyObject, ) -> *mut PyObject; pub fn PyFunction_GetCode(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_GetGlobals(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_GetModule(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_GetDefaults(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_SetDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int; pub fn PyFunction_GetKwDefaults(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_SetKwDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int; pub fn PyFunction_GetClosure(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_SetClosure(op: *mut PyObject, closure: *mut PyObject) -> c_int; pub fn PyFunction_GetAnnotations(op: *mut PyObject) -> *mut PyObject; pub fn PyFunction_SetAnnotations(op: *mut PyObject, annotations: *mut PyObject) -> c_int; } // skipped _PyFunction_Vectorcall // skipped PyFunction_GET_CODE // skipped PyFunction_GET_GLOBALS // skipped PyFunction_GET_MODULE // skipped PyFunction_GET_DEFAULTS // skipped PyFunction_GET_KW_DEFAULTS // skipped PyFunction_GET_CLOSURE // skipped PyFunction_GET_ANNOTATIONS // skipped PyClassMethod_Type // skipped PyStaticMethod_Type // skipped PyClassMethod_New // skipped PyStaticMethod_New ================================================ FILE: pyo3-ffi/src/cpython/genobject.rs ================================================ use crate::object::*; use crate::PyFrameObject; #[cfg(all(Py_3_11, not(any(PyPy, GraalPy, Py_3_14))))] use std::ffi::c_char; use std::ffi::c_int; #[cfg(not(any(PyPy, GraalPy, Py_3_14)))] #[repr(C)] pub struct PyGenObject { pub ob_base: PyObject, #[cfg(not(Py_3_11))] pub gi_frame: *mut PyFrameObject, #[cfg(not(Py_3_10))] pub gi_running: c_int, #[cfg(not(Py_3_12))] pub gi_code: *mut PyObject, pub gi_weakreflist: *mut PyObject, pub gi_name: *mut PyObject, pub gi_qualname: *mut PyObject, #[allow( private_interfaces, reason = "PyGenObject layout was public until 3.14" )] pub gi_exc_state: crate::cpython::pystate::_PyErr_StackItem, #[cfg(Py_3_11)] pub gi_origin_or_finalizer: *mut PyObject, #[cfg(Py_3_11)] pub gi_hooks_inited: c_char, #[cfg(Py_3_11)] pub gi_closed: c_char, #[cfg(Py_3_11)] pub gi_running_async: c_char, #[cfg(Py_3_11)] pub gi_frame_state: i8, #[cfg(Py_3_11)] pub gi_iframe: [*mut PyObject; 1], } #[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] opaque_struct!(pub PyGenObject); extern_libpython! { pub static mut PyGen_Type: PyTypeObject; } #[inline] pub unsafe fn PyGen_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyGen_Type) } #[inline] pub unsafe fn PyGen_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyGen_Type) as c_int } extern_libpython! { pub fn PyGen_New(frame: *mut PyFrameObject) -> *mut PyObject; // skipped PyGen_NewWithQualName // skipped _PyGen_SetStopIterationValue // skipped _PyGen_FetchStopIterationValue // skipped _PyGen_yf // skipped _PyGen_Finalize #[cfg(not(any(Py_3_9, PyPy)))] #[deprecated(note = "This function was never documented in the Python API.")] pub fn PyGen_NeedsFinalizing(op: *mut PyGenObject) -> c_int; } // skipped PyCoroObject extern_libpython! { pub static mut PyCoro_Type: PyTypeObject; } // skipped _PyCoroWrapper_Type #[inline] pub unsafe fn PyCoro_CheckExact(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyCoro_Type) } // skipped _PyCoro_GetAwaitableIter // skipped PyCoro_New // skipped PyAsyncGenObject extern_libpython! { pub static mut PyAsyncGen_Type: PyTypeObject; // skipped _PyAsyncGenASend_Type // skipped _PyAsyncGenWrappedValue_Type // skipped _PyAsyncGenAThrow_Type } // skipped PyAsyncGen_New #[inline] pub unsafe fn PyAsyncGen_CheckExact(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyAsyncGen_Type) } // skipped _PyAsyncGenValueWrapperNew ================================================ FILE: pyo3-ffi/src/cpython/import.rs ================================================ #[cfg(any(not(PyPy), Py_3_14))] use crate::PyObject; #[cfg(any(not(PyPy), Py_3_14))] use std::ffi::c_char; #[cfg(not(PyPy))] use std::ffi::{c_int, c_uchar}; #[cfg(not(PyPy))] #[repr(C)] #[derive(Copy, Clone)] pub struct _inittab { pub name: *const c_char, pub initfunc: Option *mut PyObject>, } extern_libpython! { #[cfg(not(PyPy))] pub static mut PyImport_Inittab: *mut _inittab; #[cfg(not(PyPy))] pub fn PyImport_ExtendInittab(newtab: *mut _inittab) -> c_int; } #[cfg(not(PyPy))] #[repr(C)] #[derive(Copy, Clone)] pub struct _frozen { pub name: *const c_char, pub code: *const c_uchar, pub size: c_int, #[cfg(Py_3_11)] pub is_package: c_int, #[cfg(all(Py_3_11, not(Py_3_13)))] pub get_code: Option *mut PyObject>, } extern_libpython! { #[cfg(not(PyPy))] pub static mut PyImport_FrozenModules: *const _frozen; #[cfg(Py_3_14)] pub fn PyImport_ImportModuleAttr( mod_name: *mut PyObject, attr_name: *mut PyObject, ) -> *mut PyObject; #[cfg(Py_3_14)] pub fn PyImport_ImportModuleAttrString( mod_name: *const c_char, attr_name: *const c_char, ) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/cpython/initconfig.rs ================================================ /* --- PyStatus ----------------------------------------------- */ use crate::Py_ssize_t; use libc::wchar_t; use std::ffi::{c_char, c_int, c_ulong}; #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum _PyStatus_TYPE { _PyStatus_TYPE_OK = 0, _PyStatus_TYPE_ERROR = 1, _PyStatus_TYPE_EXIT = 2, } #[repr(C)] #[derive(Copy, Clone)] pub struct PyStatus { pub _type: _PyStatus_TYPE, pub func: *const c_char, pub err_msg: *const c_char, pub exitcode: c_int, } extern_libpython! { pub fn PyStatus_Ok() -> PyStatus; pub fn PyStatus_Error(err_msg: *const c_char) -> PyStatus; pub fn PyStatus_NoMemory() -> PyStatus; pub fn PyStatus_Exit(exitcode: c_int) -> PyStatus; pub fn PyStatus_IsError(err: PyStatus) -> c_int; pub fn PyStatus_IsExit(err: PyStatus) -> c_int; pub fn PyStatus_Exception(err: PyStatus) -> c_int; } /* --- PyWideStringList ------------------------------------------------ */ #[repr(C)] #[derive(Copy, Clone)] pub struct PyWideStringList { pub length: Py_ssize_t, pub items: *mut *mut wchar_t, } extern_libpython! { pub fn PyWideStringList_Append(list: *mut PyWideStringList, item: *const wchar_t) -> PyStatus; pub fn PyWideStringList_Insert( list: *mut PyWideStringList, index: Py_ssize_t, item: *const wchar_t, ) -> PyStatus; } /* --- PyPreConfig ----------------------------------------------- */ #[repr(C)] #[derive(Copy, Clone)] pub struct PyPreConfig { pub _config_init: c_int, pub parse_argv: c_int, pub isolated: c_int, pub use_environment: c_int, pub configure_locale: c_int, pub coerce_c_locale: c_int, pub coerce_c_locale_warn: c_int, #[cfg(windows)] pub legacy_windows_fs_encoding: c_int, pub utf8_mode: c_int, pub dev_mode: c_int, pub allocator: c_int, } extern_libpython! { pub fn PyPreConfig_InitPythonConfig(config: *mut PyPreConfig); pub fn PyPreConfig_InitIsolatedConfig(config: *mut PyPreConfig); } /* --- PyConfig ---------------------------------------------- */ #[repr(C)] #[derive(Copy, Clone)] pub struct PyConfig { pub _config_init: c_int, pub isolated: c_int, pub use_environment: c_int, pub dev_mode: c_int, pub install_signal_handlers: c_int, pub use_hash_seed: c_int, pub hash_seed: c_ulong, pub faulthandler: c_int, #[cfg(all(Py_3_9, not(Py_3_10)))] pub _use_peg_parser: c_int, pub tracemalloc: c_int, #[cfg(Py_3_12)] pub perf_profiling: c_int, #[cfg(Py_3_14)] pub remote_debug: c_int, pub import_time: c_int, #[cfg(Py_3_11)] pub code_debug_ranges: c_int, pub show_ref_count: c_int, #[cfg(not(Py_3_9))] pub show_alloc_count: c_int, pub dump_refs: c_int, #[cfg(Py_3_11)] pub dump_refs_file: *mut wchar_t, pub malloc_stats: c_int, pub filesystem_encoding: *mut wchar_t, pub filesystem_errors: *mut wchar_t, pub pycache_prefix: *mut wchar_t, pub parse_argv: c_int, #[cfg(Py_3_10)] pub orig_argv: PyWideStringList, pub argv: PyWideStringList, #[cfg(not(Py_3_10))] pub program_name: *mut wchar_t, pub xoptions: PyWideStringList, pub warnoptions: PyWideStringList, pub site_import: c_int, pub bytes_warning: c_int, #[cfg(Py_3_10)] pub warn_default_encoding: c_int, pub inspect: c_int, pub interactive: c_int, pub optimization_level: c_int, pub parser_debug: c_int, pub write_bytecode: c_int, pub verbose: c_int, pub quiet: c_int, pub user_site_directory: c_int, pub configure_c_stdio: c_int, pub buffered_stdio: c_int, pub stdio_encoding: *mut wchar_t, pub stdio_errors: *mut wchar_t, #[cfg(windows)] pub legacy_windows_stdio: c_int, pub check_hash_pycs_mode: *mut wchar_t, #[cfg(Py_3_11)] pub use_frozen_modules: c_int, #[cfg(Py_3_11)] pub safe_path: c_int, #[cfg(Py_3_12)] pub int_max_str_digits: c_int, #[cfg(Py_3_14)] pub thread_inherit_context: c_int, #[cfg(Py_3_14)] pub context_aware_warnings: c_int, #[cfg(all(Py_3_14, target_os = "macos"))] pub use_system_logger: c_int, #[cfg(Py_3_13)] pub cpu_count: c_int, #[cfg(Py_GIL_DISABLED)] pub enable_gil: c_int, #[cfg(all(Py_3_14, Py_GIL_DISABLED))] pub tlbc_enabled: c_int, #[cfg(Py_3_15)] pub lazy_imports: c_int, pub pathconfig_warnings: c_int, #[cfg(Py_3_10)] pub program_name: *mut wchar_t, pub pythonpath_env: *mut wchar_t, pub home: *mut wchar_t, #[cfg(Py_3_10)] pub platlibdir: *mut wchar_t, pub module_search_paths_set: c_int, pub module_search_paths: PyWideStringList, #[cfg(Py_3_11)] pub stdlib_dir: *mut wchar_t, pub executable: *mut wchar_t, pub base_executable: *mut wchar_t, pub prefix: *mut wchar_t, pub base_prefix: *mut wchar_t, pub exec_prefix: *mut wchar_t, pub base_exec_prefix: *mut wchar_t, #[cfg(all(Py_3_9, not(Py_3_10)))] pub platlibdir: *mut wchar_t, pub skip_source_first_line: c_int, pub run_command: *mut wchar_t, pub run_module: *mut wchar_t, pub run_filename: *mut wchar_t, #[cfg(Py_3_13)] pub sys_path_0: *mut wchar_t, pub _install_importlib: c_int, pub _init_main: c_int, #[cfg(all(Py_3_9, not(Py_3_12)))] pub _isolated_interpreter: c_int, #[cfg(Py_3_11)] pub _is_python_build: c_int, #[cfg(all(Py_3_9, not(Py_3_10)))] pub _orig_argv: PyWideStringList, #[cfg(all(Py_3_13, py_sys_config = "Py_DEBUG"))] pub run_presite: *mut wchar_t, } extern_libpython! { pub fn PyConfig_InitPythonConfig(config: *mut PyConfig); pub fn PyConfig_InitIsolatedConfig(config: *mut PyConfig); pub fn PyConfig_Clear(config: *mut PyConfig); pub fn PyConfig_SetString( config: *mut PyConfig, config_str: *mut *mut wchar_t, str: *const wchar_t, ) -> PyStatus; pub fn PyConfig_SetBytesString( config: *mut PyConfig, config_str: *mut *mut wchar_t, str: *const c_char, ) -> PyStatus; pub fn PyConfig_Read(config: *mut PyConfig) -> PyStatus; pub fn PyConfig_SetBytesArgv( config: *mut PyConfig, argc: Py_ssize_t, argv: *mut *const c_char, ) -> PyStatus; pub fn PyConfig_SetArgv( config: *mut PyConfig, argc: Py_ssize_t, argv: *mut *const wchar_t, ) -> PyStatus; pub fn PyConfig_SetWideStringList( config: *mut PyConfig, list: *mut PyWideStringList, length: Py_ssize_t, items: *mut *mut wchar_t, ) -> PyStatus; } /* --- Helper functions --------------------------------------- */ extern_libpython! { pub fn Py_GetArgcArgv(argc: *mut c_int, argv: *mut *mut *mut wchar_t); } ================================================ FILE: pyo3-ffi/src/cpython/listobject.rs ================================================ use crate::object::*; #[cfg(not(PyPy))] use crate::pyport::Py_ssize_t; #[cfg(not(PyPy))] #[repr(C)] pub struct PyListObject { pub ob_base: PyVarObject, pub ob_item: *mut *mut PyObject, pub allocated: Py_ssize_t, } #[cfg(PyPy)] pub struct PyListObject { pub ob_base: PyObject, } // skipped _PyList_Extend // skipped _PyList_DebugMallocStats // skipped _PyList_CAST (used inline below) /// Macro, trading safety for speed #[inline] #[cfg(not(any(PyPy, GraalPy)))] pub unsafe fn PyList_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject { *(*(op as *mut PyListObject)).ob_item.offset(i) } /// Macro, *only* to be used to fill in brand new lists #[inline] #[cfg(not(any(PyPy, GraalPy)))] pub unsafe fn PyList_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) { *(*(op as *mut PyListObject)).ob_item.offset(i) = v; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyList_GET_SIZE(op: *mut PyObject) -> Py_ssize_t { Py_SIZE(op) } ================================================ FILE: pyo3-ffi/src/cpython/lock.rs ================================================ #[cfg(Py_3_14)] use std::os::raw::c_int; use std::sync::atomic::AtomicU8; #[repr(transparent)] #[derive(Debug)] pub struct PyMutex { pub(crate) _bits: AtomicU8, } // we don't impl Default because PyO3's safe wrappers don't need it #[allow(clippy::new_without_default)] impl PyMutex { pub const fn new() -> PyMutex { PyMutex { _bits: AtomicU8::new(0), } } } extern_libpython! { pub fn PyMutex_Lock(m: *mut PyMutex); pub fn PyMutex_Unlock(m: *mut PyMutex); #[cfg(Py_3_14)] pub fn PyMutex_IsLocked(m: *mut PyMutex) -> c_int; } ================================================ FILE: pyo3-ffi/src/cpython/longobject.rs ================================================ use crate::longobject::*; use crate::object::*; #[cfg(Py_3_13)] use crate::pyport::Py_ssize_t; use libc::size_t; #[cfg(Py_3_13)] use std::ffi::c_void; use std::ffi::{c_int, c_uchar}; #[cfg(Py_3_13)] extern_libpython! { pub fn PyLong_FromUnicodeObject(u: *mut PyObject, base: c_int) -> *mut PyObject; } #[cfg(Py_3_13)] pub const Py_ASNATIVEBYTES_DEFAULTS: c_int = -1; #[cfg(Py_3_13)] pub const Py_ASNATIVEBYTES_BIG_ENDIAN: c_int = 0; #[cfg(Py_3_13)] pub const Py_ASNATIVEBYTES_LITTLE_ENDIAN: c_int = 1; #[cfg(Py_3_13)] pub const Py_ASNATIVEBYTES_NATIVE_ENDIAN: c_int = 3; #[cfg(Py_3_13)] pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4; #[cfg(Py_3_13)] pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8; extern_libpython! { // skipped _PyLong_Sign #[cfg(Py_3_13)] pub fn PyLong_AsNativeBytes( v: *mut PyObject, buffer: *mut c_void, n_bytes: Py_ssize_t, flags: c_int, ) -> Py_ssize_t; #[cfg(Py_3_13)] pub fn PyLong_FromNativeBytes( buffer: *const c_void, n_bytes: size_t, flags: c_int, ) -> *mut PyObject; #[cfg(Py_3_13)] pub fn PyLong_FromUnsignedNativeBytes( buffer: *const c_void, n_bytes: size_t, flags: c_int, ) -> *mut PyObject; // skipped PyUnstable_Long_IsCompact // skipped PyUnstable_Long_CompactValue #[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")] pub fn _PyLong_FromByteArray( bytes: *const c_uchar, n: size_t, little_endian: c_int, is_signed: c_int, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] pub fn _PyLong_AsByteArray( v: *mut PyLongObject, bytes: *mut c_uchar, n: size_t, little_endian: c_int, is_signed: c_int, ) -> c_int; // skipped _PyLong_GCD } ================================================ FILE: pyo3-ffi/src/cpython/methodobject.rs ================================================ use crate::object::*; #[cfg(not(GraalPy))] use crate::{PyCFunctionObject, PyMethodDefPointer, METH_METHOD, METH_STATIC}; use std::ffi::c_int; #[cfg(not(GraalPy))] pub struct PyCMethodObject { pub func: PyCFunctionObject, pub mm_class: *mut PyTypeObject, } extern_libpython! { pub static mut PyCMethod_Type: PyTypeObject; } #[inline] pub unsafe fn PyCMethod_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyCMethod_Type) as c_int } #[inline] pub unsafe fn PyCMethod_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyCMethod_Type) } #[cfg(not(GraalPy))] #[inline] pub unsafe fn PyCFunction_GET_FUNCTION(func: *mut PyObject) -> PyMethodDefPointer { debug_assert_eq!(PyCMethod_Check(func), 1); let func = func.cast::(); (*(*func).m_ml).ml_meth } #[cfg(not(GraalPy))] #[inline] pub unsafe fn PyCFunction_GET_SELF(func: *mut PyObject) -> *mut PyObject { debug_assert_eq!(PyCMethod_Check(func), 1); let func = func.cast::(); if (*(*func).m_ml).ml_flags & METH_STATIC != 0 { std::ptr::null_mut() } else { (*func).m_self } } #[cfg(not(GraalPy))] #[inline] pub unsafe fn PyCFunction_GET_FLAGS(func: *mut PyObject) -> c_int { debug_assert_eq!(PyCMethod_Check(func), 1); let func = func.cast::(); (*(*func).m_ml).ml_flags } #[cfg(not(GraalPy))] #[inline] pub unsafe fn PyCFunction_GET_CLASS(func: *mut PyObject) -> *mut PyTypeObject { debug_assert_eq!(PyCMethod_Check(func), 1); let func = func.cast::(); if (*(*func).m_ml).ml_flags & METH_METHOD != 0 { let func = func.cast::(); (*func).mm_class } else { std::ptr::null_mut() } } ================================================ FILE: pyo3-ffi/src/cpython/mod.rs ================================================ pub(crate) mod abstract_; // skipped bytearrayobject.h pub(crate) mod bytesobject; #[cfg(not(PyPy))] pub(crate) mod ceval; pub(crate) mod code; pub(crate) mod compile; pub(crate) mod complexobject; #[cfg(Py_3_13)] pub(crate) mod critical_section; pub(crate) mod descrobject; pub(crate) mod dictobject; // skipped fileobject.h // skipped fileutils.h pub(crate) mod frameobject; pub(crate) mod funcobject; pub(crate) mod genobject; #[cfg(any(not(PyPy), Py_3_14))] pub(crate) mod import; #[cfg(all(Py_3_8, not(PyPy)))] pub(crate) mod initconfig; // skipped interpreteridobject.h pub(crate) mod listobject; #[cfg(Py_3_13)] pub(crate) mod lock; pub(crate) mod longobject; #[cfg(all(Py_3_9, not(PyPy)))] pub(crate) mod methodobject; pub(crate) mod object; pub(crate) mod objimpl; pub(crate) mod pydebug; pub(crate) mod pyerrors; #[cfg(all(Py_3_8, not(PyPy)))] pub(crate) mod pylifecycle; pub(crate) mod pymem; pub(crate) mod pystate; pub(crate) mod pythonrun; // skipped sysmodule.h pub(crate) mod floatobject; pub(crate) mod pyframe; pub(crate) mod pyhash; pub(crate) mod tupleobject; pub(crate) mod unicodeobject; pub(crate) mod weakrefobject; pub use self::abstract_::*; pub use self::bytesobject::*; #[cfg(not(PyPy))] pub use self::ceval::*; pub use self::code::*; pub use self::compile::*; pub use self::complexobject::*; #[cfg(Py_3_13)] pub use self::critical_section::*; pub use self::descrobject::*; pub use self::dictobject::*; pub use self::floatobject::*; pub use self::frameobject::*; pub use self::funcobject::*; pub use self::genobject::*; #[cfg(any(not(PyPy), Py_3_14))] pub use self::import::*; #[cfg(all(Py_3_8, not(PyPy)))] pub use self::initconfig::*; pub use self::listobject::*; #[cfg(Py_3_13)] pub use self::lock::*; pub use self::longobject::*; #[cfg(all(Py_3_9, not(PyPy)))] pub use self::methodobject::*; pub use self::object::*; pub use self::objimpl::*; pub use self::pydebug::*; pub use self::pyerrors::*; pub use self::pyframe::*; #[cfg(any(not(PyPy), Py_3_13))] pub use self::pyhash::*; #[cfg(all(Py_3_8, not(PyPy)))] pub use self::pylifecycle::*; pub use self::pymem::*; pub use self::pystate::*; pub use self::pythonrun::*; pub use self::tupleobject::*; pub use self::unicodeobject::*; #[cfg(not(any(PyPy, GraalPy)))] pub use self::weakrefobject::*; ================================================ FILE: pyo3-ffi/src/cpython/object.rs ================================================ #[cfg(Py_3_8)] use crate::vectorcallfunc; use crate::{object, PyGetSetDef, PyMemberDef, PyMethodDef, PyObject, Py_ssize_t}; use std::ffi::{c_char, c_int, c_uint, c_void}; use std::mem; // skipped private _Py_NewReference // skipped private _Py_NewReferenceNoTotal // skipped private _Py_ResurrectReference // skipped private _Py_GetGlobalRefTotal // skipped private _Py_GetRefTotal // skipped private _Py_GetLegacyRefTotal // skipped private _PyInterpreterState_GetRefTotal // skipped private _Py_Identifier // skipped private _Py_static_string_init // skipped private _Py_static_string // skipped private _Py_IDENTIFIER #[cfg(not(Py_3_11))] // moved to src/buffer.rs from Python mod bufferinfo { use crate::Py_ssize_t; use std::ffi::{c_char, c_int, c_void}; use std::ptr; #[repr(C)] #[derive(Copy, Clone)] pub struct Py_buffer { pub buf: *mut c_void, /// Owned reference pub obj: *mut crate::PyObject, pub len: Py_ssize_t, pub itemsize: Py_ssize_t, pub readonly: c_int, pub ndim: c_int, pub format: *mut c_char, pub shape: *mut Py_ssize_t, pub strides: *mut Py_ssize_t, pub suboffsets: *mut Py_ssize_t, pub internal: *mut c_void, #[cfg(PyPy)] pub flags: c_int, #[cfg(PyPy)] pub _strides: [Py_ssize_t; PyBUF_MAX_NDIM as usize], #[cfg(PyPy)] pub _shape: [Py_ssize_t; PyBUF_MAX_NDIM as usize], } impl Py_buffer { #[allow(clippy::new_without_default)] pub const fn new() -> Self { Py_buffer { buf: ptr::null_mut(), obj: ptr::null_mut(), len: 0, itemsize: 0, readonly: 0, ndim: 0, format: ptr::null_mut(), shape: ptr::null_mut(), strides: ptr::null_mut(), suboffsets: ptr::null_mut(), internal: ptr::null_mut(), #[cfg(PyPy)] flags: 0, #[cfg(PyPy)] _strides: [0; PyBUF_MAX_NDIM as usize], #[cfg(PyPy)] _shape: [0; PyBUF_MAX_NDIM as usize], } } } pub type getbufferproc = unsafe extern "C" fn( arg1: *mut crate::PyObject, arg2: *mut Py_buffer, arg3: c_int, ) -> c_int; pub type releasebufferproc = unsafe extern "C" fn(arg1: *mut crate::PyObject, arg2: *mut Py_buffer); /// Maximum number of dimensions pub const PyBUF_MAX_NDIM: c_int = if cfg!(PyPy) { 36 } else { 64 }; /* Flags for getting buffers */ pub const PyBUF_SIMPLE: c_int = 0; pub const PyBUF_WRITABLE: c_int = 0x0001; /* we used to include an E, backwards compatible alias */ pub const PyBUF_WRITEABLE: c_int = PyBUF_WRITABLE; pub const PyBUF_FORMAT: c_int = 0x0004; pub const PyBUF_ND: c_int = 0x0008; pub const PyBUF_STRIDES: c_int = 0x0010 | PyBUF_ND; pub const PyBUF_C_CONTIGUOUS: c_int = 0x0020 | PyBUF_STRIDES; pub const PyBUF_F_CONTIGUOUS: c_int = 0x0040 | PyBUF_STRIDES; pub const PyBUF_ANY_CONTIGUOUS: c_int = 0x0080 | PyBUF_STRIDES; pub const PyBUF_INDIRECT: c_int = 0x0100 | PyBUF_STRIDES; pub const PyBUF_CONTIG: c_int = PyBUF_ND | PyBUF_WRITABLE; pub const PyBUF_CONTIG_RO: c_int = PyBUF_ND; pub const PyBUF_STRIDED: c_int = PyBUF_STRIDES | PyBUF_WRITABLE; pub const PyBUF_STRIDED_RO: c_int = PyBUF_STRIDES; pub const PyBUF_RECORDS: c_int = PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT; pub const PyBUF_RECORDS_RO: c_int = PyBUF_STRIDES | PyBUF_FORMAT; pub const PyBUF_FULL: c_int = PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT; pub const PyBUF_FULL_RO: c_int = PyBUF_INDIRECT | PyBUF_FORMAT; pub const PyBUF_READ: c_int = 0x100; pub const PyBUF_WRITE: c_int = 0x200; } #[cfg(not(Py_3_11))] pub use self::bufferinfo::*; #[repr(C)] #[derive(Copy, Clone)] pub struct PyNumberMethods { pub nb_add: Option, pub nb_subtract: Option, pub nb_multiply: Option, pub nb_remainder: Option, pub nb_divmod: Option, pub nb_power: Option, pub nb_negative: Option, pub nb_positive: Option, pub nb_absolute: Option, pub nb_bool: Option, pub nb_invert: Option, pub nb_lshift: Option, pub nb_rshift: Option, pub nb_and: Option, pub nb_xor: Option, pub nb_or: Option, pub nb_int: Option, pub nb_reserved: *mut c_void, pub nb_float: Option, pub nb_inplace_add: Option, pub nb_inplace_subtract: Option, pub nb_inplace_multiply: Option, pub nb_inplace_remainder: Option, pub nb_inplace_power: Option, pub nb_inplace_lshift: Option, pub nb_inplace_rshift: Option, pub nb_inplace_and: Option, pub nb_inplace_xor: Option, pub nb_inplace_or: Option, pub nb_floor_divide: Option, pub nb_true_divide: Option, pub nb_inplace_floor_divide: Option, pub nb_inplace_true_divide: Option, pub nb_index: Option, pub nb_matrix_multiply: Option, pub nb_inplace_matrix_multiply: Option, } #[repr(C)] #[derive(Clone)] pub struct PySequenceMethods { pub sq_length: Option, pub sq_concat: Option, pub sq_repeat: Option, pub sq_item: Option, pub was_sq_slice: *mut c_void, pub sq_ass_item: Option, pub was_sq_ass_slice: *mut c_void, pub sq_contains: Option, pub sq_inplace_concat: Option, pub sq_inplace_repeat: Option, } #[repr(C)] #[derive(Clone, Default)] pub struct PyMappingMethods { pub mp_length: Option, pub mp_subscript: Option, pub mp_ass_subscript: Option, } #[cfg(Py_3_10)] pub type sendfunc = unsafe extern "C" fn( iter: *mut PyObject, value: *mut PyObject, result: *mut *mut PyObject, ) -> object::PySendResult; #[repr(C)] #[derive(Clone, Default)] pub struct PyAsyncMethods { pub am_await: Option, pub am_aiter: Option, pub am_anext: Option, #[cfg(Py_3_10)] pub am_send: Option, } #[repr(C)] #[derive(Clone, Default)] pub struct PyBufferProcs { pub bf_getbuffer: Option, pub bf_releasebuffer: Option, } pub type printfunc = unsafe extern "C" fn(arg1: *mut PyObject, arg2: *mut ::libc::FILE, arg3: c_int) -> c_int; #[repr(C)] #[derive(Debug)] pub struct PyTypeObject { pub ob_base: object::PyVarObject, pub tp_name: *const c_char, pub tp_basicsize: Py_ssize_t, pub tp_itemsize: Py_ssize_t, pub tp_dealloc: Option, #[cfg(not(Py_3_8))] pub tp_print: Option, #[cfg(Py_3_8)] pub tp_vectorcall_offset: Py_ssize_t, pub tp_getattr: Option, pub tp_setattr: Option, pub tp_as_async: *mut PyAsyncMethods, pub tp_repr: Option, pub tp_as_number: *mut PyNumberMethods, pub tp_as_sequence: *mut PySequenceMethods, pub tp_as_mapping: *mut PyMappingMethods, pub tp_hash: Option, pub tp_call: Option, pub tp_str: Option, pub tp_getattro: Option, pub tp_setattro: Option, pub tp_as_buffer: *mut PyBufferProcs, #[cfg(not(Py_GIL_DISABLED))] pub tp_flags: std::ffi::c_ulong, #[cfg(Py_GIL_DISABLED)] pub tp_flags: crate::impl_::AtomicCULong, pub tp_doc: *const c_char, pub tp_traverse: Option, pub tp_clear: Option, pub tp_richcompare: Option, pub tp_weaklistoffset: Py_ssize_t, pub tp_iter: Option, pub tp_iternext: Option, pub tp_methods: *mut PyMethodDef, pub tp_members: *mut PyMemberDef, pub tp_getset: *mut PyGetSetDef, pub tp_base: *mut PyTypeObject, pub tp_dict: *mut object::PyObject, pub tp_descr_get: Option, pub tp_descr_set: Option, pub tp_dictoffset: Py_ssize_t, pub tp_init: Option, pub tp_alloc: Option, pub tp_new: Option, pub tp_free: Option, pub tp_is_gc: Option, pub tp_bases: *mut object::PyObject, pub tp_mro: *mut object::PyObject, pub tp_cache: *mut object::PyObject, pub tp_subclasses: *mut object::PyObject, pub tp_weaklist: *mut object::PyObject, pub tp_del: Option, pub tp_version_tag: c_uint, pub tp_finalize: Option, #[cfg(Py_3_8)] pub tp_vectorcall: Option, #[cfg(Py_3_12)] pub tp_watched: c_char, #[cfg(all(not(PyPy), Py_3_8, not(Py_3_9)))] pub tp_print: Option, #[cfg(py_sys_config = "COUNT_ALLOCS")] pub tp_allocs: Py_ssize_t, #[cfg(py_sys_config = "COUNT_ALLOCS")] pub tp_frees: Py_ssize_t, #[cfg(py_sys_config = "COUNT_ALLOCS")] pub tp_maxalloc: Py_ssize_t, #[cfg(py_sys_config = "COUNT_ALLOCS")] pub tp_prev: *mut PyTypeObject, #[cfg(py_sys_config = "COUNT_ALLOCS")] pub tp_next: *mut PyTypeObject, } #[cfg(Py_3_11)] #[repr(C)] #[derive(Clone)] struct _specialization_cache { getitem: *mut PyObject, #[cfg(Py_3_12)] getitem_version: u32, #[cfg(Py_3_13)] init: *mut PyObject, } #[repr(C)] pub struct PyHeapTypeObject { pub ht_type: PyTypeObject, pub as_async: PyAsyncMethods, pub as_number: PyNumberMethods, pub as_mapping: PyMappingMethods, pub as_sequence: PySequenceMethods, pub as_buffer: PyBufferProcs, pub ht_name: *mut object::PyObject, pub ht_slots: *mut object::PyObject, pub ht_qualname: *mut object::PyObject, #[cfg(not(PyPy))] pub ht_cached_keys: *mut c_void, #[cfg(Py_3_9)] pub ht_module: *mut object::PyObject, #[cfg(all(Py_3_11, not(PyPy)))] _ht_tpname: *mut c_char, #[cfg(Py_3_14)] pub ht_token: *mut c_void, #[cfg(all(Py_3_11, not(PyPy)))] _spec_cache: _specialization_cache, #[cfg(all(Py_GIL_DISABLED, Py_3_14))] pub unique_id: Py_ssize_t, } impl Default for PyHeapTypeObject { #[inline] fn default() -> Self { unsafe { mem::zeroed() } } } #[inline] #[cfg(not(Py_3_11))] pub unsafe fn PyHeapType_GET_MEMBERS(etype: *mut PyHeapTypeObject) -> *mut PyMemberDef { let py_type = object::Py_TYPE(etype as *mut object::PyObject); let ptr = etype.offset((*py_type).tp_basicsize); ptr as *mut PyMemberDef } // skipped private _PyType_Name // skipped private _PyType_Lookup // skipped private _PyType_LookupRef extern_libpython! { #[cfg(Py_3_12)] pub fn PyType_GetDict(o: *mut PyTypeObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Print")] pub fn PyObject_Print(o: *mut PyObject, fp: *mut ::libc::FILE, flags: c_int) -> c_int; // skipped private _Py_BreakPoint // skipped private _PyObject_Dump // skipped _PyObject_GetAttrId // skipped private _PyObject_GetDictPtr pub fn PyObject_CallFinalizer(arg1: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyObject_CallFinalizerFromDealloc")] pub fn PyObject_CallFinalizerFromDealloc(arg1: *mut PyObject) -> c_int; // skipped private _PyObject_GenericGetAttrWithDict // skipped private _PyObject_GenericSetAttrWithDict // skipped private _PyObject_FunctionStr } // skipped Py_SETREF // skipped Py_XSETREF // skipped private _PyObject_ASSERT_FROM // skipped private _PyObject_ASSERT_WITH_MSG // skipped private _PyObject_ASSERT // skipped private _PyObject_ASSERT_FAILED_MSG // skipped private _PyObject_AssertFailed // skipped private _PyTrash_begin // skipped private _PyTrash_end // skipped _PyTrash_thread_deposit_object // skipped _PyTrash_thread_destroy_chain // skipped Py_TRASHCAN_BEGIN // skipped Py_TRASHCAN_END // skipped PyObject_GetItemData // skipped PyObject_VisitManagedDict // skipped _PyObject_SetManagedDict // skipped PyObject_ClearManagedDict // skipped TYPE_MAX_WATCHERS // skipped PyType_WatchCallback // skipped PyType_AddWatcher // skipped PyType_ClearWatcher // skipped PyType_Watch // skipped PyType_Unwatch // skipped PyUnstable_Type_AssignVersionTag // skipped PyRefTracerEvent // skipped PyRefTracer // skipped PyRefTracer_SetTracer // skipped PyRefTracer_GetTracer #[cfg(Py_3_14)] extern_libpython! { // skipped PyUnstable_Object_EnableDeferredRefcount pub fn PyUnstable_Object_IsUniqueReferencedTemporary(obj: *mut PyObject) -> c_int; // skipped PyUnstable_IsImmortal pub fn PyUnstable_TryIncRef(obj: *mut PyObject) -> c_int; pub fn PyUnstable_EnableTryIncRef(obj: *mut PyObject) -> c_void; pub fn PyUnstable_Object_IsUniquelyReferenced(op: *mut PyObject) -> c_int; } #[cfg(Py_3_15)] extern_libpython! { pub fn PyUnstable_SetImmortal(op: *mut PyObject) -> c_int; } ================================================ FILE: pyo3-ffi/src/cpython/objimpl.rs ================================================ #[cfg(not(all(Py_3_11, any(PyPy, GraalPy))))] use libc::size_t; use std::ffi::c_int; #[cfg(not(any(PyPy, GraalPy)))] use std::ffi::c_void; use crate::object::*; // skipped _PyObject_SIZE // skipped _PyObject_VAR_SIZE #[cfg(not(Py_3_11))] extern_libpython! { pub fn _Py_GetAllocatedBlocks() -> crate::Py_ssize_t; } #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyObjectArenaAllocator { pub ctx: *mut c_void, pub alloc: Option *mut c_void>, pub free: Option, } #[cfg(not(any(PyPy, GraalPy)))] impl Default for PyObjectArenaAllocator { #[inline] fn default() -> Self { unsafe { std::mem::zeroed() } } } extern_libpython! { #[cfg(not(any(PyPy, GraalPy)))] pub fn PyObject_GetArenaAllocator(allocator: *mut PyObjectArenaAllocator); #[cfg(not(any(PyPy, GraalPy)))] pub fn PyObject_SetArenaAllocator(allocator: *mut PyObjectArenaAllocator); #[cfg(Py_3_9)] pub fn PyObject_IS_GC(o: *mut PyObject) -> c_int; } #[inline] #[cfg(not(Py_3_9))] pub unsafe fn PyObject_IS_GC(o: *mut PyObject) -> c_int { (crate::PyType_IS_GC(Py_TYPE(o)) != 0 && match (*Py_TYPE(o)).tp_is_gc { Some(tp_is_gc) => tp_is_gc(o) != 0, None => true, }) as c_int } #[cfg(not(Py_3_11))] extern_libpython! { pub fn _PyObject_GC_Malloc(size: size_t) -> *mut PyObject; pub fn _PyObject_GC_Calloc(size: size_t) -> *mut PyObject; } #[inline] pub unsafe fn PyType_SUPPORTS_WEAKREFS(t: *mut PyTypeObject) -> c_int { ((*t).tp_weaklistoffset > 0) as c_int } #[inline] pub unsafe fn PyObject_GET_WEAKREFS_LISTPTR(o: *mut PyObject) -> *mut *mut PyObject { let weaklistoffset = (*Py_TYPE(o)).tp_weaklistoffset; o.offset(weaklistoffset) as *mut *mut PyObject } // skipped PyUnstable_Object_GC_NewWithExtraData ================================================ FILE: pyo3-ffi/src/cpython/pydebug.rs ================================================ use std::ffi::{c_char, c_int}; #[cfg(not(Py_LIMITED_API))] extern_libpython! { #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_DebugFlag")] pub static mut Py_DebugFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_VerboseFlag")] pub static mut Py_VerboseFlag: c_int; #[deprecated(note = "Python 3.12")] pub static mut Py_QuietFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_InteractiveFlag")] pub static mut Py_InteractiveFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_InspectFlag")] pub static mut Py_InspectFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_OptimizeFlag")] pub static mut Py_OptimizeFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_NoSiteFlag")] pub static mut Py_NoSiteFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_BytesWarningFlag")] pub static mut Py_BytesWarningFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_UseClassExceptionsFlag")] pub static mut Py_UseClassExceptionsFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_FrozenFlag")] pub static mut Py_FrozenFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_IgnoreEnvironmentFlag")] pub static mut Py_IgnoreEnvironmentFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_DontWriteBytecodeFlag")] pub static mut Py_DontWriteBytecodeFlag: c_int; #[deprecated(note = "Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPy_NoUserSiteDirectory")] pub static mut Py_NoUserSiteDirectory: c_int; #[deprecated(note = "Python 3.12")] pub static mut Py_UnbufferedStdioFlag: c_int; #[cfg_attr(PyPy, link_name = "PyPy_HashRandomizationFlag")] pub static mut Py_HashRandomizationFlag: c_int; #[deprecated(note = "Python 3.12")] pub static mut Py_IsolatedFlag: c_int; #[cfg(windows)] #[deprecated(note = "Python 3.12")] pub static mut Py_LegacyWindowsFSEncodingFlag: c_int; #[cfg(windows)] #[deprecated(note = "Python 3.12")] pub static mut Py_LegacyWindowsStdioFlag: c_int; } extern_libpython! { #[cfg(Py_3_11)] pub fn Py_GETENV(name: *const c_char) -> *mut c_char; } #[cfg(not(Py_3_11))] #[inline(always)] pub unsafe fn Py_GETENV(name: *const c_char) -> *mut c_char { #[allow(deprecated)] if Py_IgnoreEnvironmentFlag != 0 { std::ptr::null_mut() } else { libc::getenv(name) } } ================================================ FILE: pyo3-ffi/src/cpython/pyerrors.rs ================================================ use crate::PyObject; #[cfg(not(any(PyPy, GraalPy)))] use crate::Py_ssize_t; #[repr(C)] #[derive(Debug)] pub struct PyBaseExceptionObject { pub ob_base: PyObject, #[cfg(not(PyPy))] pub dict: *mut PyObject, #[cfg(not(PyPy))] pub args: *mut PyObject, #[cfg(all(Py_3_11, not(PyPy)))] pub notes: *mut PyObject, #[cfg(not(PyPy))] pub traceback: *mut PyObject, #[cfg(not(PyPy))] pub context: *mut PyObject, #[cfg(not(PyPy))] pub cause: *mut PyObject, #[cfg(not(PyPy))] pub suppress_context: char, } #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] pub struct PySyntaxErrorObject { pub ob_base: PyObject, pub dict: *mut PyObject, pub args: *mut PyObject, #[cfg(Py_3_11)] pub notes: *mut PyObject, pub traceback: *mut PyObject, pub context: *mut PyObject, pub cause: *mut PyObject, pub suppress_context: char, pub msg: *mut PyObject, pub filename: *mut PyObject, pub lineno: *mut PyObject, pub offset: *mut PyObject, #[cfg(Py_3_10)] pub end_lineno: *mut PyObject, #[cfg(Py_3_10)] pub end_offset: *mut PyObject, pub text: *mut PyObject, pub print_file_and_line: *mut PyObject, #[cfg(Py_3_14)] pub metadata: *mut PyObject, } #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] pub struct PyImportErrorObject { pub ob_base: PyObject, pub dict: *mut PyObject, pub args: *mut PyObject, #[cfg(Py_3_11)] pub notes: *mut PyObject, pub traceback: *mut PyObject, pub context: *mut PyObject, pub cause: *mut PyObject, pub suppress_context: char, pub msg: *mut PyObject, pub name: *mut PyObject, pub path: *mut PyObject, #[cfg(Py_3_12)] pub name_from: *mut PyObject, } #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] pub struct PyUnicodeErrorObject { pub ob_base: PyObject, pub dict: *mut PyObject, pub args: *mut PyObject, #[cfg(Py_3_11)] pub notes: *mut PyObject, pub traceback: *mut PyObject, pub context: *mut PyObject, pub cause: *mut PyObject, pub suppress_context: char, pub encoding: *mut PyObject, pub object: *mut PyObject, pub start: Py_ssize_t, pub end: Py_ssize_t, pub reason: *mut PyObject, } #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] pub struct PySystemExitObject { pub ob_base: PyObject, pub dict: *mut PyObject, pub args: *mut PyObject, #[cfg(Py_3_11)] pub notes: *mut PyObject, pub traceback: *mut PyObject, pub context: *mut PyObject, pub cause: *mut PyObject, pub suppress_context: char, pub code: *mut PyObject, } #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] pub struct PyOSErrorObject { pub ob_base: PyObject, pub dict: *mut PyObject, pub args: *mut PyObject, #[cfg(Py_3_11)] pub notes: *mut PyObject, pub traceback: *mut PyObject, pub context: *mut PyObject, pub cause: *mut PyObject, pub suppress_context: char, pub myerrno: *mut PyObject, pub strerror: *mut PyObject, pub filename: *mut PyObject, pub filename2: *mut PyObject, #[cfg(windows)] pub winerror: *mut PyObject, pub written: Py_ssize_t, } #[repr(C)] #[derive(Debug)] pub struct PyStopIterationObject { pub ob_base: PyObject, #[cfg(not(PyPy))] pub dict: *mut PyObject, #[cfg(not(PyPy))] pub args: *mut PyObject, #[cfg(all(Py_3_11, not(PyPy)))] pub notes: *mut PyObject, #[cfg(not(PyPy))] pub traceback: *mut PyObject, #[cfg(not(PyPy))] pub context: *mut PyObject, #[cfg(not(PyPy))] pub cause: *mut PyObject, #[cfg(not(PyPy))] pub suppress_context: char, pub value: *mut PyObject, } // skipped _PyErr_ChainExceptions // skipped PyNameErrorObject // skipped PyAttributeErrorObject // skipped PyEnvironmentErrorObject // skipped PyWindowsErrorObject // skipped _PyErr_SetKeyError // skipped _PyErr_GetTopmostException // skipped _PyErr_GetExcInfo // skipped PyErr_SetFromErrnoWithUnicodeFilename // skipped _PyErr_FormatFromCause // skipped PyErr_SetFromWindowsErrWithUnicodeFilename // skipped PyErr_SetExcFromWindowsErrWithUnicodeFilename // skipped _PyErr_TrySetFromCause // skipped PySignal_SetWakeupFd // skipped _PyErr_CheckSignals // skipped PyErr_SyntaxLocationObject // skipped PyErr_RangedSyntaxLocationObject // skipped PyErr_ProgramTextObject // skipped _PyErr_ProgramDecodedTextObject // skipped _PyUnicodeTranslateError_Create // skipped _PyErr_WriteUnraisableMsg // skipped _Py_FatalErrorFunc // skipped _Py_FatalErrorFormat // skipped Py_FatalError ================================================ FILE: pyo3-ffi/src/cpython/pyframe.rs ================================================ #[cfg(any(Py_3_11, all(Py_3_9, not(PyPy))))] use crate::PyFrameObject; use crate::{PyObject, PyTypeObject, Py_TYPE}; #[cfg(Py_3_12)] use std::ffi::c_char; use std::ffi::c_int; // NB used in `_PyEval_EvalFrameDefault`, maybe we remove this too. #[cfg(all(Py_3_11, not(PyPy)))] opaque_struct!(pub _PyInterpreterFrame); extern_libpython! { pub static mut PyFrame_Type: PyTypeObject; #[cfg(Py_3_13)] pub static mut PyFrameLocalsProxy_Type: PyTypeObject; } #[inline] pub unsafe fn PyFrame_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyFrame_Type) as c_int } #[cfg(Py_3_13)] #[inline] pub unsafe fn PyFrameLocalsProxy_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyFrameLocalsProxy_Type) as c_int } extern_libpython! { #[cfg(all(Py_3_9, not(PyPy)))] pub fn PyFrame_GetBack(frame: *mut PyFrameObject) -> *mut PyFrameObject; #[cfg(Py_3_11)] pub fn PyFrame_GetLocals(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] pub fn PyFrame_GetGlobals(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] pub fn PyFrame_GetBuiltins(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] pub fn PyFrame_GetGenerator(frame: *mut PyFrameObject) -> *mut PyObject; #[cfg(Py_3_11)] pub fn PyFrame_GetLasti(frame: *mut PyFrameObject) -> c_int; #[cfg(Py_3_12)] pub fn PyFrame_GetVar(frame: *mut PyFrameObject, name: *mut PyObject) -> *mut PyObject; #[cfg(Py_3_12)] pub fn PyFrame_GetVarString(frame: *mut PyFrameObject, name: *mut c_char) -> *mut PyObject; // skipped PyUnstable_InterpreterFrame_GetCode // skipped PyUnstable_InterpreterFrame_GetLasti // skipped PyUnstable_InterpreterFrame_GetLine // skipped PyUnstable_ExecutableKinds } ================================================ FILE: pyo3-ffi/src/cpython/pyhash.rs ================================================ #[cfg(Py_3_14)] use crate::Py_ssize_t; #[cfg(Py_3_13)] use crate::{PyObject, Py_hash_t}; #[cfg(any(Py_3_13, not(PyPy)))] use std::ffi::c_void; #[cfg(not(PyPy))] use std::ffi::{c_char, c_int}; #[cfg(not(PyPy))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyHash_FuncDef { pub hash: Option crate::Py_hash_t>, pub name: *const c_char, pub hash_bits: c_int, pub seed_bits: c_int, } #[cfg(not(PyPy))] impl Default for PyHash_FuncDef { #[inline] fn default() -> Self { unsafe { std::mem::zeroed() } } } extern_libpython! { #[cfg(not(PyPy))] pub fn PyHash_GetFuncDef() -> *mut PyHash_FuncDef; #[cfg(Py_3_13)] pub fn Py_HashPointer(ptr: *const c_void) -> Py_hash_t; #[cfg(Py_3_13)] pub fn PyObject_GenericHash(obj: *mut PyObject) -> Py_hash_t; #[cfg(Py_3_14)] pub fn Py_HashBuffer(ptr: *const c_void, len: Py_ssize_t) -> Py_hash_t; } ================================================ FILE: pyo3-ffi/src/cpython/pylifecycle.rs ================================================ use crate::{PyConfig, PyPreConfig, PyStatus, Py_ssize_t}; use libc::wchar_t; use std::ffi::{c_char, c_int}; extern_libpython! { // skipped Py_FrozenMain pub fn Py_PreInitialize(src_config: *const PyPreConfig) -> PyStatus; pub fn Py_PreInitializeFromBytesArgs( src_config: *const PyPreConfig, argc: Py_ssize_t, argv: *mut *mut c_char, ) -> PyStatus; pub fn Py_PreInitializeFromArgs( src_config: *const PyPreConfig, argc: Py_ssize_t, argv: *mut *mut wchar_t, ) -> PyStatus; pub fn Py_InitializeFromConfig(config: *const PyConfig) -> PyStatus; pub fn Py_RunMain() -> c_int; pub fn Py_ExitStatusException(status: PyStatus) -> !; // skipped Py_FdIsInteractive } #[cfg(Py_3_12)] pub const PyInterpreterConfig_DEFAULT_GIL: c_int = 0; #[cfg(Py_3_12)] pub const PyInterpreterConfig_SHARED_GIL: c_int = 1; #[cfg(Py_3_12)] pub const PyInterpreterConfig_OWN_GIL: c_int = 2; #[cfg(Py_3_12)] #[repr(C)] pub struct PyInterpreterConfig { pub use_main_obmalloc: c_int, pub allow_fork: c_int, pub allow_exec: c_int, pub allow_threads: c_int, pub allow_daemon_threads: c_int, pub check_multi_interp_extensions: c_int, pub gil: c_int, } #[cfg(Py_3_12)] pub const _PyInterpreterConfig_INIT: PyInterpreterConfig = PyInterpreterConfig { use_main_obmalloc: 0, allow_fork: 0, allow_exec: 0, allow_threads: 1, allow_daemon_threads: 0, check_multi_interp_extensions: 1, gil: PyInterpreterConfig_OWN_GIL, }; // https://github.com/python/cpython/blob/902de283a8303177eb95bf5bc252d2421fcbd758/Include/cpython/pylifecycle.h#L63-L65 #[cfg(Py_3_12)] const _PyInterpreterConfig_LEGACY_CHECK_MULTI_INTERP_EXTENSIONS: c_int = if cfg!(Py_GIL_DISABLED) { 1 } else { 0 }; #[cfg(Py_3_12)] pub const _PyInterpreterConfig_LEGACY_INIT: PyInterpreterConfig = PyInterpreterConfig { use_main_obmalloc: 1, allow_fork: 1, allow_exec: 1, allow_threads: 1, allow_daemon_threads: 1, check_multi_interp_extensions: _PyInterpreterConfig_LEGACY_CHECK_MULTI_INTERP_EXTENSIONS, gil: PyInterpreterConfig_SHARED_GIL, }; extern_libpython! { #[cfg(Py_3_12)] pub fn Py_NewInterpreterFromConfig( tstate_p: *mut *mut crate::PyThreadState, config: *const PyInterpreterConfig, ) -> PyStatus; } // skipped atexit_datacallbackfunc // skipped PyUnstable_AtExit ================================================ FILE: pyo3-ffi/src/cpython/pymem.rs ================================================ use libc::size_t; use std::ffi::c_void; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyMem_RawMalloc")] pub fn PyMem_RawMalloc(size: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyMem_RawCalloc")] pub fn PyMem_RawCalloc(nelem: size_t, elsize: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyMem_RawRealloc")] pub fn PyMem_RawRealloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyMem_RawFree")] pub fn PyMem_RawFree(ptr: *mut c_void); // skipped _PyMem_GetCurrentAllocatorName // skipped _PyMem_RawStrdup // skipped _PyMem_Strdup // skipped _PyMem_RawWcsdup } #[repr(C)] #[derive(Copy, Clone)] pub enum PyMemAllocatorDomain { PYMEM_DOMAIN_RAW, PYMEM_DOMAIN_MEM, PYMEM_DOMAIN_OBJ, } // skipped PyMemAllocatorName #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyMemAllocatorEx { pub ctx: *mut c_void, pub malloc: Option *mut c_void>, pub calloc: Option *mut c_void>, pub realloc: Option *mut c_void>, pub free: Option, } extern_libpython! { #[cfg(not(any(PyPy, GraalPy)))] pub fn PyMem_GetAllocator(domain: PyMemAllocatorDomain, allocator: *mut PyMemAllocatorEx); #[cfg(not(any(PyPy, GraalPy)))] pub fn PyMem_SetAllocator(domain: PyMemAllocatorDomain, allocator: *mut PyMemAllocatorEx); #[cfg(not(any(PyPy, GraalPy)))] pub fn PyMem_SetupDebugHooks(); } ================================================ FILE: pyo3-ffi/src/cpython/pystate.rs ================================================ use crate::PyThreadState; use crate::{PyFrameObject, PyInterpreterState, PyObject}; use std::ffi::c_int; // skipped private _PyInterpreterState_RequiresIDRef // skipped private _PyInterpreterState_RequireIDRef pub type Py_tracefunc = unsafe extern "C" fn( obj: *mut PyObject, frame: *mut PyFrameObject, what: c_int, arg: *mut PyObject, ) -> c_int; pub const PyTrace_CALL: c_int = 0; pub const PyTrace_EXCEPTION: c_int = 1; pub const PyTrace_LINE: c_int = 2; pub const PyTrace_RETURN: c_int = 3; pub const PyTrace_C_CALL: c_int = 4; pub const PyTrace_C_EXCEPTION: c_int = 5; pub const PyTrace_C_RETURN: c_int = 6; pub const PyTrace_OPCODE: c_int = 7; // skipped private _Py_MAX_SCRIPT_PATH_SIZE // skipped private _PyRemoteDebuggerSupport /// Private structure used inline in `PyGenObject` /// /// `PyGenObject` was made opaque in Python 3.14, so we don't bother defining this /// structure for that version and later. #[cfg(not(any(PyPy, Py_3_14)))] #[repr(C)] #[derive(Clone, Copy)] pub(crate) struct _PyErr_StackItem { #[cfg(not(Py_3_11))] exc_type: *mut PyObject, exc_value: *mut PyObject, #[cfg(not(Py_3_11))] exc_traceback: *mut PyObject, previous_item: *mut _PyErr_StackItem, } // skipped private _PyStackChunk // skipped private _PY_DATA_STACK_CHUNK_SIZE // skipped private _ts (aka PyThreadState) extern_libpython! { #[cfg(Py_3_13)] pub fn PyThreadState_GetUnchecked() -> *mut PyThreadState; #[cfg(not(Py_3_13))] pub(crate) fn _PyThreadState_UncheckedGet() -> *mut PyThreadState; #[cfg(Py_3_11)] pub fn PyThreadState_EnterTracing(state: *mut PyThreadState); #[cfg(Py_3_11)] pub fn PyThreadState_LeaveTracing(state: *mut PyThreadState); #[cfg_attr(PyPy, link_name = "PyPyGILState_Check")] pub fn PyGILState_Check() -> c_int; // skipped private _PyThread_CurrentFrames // skipped PyUnstable_ThreadState_SetStackProtection // skipped PyUnstable_ThreadState_ResetStackProtection #[cfg(not(PyPy))] pub fn PyInterpreterState_Main() -> *mut PyInterpreterState; #[cfg_attr(PyPy, link_name = "PyPyInterpreterState_Head")] pub fn PyInterpreterState_Head() -> *mut PyInterpreterState; #[cfg_attr(PyPy, link_name = "PyPyInterpreterState_Next")] pub fn PyInterpreterState_Next(interp: *mut PyInterpreterState) -> *mut PyInterpreterState; #[cfg(not(PyPy))] pub fn PyInterpreterState_ThreadHead(interp: *mut PyInterpreterState) -> *mut PyThreadState; #[cfg(not(PyPy))] pub fn PyThreadState_Next(tstate: *mut PyThreadState) -> *mut PyThreadState; #[cfg_attr(PyPy, link_name = "PyPyThreadState_DeleteCurrent")] pub fn PyThreadState_DeleteCurrent(); } // skipped private _PyFrameEvalFunction // skipped private _PyInterpreterState_GetEvalFrameFunc // skipped private _PyInterpreterState_SetEvalFrameFunc ================================================ FILE: pyo3-ffi/src/cpython/pythonrun.rs ================================================ use crate::object::*; #[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API, Py_3_10)))] use crate::pyarena::PyArena; use crate::PyCompilerFlags; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] use crate::{_mod, _node}; use libc::FILE; use std::ffi::{c_char, c_int}; extern_libpython! { pub fn PyRun_SimpleStringFlags(arg1: *const c_char, arg2: *mut PyCompilerFlags) -> c_int; pub fn _PyRun_SimpleFileObject( fp: *mut FILE, filename: *mut PyObject, closeit: c_int, flags: *mut PyCompilerFlags, ) -> c_int; pub fn PyRun_AnyFileExFlags( fp: *mut FILE, filename: *const c_char, closeit: c_int, flags: *mut PyCompilerFlags, ) -> c_int; pub fn _PyRun_AnyFileObject( fp: *mut FILE, filename: *mut PyObject, closeit: c_int, flags: *mut PyCompilerFlags, ) -> c_int; pub fn PyRun_SimpleFileExFlags( fp: *mut FILE, filename: *const c_char, closeit: c_int, flags: *mut PyCompilerFlags, ) -> c_int; pub fn PyRun_InteractiveOneFlags( fp: *mut FILE, filename: *const c_char, flags: *mut PyCompilerFlags, ) -> c_int; pub fn PyRun_InteractiveOneObject( fp: *mut FILE, filename: *mut PyObject, flags: *mut PyCompilerFlags, ) -> c_int; pub fn PyRun_InteractiveLoopFlags( fp: *mut FILE, filename: *const c_char, flags: *mut PyCompilerFlags, ) -> c_int; pub fn _PyRun_InteractiveLoopObject( fp: *mut FILE, filename: *mut PyObject, flags: *mut PyCompilerFlags, ) -> c_int; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] pub fn PyParser_ASTFromString( s: *const c_char, filename: *const c_char, start: c_int, flags: *mut PyCompilerFlags, arena: *mut PyArena, ) -> *mut _mod; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] pub fn PyParser_ASTFromStringObject( s: *const c_char, filename: *mut PyObject, start: c_int, flags: *mut PyCompilerFlags, arena: *mut PyArena, ) -> *mut _mod; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] pub fn PyParser_ASTFromFile( fp: *mut FILE, filename: *const c_char, enc: *const c_char, start: c_int, ps1: *const c_char, ps2: *const c_char, flags: *mut PyCompilerFlags, errcode: *mut c_int, arena: *mut PyArena, ) -> *mut _mod; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] pub fn PyParser_ASTFromFileObject( fp: *mut FILE, filename: *mut PyObject, enc: *const c_char, start: c_int, ps1: *const c_char, ps2: *const c_char, flags: *mut PyCompilerFlags, errcode: *mut c_int, arena: *mut PyArena, ) -> *mut _mod; } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyRun_StringFlags")] pub fn PyRun_StringFlags( arg1: *const c_char, arg2: c_int, arg3: *mut PyObject, arg4: *mut PyObject, arg5: *mut PyCompilerFlags, ) -> *mut PyObject; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_FileExFlags( fp: *mut FILE, filename: *const c_char, start: c_int, globals: *mut PyObject, locals: *mut PyObject, closeit: c_int, flags: *mut PyCompilerFlags, ) -> *mut PyObject; #[cfg(not(any(PyPy, GraalPy)))] pub fn Py_CompileStringExFlags( str: *const c_char, filename: *const c_char, start: c_int, flags: *mut PyCompilerFlags, optimize: c_int, ) -> *mut PyObject; #[cfg(not(Py_LIMITED_API))] pub fn Py_CompileStringObject( str: *const c_char, filename: *mut PyObject, start: c_int, flags: *mut PyCompilerFlags, optimize: c_int, ) -> *mut PyObject; } #[inline] #[cfg(not(any(PyPy, GraalPy)))] pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject { Py_CompileStringExFlags(string, p, s, std::ptr::null_mut(), -1) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] pub unsafe fn Py_CompileStringFlags( string: *const c_char, p: *const c_char, s: c_int, f: *mut PyCompilerFlags, ) -> *mut PyObject { Py_CompileStringExFlags(string, p, s, f, -1) } // skipped _Py_SourceAsString extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyRun_String")] pub fn PyRun_String( string: *const c_char, s: c_int, g: *mut PyObject, l: *mut PyObject, ) -> *mut PyObject; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_AnyFile(fp: *mut FILE, name: *const c_char) -> c_int; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_AnyFileEx(fp: *mut FILE, name: *const c_char, closeit: c_int) -> c_int; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_AnyFileFlags( arg1: *mut FILE, arg2: *const c_char, arg3: *mut PyCompilerFlags, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyRun_SimpleString")] pub fn PyRun_SimpleString(s: *const c_char) -> c_int; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_SimpleFile(f: *mut FILE, p: *const c_char) -> c_int; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_SimpleFileEx(f: *mut FILE, p: *const c_char, c: c_int) -> c_int; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_InteractiveOne(f: *mut FILE, p: *const c_char) -> c_int; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_InteractiveLoop(f: *mut FILE, p: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyRun_File")] pub fn PyRun_File( fp: *mut FILE, p: *const c_char, s: c_int, g: *mut PyObject, l: *mut PyObject, ) -> *mut PyObject; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_FileEx( fp: *mut FILE, p: *const c_char, s: c_int, g: *mut PyObject, l: *mut PyObject, c: c_int, ) -> *mut PyObject; #[cfg(not(any(PyPy, GraalPy)))] pub fn PyRun_FileFlags( fp: *mut FILE, p: *const c_char, s: c_int, g: *mut PyObject, l: *mut PyObject, flags: *mut PyCompilerFlags, ) -> *mut PyObject; } // skipped macro PyRun_String // skipped macro PyRun_AnyFile // skipped macro PyRun_AnyFileEx // skipped macro PyRun_AnyFileFlags extern_libpython! { #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] pub fn PyParser_SimpleParseStringFlags( arg1: *const c_char, arg2: c_int, arg3: c_int, ) -> *mut _node; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] pub fn PyParser_SimpleParseStringFlagsFilename( arg1: *const c_char, arg2: *const c_char, arg3: c_int, arg4: c_int, ) -> *mut _node; #[cfg(not(any(PyPy, GraalPy, Py_3_10)))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] pub fn PyParser_SimpleParseFileFlags( arg1: *mut FILE, arg2: *const c_char, arg3: c_int, arg4: c_int, ) -> *mut _node; #[cfg(PyPy)] #[cfg_attr(PyPy, link_name = "PyPy_CompileStringFlags")] pub fn Py_CompileStringFlags( string: *const c_char, p: *const c_char, s: c_int, f: *mut PyCompilerFlags, ) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/cpython/tupleobject.rs ================================================ use crate::object::*; #[cfg(Py_3_14)] use crate::pyport::Py_hash_t; #[cfg(not(PyPy))] use crate::pyport::Py_ssize_t; #[repr(C)] pub struct PyTupleObject { pub ob_base: PyVarObject, #[cfg(Py_3_14)] pub ob_hash: Py_hash_t, pub ob_item: [*mut PyObject; 1], } // skipped _PyTuple_Resize // skipped _PyTuple_MaybeUntrack // skipped _PyTuple_CAST /// Macro, trading safety for speed #[inline] #[cfg(not(PyPy))] pub unsafe fn PyTuple_GET_SIZE(op: *mut PyObject) -> Py_ssize_t { Py_SIZE(op) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] pub unsafe fn PyTuple_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject { *(*(op as *mut PyTupleObject)).ob_item.as_ptr().offset(i) } /// Macro, *only* to be used to fill in brand new tuples #[inline] #[cfg(not(any(PyPy, GraalPy)))] pub unsafe fn PyTuple_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) { *(*(op as *mut PyTupleObject)).ob_item.as_mut_ptr().offset(i) = v; } // skipped _PyTuple_DebugMallocStats ================================================ FILE: pyo3-ffi/src/cpython/unicodeobject.rs ================================================ #[cfg(any(Py_3_11, not(PyPy)))] use crate::Py_hash_t; use crate::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_ssize_t}; use libc::wchar_t; use std::ffi::{c_char, c_int, c_uint, c_void}; // skipped Py_UNICODE_ISSPACE() // skipped Py_UNICODE_ISLOWER() // skipped Py_UNICODE_ISUPPER() // skipped Py_UNICODE_ISTITLE() // skipped Py_UNICODE_ISLINEBREAK // skipped Py_UNICODE_TOLOWER // skipped Py_UNICODE_TOUPPER // skipped Py_UNICODE_TOTITLE // skipped Py_UNICODE_ISDECIMAL // skipped Py_UNICODE_ISDIGIT // skipped Py_UNICODE_ISNUMERIC // skipped Py_UNICODE_ISPRINTABLE // skipped Py_UNICODE_TODECIMAL // skipped Py_UNICODE_TODIGIT // skipped Py_UNICODE_TONUMERIC // skipped Py_UNICODE_ISALPHA // skipped Py_UNICODE_ISALNUM // skipped Py_UNICODE_COPY // skipped Py_UNICODE_FILL // skipped Py_UNICODE_IS_SURROGATE // skipped Py_UNICODE_IS_HIGH_SURROGATE // skipped Py_UNICODE_IS_LOW_SURROGATE // skipped Py_UNICODE_JOIN_SURROGATES // skipped Py_UNICODE_HIGH_SURROGATE // skipped Py_UNICODE_LOW_SURROGATE // generated by bindgen v0.63.0 (with small adaptations) #[cfg(not(Py_3_14))] #[repr(C)] struct BitfieldUnit { storage: Storage, } #[cfg(not(Py_3_14))] impl BitfieldUnit { #[inline] pub const fn new(storage: Storage) -> Self { Self { storage } } } #[cfg(not(any(GraalPy, Py_3_14)))] impl BitfieldUnit where Storage: AsRef<[u8]> + AsMut<[u8]>, { #[inline] fn get_bit(&self, index: usize) -> bool { debug_assert!(index / 8 < self.storage.as_ref().len()); let byte_index = index / 8; let byte = self.storage.as_ref()[byte_index]; let bit_index = if cfg!(target_endian = "big") { 7 - (index % 8) } else { index % 8 }; let mask = 1 << bit_index; byte & mask == mask } #[inline] fn set_bit(&mut self, index: usize, val: bool) { debug_assert!(index / 8 < self.storage.as_ref().len()); let byte_index = index / 8; let byte = &mut self.storage.as_mut()[byte_index]; let bit_index = if cfg!(target_endian = "big") { 7 - (index % 8) } else { index % 8 }; let mask = 1 << bit_index; if val { *byte |= mask; } else { *byte &= !mask; } } #[inline] fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { debug_assert!(bit_width <= 64); debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); let mut val = 0; for i in 0..(bit_width as usize) { if self.get_bit(i + bit_offset) { let index = if cfg!(target_endian = "big") { bit_width as usize - 1 - i } else { i }; val |= 1 << index; } } val } #[inline] fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { debug_assert!(bit_width <= 64); debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); for i in 0..(bit_width as usize) { let mask = 1 << i; let val_bit_is_set = val & mask == mask; let index = if cfg!(target_endian = "big") { bit_width as usize - 1 - i } else { i }; self.set_bit(index + bit_offset, val_bit_is_set); } } } #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_INTERNED_INDEX: usize = 0; #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_INTERNED_WIDTH: u8 = 2; #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_KIND_INDEX: usize = STATE_INTERNED_WIDTH as usize; #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_KIND_WIDTH: u8 = 3; #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_COMPACT_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH) as usize; #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_COMPACT_WIDTH: u8 = 1; #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_ASCII_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH) as usize; #[cfg(not(any(GraalPy, Py_3_14)))] const STATE_ASCII_WIDTH: u8 = 1; #[cfg(all(not(any(GraalPy, Py_3_14)), Py_3_12))] const STATE_STATICALLY_ALLOCATED_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH + STATE_ASCII_WIDTH) as usize; #[cfg(all(not(any(GraalPy, Py_3_14)), Py_3_12))] const STATE_STATICALLY_ALLOCATED_WIDTH: u8 = 1; #[cfg(not(any(Py_3_12, GraalPy)))] const STATE_READY_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH + STATE_ASCII_WIDTH) as usize; #[cfg(not(any(Py_3_12, GraalPy)))] const STATE_READY_WIDTH: u8 = 1; // generated by bindgen v0.63.0 (with small adaptations) // The same code is generated for Python 3.7, 3.8, 3.9, 3.10, and 3.11, but the "ready" field // has been removed from Python 3.12. /// Wrapper around the `PyASCIIObject.state` bitfield with getters and setters that work /// on most little- and big-endian architectures. /// /// Memory layout of C bitfields is implementation defined, so these functions are still /// unsafe. Users must verify that they work as expected on the architectures they target. #[cfg(not(Py_3_14))] #[repr(C)] struct PyASCIIObjectState { bitfield_align: [u8; 0], bitfield: BitfieldUnit<[u8; 4usize]>, } // c_uint and u32 are not necessarily the same type on all targets / architectures #[cfg(not(any(GraalPy, Py_3_14)))] #[allow(clippy::useless_transmute)] impl PyASCIIObjectState { #[inline] unsafe fn interned(&self) -> c_uint { std::mem::transmute( self.bitfield .get(STATE_INTERNED_INDEX, STATE_INTERNED_WIDTH) as u32, ) } #[inline] unsafe fn set_interned(&mut self, val: c_uint) { let val: u32 = std::mem::transmute(val); self.bitfield .set(STATE_INTERNED_INDEX, STATE_INTERNED_WIDTH, val as u64) } #[inline] unsafe fn kind(&self) -> c_uint { std::mem::transmute(self.bitfield.get(STATE_KIND_INDEX, STATE_KIND_WIDTH) as u32) } #[inline] unsafe fn set_kind(&mut self, val: c_uint) { let val: u32 = std::mem::transmute(val); self.bitfield .set(STATE_KIND_INDEX, STATE_KIND_WIDTH, val as u64) } #[inline] unsafe fn compact(&self) -> c_uint { std::mem::transmute(self.bitfield.get(STATE_COMPACT_INDEX, STATE_COMPACT_WIDTH) as u32) } #[inline] unsafe fn set_compact(&mut self, val: c_uint) { let val: u32 = std::mem::transmute(val); self.bitfield .set(STATE_COMPACT_INDEX, STATE_COMPACT_WIDTH, val as u64) } #[inline] unsafe fn ascii(&self) -> c_uint { std::mem::transmute(self.bitfield.get(STATE_ASCII_INDEX, STATE_ASCII_WIDTH) as u32) } #[inline] unsafe fn set_ascii(&mut self, val: c_uint) { let val: u32 = std::mem::transmute(val); self.bitfield .set(STATE_ASCII_INDEX, STATE_ASCII_WIDTH, val as u64) } #[cfg(Py_3_12)] #[inline] unsafe fn statically_allocated(&self) -> c_uint { std::mem::transmute(self.bitfield.get( STATE_STATICALLY_ALLOCATED_INDEX, STATE_STATICALLY_ALLOCATED_WIDTH, ) as u32) } #[cfg(Py_3_12)] #[inline] unsafe fn set_statically_allocated(&mut self, val: c_uint) { let val: u32 = std::mem::transmute(val); self.bitfield.set( STATE_STATICALLY_ALLOCATED_INDEX, STATE_STATICALLY_ALLOCATED_WIDTH, val as u64, ) } #[cfg(not(Py_3_12))] #[inline] unsafe fn ready(&self) -> c_uint { std::mem::transmute(self.bitfield.get(STATE_READY_INDEX, STATE_READY_WIDTH) as u32) } #[cfg(not(Py_3_12))] #[inline] unsafe fn set_ready(&mut self, val: c_uint) { let val: u32 = std::mem::transmute(val); self.bitfield .set(STATE_READY_INDEX, STATE_READY_WIDTH, val as u64) } } #[cfg(not(Py_3_14))] impl From for PyASCIIObjectState { #[inline] fn from(value: u32) -> Self { PyASCIIObjectState { bitfield_align: [], bitfield: BitfieldUnit::new(value.to_ne_bytes()), } } } #[cfg(not(Py_3_14))] impl From for u32 { #[inline] fn from(value: PyASCIIObjectState) -> Self { u32::from_ne_bytes(value.bitfield.storage) } } #[repr(C)] pub struct PyASCIIObject { pub ob_base: PyObject, pub length: Py_ssize_t, #[cfg(any(Py_3_11, not(PyPy)))] pub hash: Py_hash_t, /// A bit field with various properties. /// /// Rust doesn't expose bitfields. So we have accessor functions for /// retrieving values. /// /// Before 3.12: /// unsigned int interned:2; // SSTATE_* constants. /// unsigned int kind:3; // PyUnicode_*_KIND constants. /// unsigned int compact:1; /// unsigned int ascii:1; /// unsigned int ready:1; /// unsigned int :24; /// /// 3.12, and 3.13 /// unsigned int interned:2; // SSTATE_* constants. /// unsigned int kind:3; // PyUnicode_*_KIND constants. /// unsigned int compact:1; /// unsigned int ascii:1; /// unsigned int statically_allocated:1; /// unsigned int :24; /// on 3.14 and higher PyO3 doesn't access the internal state pub state: u32, #[cfg(not(Py_3_12))] pub wstr: *mut wchar_t, } /// Interacting with the bitfield is not actually well-defined, so we mark these APIs unsafe. #[cfg(not(any(GraalPy, Py_3_14)))] impl PyASCIIObject { #[cfg_attr(not(Py_3_12), allow(rustdoc::broken_intra_doc_links))] // SSTATE_INTERNED_IMMORTAL_STATIC requires 3.12 /// Get the `interned` field of the [`PyASCIIObject`] state bitfield. /// /// Returns one of: [`SSTATE_NOT_INTERNED`], [`SSTATE_INTERNED_MORTAL`], /// [`SSTATE_INTERNED_IMMORTAL`], or [`SSTATE_INTERNED_IMMORTAL_STATIC`]. #[inline] pub unsafe fn interned(&self) -> c_uint { PyASCIIObjectState::from(self.state).interned() } #[cfg_attr(not(Py_3_12), allow(rustdoc::broken_intra_doc_links))] // SSTATE_INTERNED_IMMORTAL_STATIC requires 3.12 /// Set the `interned` field of the [`PyASCIIObject`] state bitfield. /// /// Calling this function with an argument that is not [`SSTATE_NOT_INTERNED`], /// [`SSTATE_INTERNED_MORTAL`], [`SSTATE_INTERNED_IMMORTAL`], or /// [`SSTATE_INTERNED_IMMORTAL_STATIC`] is invalid. #[inline] pub unsafe fn set_interned(&mut self, val: c_uint) { let mut state = PyASCIIObjectState::from(self.state); state.set_interned(val); self.state = u32::from(state); } /// Get the `kind` field of the [`PyASCIIObject`] state bitfield. /// /// Returns one of: #[cfg_attr(not(Py_3_12), doc = "[`PyUnicode_WCHAR_KIND`], ")] /// [`PyUnicode_1BYTE_KIND`], [`PyUnicode_2BYTE_KIND`], or [`PyUnicode_4BYTE_KIND`]. #[inline] pub unsafe fn kind(&self) -> c_uint { PyASCIIObjectState::from(self.state).kind() } /// Set the `kind` field of the [`PyASCIIObject`] state bitfield. /// /// Calling this function with an argument that is not #[cfg_attr(not(Py_3_12), doc = "[`PyUnicode_WCHAR_KIND`], ")] /// [`PyUnicode_1BYTE_KIND`], [`PyUnicode_2BYTE_KIND`], or [`PyUnicode_4BYTE_KIND`] is invalid. #[inline] pub unsafe fn set_kind(&mut self, val: c_uint) { let mut state = PyASCIIObjectState::from(self.state); state.set_kind(val); self.state = u32::from(state); } /// Get the `compact` field of the [`PyASCIIObject`] state bitfield. /// /// Returns either `0` or `1`. #[inline] pub unsafe fn compact(&self) -> c_uint { PyASCIIObjectState::from(self.state).compact() } /// Set the `compact` flag of the [`PyASCIIObject`] state bitfield. /// /// Calling this function with an argument that is neither `0` nor `1` is invalid. #[inline] pub unsafe fn set_compact(&mut self, val: c_uint) { let mut state = PyASCIIObjectState::from(self.state); state.set_compact(val); self.state = u32::from(state); } /// Get the `ascii` field of the [`PyASCIIObject`] state bitfield. /// /// Returns either `0` or `1`. #[inline] pub unsafe fn ascii(&self) -> c_uint { PyASCIIObjectState::from(self.state).ascii() } /// Set the `ascii` flag of the [`PyASCIIObject`] state bitfield. /// /// Calling this function with an argument that is neither `0` nor `1` is invalid. #[inline] #[cfg(not(all(Py_3_14, Py_GIL_DISABLED)))] pub unsafe fn set_ascii(&mut self, val: c_uint) { let mut state = PyASCIIObjectState::from(self.state); state.set_ascii(val); self.state = u32::from(state); } /// Get the `ready` field of the [`PyASCIIObject`] state bitfield. /// /// Returns either `0` or `1`. #[cfg(not(Py_3_12))] #[inline] pub unsafe fn ready(&self) -> c_uint { PyASCIIObjectState::from(self.state).ready() } /// Set the `ready` flag of the [`PyASCIIObject`] state bitfield. /// /// Calling this function with an argument that is neither `0` nor `1` is invalid. #[cfg(not(Py_3_12))] #[inline] pub unsafe fn set_ready(&mut self, val: c_uint) { let mut state = PyASCIIObjectState::from(self.state); state.set_ready(val); self.state = u32::from(state); } /// Get the `statically_allocated` field of the [`PyASCIIObject`] state bitfield. /// /// Returns either `0` or `1`. #[inline] #[cfg(Py_3_12)] pub unsafe fn statically_allocated(&self) -> c_uint { PyASCIIObjectState::from(self.state).statically_allocated() } /// Set the `statically_allocated` flag of the [`PyASCIIObject`] state bitfield. /// /// Calling this function with an argument that is neither `0` nor `1` is invalid. #[inline] #[cfg(Py_3_12)] pub unsafe fn set_statically_allocated(&mut self, val: c_uint) { let mut state = PyASCIIObjectState::from(self.state); state.set_statically_allocated(val); self.state = u32::from(state); } } #[repr(C)] pub struct PyCompactUnicodeObject { pub _base: PyASCIIObject, pub utf8_length: Py_ssize_t, pub utf8: *mut c_char, #[cfg(not(Py_3_12))] pub wstr_length: Py_ssize_t, } #[repr(C)] pub union PyUnicodeObjectData { pub any: *mut c_void, pub latin1: *mut Py_UCS1, pub ucs2: *mut Py_UCS2, pub ucs4: *mut Py_UCS4, } #[repr(C)] pub struct PyUnicodeObject { pub _base: PyCompactUnicodeObject, pub data: PyUnicodeObjectData, } extern_libpython! { #[cfg(not(any(PyPy, GraalPy)))] pub fn _PyUnicode_CheckConsistency(op: *mut PyObject, check_content: c_int) -> c_int; } // skipped PyUnicode_GET_SIZE // skipped PyUnicode_GET_DATA_SIZE // skipped PyUnicode_AS_UNICODE // skipped PyUnicode_AS_DATA pub const SSTATE_NOT_INTERNED: c_uint = 0; pub const SSTATE_INTERNED_MORTAL: c_uint = 1; pub const SSTATE_INTERNED_IMMORTAL: c_uint = 2; #[cfg(Py_3_12)] pub const SSTATE_INTERNED_IMMORTAL_STATIC: c_uint = 3; #[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { debug_assert!(crate::PyUnicode_Check(op) != 0); #[cfg(not(Py_3_12))] debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).ascii() } #[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_IS_COMPACT(op: *mut PyObject) -> c_uint { (*(op as *mut PyASCIIObject)).compact() } #[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_IS_COMPACT_ASCII(op: *mut PyObject) -> c_uint { ((*(op as *mut PyASCIIObject)).ascii() != 0 && PyUnicode_IS_COMPACT(op) != 0).into() } #[cfg(not(Py_3_12))] #[deprecated(note = "Removed in Python 3.12")] pub const PyUnicode_WCHAR_KIND: c_uint = 0; pub const PyUnicode_1BYTE_KIND: c_uint = 1; pub const PyUnicode_2BYTE_KIND: c_uint = 2; pub const PyUnicode_4BYTE_KIND: c_uint = 4; #[cfg(not(any(GraalPy, PyPy)))] #[inline] pub unsafe fn PyUnicode_1BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS1 { PyUnicode_DATA(op) as *mut Py_UCS1 } #[cfg(not(any(GraalPy, PyPy)))] #[inline] pub unsafe fn PyUnicode_2BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS2 { PyUnicode_DATA(op) as *mut Py_UCS2 } #[cfg(not(any(GraalPy, PyPy)))] #[inline] pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 { PyUnicode_DATA(op) as *mut Py_UCS4 } #[cfg(all(not(GraalPy), Py_3_14))] extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyUnicode_KIND")] pub fn PyUnicode_KIND(op: *mut PyObject) -> c_uint; } #[cfg(all(not(GraalPy), not(Py_3_14)))] #[inline] pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { debug_assert!(crate::PyUnicode_Check(op) != 0); #[cfg(not(Py_3_12))] debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).kind() } #[cfg(not(any(GraalPy, Py_3_14)))] #[inline] pub unsafe fn _PyUnicode_COMPACT_DATA(op: *mut PyObject) -> *mut c_void { if PyUnicode_IS_ASCII(op) != 0 { (op as *mut PyASCIIObject).offset(1) as *mut c_void } else { (op as *mut PyCompactUnicodeObject).offset(1) as *mut c_void } } #[cfg(not(any(GraalPy, PyPy)))] #[inline] pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void { debug_assert!(!(*(op as *mut PyUnicodeObject)).data.any.is_null()); (*(op as *mut PyUnicodeObject)).data.any } #[cfg(not(any(GraalPy, PyPy, Py_3_14)))] #[inline] pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { debug_assert!(crate::PyUnicode_Check(op) != 0); if PyUnicode_IS_COMPACT(op) != 0 { _PyUnicode_COMPACT_DATA(op) } else { _PyUnicode_NONCOMPACT_DATA(op) } } #[cfg(Py_3_14)] #[cfg(all(not(GraalPy), Py_3_14))] extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyUnicode_DATA")] pub fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void; } // skipped PyUnicode_WRITE // skipped PyUnicode_READ // skipped PyUnicode_READ_CHAR #[cfg(not(GraalPy))] #[inline] pub unsafe fn PyUnicode_GET_LENGTH(op: *mut PyObject) -> Py_ssize_t { debug_assert!(crate::PyUnicode_Check(op) != 0); #[cfg(not(Py_3_12))] debug_assert!(PyUnicode_IS_READY(op) != 0); (*(op as *mut PyASCIIObject)).length } #[cfg(any(Py_3_12, GraalPy))] #[inline] pub unsafe fn PyUnicode_IS_READY(_op: *mut PyObject) -> c_uint { // kept in CPython for backwards compatibility 1 } #[cfg(not(any(GraalPy, Py_3_12)))] #[inline] pub unsafe fn PyUnicode_IS_READY(op: *mut PyObject) -> c_uint { (*(op as *mut PyASCIIObject)).ready() } #[cfg(any(Py_3_12, GraalPy))] #[inline] pub unsafe fn PyUnicode_READY(_op: *mut PyObject) -> c_int { 0 } #[cfg(not(any(Py_3_12, GraalPy)))] #[inline] pub unsafe fn PyUnicode_READY(op: *mut PyObject) -> c_int { debug_assert!(crate::PyUnicode_Check(op) != 0); if PyUnicode_IS_READY(op) != 0 { 0 } else { _PyUnicode_Ready(op) } } // skipped PyUnicode_MAX_CHAR_VALUE // skipped _PyUnicode_get_wstr_length // skipped PyUnicode_WSTR_LENGTH extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyUnicode_New")] pub fn PyUnicode_New(size: Py_ssize_t, maxchar: Py_UCS4) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "_PyPyUnicode_Ready")] pub fn _PyUnicode_Ready(unicode: *mut PyObject) -> c_int; // skipped _PyUnicode_Copy #[cfg(not(PyPy))] pub fn PyUnicode_CopyCharacters( to: *mut PyObject, to_start: Py_ssize_t, from: *mut PyObject, from_start: Py_ssize_t, how_many: Py_ssize_t, ) -> Py_ssize_t; // skipped _PyUnicode_FastCopyCharacters #[cfg(not(PyPy))] pub fn PyUnicode_Fill( unicode: *mut PyObject, start: Py_ssize_t, length: Py_ssize_t, fill_char: Py_UCS4, ) -> Py_ssize_t; // skipped _PyUnicode_FastFill #[cfg(not(Py_3_12))] #[deprecated] #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromUnicode")] pub fn PyUnicode_FromUnicode(u: *const wchar_t, size: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromKindAndData")] pub fn PyUnicode_FromKindAndData( kind: c_int, buffer: *const c_void, size: Py_ssize_t, ) -> *mut PyObject; // skipped _PyUnicode_FromASCII // skipped _PyUnicode_FindMaxChar #[cfg(not(Py_3_12))] #[deprecated] #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUnicode")] pub fn PyUnicode_AsUnicode(unicode: *mut PyObject) -> *mut wchar_t; // skipped _PyUnicode_AsUnicode #[cfg(not(Py_3_12))] #[deprecated] #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUnicodeAndSize")] pub fn PyUnicode_AsUnicodeAndSize( unicode: *mut PyObject, size: *mut Py_ssize_t, ) -> *mut wchar_t; // skipped PyUnicode_GetMax } #[cfg(Py_3_14)] opaque_struct!(pub PyUnicodeWriter); extern_libpython! { #[cfg(Py_3_14)] pub fn PyUnicodeWriter_Create(length: Py_ssize_t) -> *mut PyUnicodeWriter; #[cfg(Py_3_14)] pub fn PyUnicodeWriter_Finish(writer: *mut PyUnicodeWriter) -> *mut PyObject; #[cfg(Py_3_14)] pub fn PyUnicodeWriter_Discard(writer: *mut PyUnicodeWriter); #[cfg(Py_3_14)] pub fn PyUnicodeWriter_WriteChar(writer: *mut PyUnicodeWriter, ch: Py_UCS4) -> c_int; #[cfg(Py_3_14)] pub fn PyUnicodeWriter_WriteUTF8( writer: *mut PyUnicodeWriter, str: *const c_char, size: Py_ssize_t, ) -> c_int; } // skipped _PyUnicodeWriter // skipped _PyUnicodeWriter_Init // skipped _PyUnicodeWriter_Prepare // skipped _PyUnicodeWriter_PrepareInternal // skipped _PyUnicodeWriter_PrepareKind // skipped _PyUnicodeWriter_PrepareKindInternal // skipped _PyUnicodeWriter_WriteChar // skipped _PyUnicodeWriter_WriteStr // skipped _PyUnicodeWriter_WriteSubstring // skipped _PyUnicodeWriter_WriteASCIIString // skipped _PyUnicodeWriter_WriteLatin1String // skipped _PyUnicodeWriter_Finish // skipped _PyUnicodeWriter_Dealloc // skipped _PyUnicode_FormatAdvancedWriter extern_libpython! { // skipped _PyUnicode_AsStringAndSize #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8")] pub fn PyUnicode_AsUTF8(unicode: *mut PyObject) -> *const c_char; // skipped _PyUnicode_AsString pub fn PyUnicode_Encode( s: *const wchar_t, size: Py_ssize_t, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_EncodeUTF7( data: *const wchar_t, length: Py_ssize_t, base64SetO: c_int, base64WhiteSpace: c_int, errors: *const c_char, ) -> *mut PyObject; // skipped _PyUnicode_EncodeUTF7 // skipped _PyUnicode_AsUTF8String #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeUTF8")] pub fn PyUnicode_EncodeUTF8( data: *const wchar_t, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_EncodeUTF32( data: *const wchar_t, length: Py_ssize_t, errors: *const c_char, byteorder: c_int, ) -> *mut PyObject; // skipped _PyUnicode_EncodeUTF32 pub fn PyUnicode_EncodeUTF16( data: *const wchar_t, length: Py_ssize_t, errors: *const c_char, byteorder: c_int, ) -> *mut PyObject; // skipped _PyUnicode_EncodeUTF16 // skipped _PyUnicode_DecodeUnicodeEscape pub fn PyUnicode_EncodeUnicodeEscape(data: *const wchar_t, length: Py_ssize_t) -> *mut PyObject; pub fn PyUnicode_EncodeRawUnicodeEscape( data: *const wchar_t, length: Py_ssize_t, ) -> *mut PyObject; // skipped _PyUnicode_AsLatin1String #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeLatin1")] pub fn PyUnicode_EncodeLatin1( data: *const wchar_t, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; // skipped _PyUnicode_AsASCIIString #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeASCII")] pub fn PyUnicode_EncodeASCII( data: *const wchar_t, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_EncodeCharmap( data: *const wchar_t, length: Py_ssize_t, mapping: *mut PyObject, errors: *const c_char, ) -> *mut PyObject; // skipped _PyUnicode_EncodeCharmap pub fn PyUnicode_TranslateCharmap( data: *const wchar_t, length: Py_ssize_t, table: *mut PyObject, errors: *const c_char, ) -> *mut PyObject; // skipped PyUnicode_EncodeMBCS #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeDecimal")] pub fn PyUnicode_EncodeDecimal( s: *mut wchar_t, length: Py_ssize_t, output: *mut c_char, errors: *const c_char, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyUnicode_TransformDecimalToASCII")] pub fn PyUnicode_TransformDecimalToASCII(s: *mut wchar_t, length: Py_ssize_t) -> *mut PyObject; // skipped _PyUnicode_TransformDecimalAndSpaceToASCII } // skipped _PyUnicode_JoinArray // skipped _PyUnicode_EqualToASCIIId // skipped _PyUnicode_EqualToASCIIString // skipped _PyUnicode_XStrip // skipped _PyUnicode_InsertThousandsGrouping // skipped _Py_ascii_whitespace // skipped _PyUnicode_IsLowercase // skipped _PyUnicode_IsUppercase // skipped _PyUnicode_IsTitlecase // skipped _PyUnicode_IsXidStart // skipped _PyUnicode_IsXidContinue // skipped _PyUnicode_IsWhitespace // skipped _PyUnicode_IsLinebreak // skipped _PyUnicode_ToLowercase // skipped _PyUnicode_ToUppercase // skipped _PyUnicode_ToTitlecase // skipped _PyUnicode_ToLowerFull // skipped _PyUnicode_ToTitleFull // skipped _PyUnicode_ToUpperFull // skipped _PyUnicode_ToFoldedFull // skipped _PyUnicode_IsCaseIgnorable // skipped _PyUnicode_IsCased // skipped _PyUnicode_ToDecimalDigit // skipped _PyUnicode_ToDigit // skipped _PyUnicode_ToNumeric // skipped _PyUnicode_IsDecimalDigit // skipped _PyUnicode_IsDigit // skipped _PyUnicode_IsNumeric // skipped _PyUnicode_IsPrintable // skipped _PyUnicode_IsAlpha // skipped Py_UNICODE_strlen // skipped Py_UNICODE_strcpy // skipped Py_UNICODE_strcat // skipped Py_UNICODE_strncpy // skipped Py_UNICODE_strcmp // skipped Py_UNICODE_strncmp // skipped Py_UNICODE_strchr // skipped Py_UNICODE_strrchr // skipped _PyUnicode_FormatLong // skipped PyUnicode_AsUnicodeCopy // skipped _PyUnicode_FromId // skipped _PyUnicode_EQ // skipped _PyUnicode_ScanIdentifier ================================================ FILE: pyo3-ffi/src/cpython/weakrefobject.rs ================================================ // NB publicly re-exported in `src/weakrefobject.rs` #[cfg(not(any(PyPy, GraalPy)))] pub struct _PyWeakReference { pub ob_base: crate::PyObject, pub wr_object: *mut crate::PyObject, pub wr_callback: *mut crate::PyObject, pub hash: crate::Py_hash_t, pub wr_prev: *mut crate::PyWeakReference, pub wr_next: *mut crate::PyWeakReference, #[cfg(Py_3_11)] pub vectorcall: Option, #[cfg(all(Py_3_13, Py_GIL_DISABLED))] pub weakrefs_lock: *mut crate::PyMutex, } // skipped _PyWeakref_GetWeakrefCount // skipped _PyWeakref_ClearRef // skipped PyWeakRef_GET_OBJECT ================================================ FILE: pyo3-ffi/src/datetime.rs ================================================ //! FFI bindings to the functions and structs defined in `datetime.h` //! //! This is the unsafe thin wrapper around the [CPython C API](https://docs.python.org/3/c-api/datetime.html), //! and covers the various date and time related objects in the Python `datetime` //! standard library module. #[cfg(not(PyPy))] use crate::PyCapsule_Import; #[cfg(GraalPy)] use crate::{PyLong_AsLong, PyLong_Check, PyObject_GetAttrString, Py_DecRef}; use crate::{PyObject, PyObject_TypeCheck, PyTypeObject, Py_TYPE}; use std::ffi::c_char; use std::ffi::c_int; use std::ptr; use std::sync::Once; use std::{cell::UnsafeCell, ffi::CStr}; #[cfg(not(PyPy))] use {crate::Py_hash_t, std::ffi::c_uchar}; // Type struct wrappers const _PyDateTime_DATE_DATASIZE: usize = 4; const _PyDateTime_TIME_DATASIZE: usize = 6; const _PyDateTime_DATETIME_DATASIZE: usize = 10; #[repr(C)] #[derive(Debug)] /// Structure representing a `datetime.timedelta`. pub struct PyDateTime_Delta { pub ob_base: PyObject, #[cfg(not(PyPy))] pub hashcode: Py_hash_t, pub days: c_int, pub seconds: c_int, pub microseconds: c_int, } // skipped non-limited PyDateTime_TZInfo // skipped non-limited _PyDateTime_BaseTZInfo #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] /// Structure representing a `datetime.time` without a `tzinfo` member. pub struct _PyDateTime_BaseTime { pub ob_base: PyObject, pub hashcode: Py_hash_t, pub hastzinfo: c_char, pub data: [c_uchar; _PyDateTime_TIME_DATASIZE], } #[repr(C)] #[derive(Debug)] /// Structure representing a `datetime.time`. pub struct PyDateTime_Time { pub ob_base: PyObject, #[cfg(not(PyPy))] pub hashcode: Py_hash_t, pub hastzinfo: c_char, #[cfg(not(PyPy))] pub data: [c_uchar; _PyDateTime_TIME_DATASIZE], #[cfg(not(PyPy))] pub fold: c_uchar, /// # Safety /// /// Care should be taken when reading this field. If the time does not have a /// tzinfo then CPython may allocate as a `_PyDateTime_BaseTime` without this field. pub tzinfo: *mut PyObject, } #[repr(C)] #[derive(Debug)] /// Structure representing a `datetime.date` pub struct PyDateTime_Date { pub ob_base: PyObject, #[cfg(not(PyPy))] pub hashcode: Py_hash_t, #[cfg(not(PyPy))] pub hastzinfo: c_char, #[cfg(not(PyPy))] pub data: [c_uchar; _PyDateTime_DATE_DATASIZE], } #[cfg(not(any(PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] /// Structure representing a `datetime.datetime` without a `tzinfo` member. pub struct _PyDateTime_BaseDateTime { pub ob_base: PyObject, pub hashcode: Py_hash_t, pub hastzinfo: c_char, pub data: [c_uchar; _PyDateTime_DATETIME_DATASIZE], } #[repr(C)] #[derive(Debug)] /// Structure representing a `datetime.datetime`. pub struct PyDateTime_DateTime { pub ob_base: PyObject, #[cfg(not(PyPy))] pub hashcode: Py_hash_t, pub hastzinfo: c_char, #[cfg(not(PyPy))] pub data: [c_uchar; _PyDateTime_DATETIME_DATASIZE], #[cfg(not(PyPy))] pub fold: c_uchar, /// # Safety /// /// Care should be taken when reading this field. If the time does not have a /// tzinfo then CPython may allocate as a `_PyDateTime_BaseDateTime` without this field. pub tzinfo: *mut PyObject, } // skipped non-limited _PyDateTime_HAS_TZINFO // Accessor functions for PyDateTime_Date and PyDateTime_DateTime #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the year component of a `PyDateTime_Date` or `PyDateTime_DateTime`. /// Returns a signed integer greater than 0. pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int { // This should work for Date or DateTime let data = (*(o as *mut PyDateTime_Date)).data; (c_int::from(data[0]) << 8) | c_int::from(data[1]) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the month component of a `PyDateTime_Date` or `PyDateTime_DateTime`. /// Returns a signed integer in the range `[1, 12]`. pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int { let data = (*(o as *mut PyDateTime_Date)).data; c_int::from(data[2]) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the day component of a `PyDateTime_Date` or `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[1, 31]`. pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int { let data = (*(o as *mut PyDateTime_Date)).data; c_int::from(data[3]) } // Accessor macros for times #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _PyDateTime_GET_HOUR { ($o: expr, $offset:expr) => { c_int::from((*$o).data[$offset + 0]) }; } #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _PyDateTime_GET_MINUTE { ($o: expr, $offset:expr) => { c_int::from((*$o).data[$offset + 1]) }; } #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _PyDateTime_GET_SECOND { ($o: expr, $offset:expr) => { c_int::from((*$o).data[$offset + 2]) }; } #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _PyDateTime_GET_MICROSECOND { ($o: expr, $offset:expr) => { (c_int::from((*$o).data[$offset + 3]) << 16) | (c_int::from((*$o).data[$offset + 4]) << 8) | (c_int::from((*$o).data[$offset + 5])) }; } #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _PyDateTime_GET_FOLD { ($o: expr) => { (*$o).fold }; } #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _PyDateTime_GET_TZINFO { ($o: expr) => { if (*$o).hastzinfo != 0 { (*$o).tzinfo } else { $crate::Py_None() } }; } // Accessor functions for DateTime #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the hour component of a `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[0, 23]` pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int { _PyDateTime_GET_HOUR!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the minute component of a `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[0, 59]` pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int { _PyDateTime_GET_MINUTE!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the second component of a `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[0, 59]` pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int { _PyDateTime_GET_SECOND!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the microsecond component of a `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[0, 999999]` pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int { _PyDateTime_GET_MICROSECOND!((o as *mut PyDateTime_DateTime), _PyDateTime_DATE_DATASIZE) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the fold component of a `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[0, 1]` pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_uchar { _PyDateTime_GET_FOLD!(o as *mut PyDateTime_DateTime) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the tzinfo component of a `PyDateTime_DateTime`. /// Returns a pointer to a `PyObject` that should be either NULL or an instance /// of a `datetime.tzinfo` subclass. pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { _PyDateTime_GET_TZINFO!(o as *mut PyDateTime_DateTime) } // Accessor functions for Time #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the hour component of a `PyDateTime_Time`. /// Returns a signed integer in the interval `[0, 23]` pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int { _PyDateTime_GET_HOUR!((o as *mut PyDateTime_Time), 0) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the minute component of a `PyDateTime_Time`. /// Returns a signed integer in the interval `[0, 59]` pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int { _PyDateTime_GET_MINUTE!((o as *mut PyDateTime_Time), 0) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the second component of a `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[0, 59]` pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int { _PyDateTime_GET_SECOND!((o as *mut PyDateTime_Time), 0) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the microsecond component of a `PyDateTime_DateTime`. /// Returns a signed integer in the interval `[0, 999999]` pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int { _PyDateTime_GET_MICROSECOND!((o as *mut PyDateTime_Time), 0) } #[cfg(not(any(PyPy, GraalPy)))] #[inline] /// Retrieve the fold component of a `PyDateTime_Time`. /// Returns a signed integer in the interval `[0, 1]` pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_uchar { _PyDateTime_GET_FOLD!(o as *mut PyDateTime_Time) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the tzinfo component of a `PyDateTime_Time`. /// Returns a pointer to a `PyObject` that should be either NULL or an instance /// of a `datetime.tzinfo` subclass. pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { _PyDateTime_GET_TZINFO!(o as *mut PyDateTime_Time) } // Accessor functions #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _access_field { ($obj:expr, $type: ident, $field:ident) => { (*($obj as *mut $type)).$field }; } // Accessor functions for PyDateTime_Delta #[cfg(not(any(PyPy, GraalPy)))] macro_rules! _access_delta_field { ($obj:expr, $field:ident) => { _access_field!($obj, PyDateTime_Delta, $field) }; } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the days component of a `PyDateTime_Delta`. /// /// Returns a signed integer in the interval [-999999999, 999999999]. /// /// Note: This retrieves a component from the underlying structure, it is *not* /// a representation of the total duration of the structure. pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int { _access_delta_field!(o, days) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the seconds component of a `PyDateTime_Delta`. /// /// Returns a signed integer in the interval [0, 86399]. /// /// Note: This retrieves a component from the underlying structure, it is *not* /// a representation of the total duration of the structure. pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int { _access_delta_field!(o, seconds) } #[inline] #[cfg(not(any(PyPy, GraalPy)))] /// Retrieve the seconds component of a `PyDateTime_Delta`. /// /// Returns a signed integer in the interval [0, 999999]. /// /// Note: This retrieves a component from the underlying structure, it is *not* /// a representation of the total duration of the structure. pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int { _access_delta_field!(o, microseconds) } // Accessor functions for GraalPy. The macros on GraalPy work differently, // but copying them seems suboptimal #[inline] #[cfg(GraalPy)] pub unsafe fn _get_attr(obj: *mut PyObject, field: &std::ffi::CStr) -> c_int { let result = PyObject_GetAttrString(obj, field.as_ptr()); Py_DecRef(result); // the original macros are borrowing if PyLong_Check(result) == 1 { PyLong_AsLong(result) as c_int } else { 0 } } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int { _get_attr(o, c"year") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int { _get_attr(o, c"month") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int { _get_attr(o, c"day") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int { _get_attr(o, c"hour") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int { _get_attr(o, c"minute") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int { _get_attr(o, c"second") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int { _get_attr(o, c"microsecond") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int { _get_attr(o, c"fold") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { let res = PyObject_GetAttrString(o, c"tzinfo".as_ptr().cast()); Py_DecRef(res); // the original macros are borrowing res } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int { _get_attr(o, c"hour") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int { _get_attr(o, c"minute") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int { _get_attr(o, c"second") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int { _get_attr(o, c"microsecond") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int { _get_attr(o, c"fold") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { let res = PyObject_GetAttrString(o, c"tzinfo".as_ptr().cast()); Py_DecRef(res); // the original macros are borrowing res } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int { _get_attr(o, c"days") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int { _get_attr(o, c"seconds") } #[inline] #[cfg(GraalPy)] pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int { _get_attr(o, c"microseconds") } #[cfg(PyPy)] extern_libpython! { // skipped _PyDateTime_HAS_TZINFO (not in PyPy) #[link_name = "PyPyDateTime_GET_YEAR"] pub fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_GET_MONTH"] pub fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_GET_DAY"] pub fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_DATE_GET_HOUR"] pub fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_DATE_GET_MINUTE"] pub fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_DATE_GET_SECOND"] pub fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_DATE_GET_MICROSECOND"] pub fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_GET_FOLD"] pub fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_DATE_GET_TZINFO"] #[cfg(Py_3_10)] pub fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject; #[link_name = "PyPyDateTime_TIME_GET_HOUR"] pub fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_TIME_GET_MINUTE"] pub fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_TIME_GET_SECOND"] pub fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_TIME_GET_MICROSECOND"] pub fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_TIME_GET_FOLD"] pub fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_TIME_GET_TZINFO"] #[cfg(Py_3_10)] pub fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject; #[link_name = "PyPyDateTime_DELTA_GET_DAYS"] pub fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_DELTA_GET_SECONDS"] pub fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int; #[link_name = "PyPyDateTime_DELTA_GET_MICROSECONDS"] pub fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int; } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct PyDateTime_CAPI { pub DateType: *mut PyTypeObject, pub DateTimeType: *mut PyTypeObject, pub TimeType: *mut PyTypeObject, pub DeltaType: *mut PyTypeObject, pub TZInfoType: *mut PyTypeObject, pub TimeZone_UTC: *mut PyObject, pub Date_FromDate: unsafe extern "C" fn( year: c_int, month: c_int, day: c_int, cls: *mut PyTypeObject, ) -> *mut PyObject, pub DateTime_FromDateAndTime: unsafe extern "C" fn( year: c_int, month: c_int, day: c_int, hour: c_int, minute: c_int, second: c_int, microsecond: c_int, tzinfo: *mut PyObject, cls: *mut PyTypeObject, ) -> *mut PyObject, pub Time_FromTime: unsafe extern "C" fn( hour: c_int, minute: c_int, second: c_int, microsecond: c_int, tzinfo: *mut PyObject, cls: *mut PyTypeObject, ) -> *mut PyObject, pub Delta_FromDelta: unsafe extern "C" fn( days: c_int, seconds: c_int, microseconds: c_int, normalize: c_int, cls: *mut PyTypeObject, ) -> *mut PyObject, pub TimeZone_FromTimeZone: unsafe extern "C" fn(offset: *mut PyObject, name: *mut PyObject) -> *mut PyObject, pub DateTime_FromTimestamp: unsafe extern "C" fn( cls: *mut PyTypeObject, args: *mut PyObject, kwargs: *mut PyObject, ) -> *mut PyObject, pub Date_FromTimestamp: unsafe extern "C" fn(cls: *mut PyTypeObject, args: *mut PyObject) -> *mut PyObject, pub DateTime_FromDateAndTimeAndFold: unsafe extern "C" fn( year: c_int, month: c_int, day: c_int, hour: c_int, minute: c_int, second: c_int, microsecond: c_int, tzinfo: *mut PyObject, fold: c_int, cls: *mut PyTypeObject, ) -> *mut PyObject, pub Time_FromTimeAndFold: unsafe extern "C" fn( hour: c_int, minute: c_int, second: c_int, microsecond: c_int, tzinfo: *mut PyObject, fold: c_int, cls: *mut PyTypeObject, ) -> *mut PyObject, } // Python already shares this object between threads, so it's no more evil for us to do it too! unsafe impl Sync for PyDateTime_CAPI {} pub const PyDateTime_CAPSULE_NAME: &CStr = c"datetime.datetime_CAPI"; /// Returns a pointer to a `PyDateTime_CAPI` instance /// /// # Note /// This function will return a null pointer until /// `PyDateTime_IMPORT` is called #[inline] pub unsafe fn PyDateTimeAPI() -> *mut PyDateTime_CAPI { *PyDateTimeAPI_impl.ptr.get() } /// Populates the `PyDateTimeAPI` object pub unsafe fn PyDateTime_IMPORT() { if !PyDateTimeAPI_impl.once.is_completed() { // PyPy expects the C-API to be initialized via PyDateTime_Import, so trying to use // `PyCapsule_Import` will behave unexpectedly in pypy. #[cfg(PyPy)] let py_datetime_c_api = PyDateTime_Import(); #[cfg(not(PyPy))] let py_datetime_c_api = PyCapsule_Import(PyDateTime_CAPSULE_NAME.as_ptr(), 1) as *mut PyDateTime_CAPI; if py_datetime_c_api.is_null() { return; } // Protect against race conditions when the datetime API is concurrently // initialized in multiple threads. UnsafeCell.get() cannot panic so this // won't panic either. PyDateTimeAPI_impl.once.call_once(|| { *PyDateTimeAPI_impl.ptr.get() = py_datetime_c_api; }); } } #[inline] pub unsafe fn PyDateTime_TimeZone_UTC() -> *mut PyObject { (*PyDateTimeAPI()).TimeZone_UTC } /// Type Check macros /// /// These are bindings around the C API typecheck macros, all of them return /// `1` if True and `0` if False. In all type check macros, the argument (`op`) /// must not be `NULL`. #[inline] /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype. pub unsafe fn PyDate_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DateType`. pub unsafe fn PyDate_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == (*PyDateTimeAPI()).DateType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype. pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, (*PyDateTimeAPI()).DateTimeType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DateTimeType`. pub unsafe fn PyDateTime_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == (*PyDateTimeAPI()).DateTimeType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype. pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, (*PyDateTimeAPI()).TimeType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.TimeType`. pub unsafe fn PyTime_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == (*PyDateTimeAPI()).TimeType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype. pub unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, (*PyDateTimeAPI()).DeltaType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.DeltaType`. pub unsafe fn PyDelta_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == (*PyDateTimeAPI()).DeltaType) as c_int } #[inline] /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype. pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, (*PyDateTimeAPI()).TZInfoType) as c_int } #[inline] /// Check if `op`'s type is exactly `PyDateTimeAPI.TZInfoType`. pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == (*PyDateTimeAPI()).TZInfoType) as c_int } // skipped non-limited PyDate_FromDate // skipped non-limited PyDateTime_FromDateAndTime // skipped non-limited PyDateTime_FromDateAndTimeAndFold // skipped non-limited PyTime_FromTime // skipped non-limited PyTime_FromTimeAndFold // skipped non-limited PyDelta_FromDSU pub unsafe fn PyTimeZone_FromOffset(offset: *mut PyObject) -> *mut PyObject { ((*PyDateTimeAPI()).TimeZone_FromTimeZone)(offset, std::ptr::null_mut()) } pub unsafe fn PyTimeZone_FromOffsetAndName( offset: *mut PyObject, name: *mut PyObject, ) -> *mut PyObject { ((*PyDateTimeAPI()).TimeZone_FromTimeZone)(offset, name) } #[cfg(not(PyPy))] pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject { let f = (*PyDateTimeAPI()).DateTime_FromTimestamp; f((*PyDateTimeAPI()).DateTimeType, args, std::ptr::null_mut()) } #[cfg(not(PyPy))] pub unsafe fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject { let f = (*PyDateTimeAPI()).Date_FromTimestamp; f((*PyDateTimeAPI()).DateType, args) } #[cfg(PyPy)] extern_libpython! { #[link_name = "PyPyDate_FromTimestamp"] pub fn PyDate_FromTimestamp(args: *mut PyObject) -> *mut PyObject; #[link_name = "PyPyDateTime_FromTimestamp"] pub fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject; } #[cfg(PyPy)] extern_libpython! { #[link_name = "_PyPyDateTime_Import"] pub fn PyDateTime_Import() -> *mut PyDateTime_CAPI; } // Rust specific implementation details struct PyDateTimeAPISingleton { once: Once, ptr: UnsafeCell<*mut PyDateTime_CAPI>, } unsafe impl Sync for PyDateTimeAPISingleton {} static PyDateTimeAPI_impl: PyDateTimeAPISingleton = PyDateTimeAPISingleton { once: Once::new(), ptr: UnsafeCell::new(ptr::null_mut()), }; ================================================ FILE: pyo3-ffi/src/descrobject.rs ================================================ use crate::methodobject::PyMethodDef; use crate::object::{PyObject, PyTypeObject}; use crate::Py_ssize_t; use std::ffi::{c_char, c_int, c_void}; use std::ptr; pub type getter = unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void) -> *mut PyObject; pub type setter = unsafe extern "C" fn(slf: *mut PyObject, value: *mut PyObject, closure: *mut c_void) -> c_int; /// Represents the [PyGetSetDef](https://docs.python.org/3/c-api/structures.html#c.PyGetSetDef) /// structure. /// /// Note that CPython may leave fields uninitialized. You must ensure that /// `name` != NULL before dereferencing or reading other fields. #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct PyGetSetDef { pub name: *const c_char, pub get: Option, pub set: Option, pub doc: *const c_char, pub closure: *mut c_void, } impl Default for PyGetSetDef { fn default() -> PyGetSetDef { PyGetSetDef { name: ptr::null(), get: None, set: None, doc: ptr::null(), closure: ptr::null_mut(), } } } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyClassMethodDescr_Type")] pub static mut PyClassMethodDescr_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyGetSetDescr_Type")] pub static mut PyGetSetDescr_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyMemberDescr_Type")] pub static mut PyMemberDescr_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyMethodDescr_Type")] pub static mut PyMethodDescr_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyWrapperDescr_Type")] pub static mut PyWrapperDescr_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyDictProxy_Type")] pub static mut PyDictProxy_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyProperty_Type")] pub static mut PyProperty_Type: PyTypeObject; } extern_libpython! { pub fn PyDescr_NewMethod(arg1: *mut PyTypeObject, arg2: *mut PyMethodDef) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDescr_NewClassMethod")] pub fn PyDescr_NewClassMethod(arg1: *mut PyTypeObject, arg2: *mut PyMethodDef) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDescr_NewMember")] pub fn PyDescr_NewMember(arg1: *mut PyTypeObject, arg2: *mut PyMemberDef) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDescr_NewGetSet")] pub fn PyDescr_NewGetSet(arg1: *mut PyTypeObject, arg2: *mut PyGetSetDef) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDictProxy_New")] pub fn PyDictProxy_New(arg1: *mut PyObject) -> *mut PyObject; pub fn PyWrapper_New(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; } /// Represents the [PyMemberDef](https://docs.python.org/3/c-api/structures.html#c.PyMemberDef) /// structure. /// /// Note that CPython may leave fields uninitialized. You must always ensure that /// `name` != NULL before dereferencing or reading other fields. #[repr(C)] #[derive(Copy, Clone, Eq, PartialEq)] pub struct PyMemberDef { pub name: *const c_char, pub type_code: c_int, pub offset: Py_ssize_t, pub flags: c_int, pub doc: *const c_char, } impl Default for PyMemberDef { fn default() -> PyMemberDef { PyMemberDef { name: ptr::null_mut(), type_code: 0, offset: 0, flags: 0, doc: ptr::null_mut(), } } } /* Types */ pub const Py_T_SHORT: c_int = 0; pub const Py_T_INT: c_int = 1; pub const Py_T_LONG: c_int = 2; pub const Py_T_FLOAT: c_int = 3; pub const Py_T_DOUBLE: c_int = 4; pub const Py_T_STRING: c_int = 5; #[deprecated(note = "Use Py_T_OBJECT_EX instead")] pub const _Py_T_OBJECT: c_int = 6; pub const Py_T_CHAR: c_int = 7; pub const Py_T_BYTE: c_int = 8; pub const Py_T_UBYTE: c_int = 9; pub const Py_T_USHORT: c_int = 10; pub const Py_T_UINT: c_int = 11; pub const Py_T_ULONG: c_int = 12; pub const Py_T_STRING_INPLACE: c_int = 13; pub const Py_T_BOOL: c_int = 14; pub const Py_T_OBJECT_EX: c_int = 16; pub const Py_T_LONGLONG: c_int = 17; pub const Py_T_ULONGLONG: c_int = 18; pub const Py_T_PYSSIZET: c_int = 19; #[deprecated(note = "Value is always none")] pub const _Py_T_NONE: c_int = 20; /* Flags */ pub const Py_READONLY: c_int = 1; #[cfg(Py_3_10)] pub const Py_AUDIT_READ: c_int = 2; // Added in 3.10, harmless no-op before that #[deprecated] pub const _Py_WRITE_RESTRICTED: c_int = 4; // Deprecated, no-op. Do not reuse the value. pub const Py_RELATIVE_OFFSET: c_int = 8; extern_libpython! { pub fn PyMember_GetOne(addr: *const c_char, l: *mut PyMemberDef) -> *mut PyObject; pub fn PyMember_SetOne(addr: *mut c_char, l: *mut PyMemberDef, value: *mut PyObject) -> c_int; } ================================================ FILE: pyo3-ffi/src/dictobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyDict_Type")] pub static mut PyDict_Type: PyTypeObject; } #[inline] pub unsafe fn PyDict_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) } #[inline] pub unsafe fn PyDict_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyDict_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyDict_New")] pub fn PyDict_New() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_GetItem")] pub fn PyDict_GetItem(mp: *mut PyObject, key: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemWithError")] pub fn PyDict_GetItemWithError(mp: *mut PyObject, key: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_SetItem")] pub fn PyDict_SetItem(mp: *mut PyObject, key: *mut PyObject, item: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyDict_DelItem")] pub fn PyDict_DelItem(mp: *mut PyObject, key: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyDict_Clear")] pub fn PyDict_Clear(mp: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyDict_Next")] pub fn PyDict_Next( mp: *mut PyObject, pos: *mut Py_ssize_t, key: *mut *mut PyObject, value: *mut *mut PyObject, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyDict_Keys")] pub fn PyDict_Keys(mp: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_Values")] pub fn PyDict_Values(mp: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_Items")] pub fn PyDict_Items(mp: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_Size")] pub fn PyDict_Size(mp: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyDict_Copy")] pub fn PyDict_Copy(mp: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_Contains")] pub fn PyDict_Contains(mp: *mut PyObject, key: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyDict_Update")] pub fn PyDict_Update(mp: *mut PyObject, other: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyDict_Merge")] pub fn PyDict_Merge(mp: *mut PyObject, other: *mut PyObject, _override: c_int) -> c_int; pub fn PyDict_MergeFromSeq2(d: *mut PyObject, seq2: *mut PyObject, _override: c_int) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemString")] pub fn PyDict_GetItemString(dp: *mut PyObject, key: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyDict_SetItemString")] pub fn PyDict_SetItemString( dp: *mut PyObject, key: *const c_char, item: *mut PyObject, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyDict_DelItemString")] pub fn PyDict_DelItemString(dp: *mut PyObject, key: *const c_char) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemRef")] pub fn PyDict_GetItemRef( dp: *mut PyObject, key: *mut PyObject, result: *mut *mut PyObject, ) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyDict_GetItemStringRef")] pub fn PyDict_GetItemStringRef( dp: *mut PyObject, key: *const c_char, result: *mut *mut PyObject, ) -> c_int; // skipped 3.10 / ex-non-limited PyObject_GenericGetDict } extern_libpython! { pub static mut PyDictKeys_Type: PyTypeObject; pub static mut PyDictValues_Type: PyTypeObject; pub static mut PyDictItems_Type: PyTypeObject; } #[inline] pub unsafe fn PyDictKeys_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyDictKeys_Type) } #[inline] pub unsafe fn PyDictValues_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyDictValues_Type) } #[inline] pub unsafe fn PyDictItems_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyDictItems_Type) } #[inline] pub unsafe fn PyDictViewSet_Check(op: *mut PyObject) -> c_int { (PyDictKeys_Check(op) != 0 || PyDictItems_Check(op) != 0) as c_int } extern_libpython! { pub static mut PyDictIterKey_Type: PyTypeObject; pub static mut PyDictIterValue_Type: PyTypeObject; pub static mut PyDictIterItem_Type: PyTypeObject; #[cfg(Py_3_8)] pub static mut PyDictRevIterKey_Type: PyTypeObject; #[cfg(Py_3_8)] pub static mut PyDictRevIterValue_Type: PyTypeObject; #[cfg(Py_3_8)] pub static mut PyDictRevIterItem_Type: PyTypeObject; } #[cfg(any(GraalPy, Py_LIMITED_API))] // TODO: remove (see https://github.com/PyO3/pyo3/pull/1341#issuecomment-751515985) opaque_struct!(pub PyDictObject); ================================================ FILE: pyo3-ffi/src/enumobject.rs ================================================ use crate::object::PyTypeObject; extern_libpython! { pub static mut PyEnum_Type: PyTypeObject; pub static mut PyReversed_Type: PyTypeObject; } ================================================ FILE: pyo3-ffi/src/fileobject.rs ================================================ use crate::object::PyObject; use std::ffi::{c_char, c_int}; pub const PY_STDIOTEXTMODE: &str = "b"; extern_libpython! { pub fn PyFile_FromFd( arg1: c_int, arg2: *const c_char, arg3: *const c_char, arg4: c_int, arg5: *const c_char, arg6: *const c_char, arg7: *const c_char, arg8: c_int, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyFile_GetLine")] pub fn PyFile_GetLine(arg1: *mut PyObject, arg2: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyFile_WriteObject")] pub fn PyFile_WriteObject(arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyFile_WriteString")] pub fn PyFile_WriteString(arg1: *const c_char, arg2: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyFile_AsFileDescriptor")] pub fn PyObject_AsFileDescriptor(arg1: *mut PyObject) -> c_int; } extern_libpython! { #[deprecated(note = "Python 3.12")] pub static mut Py_FileSystemDefaultEncoding: *const c_char; #[deprecated(note = "Python 3.12")] pub static mut Py_FileSystemDefaultEncodeErrors: *const c_char; #[deprecated(note = "Python 3.12")] pub static mut Py_HasFileSystemDefaultEncoding: c_int; // skipped 3.12-deprecated Py_UTF8Mode } // skipped _PyIsSelectable_fd ================================================ FILE: pyo3-ffi/src/fileutils.rs ================================================ use crate::pyport::Py_ssize_t; use libc::wchar_t; use std::ffi::c_char; extern_libpython! { pub fn Py_DecodeLocale(arg1: *const c_char, size: *mut Py_ssize_t) -> *mut wchar_t; pub fn Py_EncodeLocale(text: *const wchar_t, error_pos: *mut Py_ssize_t) -> *mut c_char; } ================================================ FILE: pyo3-ffi/src/floatobject.rs ================================================ use crate::object::*; use std::ffi::{c_double, c_int}; #[cfg(Py_LIMITED_API)] // TODO: remove (see https://github.com/PyO3/pyo3/pull/1341#issuecomment-751515985) opaque_struct!(pub PyFloatObject); extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyFloat_Type")] pub static mut PyFloat_Type: PyTypeObject; } #[inline] pub unsafe fn PyFloat_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyFloat_Type) } #[inline] pub unsafe fn PyFloat_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyFloat_Type) as c_int } // skipped Py_RETURN_NAN // skipped Py_RETURN_INF extern_libpython! { pub fn PyFloat_GetMax() -> c_double; pub fn PyFloat_GetMin() -> c_double; pub fn PyFloat_GetInfo() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyFloat_FromString")] pub fn PyFloat_FromString(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyFloat_FromDouble")] pub fn PyFloat_FromDouble(arg1: c_double) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyFloat_AsDouble")] pub fn PyFloat_AsDouble(arg1: *mut PyObject) -> c_double; } // skipped non-limited _PyFloat_Pack2 // skipped non-limited _PyFloat_Pack4 // skipped non-limited _PyFloat_Pack8 // skipped non-limited _PyFloat_Unpack2 // skipped non-limited _PyFloat_Unpack4 // skipped non-limited _PyFloat_Unpack8 // skipped non-limited _PyFloat_DebugMallocStats // skipped non-limited _PyFloat_FormatAdvancedWriter ================================================ FILE: pyo3-ffi/src/genericaliasobject.rs ================================================ #[cfg(Py_3_9)] use crate::object::{PyObject, PyTypeObject}; extern_libpython! { #[cfg(Py_3_9)] #[cfg_attr(PyPy, link_name = "PyPy_GenericAlias")] pub fn Py_GenericAlias(origin: *mut PyObject, args: *mut PyObject) -> *mut PyObject; #[cfg(Py_3_9)] pub static mut Py_GenericAliasType: PyTypeObject; } ================================================ FILE: pyo3-ffi/src/impl_/macros.rs ================================================ // On x86 Windows, `raw-dylib` with `import_name_type = "undecorated"` removes the // leading cdecl underscore from function names. This is expected behavior for // `import_name_type = "undecorated"` (not a rustc bug): it strips the cdecl `_` // prefix, which collides with symbols whose real names start with `_Py`. // See https://doc.rust-lang.org/reference/items/external-blocks.html#the-import_name_type-key // // That matches ordinary `Py_*` exports, but it breaks CPython's internal `_Py*` // function exports whose real DLL names already start with an underscore. For // those functions, ask rustc for one extra underscore so that x86 undecoration // lands back on CPython's export. // // Variables are intentionally excluded here: `import_name_type` does not affect // variable imports, so `_Py_*` statics continue to work without any rewriting. #[allow(unused_macros, reason = "used indirectly by extern_libpython_item!")] macro_rules! extern_libpython_cpython_private_fn { ($(#[$attrs:meta])* $vis:vis $name:ident($($args:tt)*) $(-> $ret:ty)?) => { #[cfg_attr( all(windows, target_arch = "x86", not(any(PyPy, GraalPy))), link_name = concat!("_", stringify!($name)) )] $(#[$attrs])* $vis fn $name($($args)*) $(-> $ret)?; }; } // Keep this list in sync with `_Py*` function imports declared through // `extern_libpython!`. The x86 workaround only needs to apply to functions: // statics keep their original names even when `import_name_type` is set. Match // by name only here so the function signature stays in a single generic arm. // // TODO: reduce the number of `_Py*` exports from pyo3-ffi over time — the fewer // CPython-private functions we expose, the smaller this workaround list becomes. #[allow(unused_macros, reason = "used indirectly by extern_libpython_item!")] macro_rules! extern_libpython_maybe_private_fn { ( [_PyObject_CallFunction_SizeT] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_CallMethod_SizeT] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_MakeTpCall] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PySequence_IterSearch] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyStack_AsDict] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_CheckFunctionResult] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyBytes_Resize] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyEval_EvalFrameDefault] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyEval_RequestCodeExtraIndex] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyCode_GetExtra] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyCode_SetExtra] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyLong_AsByteArray] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyLong_FromByteArray] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_GC_Calloc] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_GC_Malloc] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_GetAllocatedBlocks] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyRun_AnyFileObject] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyRun_InteractiveLoopObject] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyRun_SimpleFileObject] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyUnicode_CheckConsistency] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyUnicode_Ready] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyLong_NumBits] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyThreadState_UncheckedGet] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_GC_New] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_GC_NewVar] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_GC_Resize] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_New] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyObject_NewVar] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PyErr_BadInternalCall] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_HashBytes] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_DECREF_DecRefTotal] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_Dealloc] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_DecRef] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_INCREF_IncRefTotal] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_IncRef] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_Py_NegativeRefcount] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [_PySet_NextEntry] $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)? ) => { extern_libpython_cpython_private_fn! { $(#[$attrs])* $vis $name($($args)*) $(-> $ret)? } }; ( [$name:ident] $(#[$attrs:meta])* $vis:vis fn $fn_name:ident($($args:tt)*) $(-> $ret:ty)? ) => { $(#[$attrs])* $vis fn $fn_name($($args)*) $(-> $ret)?; }; } macro_rules! extern_libpython_item { ($(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)?) => { extern_libpython_maybe_private_fn! { [$name] $(#[$attrs])* $vis fn $name($($args)*) $(-> $ret)? } }; ($(#[$attrs:meta])* $vis:vis static mut $name:ident: $ty:ty) => { $(#[$attrs])* $vis static mut $name: $ty; }; ($(#[$attrs:meta])* $vis:vis static $name:ident: $ty:ty) => { $(#[$attrs])* $vis static $name: $ty; }; } macro_rules! extern_libpython_items { () => {}; ( $(#[$attrs:meta])* $vis:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)?; $($rest:tt)* ) => { extern_libpython_item! { $(#[$attrs])* $vis fn $name($($args)*) $(-> $ret)? } extern_libpython_items! { $($rest)* } }; ( $(#[$attrs:meta])* $vis:vis static mut $name:ident: $ty:ty; $($rest:tt)* ) => { extern_libpython_item! { $(#[$attrs])* $vis static mut $name: $ty } extern_libpython_items! { $($rest)* } }; ( $(#[$attrs:meta])* $vis:vis static $name:ident: $ty:ty; $($rest:tt)* ) => { extern_libpython_item! { $(#[$attrs])* $vis static $name: $ty } extern_libpython_items! { $($rest)* } }; } /// Helper macro to declare `extern` blocks that link against libpython on Windows /// using `raw-dylib`, eliminating the need for import libraries. /// /// The build script sets a `pyo3_dll` cfg value to the target DLL name (e.g. `python312`), /// and this macro expands to the appropriate `#[link(name = "...", kind = "raw-dylib")]` /// attribute for that DLL. /// /// # Usage /// /// ```rust,ignore /// // Default ABI "C" (most common): /// extern_libpython! { /// pub fn PyObject_Call( /// callable: *mut PyObject, /// args: *mut PyObject, /// kwargs: *mut PyObject, /// ) -> *mut PyObject; /// } /// /// // Explicit ABI: /// extern_libpython! { "C-unwind" { /// pub fn PyGILState_Ensure() -> PyGILState_STATE; /// }} /// ``` macro_rules! extern_libpython { // Explicit ABI ($abi:literal { $($body:tt)* }) => { extern_libpython!(@impl $abi { $($body)* } // abi3 "python3", "python3_d", // Python 3.7 - 3.15 "python37", "python37_d", "python38", "python38_d", "python39", "python39_d", "python310", "python310_d", "python311", "python311_d", "python312", "python312_d", "python313", "python313_d", "python314", "python314_d", "python315", "python315_d", // free-threaded builds (3.13+) "python313t", "python313t_d", "python314t", "python314t_d", "python315t", "python315t_d", // PyPy (DLL is libpypy3.X-c.dll, not pythonXY.dll) "libpypy3.11-c", ); }; // Internal: generate cfg_attr for each DLL name. One of these will be selected // by `pyo3-ffi`'s `build.rs`. // // On x86 Windows, Python DLLs export undecorated symbol names (no leading // underscore), but the default for raw-dylib on x86 is fully-decorated // (cdecl adds a `_` prefix). We use `import_name_type = "undecorated"` to // match. The `import_name_type` key is only valid on x86, so we need // separate cfg_attr arms per architecture. (@impl $abi:literal { $($body:tt)* } $($dll:literal),* $(,)?) => { $( #[cfg_attr(all(windows, target_arch = "x86", pyo3_dll = $dll), link(name = $dll, kind = "raw-dylib", import_name_type = "undecorated"))] #[cfg_attr(all(windows, not(target_arch = "x86"), pyo3_dll = $dll), link(name = $dll, kind = "raw-dylib"))] )* extern $abi { extern_libpython_items! { $($body)* } } }; // Default ABI: "C" ($($body:tt)*) => { extern_libpython!("C" { $($body)* }); }; } ================================================ FILE: pyo3-ffi/src/impl_/mod.rs ================================================ #[cfg(Py_GIL_DISABLED)] mod atomic_c_ulong { pub struct GetAtomicCULong(); pub trait AtomicCULongType { type Type; } impl AtomicCULongType for GetAtomicCULong<32> { type Type = std::sync::atomic::AtomicU32; } impl AtomicCULongType for GetAtomicCULong<64> { type Type = std::sync::atomic::AtomicU64; } pub type TYPE = () * 8 }> as AtomicCULongType>::Type; } /// Typedef for an atomic integer to match the platform-dependent c_ulong type. #[cfg(Py_GIL_DISABLED)] #[doc(hidden)] pub type AtomicCULong = atomic_c_ulong::TYPE; ================================================ FILE: pyo3-ffi/src/import.rs ================================================ use crate::object::PyObject; use std::ffi::{c_char, c_int, c_long}; extern_libpython! { pub fn PyImport_GetMagicNumber() -> c_long; pub fn PyImport_GetMagicTag() -> *const c_char; #[cfg_attr(PyPy, link_name = "PyPyImport_ExecCodeModule")] pub fn PyImport_ExecCodeModule(name: *const c_char, co: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_ExecCodeModuleEx")] pub fn PyImport_ExecCodeModuleEx( name: *const c_char, co: *mut PyObject, pathname: *const c_char, ) -> *mut PyObject; pub fn PyImport_ExecCodeModuleWithPathnames( name: *const c_char, co: *mut PyObject, pathname: *const c_char, cpathname: *const c_char, ) -> *mut PyObject; pub fn PyImport_ExecCodeModuleObject( name: *mut PyObject, co: *mut PyObject, pathname: *mut PyObject, cpathname: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_GetModuleDict")] pub fn PyImport_GetModuleDict() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_GetModule")] pub fn PyImport_GetModule(name: *mut PyObject) -> *mut PyObject; pub fn PyImport_AddModuleObject(name: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_AddModule")] pub fn PyImport_AddModule(name: *const c_char) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyImport_AddModuleRef")] pub fn PyImport_AddModuleRef(name: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModule")] pub fn PyImport_ImportModule(name: *const c_char) -> *mut PyObject; #[deprecated(note = "Python 3.13")] #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleNoBlock")] pub fn PyImport_ImportModuleNoBlock(name: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleLevel")] pub fn PyImport_ImportModuleLevel( name: *const c_char, globals: *mut PyObject, locals: *mut PyObject, fromlist: *mut PyObject, level: c_int, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_ImportModuleLevelObject")] pub fn PyImport_ImportModuleLevelObject( name: *mut PyObject, globals: *mut PyObject, locals: *mut PyObject, fromlist: *mut PyObject, level: c_int, ) -> *mut PyObject; } #[inline] pub unsafe fn PyImport_ImportModuleEx( name: *const c_char, globals: *mut PyObject, locals: *mut PyObject, fromlist: *mut PyObject, ) -> *mut PyObject { PyImport_ImportModuleLevel(name, globals, locals, fromlist, 0) } extern_libpython! { pub fn PyImport_GetImporter(path: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_Import")] pub fn PyImport_Import(name: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyImport_ReloadModule")] pub fn PyImport_ReloadModule(m: *mut PyObject) -> *mut PyObject; #[cfg(not(Py_3_9))] #[deprecated(note = "Removed in Python 3.9 as it was \"For internal use only\".")] pub fn PyImport_Cleanup(); pub fn PyImport_ImportFrozenModuleObject(name: *mut PyObject) -> c_int; pub fn PyImport_ImportFrozenModule(name: *const c_char) -> c_int; pub fn PyImport_AppendInittab( name: *const c_char, initfunc: Option *mut PyObject>, ) -> c_int; } ================================================ FILE: pyo3-ffi/src/intrcheck.rs ================================================ use std::ffi::c_int; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyOS_InterruptOccurred")] pub fn PyOS_InterruptOccurred() -> c_int; #[cfg(not(Py_3_10))] #[deprecated(note = "Not documented in Python API; see Python 3.10 release notes")] pub fn PyOS_InitInterrupts(); pub fn PyOS_BeforeFork(); pub fn PyOS_AfterFork_Parent(); pub fn PyOS_AfterFork_Child(); #[deprecated(note = "use PyOS_AfterFork_Child instead")] #[cfg_attr(PyPy, link_name = "PyPyOS_AfterFork")] pub fn PyOS_AfterFork(); // skipped non-limited _PyOS_IsMainThread // skipped non-limited Windows _PyOS_SigintEvent } ================================================ FILE: pyo3-ffi/src/iterobject.rs ================================================ use crate::object::*; use std::ffi::c_int; extern_libpython! { pub static mut PySeqIter_Type: PyTypeObject; pub static mut PyCallIter_Type: PyTypeObject; } #[inline] pub unsafe fn PySeqIter_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PySeqIter_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySeqIter_New")] pub fn PySeqIter_New(arg1: *mut PyObject) -> *mut PyObject; } #[inline] pub unsafe fn PyCallIter_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyCallIter_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyCallIter_New")] pub fn PyCallIter_New(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/lib.rs ================================================ #![cfg_attr(docsrs, feature(doc_cfg))] //! Raw FFI declarations for Python's C API. //! //! PyO3 can be used to write native Python modules or run Python code and modules from Rust. //! //! This crate just provides low level bindings to the Python interpreter. //! It is meant for advanced users only - regular PyO3 users shouldn't //! need to interact with this crate at all. //! //! The contents of this crate are not documented here, as it would entail //! basically copying the documentation from CPython. Consult the [Python/C API Reference //! Manual][capi] for up-to-date documentation. //! //! # Safety //! //! The functions in this crate lack individual safety documentation, but //! generally the following apply: //! - Pointer arguments have to point to a valid Python object of the correct type, //! although null pointers are sometimes valid input. //! - The vast majority can only be used safely while the thread is attached to the Python interpreter. //! - Some functions have additional safety requirements, consult the //! [Python/C API Reference Manual][capi] //! for more information. //! //! //! # Feature flags //! //! PyO3 uses [feature flags] to enable you to opt-in to additional functionality. For a detailed //! description, see the [Features chapter of the guide]. //! //! ## Optional feature flags //! //! The following features customize PyO3's behavior: //! //! - `abi3`: Restricts PyO3's API to a subset of the full Python API which is guaranteed by //! [PEP 384] to be forward-compatible with future Python versions. //! //! ## `rustc` environment flags //! //! PyO3 uses `rustc`'s `--cfg` flags to enable or disable code used for different Python versions. //! If you want to do this for your own crate, you can do so with the [`pyo3-build-config`] crate. //! //! - `Py_3_7`, `Py_3_8`, `Py_3_9`, `Py_3_10`, `Py_3_11`, `Py_3_12`, `Py_3_13`: Marks code that is //! only enabled when compiling for a given minimum Python version. //! - `Py_LIMITED_API`: Marks code enabled when the `abi3` feature flag is enabled. //! - `Py_GIL_DISABLED`: Marks code that runs only in the free-threaded build of CPython. //! - `PyPy` - Marks code enabled when compiling for PyPy. //! - `GraalPy` - Marks code enabled when compiling for GraalPy. //! //! Additionally, you can query for the values `Py_DEBUG`, `Py_REF_DEBUG`, //! `Py_TRACE_REFS`, and `COUNT_ALLOCS` from `py_sys_config` to query for the //! corresponding C build-time defines. For example, to conditionally define //! debug code using `Py_DEBUG`, you could do: //! //! ```rust,ignore //! #[cfg(py_sys_config = "Py_DEBUG")] //! println!("only runs if python was compiled with Py_DEBUG") //! ``` //! //! To use these attributes, add [`pyo3-build-config`] as a build dependency in //! your `Cargo.toml`: //! //! ```toml //! [build-dependencies] #![doc = concat!("pyo3-build-config =\"", env!("CARGO_PKG_VERSION"), "\"")] //! ``` //! //! And then either create a new `build.rs` file in the project root or modify //! the existing `build.rs` file to call `use_pyo3_cfgs()`: //! //! ```rust,ignore //! fn main() { //! pyo3_build_config::use_pyo3_cfgs(); //! } //! ``` //! //! # Minimum supported Rust and Python versions //! //! `pyo3-ffi` supports the following Python distributions: //! - CPython 3.7 or greater //! - PyPy 7.3 (Python 3.11+) //! - GraalPy 24.0 or greater (Python 3.10+) //! //! # Example: Building Python Native modules //! //! PyO3 can be used to generate a native Python module. The easiest way to try this out for the //! first time is to use [`maturin`]. `maturin` is a tool for building and publishing Rust-based //! Python packages with minimal configuration. The following steps set up some files for an example //! Python module, install `maturin`, and then show how to build and import the Python module. //! //! First, create a new folder (let's call it `string_sum`) containing the following two files: //! //! **`Cargo.toml`** //! //! ```toml //! [lib] //! name = "string_sum" //! # "cdylib" is necessary to produce a shared library for Python to import from. //! # //! # Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able //! # to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.: //! # crate-type = ["cdylib", "rlib"] //! crate-type = ["cdylib"] //! //! [dependencies] #![doc = concat!("pyo3-ffi = \"", env!("CARGO_PKG_VERSION"), "\"")] //! //! [build-dependencies] //! # This is only necessary if you need to configure your build based on //! # the Python version or the compile-time configuration for the interpreter. #![doc = concat!("pyo3_build_config = \"", env!("CARGO_PKG_VERSION"), "\"")] //! ``` //! //! If you need to use conditional compilation based on Python version or how //! Python was compiled, you need to add `pyo3-build-config` as a //! `build-dependency` in your `Cargo.toml` as in the example above and either //! create a new `build.rs` file or modify an existing one so that //! `pyo3_build_config::use_pyo3_cfgs()` gets called at build time: //! //! **`build.rs`** //! ```rust,ignore //! fn main() { //! pyo3_build_config::use_pyo3_cfgs() //! } //! ``` //! //! **`src/lib.rs`** //! ```rust,no_run //! #[cfg(Py_3_15)] //! use std::ffi::c_void; //! use std::ffi::{c_char, c_long}; //! use std::ptr; //! //! use pyo3_ffi::*; //! //! #[cfg(not(Py_3_15))] //! static mut MODULE_DEF: PyModuleDef = PyModuleDef { //! m_base: PyModuleDef_HEAD_INIT, //! m_name: c"string_sum".as_ptr(), //! m_doc: c"A Python module written in Rust.".as_ptr(), //! m_size: 0, //! m_methods: (&raw mut METHODS).cast(), //! m_slots: (&raw mut SLOTS).cast(), //! m_traverse: None, //! m_clear: None, //! m_free: None, //! }; //! //! static mut METHODS: [PyMethodDef; 2] = [ //! PyMethodDef { //! ml_name: c"sum_as_string".as_ptr(), //! ml_meth: PyMethodDefPointer { //! PyCFunctionFast: sum_as_string, //! }, //! ml_flags: METH_FASTCALL, //! ml_doc: c"returns the sum of two integers as a string".as_ptr(), //! }, //! // A zeroed PyMethodDef to mark the end of the array. //! PyMethodDef::zeroed(), //! ]; //! //! #[cfg(Py_3_15)] //! PyABIInfo_VAR!(ABI_INFO); //! //! const SLOTS_LEN: usize = //! 1 + cfg!(Py_3_12) as usize + cfg!(Py_GIL_DISABLED) as usize + 4 * (cfg!(Py_3_15) as usize); //! static mut SLOTS: [PyModuleDef_Slot; SLOTS_LEN] = [ //! #[cfg(Py_3_15)] //! PyModuleDef_Slot { //! slot: Py_mod_abi, //! value: (&raw mut ABI_INFO).cast(), //! }, //! #[cfg(Py_3_15)] //! PyModuleDef_Slot { //! slot: Py_mod_name, //! // safety: Python does not write to this field //! value: c"string_sum".as_ptr() as *mut c_void, //! }, //! #[cfg(Py_3_15)] //! PyModuleDef_Slot { //! slot: Py_mod_doc, //! // safety: Python does not write to this field //! value: c"A Python module written in Rust.".as_ptr() as *mut c_void, //! }, //! #[cfg(Py_3_15)] //! PyModuleDef_Slot { //! slot: Py_mod_methods, //! value: (&raw mut METHODS).cast(), //! }, //! #[cfg(Py_3_12)] //! PyModuleDef_Slot { //! slot: Py_mod_multiple_interpreters, //! value: Py_MOD_PER_INTERPRETER_GIL_SUPPORTED, //! }, //! #[cfg(Py_GIL_DISABLED)] //! PyModuleDef_Slot { //! slot: Py_mod_gil, //! value: Py_MOD_GIL_NOT_USED, //! }, //! PyModuleDef_Slot { //! slot: 0, //! value: ptr::null_mut(), //! }, //! ]; //! //! // The module initialization function //! #[cfg(not(Py_3_15))] //! #[allow(non_snake_case, reason = "must be named `PyInit_`")] //! #[no_mangle] //! pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject { //! PyModuleDef_Init(&raw mut MODULE_DEF) //! } //! //! #[cfg(Py_3_15)] //! #[allow(non_snake_case, reason = "must be named `PyModExport_`")] //! #[no_mangle] //! pub unsafe extern "C" fn PyModExport_string_sum() -> *mut PyModuleDef_Slot { //! (&raw mut SLOTS).cast() //! } //! //! /// A helper to parse function arguments //! /// If we used PyO3's proc macros they'd handle all of this boilerplate for us :) //! unsafe fn parse_arg_as_i32(obj: *mut PyObject, n_arg: usize) -> Option { //! if PyLong_Check(obj) == 0 { //! let msg = format!( //! "sum_as_string expected an int for positional argument {}\0", //! n_arg //! ); //! PyErr_SetString(PyExc_TypeError, msg.as_ptr().cast::()); //! return None; //! } //! //! // Let's keep the behaviour consistent on platforms where `c_long` is bigger than 32 bits. //! // In particular, it is an i32 on Windows but i64 on most Linux systems //! let mut overflow = 0; //! let i_long: c_long = PyLong_AsLongAndOverflow(obj, &mut overflow); //! //! #[allow( //! irrefutable_let_patterns, //! reason = "some platforms have c_long equal to i32" //! )] //! if overflow != 0 { //! raise_overflowerror(obj); //! None //! } else if let Ok(i) = i_long.try_into() { //! Some(i) //! } else { //! raise_overflowerror(obj); //! None //! } //! } //! //! unsafe fn raise_overflowerror(obj: *mut PyObject) { //! let obj_repr = PyObject_Str(obj); //! if !obj_repr.is_null() { //! let mut size = 0; //! let p = PyUnicode_AsUTF8AndSize(obj_repr, &mut size); //! if !p.is_null() { //! let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts( //! p.cast::(), //! size as usize, //! )); //! let msg = format!("cannot fit {} in 32 bits\0", s); //! //! PyErr_SetString(PyExc_OverflowError, msg.as_ptr().cast::()); //! } //! Py_DECREF(obj_repr); //! } //! } //! //! pub unsafe extern "C" fn sum_as_string( //! _self: *mut PyObject, //! args: *mut *mut PyObject, //! nargs: Py_ssize_t, //! ) -> *mut PyObject { //! if nargs != 2 { //! PyErr_SetString( //! PyExc_TypeError, //! c"sum_as_string expected 2 positional arguments".as_ptr(), //! ); //! return std::ptr::null_mut(); //! } //! //! let (first, second) = (*args, *args.add(1)); //! //! let first = match parse_arg_as_i32(first, 1) { //! Some(x) => x, //! None => return std::ptr::null_mut(), //! }; //! let second = match parse_arg_as_i32(second, 2) { //! Some(x) => x, //! None => return std::ptr::null_mut(), //! }; //! //! match first.checked_add(second) { //! Some(sum) => { //! let string = sum.to_string(); //! PyUnicode_FromStringAndSize(string.as_ptr().cast::(), string.len() as isize) //! } //! None => { //! PyErr_SetString(PyExc_OverflowError, c"arguments too large to add".as_ptr()); //! std::ptr::null_mut() //! } //! } //! } //! ``` //! //! With those two files in place, now `maturin` needs to be installed. This can be done using //! Python's package manager `pip`. First, load up a new Python `virtualenv`, and install `maturin` //! into it: //! ```bash //! $ cd string_sum //! $ python -m venv .env //! $ source .env/bin/activate //! $ pip install maturin //! ``` //! //! Now build and execute the module: //! ```bash //! $ maturin develop //! # lots of progress output as maturin runs the compilation... //! $ python //! >>> import string_sum //! >>> string_sum.sum_as_string(5, 20) //! '25' //! ``` //! //! As well as with `maturin`, it is possible to build using [setuptools-rust] or //! [manually][manual_builds]. Both offer more flexibility than `maturin` but require further //! configuration. //! //! This example stores the module definition statically and uses the `PyModule_Create` function //! in the CPython C API to register the module. This is the "old" style for registering modules //! and has the limitation that it cannot support subinterpreters. You can also create a module //! using the new multi-phase initialization API that does support subinterpreters. See the //! `sequential` project located in the `examples` directory at the root of the `pyo3-ffi` crate //! for a worked example of how to this using `pyo3-ffi`. //! //! # Using Python from Rust //! //! To embed Python into a Rust binary, you need to ensure that your Python installation contains a //! shared library. The following steps demonstrate how to ensure this (for Ubuntu). //! //! To install the Python shared library on Ubuntu: //! ```bash //! sudo apt install python3-dev //! ``` //! //! While most projects use the safe wrapper provided by pyo3, //! you can take a look at the [`orjson`] library as an example on how to use `pyo3-ffi` directly. //! For those well versed in C and Rust the [tutorials] from the CPython documentation //! can be easily converted to rust as well. //! //! [tutorials]: https://docs.python.org/3/extending/ //! [`orjson`]: https://github.com/ijl/orjson //! [capi]: https://docs.python.org/3/c-api/index.html //! [`maturin`]: https://github.com/PyO3/maturin "Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages" //! [`pyo3-build-config`]: https://docs.rs/pyo3-build-config //! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book" #![doc = concat!("[manual_builds]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/building-and-distribution.html#manual-builds \"Manual builds - Building and Distribution - PyO3 user guide\"")] //! [setuptools-rust]: https://github.com/PyO3/setuptools-rust "Setuptools plugin for Rust extensions" //! [PEP 384]: https://www.python.org/dev/peps/pep-0384 "PEP 384 -- Defining a Stable ABI" #![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features reference - PyO3 user guide\"")] #![allow( missing_docs, non_camel_case_types, non_snake_case, non_upper_case_globals, clippy::upper_case_acronyms, clippy::missing_safety_doc, clippy::ptr_eq )] #![warn(elided_lifetimes_in_paths, unused_lifetimes)] // This crate is a hand-maintained translation of CPython's headers, so requiring "unsafe" // blocks within those translations increases maintenance burden without providing any // additional safety. The safety of the functions in this crate is determined by the // original CPython headers #![allow(unsafe_op_in_unsafe_fn)] // Until `extern type` is stabilized, use the recommended approach to // model opaque types: // https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs macro_rules! opaque_struct { ($(#[$attrs:meta])* $pub:vis $name:ident) => { $(#[$attrs])* #[repr(C)] $pub struct $name([u8; 0]); }; } /// This is a helper macro to create a `&'static CStr`. /// /// It can be used on all Rust versions supported by PyO3, unlike c"" literals which /// were stabilised in Rust 1.77. /// /// Due to the nature of PyO3 making heavy use of C FFI interop with Python, it is /// common for PyO3 to use CStr. /// /// Examples: /// /// ```rust,no_run /// use std::ffi::CStr; /// /// const HELLO: &CStr = pyo3_ffi::c_str!("hello"); /// static WORLD: &CStr = pyo3_ffi::c_str!("world"); /// ``` #[macro_export] macro_rules! c_str { // TODO: deprecate this now MSRV is above 1.77 ($s:expr) => { $crate::_cstr_from_utf8_with_nul_checked(concat!($s, "\0")) }; } /// Private helper for `c_str!` macro. #[doc(hidden)] pub const fn _cstr_from_utf8_with_nul_checked(s: &str) -> &std::ffi::CStr { match std::ffi::CStr::from_bytes_with_nul(s.as_bytes()) { Ok(cstr) => cstr, Err(_) => panic!("string contains nul bytes"), } } // Macros for declaring `extern` blocks that link against libpython. // See `impl_/macros.rs` for the implementation. include!("impl_/macros.rs"); pub mod compat; mod impl_; pub use self::abstract_::*; pub use self::bltinmodule::*; pub use self::boolobject::*; pub use self::bytearrayobject::*; pub use self::bytesobject::*; pub use self::ceval::*; pub use self::codecs::*; pub use self::compile::*; pub use self::complexobject::*; #[cfg(all(Py_3_8, not(Py_LIMITED_API)))] pub use self::context::*; #[cfg(not(Py_LIMITED_API))] pub use self::datetime::*; pub use self::descrobject::*; pub use self::dictobject::*; pub use self::enumobject::*; pub use self::fileobject::*; pub use self::fileutils::*; pub use self::floatobject::*; #[cfg(Py_3_9)] pub use self::genericaliasobject::*; pub use self::import::*; pub use self::intrcheck::*; pub use self::iterobject::*; pub use self::listobject::*; pub use self::longobject::*; #[cfg(not(Py_LIMITED_API))] pub use self::marshal::*; pub use self::memoryobject::*; pub use self::methodobject::*; pub use self::modsupport::*; pub use self::moduleobject::*; pub use self::object::*; pub use self::objimpl::*; pub use self::osmodule::*; #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] pub use self::pyarena::*; #[cfg(Py_3_11)] pub use self::pybuffer::*; pub use self::pycapsule::*; pub use self::pyerrors::*; pub use self::pyframe::*; pub use self::pyhash::*; pub use self::pylifecycle::*; pub use self::pymem::*; pub use self::pyport::*; pub use self::pystate::*; pub use self::pystrtod::*; pub use self::pythonrun::*; pub use self::pytypedefs::*; pub use self::rangeobject::*; pub use self::refcount::*; pub use self::setobject::*; pub use self::sliceobject::*; pub use self::structseq::*; pub use self::sysmodule::*; pub use self::traceback::*; pub use self::tupleobject::*; pub use self::typeslots::*; pub use self::unicodeobject::*; pub use self::warnings::*; pub use self::weakrefobject::*; mod abstract_; // skipped asdl.h // skipped ast.h mod bltinmodule; mod boolobject; mod bytearrayobject; mod bytesobject; // skipped cellobject.h mod ceval; // skipped classobject.h mod codecs; mod compile; mod complexobject; #[cfg(all(Py_3_8, not(Py_LIMITED_API)))] mod context; // It's actually 3.7.1, but no cfg for patches. #[cfg(not(Py_LIMITED_API))] pub(crate) mod datetime; mod descrobject; mod dictobject; // skipped dynamic_annotations.h mod enumobject; // skipped errcode.h // skipped exports.h mod fileobject; mod fileutils; mod floatobject; // skipped empty frameobject.h mod genericaliasobject; mod import; // skipped interpreteridobject.h mod intrcheck; mod iterobject; mod listobject; // skipped longintrepr.h mod longobject; #[cfg(not(Py_LIMITED_API))] pub mod marshal; mod memoryobject; mod methodobject; mod modsupport; mod moduleobject; // skipped namespaceobject.h mod object; mod objimpl; // skipped odictobject.h // skipped opcode.h // skipped osdefs.h mod osmodule; // skipped parser_interface.h // skipped patchlevel.h // skipped picklebufobject.h // skipped pyctype.h // skipped py_curses.h #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] mod pyarena; #[cfg(Py_3_11)] mod pybuffer; mod pycapsule; // skipped pydtrace.h mod pyerrors; // skipped pyexpat.h // skipped pyfpe.h mod pyframe; mod pyhash; mod pylifecycle; // skipped pymacconfig.h // skipped pymacro.h // skipped pymath.h mod pymem; mod pyport; mod pystate; // skipped pystats.h mod pythonrun; // skipped pystrhex.h // skipped pystrcmp.h mod pystrtod; // skipped pythread.h // skipped pytime.h mod pytypedefs; mod rangeobject; mod refcount; mod setobject; mod sliceobject; mod structseq; mod sysmodule; mod traceback; // skipped tracemalloc.h mod tupleobject; mod typeslots; mod unicodeobject; mod warnings; mod weakrefobject; // Additional headers that are not exported by Python.h #[deprecated(note = "Python 3.12")] pub mod structmember; // "Limited API" definitions matching Python's `include/cpython` directory. #[cfg(not(Py_LIMITED_API))] mod cpython; #[cfg(not(Py_LIMITED_API))] pub use self::cpython::*; ================================================ FILE: pyo3-ffi/src/listobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::c_int; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyList_Type")] pub static mut PyList_Type: PyTypeObject; pub static mut PyListIter_Type: PyTypeObject; pub static mut PyListRevIter_Type: PyTypeObject; } #[inline] pub unsafe fn PyList_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS) } #[inline] pub unsafe fn PyList_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyList_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyList_New")] pub fn PyList_New(size: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyList_Size")] pub fn PyList_Size(arg1: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyList_GetItem")] pub fn PyList_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyList_GetItemRef")] pub fn PyList_GetItemRef(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyList_SetItem")] pub fn PyList_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyList_Insert")] pub fn PyList_Insert(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyList_Append")] pub fn PyList_Append(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyList_GetSlice")] pub fn PyList_GetSlice( arg1: *mut PyObject, arg2: Py_ssize_t, arg3: Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyList_SetSlice")] pub fn PyList_SetSlice( arg1: *mut PyObject, arg2: Py_ssize_t, arg3: Py_ssize_t, arg4: *mut PyObject, ) -> c_int; #[cfg(Py_3_13)] pub fn PyList_Extend(list: *mut PyObject, iterable: *mut PyObject) -> c_int; #[cfg(Py_3_13)] pub fn PyList_Clear(list: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyList_Sort")] pub fn PyList_Sort(arg1: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyList_Reverse")] pub fn PyList_Reverse(arg1: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyList_AsTuple")] pub fn PyList_AsTuple(arg1: *mut PyObject) -> *mut PyObject; // CPython macros exported as functions on PyPy or GraalPy #[cfg(any(PyPy, GraalPy))] #[cfg_attr(PyPy, link_name = "PyPyList_GET_ITEM")] #[cfg_attr(GraalPy, link_name = "PyList_GetItem")] pub fn PyList_GET_ITEM(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; #[cfg(PyPy)] #[cfg_attr(PyPy, link_name = "PyPyList_GET_SIZE")] pub fn PyList_GET_SIZE(arg1: *mut PyObject) -> Py_ssize_t; #[cfg(any(PyPy, GraalPy))] #[cfg_attr(PyPy, link_name = "PyPyList_SET_ITEM")] #[cfg_attr(GraalPy, link_name = "_PyList_SET_ITEM")] pub fn PyList_SET_ITEM(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject); } ================================================ FILE: pyo3-ffi/src/longobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use libc::size_t; use std::ffi::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; opaque_struct!(pub PyLongObject); #[inline] pub unsafe fn PyLong_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LONG_SUBCLASS) } #[inline] pub unsafe fn PyLong_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyLong_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyLong_FromLong")] pub fn PyLong_FromLong(arg1: c_long) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_FromUnsignedLong")] pub fn PyLong_FromUnsignedLong(arg1: c_ulong) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_FromSize_t")] pub fn PyLong_FromSize_t(arg1: size_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_FromSsize_t")] pub fn PyLong_FromSsize_t(arg1: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_FromDouble")] pub fn PyLong_FromDouble(arg1: c_double) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_AsLong")] pub fn PyLong_AsLong(arg1: *mut PyObject) -> c_long; #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongAndOverflow")] pub fn PyLong_AsLongAndOverflow(arg1: *mut PyObject, arg2: *mut c_int) -> c_long; #[cfg_attr(PyPy, link_name = "PyPyLong_AsSsize_t")] pub fn PyLong_AsSsize_t(arg1: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyLong_AsSize_t")] pub fn PyLong_AsSize_t(arg1: *mut PyObject) -> size_t; #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLong")] pub fn PyLong_AsUnsignedLong(arg1: *mut PyObject) -> c_ulong; #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongMask")] pub fn PyLong_AsUnsignedLongMask(arg1: *mut PyObject) -> c_ulong; // skipped non-limited _PyLong_AsInt pub fn PyLong_GetInfo() -> *mut PyObject; // skipped PyLong_AS_LONG // skipped PyLong_FromPid // skipped PyLong_AsPid // skipped _Py_PARSE_INTPTR // skipped _Py_PARSE_UINTPTR // skipped non-limited _PyLong_UnsignedShort_Converter // skipped non-limited _PyLong_UnsignedInt_Converter // skipped non-limited _PyLong_UnsignedLong_Converter // skipped non-limited _PyLong_UnsignedLongLong_Converter // skipped non-limited _PyLong_Size_t_Converter // skipped non-limited _PyLong_DigitValue // skipped non-limited _PyLong_Frexp #[cfg_attr(PyPy, link_name = "PyPyLong_AsDouble")] pub fn PyLong_AsDouble(arg1: *mut PyObject) -> c_double; #[cfg_attr(PyPy, link_name = "PyPyLong_FromVoidPtr")] pub fn PyLong_FromVoidPtr(arg1: *mut c_void) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_AsVoidPtr")] pub fn PyLong_AsVoidPtr(arg1: *mut PyObject) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyLong_FromLongLong")] pub fn PyLong_FromLongLong(arg1: c_longlong) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_FromUnsignedLongLong")] pub fn PyLong_FromUnsignedLongLong(arg1: c_ulonglong) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongLong")] pub fn PyLong_AsLongLong(arg1: *mut PyObject) -> c_longlong; #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongLong")] pub fn PyLong_AsUnsignedLongLong(arg1: *mut PyObject) -> c_ulonglong; #[cfg_attr(PyPy, link_name = "PyPyLong_AsUnsignedLongLongMask")] pub fn PyLong_AsUnsignedLongLongMask(arg1: *mut PyObject) -> c_ulonglong; #[cfg_attr(PyPy, link_name = "PyPyLong_AsLongLongAndOverflow")] pub fn PyLong_AsLongLongAndOverflow(arg1: *mut PyObject, arg2: *mut c_int) -> c_longlong; #[cfg_attr(PyPy, link_name = "PyPyLong_FromString")] pub fn PyLong_FromString( arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int, ) -> *mut PyObject; } #[cfg(not(Py_LIMITED_API))] extern_libpython! { #[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")] pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t; } // skipped non-limited _PyLong_Format // skipped non-limited _PyLong_FormatWriter // skipped non-limited _PyLong_FormatBytesWriter // skipped non-limited _PyLong_FormatAdvancedWriter extern_libpython! { pub fn PyOS_strtoul(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_ulong; pub fn PyOS_strtol(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_long; } // skipped non-limited _PyLong_Rshift // skipped non-limited _PyLong_Lshift ================================================ FILE: pyo3-ffi/src/marshal.rs ================================================ use super::{PyObject, Py_ssize_t}; use std::ffi::{c_char, c_int}; // skipped Py_MARSHAL_VERSION // skipped PyMarshal_WriteLongToFile // skipped PyMarshal_WriteObjectToFile extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyMarshal_WriteObjectToString")] pub fn PyMarshal_WriteObjectToString(object: *mut PyObject, version: c_int) -> *mut PyObject; // skipped non-limited PyMarshal_ReadLongFromFile // skipped non-limited PyMarshal_ReadShortFromFile // skipped non-limited PyMarshal_ReadObjectFromFile // skipped non-limited PyMarshal_ReadLastObjectFromFile #[cfg_attr(PyPy, link_name = "PyPyMarshal_ReadObjectFromString")] pub fn PyMarshal_ReadObjectFromString(data: *const c_char, len: Py_ssize_t) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/memoryobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int}; // skipped _PyManagedBuffer_Type extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyMemoryView_Type")] pub static mut PyMemoryView_Type: PyTypeObject; } #[inline] pub unsafe fn PyMemoryView_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyMemoryView_Type) as c_int } // skipped non-limited PyMemoryView_GET_BUFFER // skipped non-limited PyMemoryView_GET_BASE extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromObject")] pub fn PyMemoryView_FromObject(base: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromMemory")] pub fn PyMemoryView_FromMemory( mem: *mut c_char, size: Py_ssize_t, flags: c_int, ) -> *mut PyObject; #[cfg(any(Py_3_11, not(Py_LIMITED_API)))] #[cfg_attr(PyPy, link_name = "PyPyMemoryView_FromBuffer")] pub fn PyMemoryView_FromBuffer(view: *const crate::Py_buffer) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyMemoryView_GetContiguous")] pub fn PyMemoryView_GetContiguous( base: *mut PyObject, buffertype: c_int, order: c_char, ) -> *mut PyObject; } // skipped remainder of file with comment: /* The structs are declared here so that macros can work, but they shouldn't be considered public. Don't access their fields directly, use the macros and functions instead! */ ================================================ FILE: pyo3-ffi/src/methodobject.rs ================================================ use crate::object::{PyObject, PyTypeObject, Py_TYPE}; #[cfg(Py_3_9)] use crate::PyObject_TypeCheck; use std::ffi::{c_char, c_int, c_void}; use std::{mem, ptr}; #[cfg(all(Py_3_9, not(Py_LIMITED_API), not(GraalPy)))] pub struct PyCFunctionObject { pub ob_base: PyObject, pub m_ml: *mut PyMethodDef, pub m_self: *mut PyObject, pub m_module: *mut PyObject, pub m_weakreflist: *mut PyObject, #[cfg(not(PyPy))] pub vectorcall: Option, } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyCFunction_Type")] pub static mut PyCFunction_Type: PyTypeObject; } #[cfg(Py_3_9)] #[inline] pub unsafe fn PyCFunction_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyCFunction_Type) as c_int } #[cfg(Py_3_9)] #[inline] pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyCFunction_Type) } #[cfg(not(Py_3_9))] #[inline] pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyCFunction_Type) as c_int } pub type PyCFunction = unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] pub type PyCFunctionFast = unsafe extern "C" fn( slf: *mut PyObject, args: *mut *mut PyObject, nargs: crate::pyport::Py_ssize_t, ) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[deprecated(note = "renamed to `PyCFunctionFast`")] pub type _PyCFunctionFast = PyCFunctionFast; pub type PyCFunctionWithKeywords = unsafe extern "C" fn( slf: *mut PyObject, args: *mut PyObject, kwds: *mut PyObject, ) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] pub type PyCFunctionFastWithKeywords = unsafe extern "C" fn( slf: *mut PyObject, args: *const *mut PyObject, nargs: crate::pyport::Py_ssize_t, kwnames: *mut PyObject, ) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")] pub type _PyCFunctionFastWithKeywords = PyCFunctionFastWithKeywords; #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] pub type PyCMethod = unsafe extern "C" fn( slf: *mut PyObject, defining_class: *mut PyTypeObject, args: *const *mut PyObject, nargs: crate::pyport::Py_ssize_t, kwnames: *mut PyObject, ) -> *mut PyObject; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyCFunction_GetFunction")] pub fn PyCFunction_GetFunction(f: *mut PyObject) -> Option; pub fn PyCFunction_GetSelf(f: *mut PyObject) -> *mut PyObject; pub fn PyCFunction_GetFlags(f: *mut PyObject) -> c_int; #[cfg(not(Py_3_13))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] pub fn PyCFunction_Call( f: *mut PyObject, args: *mut PyObject, kwds: *mut PyObject, ) -> *mut PyObject; } /// Represents the [PyMethodDef](https://docs.python.org/3/c-api/structures.html#c.PyMethodDef) /// structure. /// /// Note that CPython may leave fields uninitialized. You must ensure that /// `ml_name` != NULL before dereferencing or reading other fields. #[repr(C)] #[derive(Copy, Clone, PartialEq, Eq)] pub struct PyMethodDef { pub ml_name: *const c_char, pub ml_meth: PyMethodDefPointer, pub ml_flags: c_int, pub ml_doc: *const c_char, } impl PyMethodDef { pub const fn zeroed() -> PyMethodDef { PyMethodDef { ml_name: ptr::null(), ml_meth: PyMethodDefPointer { Void: ptr::null_mut(), }, ml_flags: 0, ml_doc: ptr::null(), } } } impl Default for PyMethodDef { fn default() -> PyMethodDef { PyMethodDef { ml_name: ptr::null(), ml_meth: PyMethodDefPointer { Void: ptr::null_mut(), }, ml_flags: 0, ml_doc: ptr::null(), } } } /// Function types used to implement Python callables. /// /// This function pointer must be accompanied by the correct [ml_flags](PyMethodDef::ml_flags), /// otherwise the behavior is undefined. /// /// See the [Python C API documentation][1] for more information. /// /// [1]: https://docs.python.org/3/c-api/structures.html#implementing-functions-and-methods #[repr(C)] #[derive(Copy, Clone, Eq)] pub union PyMethodDefPointer { /// This variant corresponds with [`METH_VARARGS`] *or* [`METH_NOARGS`] *or* [`METH_O`]. pub PyCFunction: PyCFunction, /// This variant corresponds with [`METH_VARARGS`] | [`METH_KEYWORDS`]. pub PyCFunctionWithKeywords: PyCFunctionWithKeywords, /// This variant corresponds with [`METH_FASTCALL`]. #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[deprecated(note = "renamed to `PyCFunctionFast`")] pub _PyCFunctionFast: PyCFunctionFast, /// This variant corresponds with [`METH_FASTCALL`]. #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] pub PyCFunctionFast: PyCFunctionFast, /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")] pub _PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords, /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] pub PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords, /// This variant corresponds with [`METH_METHOD`] | [`METH_FASTCALL`] | [`METH_KEYWORDS`]. #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] pub PyCMethod: PyCMethod, Void: *mut c_void, } impl PyMethodDefPointer { pub fn as_ptr(&self) -> *mut c_void { unsafe { self.Void } } pub fn is_null(&self) -> bool { self.as_ptr().is_null() } pub const fn zeroed() -> PyMethodDefPointer { PyMethodDefPointer { Void: ptr::null_mut(), } } } impl PartialEq for PyMethodDefPointer { fn eq(&self, other: &Self) -> bool { unsafe { self.Void == other.Void } } } impl std::fmt::Pointer for PyMethodDefPointer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let ptr = unsafe { self.Void }; std::fmt::Pointer::fmt(&ptr, f) } } const _: () = assert!(mem::size_of::() == mem::size_of::>()); #[cfg(not(Py_3_9))] extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyCFunction_New")] pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyCFunction_NewEx")] pub fn PyCFunction_NewEx( ml: *mut PyMethodDef, slf: *mut PyObject, module: *mut PyObject, ) -> *mut PyObject; } #[cfg(Py_3_9)] #[inline] pub unsafe fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject { PyCFunction_NewEx(ml, slf, std::ptr::null_mut()) } #[cfg(Py_3_9)] #[inline] pub unsafe fn PyCFunction_NewEx( ml: *mut PyMethodDef, slf: *mut PyObject, module: *mut PyObject, ) -> *mut PyObject { PyCMethod_New(ml, slf, module, std::ptr::null_mut()) } #[cfg(Py_3_9)] extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyCMethod_New")] pub fn PyCMethod_New( ml: *mut PyMethodDef, slf: *mut PyObject, module: *mut PyObject, cls: *mut PyTypeObject, ) -> *mut PyObject; } /* Flag passed to newmethodobject */ pub const METH_VARARGS: c_int = 0x0001; pub const METH_KEYWORDS: c_int = 0x0002; /* METH_NOARGS and METH_O must not be combined with the flags above. */ pub const METH_NOARGS: c_int = 0x0004; pub const METH_O: c_int = 0x0008; /* METH_CLASS and METH_STATIC are a little different; these control the construction of methods for a class. These cannot be used for functions in modules. */ pub const METH_CLASS: c_int = 0x0010; pub const METH_STATIC: c_int = 0x0020; /* METH_COEXIST allows a method to be entered eventhough a slot has already filled the entry. When defined, the flag allows a separate method, "__contains__" for example, to coexist with a defined slot like sq_contains. */ pub const METH_COEXIST: c_int = 0x0040; /* METH_FASTCALL indicates the PEP 590 Vectorcall calling format. It may be specified alone or with METH_KEYWORDS. */ #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] pub const METH_FASTCALL: c_int = 0x0080; // skipped METH_STACKLESS #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] pub const METH_METHOD: c_int = 0x0200; extern_libpython! { #[cfg(not(Py_3_9))] pub fn PyCFunction_ClearFreeList() -> c_int; } ================================================ FILE: pyo3-ffi/src/modsupport.rs ================================================ use crate::methodobject::PyMethodDef; use crate::moduleobject::PyModuleDef; use crate::object::PyObject; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int, c_long}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyArg_Parse")] pub fn PyArg_Parse(arg1: *mut PyObject, arg2: *const c_char, ...) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyArg_ParseTuple")] pub fn PyArg_ParseTuple(arg1: *mut PyObject, arg2: *const c_char, ...) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyArg_ParseTupleAndKeywords")] pub fn PyArg_ParseTupleAndKeywords( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *const c_char, #[cfg(not(Py_3_13))] arg4: *mut *mut c_char, #[cfg(Py_3_13)] arg4: *const *const c_char, ... ) -> c_int; // skipped PyArg_VaParse // skipped PyArg_VaParseTupleAndKeywords pub fn PyArg_ValidateKeywordArguments(arg1: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyArg_UnpackTuple")] pub fn PyArg_UnpackTuple( arg1: *mut PyObject, arg2: *const c_char, arg3: Py_ssize_t, arg4: Py_ssize_t, ... ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPy_BuildValue")] pub fn Py_BuildValue(arg1: *const c_char, ...) -> *mut PyObject; // skipped Py_VaBuildValue #[cfg(Py_3_13)] pub fn PyModule_Add(module: *mut PyObject, name: *const c_char, value: *mut PyObject) -> c_int; #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyModule_AddObjectRef")] pub fn PyModule_AddObjectRef( module: *mut PyObject, name: *const c_char, value: *mut PyObject, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyModule_AddObject")] pub fn PyModule_AddObject( module: *mut PyObject, name: *const c_char, value: *mut PyObject, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyModule_AddIntConstant")] pub fn PyModule_AddIntConstant( module: *mut PyObject, name: *const c_char, value: c_long, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyModule_AddStringConstant")] pub fn PyModule_AddStringConstant( module: *mut PyObject, name: *const c_char, value: *const c_char, ) -> c_int; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "PyPyModule_AddType")] pub fn PyModule_AddType( module: *mut PyObject, type_: *mut crate::object::PyTypeObject, ) -> c_int; // skipped PyModule_AddIntMacro // skipped PyModule_AddStringMacro pub fn PyModule_SetDocString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; pub fn PyModule_AddFunctions(arg1: *mut PyObject, arg2: *mut PyMethodDef) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyModule_ExecDef")] pub fn PyModule_ExecDef(module: *mut PyObject, def: *mut PyModuleDef) -> c_int; } pub const Py_CLEANUP_SUPPORTED: i32 = 0x2_0000; pub const PYTHON_API_VERSION: i32 = 1013; pub const PYTHON_ABI_VERSION: i32 = 3; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyModule_Create2")] pub fn PyModule_Create2(module: *mut PyModuleDef, apiver: c_int) -> *mut PyObject; } #[inline] pub unsafe fn PyModule_Create(module: *mut PyModuleDef) -> *mut PyObject { PyModule_Create2( module, if cfg!(Py_LIMITED_API) { PYTHON_ABI_VERSION } else { PYTHON_API_VERSION }, ) } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyModule_FromDefAndSpec2")] pub fn PyModule_FromDefAndSpec2( def: *mut PyModuleDef, spec: *mut PyObject, module_api_version: c_int, ) -> *mut PyObject; } #[inline] pub unsafe fn PyModule_FromDefAndSpec(def: *mut PyModuleDef, spec: *mut PyObject) -> *mut PyObject { PyModule_FromDefAndSpec2( def, spec, if cfg!(Py_LIMITED_API) { PYTHON_ABI_VERSION } else { PYTHON_API_VERSION }, ) } #[cfg(Py_3_15)] #[repr(C)] pub struct PyABIInfo { pub abiinfo_major_version: u8, pub abiinfo_minor_version: u8, pub flags: u16, pub build_version: u32, pub abi_version: u32, } #[cfg(Py_3_15)] pub const PyABIInfo_STABLE: u16 = 0x0001; #[cfg(Py_3_15)] pub const PyABIInfo_GIL: u16 = 0x0002; #[cfg(Py_3_15)] pub const PyABIInfo_FREETHREADED: u16 = 0x0004; #[cfg(Py_3_15)] pub const PyABIInfo_INTERNAL: u16 = 0x0008; #[cfg(Py_3_15)] pub const PyABIInfo_FREETHREADING_AGNOSTIC: u16 = PyABIInfo_GIL | PyABIInfo_FREETHREADED; #[cfg(Py_3_15)] extern_libpython! { pub fn PyABIInfo_Check(info: *mut PyABIInfo, module_name: *const c_char) -> c_int; } #[cfg(all(Py_LIMITED_API, Py_3_15))] const _PyABIInfo_DEFAULT_FLAG_STABLE: u16 = PyABIInfo_STABLE; #[cfg(all(Py_3_15, not(Py_LIMITED_API)))] const _PyABIInfo_DEFAULT_FLAG_STABLE: u16 = 0; // skipped PyABIInfo_DEFAULT_ABI_VERSION: depends on Py_VERSION_HEX #[cfg(all(Py_3_15, Py_GIL_DISABLED))] const _PyABIInfo_DEFAULT_FLAG_FT: u16 = PyABIInfo_FREETHREADED; #[cfg(all(Py_3_15, not(Py_GIL_DISABLED)))] const _PyABIInfo_DEFAULT_FLAG_FT: u16 = PyABIInfo_GIL; #[cfg(Py_3_15)] // has an alternate definition if Py_BUILD_CORE is set, ignore that const _PyABIInfo_DEFAULT_FLAG_INTERNAL: u16 = 0; #[cfg(Py_3_15)] pub const PyABIInfo_DEFAULT_FLAGS: u16 = _PyABIInfo_DEFAULT_FLAG_STABLE | _PyABIInfo_DEFAULT_FLAG_FT | _PyABIInfo_DEFAULT_FLAG_INTERNAL; #[cfg(Py_3_15)] // must be pub because it is used by PyABIInfo_VAR pub const _PyABIInfo_DEFAULT: PyABIInfo = PyABIInfo { abiinfo_major_version: 1, abiinfo_minor_version: 0, flags: PyABIInfo_DEFAULT_FLAGS, build_version: 0, abi_version: 0, }; #[cfg(Py_3_15)] #[macro_export] macro_rules! PyABIInfo_VAR { ($name:ident) => { static mut $name: pyo3_ffi::PyABIInfo = pyo3_ffi::_PyABIInfo_DEFAULT; }; } ================================================ FILE: pyo3-ffi/src/moduleobject.rs ================================================ use crate::methodobject::PyMethodDef; use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int, c_void}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyModule_Type")] pub static mut PyModule_Type: PyTypeObject; } #[inline] pub unsafe fn PyModule_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut PyModule_Type) } #[inline] pub unsafe fn PyModule_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyModule_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyModule_NewObject")] pub fn PyModule_NewObject(name: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyModule_New")] pub fn PyModule_New(name: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyModule_GetDict")] pub fn PyModule_GetDict(arg1: *mut PyObject) -> *mut PyObject; #[cfg(not(PyPy))] pub fn PyModule_GetNameObject(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyModule_GetName")] pub fn PyModule_GetName(arg1: *mut PyObject) -> *const c_char; #[cfg(not(all(windows, PyPy)))] #[deprecated(note = "Python 3.2")] pub fn PyModule_GetFilename(arg1: *mut PyObject) -> *const c_char; #[cfg(not(PyPy))] pub fn PyModule_GetFilenameObject(arg1: *mut PyObject) -> *mut PyObject; // skipped non-limited _PyModule_Clear // skipped non-limited _PyModule_ClearDict // skipped non-limited _PyModuleSpec_IsInitializing #[cfg_attr(PyPy, link_name = "PyPyModule_GetDef")] pub fn PyModule_GetDef(arg1: *mut PyObject) -> *mut PyModuleDef; #[cfg_attr(PyPy, link_name = "PyPyModule_GetState")] pub fn PyModule_GetState(arg1: *mut PyObject) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyModuleDef_Init")] pub fn PyModuleDef_Init(arg1: *mut PyModuleDef) -> *mut PyObject; } extern_libpython! { pub static mut PyModuleDef_Type: PyTypeObject; } #[repr(C)] pub struct PyModuleDef_Base { pub ob_base: PyObject, // Rust function pointers are non-null so an Option is needed here. pub m_init: Option *mut PyObject>, pub m_index: Py_ssize_t, pub m_copy: *mut PyObject, } #[allow( clippy::declare_interior_mutable_const, reason = "contains atomic refcount on free-threaded builds" )] pub const PyModuleDef_HEAD_INIT: PyModuleDef_Base = PyModuleDef_Base { ob_base: PyObject_HEAD_INIT, m_init: None, m_index: 0, m_copy: std::ptr::null_mut(), }; #[repr(C)] #[derive(Copy, Clone, Eq, PartialEq)] pub struct PyModuleDef_Slot { pub slot: c_int, pub value: *mut c_void, } impl Default for PyModuleDef_Slot { fn default() -> PyModuleDef_Slot { PyModuleDef_Slot { slot: 0, value: std::ptr::null_mut(), } } } pub const Py_mod_create: c_int = 1; pub const Py_mod_exec: c_int = 2; #[cfg(Py_3_12)] pub const Py_mod_multiple_interpreters: c_int = 3; #[cfg(Py_3_13)] pub const Py_mod_gil: c_int = 4; #[cfg(Py_3_15)] pub const Py_mod_abi: c_int = 5; #[cfg(Py_3_15)] pub const Py_mod_name: c_int = 6; #[cfg(Py_3_15)] pub const Py_mod_doc: c_int = 7; #[cfg(Py_3_15)] pub const Py_mod_state_size: c_int = 8; #[cfg(Py_3_15)] pub const Py_mod_methods: c_int = 9; #[cfg(Py_3_15)] pub const Py_mod_state_traverse: c_int = 10; #[cfg(Py_3_15)] pub const Py_mod_state_clear: c_int = 11; #[cfg(Py_3_15)] pub const Py_mod_state_free: c_int = 12; #[cfg(Py_3_15)] pub const Py_mod_token: c_int = 13; // skipped private _Py_mod_LAST_SLOT #[cfg(Py_3_12)] #[allow( clippy::zero_ptr, reason = "matches the way that the rest of these constants are defined" )] pub const Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED: *mut c_void = 0 as *mut c_void; #[cfg(Py_3_12)] pub const Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED: *mut c_void = 1 as *mut c_void; #[cfg(Py_3_12)] pub const Py_MOD_PER_INTERPRETER_GIL_SUPPORTED: *mut c_void = 2 as *mut c_void; #[cfg(Py_3_13)] #[allow( clippy::zero_ptr, reason = "matches the way that the rest of these constants are defined" )] pub const Py_MOD_GIL_USED: *mut c_void = 0 as *mut c_void; #[cfg(Py_3_13)] pub const Py_MOD_GIL_NOT_USED: *mut c_void = 1 as *mut c_void; #[cfg(all(not(Py_LIMITED_API), Py_GIL_DISABLED))] extern_libpython! { pub fn PyUnstable_Module_SetGIL(module: *mut PyObject, gil: *mut c_void) -> c_int; } #[cfg(Py_3_15)] extern_libpython! { pub fn PyModule_FromSlotsAndSpec( slots: *const PyModuleDef_Slot, spec: *mut PyObject, ) -> *mut PyObject; pub fn PyModule_Exec(_mod: *mut PyObject) -> c_int; pub fn PyModule_GetStateSize(_mod: *mut PyObject, result: *mut Py_ssize_t) -> c_int; pub fn PyModule_GetToken(module: *mut PyObject, result: *mut *mut c_void) -> c_int; } #[repr(C)] pub struct PyModuleDef { pub m_base: PyModuleDef_Base, pub m_name: *const c_char, pub m_doc: *const c_char, pub m_size: Py_ssize_t, pub m_methods: *mut PyMethodDef, pub m_slots: *mut PyModuleDef_Slot, // Rust function pointers are non-null so an Option is needed here. pub m_traverse: Option, pub m_clear: Option, pub m_free: Option, } ================================================ FILE: pyo3-ffi/src/object.rs ================================================ use crate::pyport::{Py_hash_t, Py_ssize_t}; #[cfg(Py_GIL_DISABLED)] use crate::refcount; #[cfg(Py_GIL_DISABLED)] use crate::PyMutex; use std::ffi::{c_char, c_int, c_uint, c_ulong, c_void}; use std::mem; #[cfg(Py_GIL_DISABLED)] use std::sync::atomic::{AtomicIsize, AtomicU32}; #[cfg(Py_LIMITED_API)] opaque_struct!(pub PyTypeObject); #[cfg(not(Py_LIMITED_API))] pub use crate::cpython::object::PyTypeObject; // skip PyObject_HEAD #[repr(C)] #[derive(Copy, Clone)] #[cfg(all( target_pointer_width = "64", Py_3_14, not(Py_GIL_DISABLED), target_endian = "big" ))] /// This struct is anonymous in CPython, so the name was given by PyO3 because /// Rust structs need a name. pub struct PyObjectObFlagsAndRefcnt { pub ob_flags: u16, pub ob_overflow: u16, pub ob_refcnt: u32, } #[repr(C)] #[derive(Copy, Clone)] #[cfg(all( target_pointer_width = "64", Py_3_14, not(Py_GIL_DISABLED), target_endian = "little" ))] /// This struct is anonymous in CPython, so the name was given by PyO3 because /// Rust structs need a name. pub struct PyObjectObFlagsAndRefcnt { pub ob_refcnt: u32, pub ob_overflow: u16, pub ob_flags: u16, } // 4-byte alignment comes from value of _PyObject_MIN_ALIGNMENT #[cfg(all(not(Py_GIL_DISABLED), Py_3_15))] #[repr(C, align(4))] #[derive(Copy, Clone)] struct Aligner(c_char); #[repr(C)] #[derive(Copy, Clone)] #[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] /// This union is anonymous in CPython, so the name was given by PyO3 because /// Rust union need a name. pub union PyObjectObRefcnt { #[cfg(all(target_pointer_width = "64", Py_3_14))] pub ob_refcnt_full: crate::PY_INT64_T, #[cfg(all(target_pointer_width = "64", Py_3_14))] pub refcnt_and_flags: PyObjectObFlagsAndRefcnt, pub ob_refcnt: Py_ssize_t, #[cfg(all(target_pointer_width = "64", not(Py_3_14)))] pub ob_refcnt_split: [crate::PY_UINT32_T; 2], #[cfg(all(not(Py_GIL_DISABLED), Py_3_15))] _aligner: Aligner, } #[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] impl std::fmt::Debug for PyObjectObRefcnt { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", unsafe { self.ob_refcnt }) } } #[cfg(all(not(Py_3_12), not(Py_GIL_DISABLED)))] pub type PyObjectObRefcnt = Py_ssize_t; const _PyObject_MIN_ALIGNMENT: usize = 4; // PyObject_HEAD_INIT comes before the PyObject definition in object.h // but we put it after PyObject because HEAD_INIT uses PyObject // repr(align(4)) corresponds to the use of _Py_ALIGNED_DEF in object.h. It is // not currently possible to use constant variables with repr(align()), see // https://github.com/rust-lang/rust/issues/52840 #[cfg_attr(not(all(Py_3_15, Py_GIL_DISABLED)), repr(C))] #[cfg_attr(all(Py_3_15, Py_GIL_DISABLED), repr(C, align(4)))] #[derive(Debug)] pub struct PyObject { #[cfg(Py_GIL_DISABLED)] pub ob_tid: libc::uintptr_t, #[cfg(all(Py_GIL_DISABLED, not(Py_3_14)))] pub _padding: u16, #[cfg(all(Py_GIL_DISABLED, Py_3_14))] pub ob_flags: u16, #[cfg(Py_GIL_DISABLED)] pub ob_mutex: PyMutex, // per-object lock #[cfg(Py_GIL_DISABLED)] pub ob_gc_bits: u8, // gc-related state #[cfg(Py_GIL_DISABLED)] pub ob_ref_local: AtomicU32, // local reference count #[cfg(Py_GIL_DISABLED)] pub ob_ref_shared: AtomicIsize, // shared reference count #[cfg(not(Py_GIL_DISABLED))] pub ob_refcnt: PyObjectObRefcnt, #[cfg(PyPy)] pub ob_pypy_link: Py_ssize_t, pub ob_type: *mut PyTypeObject, } const _: () = assert!(std::mem::align_of::() >= _PyObject_MIN_ALIGNMENT); #[allow( clippy::declare_interior_mutable_const, reason = "contains atomic refcount on free-threaded builds" )] pub const PyObject_HEAD_INIT: PyObject = PyObject { #[cfg(Py_GIL_DISABLED)] ob_tid: 0, #[cfg(all(Py_GIL_DISABLED, Py_3_15))] ob_flags: refcount::_Py_STATICALLY_ALLOCATED_FLAG as u16, #[cfg(all(Py_GIL_DISABLED, all(Py_3_14, not(Py_3_15))))] ob_flags: 0, #[cfg(all(Py_GIL_DISABLED, not(Py_3_14)))] _padding: 0, #[cfg(Py_GIL_DISABLED)] ob_mutex: PyMutex::new(), #[cfg(Py_GIL_DISABLED)] ob_gc_bits: 0, #[cfg(Py_GIL_DISABLED)] ob_ref_local: AtomicU32::new(refcount::_Py_IMMORTAL_REFCNT_LOCAL), #[cfg(Py_GIL_DISABLED)] ob_ref_shared: AtomicIsize::new(0), #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] ob_refcnt: PyObjectObRefcnt { ob_refcnt: 1 }, #[cfg(not(Py_3_12))] ob_refcnt: 1, #[cfg(PyPy)] ob_pypy_link: 0, ob_type: std::ptr::null_mut(), }; // skipped _Py_UNOWNED_TID // skipped _PyObject_CAST #[repr(C)] #[derive(Debug)] pub struct PyVarObject { pub ob_base: PyObject, #[cfg(not(GraalPy))] pub ob_size: Py_ssize_t, // On GraalPy the field is physically there, but not always populated. We hide it to prevent accidental misuse #[cfg(GraalPy)] pub _ob_size_graalpy: Py_ssize_t, } // skipped private _PyVarObject_CAST #[inline] #[cfg(not(any(GraalPy, PyPy)))] #[cfg_attr(docsrs, doc(cfg(all())))] pub unsafe fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int { (x == y).into() } #[cfg(any(GraalPy, PyPy))] #[cfg_attr(docsrs, doc(cfg(all())))] extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPy_Is")] pub fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int; } // skipped _Py_GetThreadLocal_Addr // skipped _Py_ThreadID // skipped _Py_IsOwnedByCurrentThread #[cfg(GraalPy)] extern_libpython! { #[cfg(GraalPy)] fn _Py_TYPE(arg1: *const PyObject) -> *mut PyTypeObject; #[cfg(GraalPy)] fn _Py_SIZE(arg1: *const PyObject) -> Py_ssize_t; } #[inline] #[cfg(not(Py_3_14))] pub unsafe fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject { #[cfg(not(GraalPy))] return (*ob).ob_type; #[cfg(GraalPy)] return _Py_TYPE(ob); } #[cfg(Py_3_14)] extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPy_TYPE")] pub fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject; } // skip _Py_TYPE compat shim extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyLong_Type")] pub static mut PyLong_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyBool_Type")] pub static mut PyBool_Type: PyTypeObject; } #[inline] pub unsafe fn Py_SIZE(ob: *mut PyObject) -> Py_ssize_t { #[cfg(not(GraalPy))] { debug_assert_ne!((*ob).ob_type, &raw mut crate::PyLong_Type); debug_assert_ne!((*ob).ob_type, &raw mut crate::PyBool_Type); (*ob.cast::()).ob_size } #[cfg(GraalPy)] _Py_SIZE(ob) } #[inline] pub unsafe fn Py_IS_TYPE(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_int { (Py_TYPE(ob) == tp) as c_int } // skipped Py_SET_TYPE // skipped Py_SET_SIZE pub type unaryfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; pub type binaryfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject; pub type ternaryfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject; pub type inquiry = unsafe extern "C" fn(*mut PyObject) -> c_int; pub type lenfunc = unsafe extern "C" fn(*mut PyObject) -> Py_ssize_t; pub type ssizeargfunc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t) -> *mut PyObject; pub type ssizessizeargfunc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t, Py_ssize_t) -> *mut PyObject; pub type ssizeobjargproc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t, *mut PyObject) -> c_int; pub type ssizessizeobjargproc = unsafe extern "C" fn(*mut PyObject, Py_ssize_t, Py_ssize_t, arg4: *mut PyObject) -> c_int; pub type objobjargproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; pub type objobjproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> c_int; pub type visitproc = unsafe extern "C" fn(object: *mut PyObject, arg: *mut c_void) -> c_int; pub type traverseproc = unsafe extern "C" fn(slf: *mut PyObject, visit: visitproc, arg: *mut c_void) -> c_int; pub type freefunc = unsafe extern "C" fn(*mut c_void); pub type destructor = unsafe extern "C" fn(*mut PyObject); pub type getattrfunc = unsafe extern "C" fn(*mut PyObject, *mut c_char) -> *mut PyObject; pub type getattrofunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject; pub type setattrfunc = unsafe extern "C" fn(*mut PyObject, *mut c_char, *mut PyObject) -> c_int; pub type setattrofunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; pub type reprfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; pub type hashfunc = unsafe extern "C" fn(*mut PyObject) -> Py_hash_t; pub type richcmpfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, c_int) -> *mut PyObject; pub type getiterfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; pub type iternextfunc = unsafe extern "C" fn(*mut PyObject) -> *mut PyObject; pub type descrgetfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject; pub type descrsetfunc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; pub type initproc = unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> c_int; pub type newfunc = unsafe extern "C" fn(*mut PyTypeObject, *mut PyObject, *mut PyObject) -> *mut PyObject; pub type allocfunc = unsafe extern "C" fn(*mut PyTypeObject, Py_ssize_t) -> *mut PyObject; #[cfg(Py_3_8)] pub type vectorcallfunc = unsafe extern "C" fn( callable: *mut PyObject, args: *const *mut PyObject, nargsf: libc::size_t, kwnames: *mut PyObject, ) -> *mut PyObject; #[repr(C)] #[derive(Copy, Clone)] pub struct PyType_Slot { pub slot: c_int, pub pfunc: *mut c_void, } impl Default for PyType_Slot { fn default() -> PyType_Slot { unsafe { mem::zeroed() } } } #[repr(C)] #[derive(Copy, Clone)] pub struct PyType_Spec { pub name: *const c_char, pub basicsize: c_int, pub itemsize: c_int, pub flags: c_uint, pub slots: *mut PyType_Slot, } impl Default for PyType_Spec { fn default() -> PyType_Spec { unsafe { mem::zeroed() } } } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyType_FromSpec")] pub fn PyType_FromSpec(arg1: *mut PyType_Spec) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyType_FromSpecWithBases")] pub fn PyType_FromSpecWithBases(arg1: *mut PyType_Spec, arg2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyType_GetSlot")] pub fn PyType_GetSlot(arg1: *mut PyTypeObject, arg2: c_int) -> *mut c_void; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "PyPyType_FromModuleAndSpec")] pub fn PyType_FromModuleAndSpec( module: *mut PyObject, spec: *mut PyType_Spec, bases: *mut PyObject, ) -> *mut PyObject; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "PyPyType_GetModule")] pub fn PyType_GetModule(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleState")] pub fn PyType_GetModuleState(arg1: *mut PyTypeObject) -> *mut c_void; #[cfg(Py_3_11)] #[cfg_attr(PyPy, link_name = "PyPyType_GetName")] pub fn PyType_GetName(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(Py_3_11)] #[cfg_attr(PyPy, link_name = "PyPyType_GetQualName")] pub fn PyType_GetQualName(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyType_GetFullyQualifiedName")] pub fn PyType_GetFullyQualifiedName(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleName")] pub fn PyType_GetModuleName(arg1: *mut PyTypeObject) -> *mut PyObject; #[cfg(Py_3_12)] #[cfg_attr(PyPy, link_name = "PyPyType_FromMetaclass")] pub fn PyType_FromMetaclass( metaclass: *mut PyTypeObject, module: *mut PyObject, spec: *mut PyType_Spec, bases: *mut PyObject, ) -> *mut PyObject; #[cfg(Py_3_12)] #[cfg_attr(PyPy, link_name = "PyPyObject_GetTypeData")] pub fn PyObject_GetTypeData(obj: *mut PyObject, cls: *mut PyTypeObject) -> *mut c_void; #[cfg(Py_3_12)] #[cfg_attr(PyPy, link_name = "PyPyType_GetTypeDataSize")] pub fn PyType_GetTypeDataSize(cls: *mut PyTypeObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyType_IsSubtype")] pub fn PyType_IsSubtype(a: *mut PyTypeObject, b: *mut PyTypeObject) -> c_int; } #[inline] pub unsafe fn PyObject_TypeCheck(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_int { (Py_IS_TYPE(ob, tp) != 0 || PyType_IsSubtype(Py_TYPE(ob), tp) != 0) as c_int } extern_libpython! { /// built-in 'type' #[cfg_attr(PyPy, link_name = "PyPyType_Type")] pub static mut PyType_Type: PyTypeObject; /// built-in 'object' #[cfg_attr(PyPy, link_name = "PyPyBaseObject_Type")] pub static mut PyBaseObject_Type: PyTypeObject; /// built-in 'super' pub static mut PySuper_Type: PyTypeObject; } extern_libpython! { pub fn PyType_GetFlags(arg1: *mut PyTypeObject) -> c_ulong; #[cfg_attr(PyPy, link_name = "PyPyType_Ready")] pub fn PyType_Ready(t: *mut PyTypeObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyType_GenericAlloc")] pub fn PyType_GenericAlloc(t: *mut PyTypeObject, nitems: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyType_GenericNew")] pub fn PyType_GenericNew( t: *mut PyTypeObject, args: *mut PyObject, kwds: *mut PyObject, ) -> *mut PyObject; pub fn PyType_ClearCache() -> c_uint; #[cfg_attr(PyPy, link_name = "PyPyType_Modified")] pub fn PyType_Modified(t: *mut PyTypeObject); #[cfg_attr(PyPy, link_name = "PyPyObject_Repr")] pub fn PyObject_Repr(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Str")] pub fn PyObject_Str(o: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_ASCII")] pub fn PyObject_ASCII(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_Bytes")] pub fn PyObject_Bytes(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_RichCompare")] pub fn PyObject_RichCompare( arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_RichCompareBool")] pub fn PyObject_RichCompareBool(arg1: *mut PyObject, arg2: *mut PyObject, arg3: c_int) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_GetAttrString")] pub fn PyObject_GetAttrString(arg1: *mut PyObject, arg2: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_SetAttrString")] pub fn PyObject_SetAttrString( arg1: *mut PyObject, arg2: *const c_char, arg3: *mut PyObject, ) -> c_int; #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttrString")] pub fn PyObject_DelAttrString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrString")] pub fn PyObject_HasAttrString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_GetAttr")] pub fn PyObject_GetAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyObject_GetOptionalAttr")] pub fn PyObject_GetOptionalAttr( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut *mut PyObject, ) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyObject_GetOptionalAttrString")] pub fn PyObject_GetOptionalAttrString( arg1: *mut PyObject, arg2: *const c_char, arg3: *mut *mut PyObject, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_SetAttr")] pub fn PyObject_SetAttr(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) -> c_int; #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttr")] pub fn PyObject_DelAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttr")] pub fn PyObject_HasAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrWithError")] pub fn PyObject_HasAttrWithError(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrStringWithError")] pub fn PyObject_HasAttrStringWithError(arg1: *mut PyObject, arg2: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_SelfIter")] pub fn PyObject_SelfIter(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_GenericGetAttr")] pub fn PyObject_GenericGetAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_GenericSetAttr")] pub fn PyObject_GenericSetAttr( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject, ) -> c_int; #[cfg(not(all(Py_LIMITED_API, not(Py_3_10))))] #[cfg_attr(PyPy, link_name = "PyPyObject_GenericGetDict")] pub fn PyObject_GenericGetDict(arg1: *mut PyObject, arg2: *mut c_void) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_GenericSetDict")] pub fn PyObject_GenericSetDict( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut c_void, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_Hash")] pub fn PyObject_Hash(arg1: *mut PyObject) -> Py_hash_t; #[cfg_attr(PyPy, link_name = "PyPyObject_HashNotImplemented")] pub fn PyObject_HashNotImplemented(arg1: *mut PyObject) -> Py_hash_t; #[cfg_attr(PyPy, link_name = "PyPyObject_IsTrue")] pub fn PyObject_IsTrue(arg1: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_Not")] pub fn PyObject_Not(arg1: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyCallable_Check")] pub fn PyCallable_Check(arg1: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_ClearWeakRefs")] pub fn PyObject_ClearWeakRefs(arg1: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyObject_Dir")] pub fn PyObject_Dir(arg1: *mut PyObject) -> *mut PyObject; pub fn Py_ReprEnter(arg1: *mut PyObject) -> c_int; pub fn Py_ReprLeave(arg1: *mut PyObject); } // Flag bits for printing: pub const Py_PRINT_RAW: c_int = 1; // No string quotes etc. // skipped because is a private API // const _Py_TPFLAGS_STATIC_BUILTIN: c_ulong = 1 << 1; #[cfg(all(Py_3_12, not(Py_LIMITED_API)))] pub const Py_TPFLAGS_MANAGED_WEAKREF: c_ulong = 1 << 3; #[cfg(all(Py_3_11, not(Py_LIMITED_API)))] pub const Py_TPFLAGS_MANAGED_DICT: c_ulong = 1 << 4; #[cfg(all(Py_3_10, not(Py_LIMITED_API)))] pub const Py_TPFLAGS_SEQUENCE: c_ulong = 1 << 5; #[cfg(all(Py_3_10, not(Py_LIMITED_API)))] pub const Py_TPFLAGS_MAPPING: c_ulong = 1 << 6; #[cfg(Py_3_10)] pub const Py_TPFLAGS_DISALLOW_INSTANTIATION: c_ulong = 1 << 7; #[cfg(Py_3_10)] pub const Py_TPFLAGS_IMMUTABLETYPE: c_ulong = 1 << 8; /// Set if the type object is dynamically allocated pub const Py_TPFLAGS_HEAPTYPE: c_ulong = 1 << 9; /// Set if the type allows subclassing pub const Py_TPFLAGS_BASETYPE: c_ulong = 1 << 10; /// Set if the type implements the vectorcall protocol (PEP 590) #[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] pub const Py_TPFLAGS_HAVE_VECTORCALL: c_ulong = 1 << 11; // skipped backwards-compatibility alias _Py_TPFLAGS_HAVE_VECTORCALL /// Set if the type is 'ready' -- fully initialized pub const Py_TPFLAGS_READY: c_ulong = 1 << 12; /// Set while the type is being 'readied', to prevent recursive ready calls pub const Py_TPFLAGS_READYING: c_ulong = 1 << 13; /// Objects support garbage collection (see objimp.h) pub const Py_TPFLAGS_HAVE_GC: c_ulong = 1 << 14; const Py_TPFLAGS_HAVE_STACKLESS_EXTENSION: c_ulong = 0; #[cfg(Py_3_8)] pub const Py_TPFLAGS_METHOD_DESCRIPTOR: c_ulong = 1 << 17; pub const Py_TPFLAGS_VALID_VERSION_TAG: c_ulong = 1 << 19; /* Type is abstract and cannot be instantiated */ pub const Py_TPFLAGS_IS_ABSTRACT: c_ulong = 1 << 20; // skipped non-limited / 3.10 Py_TPFLAGS_HAVE_AM_SEND #[cfg(Py_3_12)] pub const Py_TPFLAGS_ITEMS_AT_END: c_ulong = 1 << 23; /* These flags are used to determine if a type is a subclass. */ pub const Py_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24; pub const Py_TPFLAGS_LIST_SUBCLASS: c_ulong = 1 << 25; pub const Py_TPFLAGS_TUPLE_SUBCLASS: c_ulong = 1 << 26; pub const Py_TPFLAGS_BYTES_SUBCLASS: c_ulong = 1 << 27; pub const Py_TPFLAGS_UNICODE_SUBCLASS: c_ulong = 1 << 28; pub const Py_TPFLAGS_DICT_SUBCLASS: c_ulong = 1 << 29; pub const Py_TPFLAGS_BASE_EXC_SUBCLASS: c_ulong = 1 << 30; pub const Py_TPFLAGS_TYPE_SUBCLASS: c_ulong = 1 << 31; pub const Py_TPFLAGS_DEFAULT: c_ulong = if cfg!(Py_3_10) { Py_TPFLAGS_HAVE_STACKLESS_EXTENSION } else { Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | Py_TPFLAGS_HAVE_VERSION_TAG }; pub const Py_TPFLAGS_HAVE_FINALIZE: c_ulong = 1; pub const Py_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; #[cfg(Py_3_13)] pub const Py_CONSTANT_NONE: c_uint = 0; #[cfg(Py_3_13)] pub const Py_CONSTANT_FALSE: c_uint = 1; #[cfg(Py_3_13)] pub const Py_CONSTANT_TRUE: c_uint = 2; #[cfg(Py_3_13)] pub const Py_CONSTANT_ELLIPSIS: c_uint = 3; #[cfg(Py_3_13)] pub const Py_CONSTANT_NOT_IMPLEMENTED: c_uint = 4; #[cfg(Py_3_13)] pub const Py_CONSTANT_ZERO: c_uint = 5; #[cfg(Py_3_13)] pub const Py_CONSTANT_ONE: c_uint = 6; #[cfg(Py_3_13)] pub const Py_CONSTANT_EMPTY_STR: c_uint = 7; #[cfg(Py_3_13)] pub const Py_CONSTANT_EMPTY_BYTES: c_uint = 8; #[cfg(Py_3_13)] pub const Py_CONSTANT_EMPTY_TUPLE: c_uint = 9; extern_libpython! { #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPy_GetConstant")] pub fn Py_GetConstant(constant_id: c_uint) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPy_GetConstantBorrowed")] pub fn Py_GetConstantBorrowed(constant_id: c_uint) -> *mut PyObject; } extern_libpython! { #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "_PyPy_NoneStruct")] static mut _Py_NoneStruct: PyObject; #[cfg(GraalPy)] static mut _Py_NoneStructReference: *mut PyObject; } #[inline] pub unsafe fn Py_None() -> *mut PyObject { #[cfg(all(not(GraalPy), all(Py_3_13, Py_LIMITED_API)))] return Py_GetConstantBorrowed(Py_CONSTANT_NONE); #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] return &raw mut _Py_NoneStruct; #[cfg(GraalPy)] return _Py_NoneStructReference; } #[inline] pub unsafe fn Py_IsNone(x: *mut PyObject) -> c_int { Py_Is(x, Py_None()) } // skipped Py_RETURN_NONE extern_libpython! { #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "_PyPy_NotImplementedStruct")] static mut _Py_NotImplementedStruct: PyObject; #[cfg(GraalPy)] static mut _Py_NotImplementedStructReference: *mut PyObject; } #[inline] pub unsafe fn Py_NotImplemented() -> *mut PyObject { #[cfg(all(not(GraalPy), all(Py_3_13, Py_LIMITED_API)))] return Py_GetConstantBorrowed(Py_CONSTANT_NOT_IMPLEMENTED); #[cfg(all(not(GraalPy), not(all(Py_3_13, Py_LIMITED_API))))] return &raw mut _Py_NotImplementedStruct; #[cfg(GraalPy)] return _Py_NotImplementedStructReference; } // skipped Py_RETURN_NOTIMPLEMENTED /* Rich comparison opcodes */ pub const Py_LT: c_int = 0; pub const Py_LE: c_int = 1; pub const Py_EQ: c_int = 2; pub const Py_NE: c_int = 3; pub const Py_GT: c_int = 4; pub const Py_GE: c_int = 5; #[cfg(Py_3_10)] #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PySendResult { PYGEN_RETURN = 0, PYGEN_ERROR = -1, PYGEN_NEXT = 1, } // skipped Py_RETURN_RICHCOMPARE #[inline] pub unsafe fn PyType_HasFeature(ty: *mut PyTypeObject, feature: c_ulong) -> c_int { #[cfg(Py_LIMITED_API)] let flags = PyType_GetFlags(ty); #[cfg(all(not(Py_LIMITED_API), Py_GIL_DISABLED))] let flags = (*ty).tp_flags.load(std::sync::atomic::Ordering::Relaxed); #[cfg(all(not(Py_LIMITED_API), not(Py_GIL_DISABLED)))] let flags = (*ty).tp_flags; ((flags & feature) != 0) as c_int } #[inline] pub unsafe fn PyType_FastSubclass(t: *mut PyTypeObject, f: c_ulong) -> c_int { PyType_HasFeature(t, f) } #[inline] pub unsafe fn PyType_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_TYPE_SUBCLASS) } // skipped _PyType_CAST #[inline] pub unsafe fn PyType_CheckExact(op: *mut PyObject) -> c_int { Py_IS_TYPE(op, &raw mut PyType_Type) } extern_libpython! { #[cfg(any(Py_3_13, all(Py_3_11, not(Py_LIMITED_API))))] #[cfg_attr(PyPy, link_name = "PyPyType_GetModuleByDef")] pub fn PyType_GetModuleByDef( arg1: *mut crate::PyTypeObject, arg2: *mut crate::PyModuleDef, ) -> *mut PyObject; #[cfg(Py_3_14)] pub fn PyType_Freeze(tp: *mut crate::PyTypeObject) -> c_int; #[cfg(Py_3_15)] pub fn PyType_GetModuleByToken(_type: *mut PyTypeObject, token: *const c_void) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/objimpl.rs ================================================ use libc::size_t; use std::ffi::{c_int, c_void}; use crate::object::*; use crate::pyport::Py_ssize_t; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyObject_Malloc")] pub fn PyObject_Malloc(size: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyObject_Calloc")] pub fn PyObject_Calloc(nelem: size_t, elsize: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyObject_Realloc")] pub fn PyObject_Realloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyObject_Free")] pub fn PyObject_Free(ptr: *mut c_void); // skipped PyObject_MALLOC // skipped PyObject_REALLOC // skipped PyObject_FREE // skipped PyObject_Del // skipped PyObject_DEL #[cfg_attr(PyPy, link_name = "PyPyObject_Init")] pub fn PyObject_Init(arg1: *mut PyObject, arg2: *mut PyTypeObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyObject_InitVar")] pub fn PyObject_InitVar( arg1: *mut PyVarObject, arg2: *mut PyTypeObject, arg3: Py_ssize_t, ) -> *mut PyVarObject; // skipped PyObject_INIT // skipped PyObject_INIT_VAR #[cfg_attr(PyPy, link_name = "_PyPyObject_New")] fn _PyObject_New(typeobj: *mut PyTypeObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "_PyPyObject_NewVar")] fn _PyObject_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) -> *mut PyVarObject; } #[inline] pub unsafe fn PyObject_New(typeobj: *mut PyTypeObject) -> *mut T { _PyObject_New(typeobj).cast() } // skipped PyObject_NEW #[inline] pub unsafe fn PyObject_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) -> *mut T { _PyObject_NewVar(typeobj, n).cast() } // skipped PyObject_NEW_VAR extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyGC_Collect")] pub fn PyGC_Collect() -> Py_ssize_t; #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyGC_Enable")] pub fn PyGC_Enable() -> c_int; #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyGC_Disable")] pub fn PyGC_Disable() -> c_int; #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyGC_IsEnabled")] pub fn PyGC_IsEnabled() -> c_int; } #[inline] pub unsafe fn PyType_IS_GC(t: *mut PyTypeObject) -> c_int { PyType_HasFeature(t, Py_TPFLAGS_HAVE_GC) } extern_libpython! { fn _PyObject_GC_Resize(op: *mut PyVarObject, n: Py_ssize_t) -> *mut PyVarObject; } #[inline] pub unsafe fn PyObject_GC_Resize(op: *mut PyObject, n: Py_ssize_t) -> *mut T { _PyObject_GC_Resize(op.cast(), n).cast() } extern_libpython! { #[cfg_attr(PyPy, link_name = "_PyPyObject_GC_New")] fn _PyObject_GC_New(typeobj: *mut PyTypeObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "_PyPyObject_GC_NewVar")] fn _PyObject_GC_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) -> *mut PyVarObject; #[cfg(not(PyPy))] pub fn PyObject_GC_Track(arg1: *mut c_void); #[cfg(not(PyPy))] pub fn PyObject_GC_UnTrack(arg1: *mut c_void); #[cfg_attr(PyPy, link_name = "PyPyObject_GC_Del")] pub fn PyObject_GC_Del(arg1: *mut c_void); } #[inline] pub unsafe fn PyObject_GC_New(typeobj: *mut PyTypeObject) -> *mut T { _PyObject_GC_New(typeobj).cast() } #[inline] pub unsafe fn PyObject_GC_NewVar(typeobj: *mut PyTypeObject, n: Py_ssize_t) -> *mut T { _PyObject_GC_NewVar(typeobj, n).cast() } extern_libpython! { #[cfg(any(all(Py_3_9, not(PyPy)), Py_3_10))] // added in 3.9, or 3.10 on PyPy #[cfg_attr(PyPy, link_name = "PyPyObject_GC_IsTracked")] pub fn PyObject_GC_IsTracked(arg1: *mut PyObject) -> c_int; #[cfg(any(all(Py_3_9, not(PyPy)), Py_3_10))] // added in 3.9, or 3.10 on PyPy #[cfg_attr(PyPy, link_name = "PyPyObject_GC_IsFinalized")] pub fn PyObject_GC_IsFinalized(arg1: *mut PyObject) -> c_int; } // skipped Py_VISIT ================================================ FILE: pyo3-ffi/src/osmodule.rs ================================================ use crate::object::PyObject; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyOS_FSPath")] pub fn PyOS_FSPath(path: *mut PyObject) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/pyarena.rs ================================================ opaque_struct!(pub PyArena); ================================================ FILE: pyo3-ffi/src/pybuffer.rs ================================================ use crate::object::PyObject; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int, c_void}; use std::ptr; #[repr(C)] #[derive(Copy, Clone)] pub struct Py_buffer { pub buf: *mut c_void, /// Owned reference pub obj: *mut crate::PyObject, pub len: Py_ssize_t, pub itemsize: Py_ssize_t, pub readonly: c_int, pub ndim: c_int, pub format: *mut c_char, pub shape: *mut Py_ssize_t, pub strides: *mut Py_ssize_t, pub suboffsets: *mut Py_ssize_t, pub internal: *mut c_void, #[cfg(PyPy)] pub flags: c_int, #[cfg(PyPy)] pub _strides: [Py_ssize_t; PyBUF_MAX_NDIM], #[cfg(PyPy)] pub _shape: [Py_ssize_t; PyBUF_MAX_NDIM], } impl Py_buffer { #[allow(clippy::new_without_default)] pub const fn new() -> Self { Py_buffer { buf: ptr::null_mut(), obj: ptr::null_mut(), len: 0, itemsize: 0, readonly: 0, ndim: 0, format: ptr::null_mut(), shape: ptr::null_mut(), strides: ptr::null_mut(), suboffsets: ptr::null_mut(), internal: ptr::null_mut(), #[cfg(PyPy)] flags: 0, #[cfg(PyPy)] _strides: [0; PyBUF_MAX_NDIM], #[cfg(PyPy)] _shape: [0; PyBUF_MAX_NDIM], } } } pub type getbufferproc = unsafe extern "C" fn(*mut PyObject, *mut crate::Py_buffer, c_int) -> c_int; pub type releasebufferproc = unsafe extern "C" fn(*mut PyObject, *mut crate::Py_buffer); /* Return 1 if the getbuffer function is available, otherwise return 0. */ extern_libpython! { #[cfg(not(PyPy))] pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")] pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")] pub fn PyBuffer_GetPointer(view: *const Py_buffer, indices: *const Py_ssize_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")] pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")] pub fn PyBuffer_ToContiguous( buf: *mut c_void, view: *const Py_buffer, len: Py_ssize_t, order: c_char, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")] pub fn PyBuffer_FromContiguous( view: *const Py_buffer, buf: *const c_void, len: Py_ssize_t, order: c_char, ) -> c_int; pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")] pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int; pub fn PyBuffer_FillContiguousStrides( ndims: c_int, shape: *mut Py_ssize_t, strides: *mut Py_ssize_t, itemsize: c_int, fort: c_char, ); #[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")] pub fn PyBuffer_FillInfo( view: *mut Py_buffer, o: *mut PyObject, buf: *mut c_void, len: Py_ssize_t, readonly: c_int, flags: c_int, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")] pub fn PyBuffer_Release(view: *mut Py_buffer); } /// Maximum number of dimensions pub const PyBUF_MAX_NDIM: usize = 64; /* Flags for getting buffers */ pub const PyBUF_SIMPLE: c_int = 0; pub const PyBUF_WRITABLE: c_int = 0x0001; /* we used to include an E, backwards compatible alias */ pub const PyBUF_WRITEABLE: c_int = PyBUF_WRITABLE; pub const PyBUF_FORMAT: c_int = 0x0004; pub const PyBUF_ND: c_int = 0x0008; pub const PyBUF_STRIDES: c_int = 0x0010 | PyBUF_ND; pub const PyBUF_C_CONTIGUOUS: c_int = 0x0020 | PyBUF_STRIDES; pub const PyBUF_F_CONTIGUOUS: c_int = 0x0040 | PyBUF_STRIDES; pub const PyBUF_ANY_CONTIGUOUS: c_int = 0x0080 | PyBUF_STRIDES; pub const PyBUF_INDIRECT: c_int = 0x0100 | PyBUF_STRIDES; pub const PyBUF_CONTIG: c_int = PyBUF_ND | PyBUF_WRITABLE; pub const PyBUF_CONTIG_RO: c_int = PyBUF_ND; pub const PyBUF_STRIDED: c_int = PyBUF_STRIDES | PyBUF_WRITABLE; pub const PyBUF_STRIDED_RO: c_int = PyBUF_STRIDES; pub const PyBUF_RECORDS: c_int = PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT; pub const PyBUF_RECORDS_RO: c_int = PyBUF_STRIDES | PyBUF_FORMAT; pub const PyBUF_FULL: c_int = PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT; pub const PyBUF_FULL_RO: c_int = PyBUF_INDIRECT | PyBUF_FORMAT; pub const PyBUF_READ: c_int = 0x100; pub const PyBUF_WRITE: c_int = 0x200; ================================================ FILE: pyo3-ffi/src/pycapsule.rs ================================================ use crate::object::*; use std::ffi::{c_char, c_int, c_void}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyCapsule_Type")] pub static mut PyCapsule_Type: PyTypeObject; } pub type PyCapsule_Destructor = unsafe extern "C" fn(o: *mut PyObject); #[inline] pub unsafe fn PyCapsule_CheckExact(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PyCapsule_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyCapsule_New")] pub fn PyCapsule_New( pointer: *mut c_void, name: *const c_char, destructor: Option, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetPointer")] pub fn PyCapsule_GetPointer(capsule: *mut PyObject, name: *const c_char) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetDestructor")] pub fn PyCapsule_GetDestructor(capsule: *mut PyObject) -> Option; #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetName")] pub fn PyCapsule_GetName(capsule: *mut PyObject) -> *const c_char; #[cfg_attr(PyPy, link_name = "PyPyCapsule_GetContext")] pub fn PyCapsule_GetContext(capsule: *mut PyObject) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyCapsule_IsValid")] pub fn PyCapsule_IsValid(capsule: *mut PyObject, name: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetPointer")] pub fn PyCapsule_SetPointer(capsule: *mut PyObject, pointer: *mut c_void) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetDestructor")] pub fn PyCapsule_SetDestructor( capsule: *mut PyObject, destructor: Option, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetName")] pub fn PyCapsule_SetName(capsule: *mut PyObject, name: *const c_char) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyCapsule_SetContext")] pub fn PyCapsule_SetContext(capsule: *mut PyObject, context: *mut c_void) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyCapsule_Import")] pub fn PyCapsule_Import(name: *const c_char, no_block: c_int) -> *mut c_void; } ================================================ FILE: pyo3-ffi/src/pyerrors.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyErr_SetNone")] pub fn PyErr_SetNone(arg1: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyErr_SetObject")] pub fn PyErr_SetObject(arg1: *mut PyObject, arg2: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyErr_SetString")] pub fn PyErr_SetString(exception: *mut PyObject, string: *const c_char); #[cfg_attr(PyPy, link_name = "PyPyErr_Occurred")] pub fn PyErr_Occurred() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_Clear")] pub fn PyErr_Clear(); #[cfg_attr(Py_3_12, deprecated(note = "Use PyErr_GetRaisedException() instead."))] #[cfg_attr(PyPy, link_name = "PyPyErr_Fetch")] pub fn PyErr_Fetch( arg1: *mut *mut PyObject, arg2: *mut *mut PyObject, arg3: *mut *mut PyObject, ); #[cfg_attr(Py_3_12, deprecated(note = "Use PyErr_SetRaisedException() instead."))] #[cfg_attr(PyPy, link_name = "PyPyErr_Restore")] pub fn PyErr_Restore(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyErr_GetExcInfo")] pub fn PyErr_GetExcInfo( arg1: *mut *mut PyObject, arg2: *mut *mut PyObject, arg3: *mut *mut PyObject, ); #[cfg_attr(PyPy, link_name = "PyPyErr_SetExcInfo")] pub fn PyErr_SetExcInfo(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPy_FatalError")] pub fn Py_FatalError(message: *const c_char) -> !; #[cfg_attr(PyPy, link_name = "PyPyErr_GivenExceptionMatches")] pub fn PyErr_GivenExceptionMatches(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyErr_ExceptionMatches")] pub fn PyErr_ExceptionMatches(arg1: *mut PyObject) -> c_int; #[cfg_attr( Py_3_12, deprecated( note = "Use PyErr_GetRaisedException() instead, to avoid any possible de-normalization." ) )] #[cfg_attr(PyPy, link_name = "PyPyErr_NormalizeException")] pub fn PyErr_NormalizeException( arg1: *mut *mut PyObject, arg2: *mut *mut PyObject, arg3: *mut *mut PyObject, ); #[cfg(Py_3_12)] pub fn PyErr_GetRaisedException() -> *mut PyObject; #[cfg(Py_3_12)] pub fn PyErr_SetRaisedException(exc: *mut PyObject); #[cfg(Py_3_11)] #[cfg_attr(PyPy, link_name = "PyPyErr_GetHandledException")] pub fn PyErr_GetHandledException() -> *mut PyObject; #[cfg(Py_3_11)] #[cfg_attr(PyPy, link_name = "PyPyErr_SetHandledException")] pub fn PyErr_SetHandledException(exc: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyException_SetTraceback")] pub fn PyException_SetTraceback(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyException_GetTraceback")] pub fn PyException_GetTraceback(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyException_GetCause")] pub fn PyException_GetCause(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyException_SetCause")] pub fn PyException_SetCause(arg1: *mut PyObject, arg2: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyException_GetContext")] pub fn PyException_GetContext(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyException_SetContext")] pub fn PyException_SetContext(arg1: *mut PyObject, arg2: *mut PyObject); #[cfg(PyPy)] #[link_name = "PyPyExceptionInstance_Class"] pub fn PyExceptionInstance_Class(x: *mut PyObject) -> *mut PyObject; } #[inline] pub unsafe fn PyExceptionClass_Check(x: *mut PyObject) -> c_int { (PyType_Check(x) != 0 && PyType_FastSubclass(x as *mut PyTypeObject, Py_TPFLAGS_BASE_EXC_SUBCLASS) != 0) as c_int } #[inline] pub unsafe fn PyExceptionInstance_Check(x: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(x), Py_TPFLAGS_BASE_EXC_SUBCLASS) } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyExceptionInstance_Class(x: *mut PyObject) -> *mut PyObject { Py_TYPE(x) as *mut PyObject } // ported from cpython exception.c (line 2096) #[cfg(PyPy)] pub unsafe fn PyUnicodeDecodeError_Create( encoding: *const c_char, object: *const c_char, length: Py_ssize_t, start: Py_ssize_t, end: Py_ssize_t, reason: *const c_char, ) -> *mut PyObject { crate::_PyObject_CallFunction_SizeT( PyExc_UnicodeDecodeError, c"sy#nns".as_ptr(), encoding, object, length, start, end, reason, ) } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyExc_BaseException")] pub static mut PyExc_BaseException: *mut PyObject; #[cfg(Py_3_11)] #[cfg_attr(PyPy, link_name = "PyPyExc_BaseExceptionGroup")] pub static mut PyExc_BaseExceptionGroup: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_Exception")] pub static mut PyExc_Exception: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_StopAsyncIteration")] pub static mut PyExc_StopAsyncIteration: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_StopIteration")] pub static mut PyExc_StopIteration: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_GeneratorExit")] pub static mut PyExc_GeneratorExit: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ArithmeticError")] pub static mut PyExc_ArithmeticError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_LookupError")] pub static mut PyExc_LookupError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_AssertionError")] pub static mut PyExc_AssertionError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_AttributeError")] pub static mut PyExc_AttributeError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_BufferError")] pub static mut PyExc_BufferError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_EOFError")] pub static mut PyExc_EOFError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_FloatingPointError")] pub static mut PyExc_FloatingPointError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] pub static mut PyExc_OSError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ImportError")] pub static mut PyExc_ImportError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ModuleNotFoundError")] pub static mut PyExc_ModuleNotFoundError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_IndexError")] pub static mut PyExc_IndexError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_KeyError")] pub static mut PyExc_KeyError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_KeyboardInterrupt")] pub static mut PyExc_KeyboardInterrupt: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_MemoryError")] pub static mut PyExc_MemoryError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_NameError")] pub static mut PyExc_NameError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_OverflowError")] pub static mut PyExc_OverflowError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_RuntimeError")] pub static mut PyExc_RuntimeError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_RecursionError")] pub static mut PyExc_RecursionError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_NotImplementedError")] pub static mut PyExc_NotImplementedError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_SyntaxError")] pub static mut PyExc_SyntaxError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_IndentationError")] pub static mut PyExc_IndentationError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_TabError")] pub static mut PyExc_TabError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ReferenceError")] pub static mut PyExc_ReferenceError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_SystemError")] pub static mut PyExc_SystemError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_SystemExit")] pub static mut PyExc_SystemExit: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_TypeError")] pub static mut PyExc_TypeError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_UnboundLocalError")] pub static mut PyExc_UnboundLocalError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeError")] pub static mut PyExc_UnicodeError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeEncodeError")] pub static mut PyExc_UnicodeEncodeError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeDecodeError")] pub static mut PyExc_UnicodeDecodeError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeTranslateError")] pub static mut PyExc_UnicodeTranslateError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ValueError")] pub static mut PyExc_ValueError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ZeroDivisionError")] pub static mut PyExc_ZeroDivisionError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_BlockingIOError")] pub static mut PyExc_BlockingIOError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_BrokenPipeError")] pub static mut PyExc_BrokenPipeError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ChildProcessError")] pub static mut PyExc_ChildProcessError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionError")] pub static mut PyExc_ConnectionError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionAbortedError")] pub static mut PyExc_ConnectionAbortedError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionRefusedError")] pub static mut PyExc_ConnectionRefusedError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ConnectionResetError")] pub static mut PyExc_ConnectionResetError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_FileExistsError")] pub static mut PyExc_FileExistsError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_FileNotFoundError")] pub static mut PyExc_FileNotFoundError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_InterruptedError")] pub static mut PyExc_InterruptedError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_IsADirectoryError")] pub static mut PyExc_IsADirectoryError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_NotADirectoryError")] pub static mut PyExc_NotADirectoryError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_PermissionError")] pub static mut PyExc_PermissionError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ProcessLookupError")] pub static mut PyExc_ProcessLookupError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_TimeoutError")] pub static mut PyExc_TimeoutError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] pub static mut PyExc_EnvironmentError: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] pub static mut PyExc_IOError: *mut PyObject; #[cfg(windows)] #[cfg_attr(PyPy, link_name = "PyPyExc_OSError")] pub static mut PyExc_WindowsError: *mut PyObject; pub static mut PyExc_RecursionErrorInst: *mut PyObject; /* Predefined warning categories */ #[cfg_attr(PyPy, link_name = "PyPyExc_Warning")] pub static mut PyExc_Warning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_UserWarning")] pub static mut PyExc_UserWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_DeprecationWarning")] pub static mut PyExc_DeprecationWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_PendingDeprecationWarning")] pub static mut PyExc_PendingDeprecationWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_SyntaxWarning")] pub static mut PyExc_SyntaxWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_RuntimeWarning")] pub static mut PyExc_RuntimeWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_FutureWarning")] pub static mut PyExc_FutureWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ImportWarning")] pub static mut PyExc_ImportWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_UnicodeWarning")] pub static mut PyExc_UnicodeWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_BytesWarning")] pub static mut PyExc_BytesWarning: *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyExc_ResourceWarning")] pub static mut PyExc_ResourceWarning: *mut PyObject; #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyExc_EncodingWarning")] pub static mut PyExc_EncodingWarning: *mut PyObject; } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyErr_BadArgument")] pub fn PyErr_BadArgument() -> c_int; #[cfg_attr(PyPy, link_name = "PyPyErr_NoMemory")] pub fn PyErr_NoMemory() -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_SetFromErrno")] pub fn PyErr_SetFromErrno(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_SetFromErrnoWithFilenameObject")] pub fn PyErr_SetFromErrnoWithFilenameObject( arg1: *mut PyObject, arg2: *mut PyObject, ) -> *mut PyObject; pub fn PyErr_SetFromErrnoWithFilenameObjects( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject, ) -> *mut PyObject; pub fn PyErr_SetFromErrnoWithFilename( exc: *mut PyObject, filename: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_Format")] pub fn PyErr_Format(exception: *mut PyObject, format: *const c_char, ...) -> *mut PyObject; pub fn PyErr_SetImportErrorSubclass( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject, arg4: *mut PyObject, ) -> *mut PyObject; pub fn PyErr_SetImportError( arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_BadInternalCall")] pub fn PyErr_BadInternalCall(); pub fn _PyErr_BadInternalCall(filename: *const c_char, lineno: c_int); #[cfg_attr(PyPy, link_name = "PyPyErr_NewException")] pub fn PyErr_NewException( name: *const c_char, base: *mut PyObject, dict: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_NewExceptionWithDoc")] pub fn PyErr_NewExceptionWithDoc( name: *const c_char, doc: *const c_char, base: *mut PyObject, dict: *mut PyObject, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_WriteUnraisable")] pub fn PyErr_WriteUnraisable(arg1: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyErr_CheckSignals")] pub fn PyErr_CheckSignals() -> c_int; #[cfg_attr(PyPy, link_name = "PyPyErr_SetInterrupt")] pub fn PyErr_SetInterrupt(); #[cfg(Py_3_10)] #[cfg_attr(PyPy, link_name = "PyPyErr_SetInterruptEx")] pub fn PyErr_SetInterruptEx(signum: c_int); #[cfg_attr(PyPy, link_name = "PyPyErr_SyntaxLocation")] pub fn PyErr_SyntaxLocation(filename: *const c_char, lineno: c_int); #[cfg_attr(PyPy, link_name = "PyPyErr_SyntaxLocationEx")] pub fn PyErr_SyntaxLocationEx(filename: *const c_char, lineno: c_int, col_offset: c_int); #[cfg_attr(PyPy, link_name = "PyPyErr_ProgramText")] pub fn PyErr_ProgramText(filename: *const c_char, lineno: c_int) -> *mut PyObject; #[cfg(not(PyPy))] pub fn PyUnicodeDecodeError_Create( encoding: *const c_char, object: *const c_char, length: Py_ssize_t, start: Py_ssize_t, end: Py_ssize_t, reason: *const c_char, ) -> *mut PyObject; pub fn PyUnicodeEncodeError_GetEncoding(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeDecodeError_GetEncoding(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeEncodeError_GetObject(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeDecodeError_GetObject(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeTranslateError_GetObject(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeEncodeError_GetStart(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; pub fn PyUnicodeDecodeError_GetStart(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; pub fn PyUnicodeTranslateError_GetStart(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; pub fn PyUnicodeEncodeError_SetStart(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; pub fn PyUnicodeDecodeError_SetStart(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; pub fn PyUnicodeTranslateError_SetStart(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; pub fn PyUnicodeEncodeError_GetEnd(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; pub fn PyUnicodeDecodeError_GetEnd(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; pub fn PyUnicodeTranslateError_GetEnd(arg1: *mut PyObject, arg2: *mut Py_ssize_t) -> c_int; pub fn PyUnicodeEncodeError_SetEnd(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; pub fn PyUnicodeDecodeError_SetEnd(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; pub fn PyUnicodeTranslateError_SetEnd(arg1: *mut PyObject, arg2: Py_ssize_t) -> c_int; pub fn PyUnicodeEncodeError_GetReason(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeDecodeError_GetReason(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeTranslateError_GetReason(arg1: *mut PyObject) -> *mut PyObject; pub fn PyUnicodeEncodeError_SetReason(exc: *mut PyObject, reason: *const c_char) -> c_int; pub fn PyUnicodeDecodeError_SetReason(exc: *mut PyObject, reason: *const c_char) -> c_int; pub fn PyUnicodeTranslateError_SetReason(exc: *mut PyObject, reason: *const c_char) -> c_int; } ================================================ FILE: pyo3-ffi/src/pyframe.rs ================================================ #[cfg(not(GraalPy))] #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] use crate::PyCodeObject; use crate::PyFrameObject; use std::ffi::c_int; extern_libpython! { pub fn PyFrame_GetLineNumber(frame: *mut PyFrameObject) -> c_int; #[cfg(not(GraalPy))] #[cfg(any(Py_3_10, all(Py_3_9, not(Py_LIMITED_API))))] pub fn PyFrame_GetCode(frame: *mut PyFrameObject) -> *mut PyCodeObject; } ================================================ FILE: pyo3-ffi/src/pyhash.rs ================================================ #[cfg(not(any(Py_LIMITED_API, PyPy)))] use crate::pyport::{Py_hash_t, Py_ssize_t}; #[cfg(not(any(Py_LIMITED_API, PyPy)))] use std::ffi::c_void; use std::ffi::{c_int, c_ulong}; extern_libpython! { // skipped non-limited _Py_HashDouble // skipped non-limited _Py_HashPointer // skipped non-limited _Py_HashPointerRaw #[cfg(not(any(Py_LIMITED_API, PyPy)))] pub fn _Py_HashBytes(src: *const c_void, len: Py_ssize_t) -> Py_hash_t; } pub const _PyHASH_MULTIPLIER: c_ulong = 1000003; // skipped _PyHASH_BITS // skipped non-limited _Py_HashSecret_t // skipped Py_HASH_CUTOFF pub const Py_HASH_EXTERNAL: c_int = 0; pub const Py_HASH_SIPHASH24: c_int = 1; pub const Py_HASH_FNV: c_int = 2; #[cfg(Py_3_11)] pub const Py_HASH_SIPHASH13: c_int = 3; // skipped Py_HASH_ALGORITHM ================================================ FILE: pyo3-ffi/src/pylifecycle.rs ================================================ use crate::pytypedefs::PyThreadState; use libc::wchar_t; use std::ffi::{c_char, c_int}; extern_libpython! { pub fn Py_Initialize(); pub fn Py_InitializeEx(arg1: c_int); pub fn Py_Finalize(); pub fn Py_FinalizeEx() -> c_int; #[cfg_attr(PyPy, link_name = "PyPy_IsInitialized")] pub fn Py_IsInitialized() -> c_int; pub fn Py_NewInterpreter() -> *mut PyThreadState; pub fn Py_EndInterpreter(arg1: *mut PyThreadState); #[cfg_attr(PyPy, link_name = "PyPy_AtExit")] pub fn Py_AtExit(func: Option) -> c_int; pub fn Py_Exit(arg1: c_int) -> !; pub fn Py_Main(argc: c_int, argv: *mut *mut wchar_t) -> c_int; pub fn Py_BytesMain(argc: c_int, argv: *mut *mut c_char) -> c_int; #[cfg_attr( Py_3_11, deprecated(note = "Deprecated since Python 3.11. Use `PyConfig.program_name` instead.") )] pub fn Py_SetProgramName(arg1: *const wchar_t); #[cfg_attr(PyPy, link_name = "PyPy_GetProgramName")] #[cfg_attr( Py_3_13, deprecated(note = "Deprecated since Python 3.13. Use `sys.executable` instead.") )] pub fn Py_GetProgramName() -> *mut wchar_t; #[cfg_attr( Py_3_11, deprecated(note = "Deprecated since Python 3.11. Use `PyConfig.home` instead.") )] pub fn Py_SetPythonHome(arg1: *const wchar_t); #[cfg_attr( Py_3_13, deprecated( note = "Deprecated since Python 3.13. Use `PyConfig.home` or the value of the `PYTHONHOME` environment variable instead." ) )] pub fn Py_GetPythonHome() -> *mut wchar_t; #[cfg_attr( Py_3_13, deprecated(note = "Deprecated since Python 3.13. Use `sys.executable` instead.") )] pub fn Py_GetProgramFullPath() -> *mut wchar_t; #[cfg_attr( Py_3_13, deprecated(note = "Deprecated since Python 3.13. Use `sys.prefix` instead.") )] pub fn Py_GetPrefix() -> *mut wchar_t; #[cfg_attr( Py_3_13, deprecated(note = "Deprecated since Python 3.13. Use `sys.exec_prefix` instead.") )] pub fn Py_GetExecPrefix() -> *mut wchar_t; #[cfg_attr( Py_3_13, deprecated(note = "Deprecated since Python 3.13. Use `sys.path` instead.") )] pub fn Py_GetPath() -> *mut wchar_t; #[cfg(not(Py_3_13))] #[cfg_attr( Py_3_11, deprecated(note = "Deprecated since Python 3.11. Use `sys.path` instead.") )] pub fn Py_SetPath(arg1: *const wchar_t); // skipped _Py_CheckPython3 #[cfg_attr(PyPy, link_name = "PyPy_GetVersion")] pub fn Py_GetVersion() -> *const c_char; pub fn Py_GetPlatform() -> *const c_char; pub fn Py_GetCopyright() -> *const c_char; pub fn Py_GetCompiler() -> *const c_char; pub fn Py_GetBuildInfo() -> *const c_char; } type PyOS_sighandler_t = unsafe extern "C" fn(arg1: c_int); extern_libpython! { pub fn PyOS_getsig(arg1: c_int) -> PyOS_sighandler_t; pub fn PyOS_setsig(arg1: c_int, arg2: PyOS_sighandler_t) -> PyOS_sighandler_t; #[cfg(Py_3_11)] pub static Py_Version: std::ffi::c_ulong; #[cfg(Py_3_13)] pub fn Py_IsFinalizing() -> c_int; } ================================================ FILE: pyo3-ffi/src/pymem.rs ================================================ use libc::size_t; use std::ffi::c_void; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyMem_Malloc")] pub fn PyMem_Malloc(size: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyMem_Calloc")] pub fn PyMem_Calloc(nelem: size_t, elsize: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyMem_Realloc")] pub fn PyMem_Realloc(ptr: *mut c_void, new_size: size_t) -> *mut c_void; #[cfg_attr(PyPy, link_name = "PyPyMem_Free")] pub fn PyMem_Free(ptr: *mut c_void); } ================================================ FILE: pyo3-ffi/src/pyport.rs ================================================ // NB libc does not define this constant on all platforms, so we hard code it // like CPython does. // https://github.com/python/cpython/blob/d8b9011702443bb57579f8834f3effe58e290dfc/Include/pyport.h#L372 pub const INT_MAX: std::ffi::c_int = 2147483647; pub type PY_UINT32_T = u32; pub type PY_UINT64_T = u64; pub type PY_INT32_T = i32; pub type PY_INT64_T = i64; pub type Py_uintptr_t = ::libc::uintptr_t; pub type Py_intptr_t = ::libc::intptr_t; pub type Py_ssize_t = ::libc::ssize_t; pub type Py_hash_t = Py_ssize_t; pub type Py_uhash_t = ::libc::size_t; pub const PY_SSIZE_T_MIN: Py_ssize_t = Py_ssize_t::MIN; pub const PY_SSIZE_T_MAX: Py_ssize_t = Py_ssize_t::MAX; #[cfg(target_endian = "big")] pub const PY_BIG_ENDIAN: usize = 1; #[cfg(target_endian = "big")] pub const PY_LITTLE_ENDIAN: usize = 0; #[cfg(target_endian = "little")] pub const PY_BIG_ENDIAN: usize = 0; #[cfg(target_endian = "little")] pub const PY_LITTLE_ENDIAN: usize = 1; ================================================ FILE: pyo3-ffi/src/pystate.rs ================================================ use crate::moduleobject::PyModuleDef; use crate::object::PyObject; use crate::pytypedefs::{PyInterpreterState, PyThreadState}; use std::ffi::c_int; #[cfg(any(all(Py_3_9, not(Py_LIMITED_API)), Py_3_10))] #[cfg(not(PyPy))] use crate::PyFrameObject; #[cfg(not(PyPy))] use std::ffi::c_long; pub const MAX_CO_EXTRA_USERS: c_int = 255; extern_libpython! { #[cfg(not(PyPy))] pub fn PyInterpreterState_New() -> *mut PyInterpreterState; #[cfg(not(PyPy))] pub fn PyInterpreterState_Clear(arg1: *mut PyInterpreterState); #[cfg(not(PyPy))] pub fn PyInterpreterState_Delete(arg1: *mut PyInterpreterState); #[cfg(all(Py_3_9, not(PyPy)))] pub fn PyInterpreterState_Get() -> *mut PyInterpreterState; #[cfg(all(Py_3_8, not(PyPy)))] pub fn PyInterpreterState_GetDict(arg1: *mut PyInterpreterState) -> *mut PyObject; #[cfg(not(PyPy))] pub fn PyInterpreterState_GetID(arg1: *mut PyInterpreterState) -> i64; #[cfg_attr(PyPy, link_name = "PyPyState_AddModule")] pub fn PyState_AddModule(arg1: *mut PyObject, arg2: *mut PyModuleDef) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyState_RemoveModule")] pub fn PyState_RemoveModule(arg1: *mut PyModuleDef) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyState_FindModule")] pub fn PyState_FindModule(arg1: *mut PyModuleDef) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyThreadState_New")] pub fn PyThreadState_New(arg1: *mut PyInterpreterState) -> *mut PyThreadState; #[cfg_attr(PyPy, link_name = "PyPyThreadState_Clear")] pub fn PyThreadState_Clear(arg1: *mut PyThreadState); #[cfg_attr(PyPy, link_name = "PyPyThreadState_Delete")] pub fn PyThreadState_Delete(arg1: *mut PyThreadState); #[cfg_attr(PyPy, link_name = "PyPyThreadState_Get")] pub fn PyThreadState_Get() -> *mut PyThreadState; } #[inline] pub unsafe fn PyThreadState_GET() -> *mut PyThreadState { PyThreadState_Get() } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyThreadState_Swap")] pub fn PyThreadState_Swap(arg1: *mut PyThreadState) -> *mut PyThreadState; #[cfg_attr(PyPy, link_name = "PyPyThreadState_GetDict")] pub fn PyThreadState_GetDict() -> *mut PyObject; #[cfg(not(PyPy))] pub fn PyThreadState_SetAsyncExc(arg1: c_long, arg2: *mut PyObject) -> c_int; #[cfg(any(all(Py_3_9, not(Py_LIMITED_API)), Py_3_10))] #[cfg(not(PyPy))] pub fn PyThreadState_GetInterpreter(arg1: *mut PyThreadState) -> *mut PyInterpreterState; #[cfg(any(all(Py_3_9, not(Py_LIMITED_API)), Py_3_10))] #[cfg(not(PyPy))] pub fn PyThreadState_GetFrame(arg1: *mut PyThreadState) -> *mut PyFrameObject; #[cfg(any(all(Py_3_9, not(Py_LIMITED_API)), Py_3_10))] #[cfg(not(PyPy))] pub fn PyThreadState_GetID(arg1: *mut PyThreadState) -> i64; } #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PyGILState_STATE { PyGILState_LOCKED, PyGILState_UNLOCKED, } #[cfg(not(any(Py_3_14, target_arch = "wasm32")))] struct HangThread; #[cfg(not(any(Py_3_14, target_arch = "wasm32")))] impl Drop for HangThread { fn drop(&mut self) { loop { std::thread::park(); // Block forever. } } } // The PyGILState_Ensure function will call pthread_exit during interpreter shutdown, // which causes undefined behavior. Redirect to the "safe" version that hangs instead, // as Python 3.14 does. // // See https://github.com/rust-lang/rust/issues/135929 // C-unwind only supported (and necessary) since 1.71. Python 3.14+ does not do // pthread_exit from PyGILState_Ensure (https://github.com/python/cpython/issues/87135). mod raw { #[cfg(not(any(Py_3_14, target_arch = "wasm32")))] extern_libpython! { "C-unwind" { #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] pub fn PyGILState_Ensure() -> super::PyGILState_STATE; }} #[cfg(any(Py_3_14, target_arch = "wasm32"))] extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] pub fn PyGILState_Ensure() -> super::PyGILState_STATE; } } #[cfg(not(any(Py_3_14, target_arch = "wasm32")))] pub unsafe extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { let guard = HangThread; // If `PyGILState_Ensure` calls `pthread_exit`, which it does on Python < 3.14 // when the interpreter is shutting down, this will cause a forced unwind. // doing a forced unwind through a function with a Rust destructor is unspecified // behavior. // // However, currently it runs the destructor, which will cause the thread to // hang as it should. // // And if we don't catch the unwinding here, then one of our callers probably has a destructor, // so it's unspecified behavior anyway, and on many configurations causes the process to abort. // // The alternative is for pyo3 to contain custom C or C++ code that catches the `pthread_exit`, // but that's also annoying from a portability point of view. // // On Windows, `PyGILState_Ensure` calls `_endthreadex` instead, which AFAICT can't be caught // and therefore will cause unsafety if there are pinned objects on the stack. AFAICT there's // nothing we can do it other than waiting for Python 3.14 or not using Windows. At least, // if there is nothing pinned on the stack, it won't cause the process to crash. let ret: PyGILState_STATE = raw::PyGILState_Ensure(); std::mem::forget(guard); ret } #[cfg(any(Py_3_14, target_arch = "wasm32"))] pub use self::raw::PyGILState_Ensure; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyGILState_Release")] pub fn PyGILState_Release(arg1: PyGILState_STATE); #[cfg(not(PyPy))] pub fn PyGILState_GetThisThreadState() -> *mut PyThreadState; } ================================================ FILE: pyo3-ffi/src/pystrtod.rs ================================================ use crate::object::PyObject; use std::ffi::{c_char, c_double, c_int}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyOS_string_to_double")] pub fn PyOS_string_to_double( str: *const c_char, endptr: *mut *mut c_char, overflow_exception: *mut PyObject, ) -> c_double; #[cfg_attr(PyPy, link_name = "PyPyOS_double_to_string")] pub fn PyOS_double_to_string( val: c_double, format_code: c_char, precision: c_int, flags: c_int, _type: *mut c_int, ) -> *mut c_char; } // skipped non-limited _Py_string_to_number_with_underscores // skipped non-limited _Py_parse_inf_or_nan /* PyOS_double_to_string's "flags" parameter can be set to 0 or more of: */ pub const Py_DTSF_SIGN: c_int = 0x01; /* always add the sign */ pub const Py_DTSF_ADD_DOT_0: c_int = 0x02; /* if the result is an integer add ".0" */ pub const Py_DTSF_ALT: c_int = 0x04; /* "alternate" formatting. it's format_code specific */ /* PyOS_double_to_string's "type", if non-NULL, will be set to one of: */ pub const Py_DTST_FINITE: c_int = 0; pub const Py_DTST_INFINITE: c_int = 1; pub const Py_DTST_NAN: c_int = 2; ================================================ FILE: pyo3-ffi/src/pythonrun.rs ================================================ use crate::object::*; #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] use libc::FILE; #[cfg(any(Py_LIMITED_API, not(Py_3_10), PyPy, GraalPy))] use std::ffi::c_char; use std::ffi::c_int; extern_libpython! { #[cfg(any(all(Py_LIMITED_API, not(PyPy)), GraalPy))] pub fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyErr_Print")] pub fn PyErr_Print(); #[cfg_attr(PyPy, link_name = "PyPyErr_PrintEx")] pub fn PyErr_PrintEx(arg1: c_int); #[cfg_attr(PyPy, link_name = "PyPyErr_Display")] pub fn PyErr_Display(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); #[cfg(Py_3_12)] pub fn PyErr_DisplayException(exc: *mut PyObject); } #[inline] #[cfg(PyPy)] pub unsafe fn Py_CompileString(string: *const c_char, p: *const c_char, s: c_int) -> *mut PyObject { // PyPy's implementation of Py_CompileString always forwards to Py_CompileStringFlags; this // is only available in the non-limited API and has a real definition for all versions in // the cpython/ subdirectory. #[cfg(Py_LIMITED_API)] extern_libpython! { #[link_name = "PyPy_CompileStringFlags"] pub fn Py_CompileStringFlags( string: *const c_char, p: *const c_char, s: c_int, f: *mut std::ffi::c_void, // Actually *mut Py_CompilerFlags in the real definition ) -> *mut PyObject; } #[cfg(not(Py_LIMITED_API))] use crate::Py_CompileStringFlags; Py_CompileStringFlags(string, p, s, std::ptr::null_mut()) } // skipped PyOS_InputHook pub const PYOS_STACK_MARGIN: c_int = 2048; // skipped PyOS_CheckStack under Microsoft C #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] opaque_struct!(pub _mod); #[cfg(not(any(PyPy, Py_3_10)))] opaque_struct!(pub symtable); #[cfg(not(any(PyPy, Py_3_10)))] opaque_struct!(pub _node); #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] #[inline] pub unsafe fn PyParser_SimpleParseString(s: *const c_char, b: c_int) -> *mut _node { #[allow(deprecated)] crate::PyParser_SimpleParseStringFlags(s, b, 0) } #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] #[inline] pub unsafe fn PyParser_SimpleParseFile(fp: *mut FILE, s: *const c_char, b: c_int) -> *mut _node { #[allow(deprecated)] crate::PyParser_SimpleParseFileFlags(fp, s, b, 0) } extern_libpython! { #[cfg(not(any(PyPy, Py_3_10)))] pub fn Py_SymtableString( str: *const c_char, filename: *const c_char, start: c_int, ) -> *mut symtable; #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] pub fn Py_SymtableStringObject( str: *const c_char, filename: *mut PyObject, start: c_int, ) -> *mut symtable; } ================================================ FILE: pyo3-ffi/src/pytypedefs.rs ================================================ // NB: unlike C, we do not need to forward declare structs in Rust. // So we only define opaque structs for those which do not have public structure. // PyModuleDef // PyModuleDef_Slot // PyMethodDef // PyGetSetDef // PyMemberDef // PyObject // PyLongObject // PyTypeObject opaque_struct!(pub PyCodeObject); opaque_struct!(pub PyFrameObject); opaque_struct!(pub PyThreadState); opaque_struct!(pub PyInterpreterState); ================================================ FILE: pyo3-ffi/src/rangeobject.rs ================================================ use crate::object::*; use std::ffi::c_int; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyRange_Type")] pub static mut PyRange_Type: PyTypeObject; pub static mut PyRangeIter_Type: PyTypeObject; pub static mut PyLongRangeIter_Type: PyTypeObject; } #[inline] pub unsafe fn PyRange_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyRange_Type) as c_int } ================================================ FILE: pyo3-ffi/src/refcount.rs ================================================ use crate::pyport::Py_ssize_t; use crate::PyObject; #[cfg(all(not(Py_LIMITED_API), py_sys_config = "Py_REF_DEBUG"))] use std::ffi::c_char; #[cfg(any(Py_3_12, all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API))))] use std::ffi::c_int; #[cfg(all(Py_3_14, any(not(Py_GIL_DISABLED), target_pointer_width = "32")))] use std::ffi::c_long; #[cfg(any(Py_GIL_DISABLED, all(Py_3_12, not(Py_3_14))))] use std::ffi::c_uint; #[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] use std::ffi::c_ulong; use std::ptr; #[cfg(Py_GIL_DISABLED)] use std::sync::atomic::Ordering::Relaxed; #[cfg(all(Py_3_14, not(Py_3_15)))] const _Py_STATICALLY_ALLOCATED_FLAG: c_int = 1 << 7; #[cfg(Py_3_15)] pub(crate) const _Py_STATICALLY_ALLOCATED_FLAG: c_int = 1 << 2; #[cfg(all(Py_3_12, not(Py_3_14)))] const _Py_IMMORTAL_REFCNT: Py_ssize_t = { if cfg!(target_pointer_width = "64") { c_uint::MAX as Py_ssize_t } else { // for 32-bit systems, use the lower 30 bits (see comment in CPython's object.h) (c_uint::MAX >> 2) as Py_ssize_t } }; // comments in Python.h about the choices for these constants #[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = { if cfg!(target_pointer_width = "64") { ((3 as c_ulong) << (30 as c_ulong)) as Py_ssize_t } else { ((5 as c_long) << (28 as c_long)) as Py_ssize_t } }; #[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] const _Py_STATIC_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = { if cfg!(target_pointer_width = "64") { _Py_IMMORTAL_INITIAL_REFCNT | ((_Py_STATICALLY_ALLOCATED_FLAG as Py_ssize_t) << (32 as Py_ssize_t)) } else { ((7 as c_long) << (28 as c_long)) as Py_ssize_t } }; #[cfg(all(Py_3_14, target_pointer_width = "32"))] const _Py_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t = ((1 as c_long) << (30 as c_long)) as Py_ssize_t; #[cfg(all(Py_3_14, target_pointer_width = "32"))] const _Py_STATIC_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t = ((6 as c_long) << (28 as c_long)) as Py_ssize_t; #[cfg(all(Py_3_14, Py_GIL_DISABLED))] const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = c_uint::MAX as Py_ssize_t; #[cfg(Py_GIL_DISABLED)] pub(crate) const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX; #[cfg(Py_GIL_DISABLED)] const _Py_REF_SHARED_SHIFT: isize = 2; // skipped private _Py_REF_SHARED_FLAG_MASK // skipped private _Py_REF_SHARED_INIT // skipped private _Py_REF_MAYBE_WEAKREF // skipped private _Py_REF_QUEUED // skipped private _Py_REF_MERGED // skipped private _Py_REF_SHARED extern_libpython! { #[cfg(all(Py_3_14, Py_LIMITED_API))] pub fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t; } #[cfg(not(all(Py_3_14, Py_LIMITED_API)))] #[inline] pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { #[cfg(Py_GIL_DISABLED)] { let local = (*ob).ob_ref_local.load(Relaxed); if local == _Py_IMMORTAL_REFCNT_LOCAL { #[cfg(not(Py_3_14))] return _Py_IMMORTAL_REFCNT; #[cfg(Py_3_14)] return _Py_IMMORTAL_INITIAL_REFCNT; } let shared = (*ob).ob_ref_shared.load(Relaxed); local as Py_ssize_t + Py_ssize_t::from(shared >> _Py_REF_SHARED_SHIFT) } #[cfg(all(Py_LIMITED_API, Py_3_14))] { Py_REFCNT(ob) } #[cfg(all(not(Py_GIL_DISABLED), not(all(Py_LIMITED_API, Py_3_14)), Py_3_12))] { (*ob).ob_refcnt.ob_refcnt } #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), not(GraalPy)))] { (*ob).ob_refcnt } #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), GraalPy))] { _Py_REFCNT(ob) } } #[cfg(Py_3_12)] #[inline(always)] unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { #[cfg(all(target_pointer_width = "64", not(Py_GIL_DISABLED)))] { (((*op).ob_refcnt.ob_refcnt as crate::PY_INT32_T) < 0) as c_int } #[cfg(all(target_pointer_width = "32", not(Py_GIL_DISABLED)))] { #[cfg(not(Py_3_14))] { ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as c_int } #[cfg(Py_3_14)] { ((*op).ob_refcnt.ob_refcnt >= _Py_IMMORTAL_MINIMUM_REFCNT) as c_int } } #[cfg(Py_GIL_DISABLED)] { ((*op).ob_ref_local.load(Relaxed) == _Py_IMMORTAL_REFCNT_LOCAL) as c_int } } // skipped _Py_IsStaticImmortal // TODO: Py_SET_REFCNT extern_libpython! { #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] fn _Py_NegativeRefcount(filename: *const c_char, lineno: c_int, op: *mut PyObject); #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] fn _Py_INCREF_IncRefTotal(); #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] fn _Py_DECREF_DecRefTotal(); #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")] fn _Py_Dealloc(arg1: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] #[cfg_attr(GraalPy, link_name = "_Py_IncRef")] pub fn Py_IncRef(o: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] #[cfg_attr(GraalPy, link_name = "_Py_DecRef")] pub fn Py_DecRef(o: *mut PyObject); #[cfg(all(Py_3_10, not(PyPy)))] fn _Py_IncRef(o: *mut PyObject); #[cfg(all(Py_3_10, not(PyPy)))] fn _Py_DecRef(o: *mut PyObject); #[cfg(GraalPy)] fn _Py_REFCNT(arg1: *const PyObject) -> Py_ssize_t; } #[inline(always)] pub unsafe fn Py_INCREF(op: *mut PyObject) { // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. #[cfg(any( Py_GIL_DISABLED, Py_LIMITED_API, py_sys_config = "Py_REF_DEBUG", GraalPy ))] { // _Py_IncRef was added to the ABI in 3.10; skips null checks #[cfg(all(Py_3_10, not(PyPy)))] { _Py_IncRef(op); } #[cfg(any(not(Py_3_10), PyPy))] { Py_IncRef(op); } } // version-specific builds are allowed to directly manipulate the reference count #[cfg(not(any( Py_GIL_DISABLED, Py_LIMITED_API, py_sys_config = "Py_REF_DEBUG", GraalPy )))] { #[cfg(all(Py_3_14, target_pointer_width = "64"))] { let cur_refcnt = (*op).ob_refcnt.ob_refcnt; if (cur_refcnt as i32) < 0 { return; } (*op).ob_refcnt.ob_refcnt = cur_refcnt.wrapping_add(1); } #[cfg(all(Py_3_12, not(Py_3_14), target_pointer_width = "64"))] { let cur_refcnt = (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN]; let new_refcnt = cur_refcnt.wrapping_add(1); if new_refcnt == 0 { return; } (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN] = new_refcnt; } #[cfg(all(Py_3_12, target_pointer_width = "32"))] { if _Py_IsImmortal(op) != 0 { return; } (*op).ob_refcnt.ob_refcnt += 1 } #[cfg(not(Py_3_12))] { (*op).ob_refcnt += 1 } // Skipped _Py_INCREF_STAT_INC - if anyone wants this, please file an issue // or submit a PR supporting Py_STATS build option and pystats.h } } // skipped _Py_DecRefShared // skipped _Py_DecRefSharedDebug // skipped _Py_MergeZeroLocalRefcount #[inline(always)] #[cfg_attr( all(py_sys_config = "Py_REF_DEBUG", Py_3_12, not(Py_LIMITED_API)), track_caller )] pub unsafe fn Py_DECREF(op: *mut PyObject) { // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting // On 3.12+ we implement refcount debugging to get better assertion locations on negative refcounts // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. #[cfg(any( Py_GIL_DISABLED, Py_LIMITED_API, all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), GraalPy ))] { // _Py_DecRef was added to the ABI in 3.10; skips null checks #[cfg(all(Py_3_10, not(PyPy)))] { _Py_DecRef(op); } #[cfg(any(not(Py_3_10), PyPy))] { Py_DecRef(op); } } #[cfg(not(any( Py_GIL_DISABLED, Py_LIMITED_API, all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), GraalPy )))] { #[cfg(Py_3_12)] if _Py_IsImmortal(op) != 0 { return; } // Skipped _Py_DECREF_STAT_INC - if anyone needs this, please file an issue // or submit a PR supporting Py_STATS build option and pystats.h #[cfg(py_sys_config = "Py_REF_DEBUG")] _Py_DECREF_DecRefTotal(); #[cfg(Py_3_12)] { (*op).ob_refcnt.ob_refcnt -= 1; #[cfg(py_sys_config = "Py_REF_DEBUG")] if (*op).ob_refcnt.ob_refcnt < 0 { let location = std::panic::Location::caller(); let filename = std::ffi::CString::new(location.file()).unwrap(); _Py_NegativeRefcount(filename.as_ptr(), location.line() as i32, op); } if (*op).ob_refcnt.ob_refcnt == 0 { _Py_Dealloc(op); } } #[cfg(not(Py_3_12))] { (*op).ob_refcnt -= 1; if (*op).ob_refcnt == 0 { _Py_Dealloc(op); } } } } #[inline] pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) { let tmp = *op; if !tmp.is_null() { *op = ptr::null_mut(); Py_DECREF(tmp); } } #[inline] pub unsafe fn Py_XINCREF(op: *mut PyObject) { if !op.is_null() { Py_INCREF(op) } } #[inline] pub unsafe fn Py_XDECREF(op: *mut PyObject) { if !op.is_null() { Py_DECREF(op) } } extern_libpython! { #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject; #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject; } // macro _Py_NewRef not public; reimplemented directly inside Py_NewRef here // macro _Py_XNewRef not public; reimplemented directly inside Py_XNewRef here #[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] #[inline] pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject { Py_INCREF(obj); obj } #[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] #[inline] pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject { Py_XINCREF(obj); obj } ================================================ FILE: pyo3-ffi/src/setobject.rs ================================================ use crate::object::*; #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] use crate::pyport::Py_hash_t; use crate::pyport::Py_ssize_t; use std::ffi::c_int; pub const PySet_MINSIZE: usize = 8; #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] pub struct setentry { pub key: *mut PyObject, pub hash: Py_hash_t, } #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] #[repr(C)] #[derive(Debug)] pub struct PySetObject { pub ob_base: PyObject, pub fill: Py_ssize_t, pub used: Py_ssize_t, pub mask: Py_ssize_t, pub table: *mut setentry, pub hash: Py_hash_t, pub finger: Py_ssize_t, pub smalltable: [setentry; PySet_MINSIZE], pub weakreflist: *mut PyObject, } // skipped #[inline] #[cfg(all(not(any(PyPy, GraalPy)), not(Py_LIMITED_API)))] pub unsafe fn PySet_GET_SIZE(so: *mut PyObject) -> Py_ssize_t { debug_assert_eq!(PyAnySet_Check(so), 1); let so = so.cast::(); (*so).used } // skipped _PySet_Dummy extern_libpython! { #[cfg(not(Py_LIMITED_API))] #[cfg_attr(PyPy, link_name = "_PyPySet_NextEntry")] pub fn _PySet_NextEntry( set: *mut PyObject, pos: *mut Py_ssize_t, key: *mut *mut PyObject, hash: *mut super::Py_hash_t, ) -> c_int; // skipped non-limited _PySet_Update } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySet_Type")] pub static mut PySet_Type: PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_Type")] pub static mut PyFrozenSet_Type: PyTypeObject; pub static mut PySetIter_Type: PyTypeObject; } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySet_New")] pub fn PySet_New(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_New")] pub fn PyFrozenSet_New(arg1: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySet_Add")] pub fn PySet_Add(set: *mut PyObject, key: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySet_Clear")] pub fn PySet_Clear(set: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySet_Contains")] pub fn PySet_Contains(anyset: *mut PyObject, key: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySet_Discard")] pub fn PySet_Discard(set: *mut PyObject, key: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySet_Pop")] pub fn PySet_Pop(set: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySet_Size")] pub fn PySet_Size(anyset: *mut PyObject) -> Py_ssize_t; #[cfg(PyPy)] #[link_name = "PyPyFrozenSet_CheckExact"] pub fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int; } #[inline] #[cfg(not(any(PyPy, GraalPy)))] pub unsafe fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PyFrozenSet_Type) as c_int } extern_libpython! { #[cfg(PyPy)] #[link_name = "PyPyFrozenSet_Check"] pub fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyFrozenSet_Check(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PyFrozenSet_Type || PyType_IsSubtype(Py_TYPE(ob), &raw mut PyFrozenSet_Type) != 0) as c_int } extern_libpython! { #[cfg(PyPy)] #[link_name = "PyPyAnySet_CheckExact"] pub fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyAnySet_CheckExact(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PySet_Type || Py_TYPE(ob) == &raw mut PyFrozenSet_Type) as c_int } #[inline] pub unsafe fn PyAnySet_Check(ob: *mut PyObject) -> c_int { (PyAnySet_CheckExact(ob) != 0 || PyType_IsSubtype(Py_TYPE(ob), &raw mut PySet_Type) != 0 || PyType_IsSubtype(Py_TYPE(ob), &raw mut PyFrozenSet_Type) != 0) as c_int } #[inline] #[cfg(Py_3_10)] pub unsafe fn PySet_CheckExact(op: *mut PyObject) -> c_int { crate::Py_IS_TYPE(op, &raw mut PySet_Type) } extern_libpython! { #[cfg(PyPy)] #[link_name = "PyPySet_Check"] pub fn PySet_Check(ob: *mut PyObject) -> c_int; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PySet_Check(ob: *mut PyObject) -> c_int { (Py_TYPE(ob) == &raw mut PySet_Type || PyType_IsSubtype(Py_TYPE(ob), &raw mut PySet_Type) != 0) as c_int } ================================================ FILE: pyo3-ffi/src/sliceobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::c_int; extern_libpython! { #[cfg(not(GraalPy))] #[cfg_attr(PyPy, link_name = "_PyPy_EllipsisObject")] static mut _Py_EllipsisObject: PyObject; #[cfg(GraalPy)] static mut _Py_EllipsisObjectReference: *mut PyObject; } #[inline] pub unsafe fn Py_Ellipsis() -> *mut PyObject { #[cfg(not(GraalPy))] return &raw mut _Py_EllipsisObject; #[cfg(GraalPy)] return _Py_EllipsisObjectReference; } #[cfg(not(Py_LIMITED_API))] #[repr(C)] pub struct PySliceObject { pub ob_base: PyObject, #[cfg(not(GraalPy))] pub start: *mut PyObject, #[cfg(not(GraalPy))] pub stop: *mut PyObject, #[cfg(not(GraalPy))] pub step: *mut PyObject, } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySlice_Type")] pub static mut PySlice_Type: PyTypeObject; pub static mut PyEllipsis_Type: PyTypeObject; } #[inline] pub unsafe fn PySlice_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PySlice_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySlice_New")] pub fn PySlice_New( start: *mut PyObject, stop: *mut PyObject, step: *mut PyObject, ) -> *mut PyObject; // skipped non-limited _PySlice_FromIndices // skipped non-limited _PySlice_GetLongIndices #[cfg_attr(PyPy, link_name = "PyPySlice_GetIndices")] pub fn PySlice_GetIndices( r: *mut PyObject, length: Py_ssize_t, start: *mut Py_ssize_t, stop: *mut Py_ssize_t, step: *mut Py_ssize_t, ) -> c_int; } #[inline] pub unsafe fn PySlice_GetIndicesEx( slice: *mut PyObject, length: Py_ssize_t, start: *mut Py_ssize_t, stop: *mut Py_ssize_t, step: *mut Py_ssize_t, slicelength: *mut Py_ssize_t, ) -> c_int { if PySlice_Unpack(slice, start, stop, step) < 0 { *slicelength = 0; -1 } else { *slicelength = PySlice_AdjustIndices(length, start, stop, *step); 0 } } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySlice_Unpack")] pub fn PySlice_Unpack( slice: *mut PyObject, start: *mut Py_ssize_t, stop: *mut Py_ssize_t, step: *mut Py_ssize_t, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPySlice_AdjustIndices")] pub fn PySlice_AdjustIndices( length: Py_ssize_t, start: *mut Py_ssize_t, stop: *mut Py_ssize_t, step: Py_ssize_t, ) -> Py_ssize_t; } ================================================ FILE: pyo3-ffi/src/structmember.rs ================================================ use std::ffi::c_int; pub use crate::PyMemberDef; pub use crate::Py_T_BOOL as T_BOOL; pub use crate::Py_T_BYTE as T_BYTE; pub use crate::Py_T_CHAR as T_CHAR; pub use crate::Py_T_DOUBLE as T_DOUBLE; pub use crate::Py_T_FLOAT as T_FLOAT; pub use crate::Py_T_INT as T_INT; pub use crate::Py_T_LONG as T_LONG; pub use crate::Py_T_LONGLONG as T_LONGLONG; pub use crate::Py_T_OBJECT_EX as T_OBJECT_EX; pub use crate::Py_T_SHORT as T_SHORT; pub use crate::Py_T_STRING as T_STRING; pub use crate::Py_T_STRING_INPLACE as T_STRING_INPLACE; pub use crate::Py_T_UBYTE as T_UBYTE; pub use crate::Py_T_UINT as T_UINT; pub use crate::Py_T_ULONG as T_ULONG; pub use crate::Py_T_ULONGLONG as T_ULONGLONG; pub use crate::Py_T_USHORT as T_USHORT; #[allow(deprecated)] pub use crate::_Py_T_OBJECT as T_OBJECT; pub use crate::Py_T_PYSSIZET as T_PYSSIZET; #[allow(deprecated)] pub use crate::_Py_T_NONE as T_NONE; /* Flags */ pub use crate::Py_READONLY as READONLY; pub const READ_RESTRICTED: c_int = 2; pub const PY_WRITE_RESTRICTED: c_int = 4; pub const RESTRICTED: c_int = READ_RESTRICTED | PY_WRITE_RESTRICTED; ================================================ FILE: pyo3-ffi/src/structseq.rs ================================================ use crate::object::{PyObject, PyTypeObject}; #[cfg(not(PyPy))] use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int}; #[repr(C)] #[derive(Copy, Clone)] pub struct PyStructSequence_Field { pub name: *const c_char, pub doc: *const c_char, } #[repr(C)] #[derive(Copy, Clone)] pub struct PyStructSequence_Desc { pub name: *const c_char, pub doc: *const c_char, pub fields: *mut PyStructSequence_Field, pub n_in_sequence: c_int, } // skipped PyStructSequence_UnnamedField; extern_libpython! { #[cfg(not(Py_LIMITED_API))] #[cfg_attr(PyPy, link_name = "PyPyStructSequence_InitType")] pub fn PyStructSequence_InitType(_type: *mut PyTypeObject, desc: *mut PyStructSequence_Desc); #[cfg(not(Py_LIMITED_API))] #[cfg_attr(PyPy, link_name = "PyPyStructSequence_InitType2")] pub fn PyStructSequence_InitType2( _type: *mut PyTypeObject, desc: *mut PyStructSequence_Desc, ) -> c_int; #[cfg(not(PyPy))] pub fn PyStructSequence_NewType(desc: *mut PyStructSequence_Desc) -> *mut PyTypeObject; #[cfg_attr(PyPy, link_name = "PyPyStructSequence_New")] pub fn PyStructSequence_New(_type: *mut PyTypeObject) -> *mut PyObject; } #[cfg(not(Py_LIMITED_API))] pub type PyStructSequence = crate::PyTupleObject; #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] #[inline] pub unsafe fn PyStructSequence_SET_ITEM(op: *mut PyObject, i: Py_ssize_t, v: *mut PyObject) { crate::PyTuple_SET_ITEM(op, i, v) } #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] #[inline] pub unsafe fn PyStructSequence_GET_ITEM(op: *mut PyObject, i: Py_ssize_t) -> *mut PyObject { crate::PyTuple_GET_ITEM(op, i) } extern_libpython! { #[cfg(not(PyPy))] pub fn PyStructSequence_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject); #[cfg(not(PyPy))] pub fn PyStructSequence_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/sysmodule.rs ================================================ use crate::object::PyObject; use libc::wchar_t; use std::ffi::{c_char, c_int}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPySys_GetObject")] pub fn PySys_GetObject(arg1: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPySys_SetObject")] pub fn PySys_SetObject(arg1: *const c_char, arg2: *mut PyObject) -> c_int; #[cfg_attr( Py_3_11, deprecated( note = "Deprecated in Python 3.11, use `PyConfig.argv` and `PyConfig.parse_argv` instead" ) )] pub fn PySys_SetArgv(arg1: c_int, arg2: *mut *mut wchar_t); #[cfg_attr( Py_3_11, deprecated( note = "Deprecated in Python 3.11, use `PyConfig.argv` and `PyConfig.parse_argv` instead" ) )] pub fn PySys_SetArgvEx(arg1: c_int, arg2: *mut *mut wchar_t, arg3: c_int); pub fn PySys_SetPath(arg1: *const wchar_t); #[cfg_attr(PyPy, link_name = "PyPySys_WriteStdout")] pub fn PySys_WriteStdout(format: *const c_char, ...); #[cfg_attr(PyPy, link_name = "PyPySys_WriteStderr")] pub fn PySys_WriteStderr(format: *const c_char, ...); pub fn PySys_FormatStdout(format: *const c_char, ...); pub fn PySys_FormatStderr(format: *const c_char, ...); #[cfg_attr( Py_3_13, deprecated( note = "Deprecated since Python 3.13. Clear sys.warnoptions and warnings.filters instead." ) )] pub fn PySys_ResetWarnOptions(); #[cfg_attr(Py_3_11, deprecated(note = "Python 3.11"))] pub fn PySys_AddWarnOption(arg1: *const wchar_t); #[cfg_attr(Py_3_11, deprecated(note = "Python 3.11"))] pub fn PySys_AddWarnOptionUnicode(arg1: *mut PyObject); #[cfg_attr(Py_3_11, deprecated(note = "Python 3.11"))] pub fn PySys_HasWarnOptions() -> c_int; pub fn PySys_AddXOption(arg1: *const wchar_t); pub fn PySys_GetXOptions() -> *mut PyObject; } ================================================ FILE: pyo3-ffi/src/traceback.rs ================================================ use crate::object::*; use std::ffi::c_int; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Here")] pub fn PyTraceBack_Here(arg1: *mut crate::PyFrameObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Print")] pub fn PyTraceBack_Print(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyTraceBack_Type")] pub static mut PyTraceBack_Type: PyTypeObject; #[cfg(PyPy)] #[link_name = "PyPyTraceBack_Check"] pub fn PyTraceBack_Check(op: *mut PyObject) -> c_int; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyTraceBack_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyTraceBack_Type) as c_int } ================================================ FILE: pyo3-ffi/src/tupleobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use std::ffi::c_int; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyTuple_Type")] pub static mut PyTuple_Type: PyTypeObject; pub static mut PyTupleIter_Type: PyTypeObject; } #[inline] pub unsafe fn PyTuple_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_TUPLE_SUBCLASS) } #[inline] pub unsafe fn PyTuple_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyTuple_Type) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyTuple_New")] pub fn PyTuple_New(size: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyTuple_Size")] pub fn PyTuple_Size(arg1: *mut PyObject) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyTuple_GetItem")] pub fn PyTuple_GetItem(arg1: *mut PyObject, arg2: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyTuple_SetItem")] pub fn PyTuple_SetItem(arg1: *mut PyObject, arg2: Py_ssize_t, arg3: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyTuple_GetSlice")] pub fn PyTuple_GetSlice( arg1: *mut PyObject, arg2: Py_ssize_t, arg3: Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyTuple_Pack")] pub fn PyTuple_Pack(arg1: Py_ssize_t, ...) -> *mut PyObject; #[cfg(not(Py_3_9))] pub fn PyTuple_ClearFreeList() -> c_int; } ================================================ FILE: pyo3-ffi/src/typeslots.rs ================================================ use std::ffi::c_int; pub const Py_bf_getbuffer: c_int = 1; pub const Py_bf_releasebuffer: c_int = 2; pub const Py_mp_ass_subscript: c_int = 3; pub const Py_mp_length: c_int = 4; pub const Py_mp_subscript: c_int = 5; pub const Py_nb_absolute: c_int = 6; pub const Py_nb_add: c_int = 7; pub const Py_nb_and: c_int = 8; pub const Py_nb_bool: c_int = 9; pub const Py_nb_divmod: c_int = 10; pub const Py_nb_float: c_int = 11; pub const Py_nb_floor_divide: c_int = 12; pub const Py_nb_index: c_int = 13; pub const Py_nb_inplace_add: c_int = 14; pub const Py_nb_inplace_and: c_int = 15; pub const Py_nb_inplace_floor_divide: c_int = 16; pub const Py_nb_inplace_lshift: c_int = 17; pub const Py_nb_inplace_multiply: c_int = 18; pub const Py_nb_inplace_or: c_int = 19; pub const Py_nb_inplace_power: c_int = 20; pub const Py_nb_inplace_remainder: c_int = 21; pub const Py_nb_inplace_rshift: c_int = 22; pub const Py_nb_inplace_subtract: c_int = 23; pub const Py_nb_inplace_true_divide: c_int = 24; pub const Py_nb_inplace_xor: c_int = 25; pub const Py_nb_int: c_int = 26; pub const Py_nb_invert: c_int = 27; pub const Py_nb_lshift: c_int = 28; pub const Py_nb_multiply: c_int = 29; pub const Py_nb_negative: c_int = 30; pub const Py_nb_or: c_int = 31; pub const Py_nb_positive: c_int = 32; pub const Py_nb_power: c_int = 33; pub const Py_nb_remainder: c_int = 34; pub const Py_nb_rshift: c_int = 35; pub const Py_nb_subtract: c_int = 36; pub const Py_nb_true_divide: c_int = 37; pub const Py_nb_xor: c_int = 38; pub const Py_sq_ass_item: c_int = 39; pub const Py_sq_concat: c_int = 40; pub const Py_sq_contains: c_int = 41; pub const Py_sq_inplace_concat: c_int = 42; pub const Py_sq_inplace_repeat: c_int = 43; pub const Py_sq_item: c_int = 44; pub const Py_sq_length: c_int = 45; pub const Py_sq_repeat: c_int = 46; pub const Py_tp_alloc: c_int = 47; pub const Py_tp_base: c_int = 48; pub const Py_tp_bases: c_int = 49; pub const Py_tp_call: c_int = 50; pub const Py_tp_clear: c_int = 51; pub const Py_tp_dealloc: c_int = 52; pub const Py_tp_del: c_int = 53; pub const Py_tp_descr_get: c_int = 54; pub const Py_tp_descr_set: c_int = 55; pub const Py_tp_doc: c_int = 56; pub const Py_tp_getattr: c_int = 57; pub const Py_tp_getattro: c_int = 58; pub const Py_tp_hash: c_int = 59; pub const Py_tp_init: c_int = 60; pub const Py_tp_is_gc: c_int = 61; pub const Py_tp_iter: c_int = 62; pub const Py_tp_iternext: c_int = 63; pub const Py_tp_methods: c_int = 64; pub const Py_tp_new: c_int = 65; pub const Py_tp_repr: c_int = 66; pub const Py_tp_richcompare: c_int = 67; pub const Py_tp_setattr: c_int = 68; pub const Py_tp_setattro: c_int = 69; pub const Py_tp_str: c_int = 70; pub const Py_tp_traverse: c_int = 71; pub const Py_tp_members: c_int = 72; pub const Py_tp_getset: c_int = 73; pub const Py_tp_free: c_int = 74; pub const Py_nb_matrix_multiply: c_int = 75; pub const Py_nb_inplace_matrix_multiply: c_int = 76; pub const Py_am_await: c_int = 77; pub const Py_am_aiter: c_int = 78; pub const Py_am_anext: c_int = 79; pub const Py_tp_finalize: c_int = 80; ================================================ FILE: pyo3-ffi/src/unicodeobject.rs ================================================ use crate::object::*; use crate::pyport::Py_ssize_t; use libc::wchar_t; use std::ffi::{c_char, c_int, c_void}; #[cfg(not(Py_LIMITED_API))] #[cfg_attr( Py_3_13, deprecated(note = "Deprecated since Python 3.13. Use `libc::wchar_t` instead.") )] pub type Py_UNICODE = wchar_t; pub type Py_UCS4 = u32; pub type Py_UCS2 = u16; pub type Py_UCS1 = u8; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyUnicode_Type")] pub static mut PyUnicode_Type: PyTypeObject; pub static mut PyUnicodeIter_Type: PyTypeObject; #[cfg(PyPy)] #[link_name = "PyPyUnicode_Check"] pub fn PyUnicode_Check(op: *mut PyObject) -> c_int; #[cfg(PyPy)] #[link_name = "PyPyUnicode_CheckExact"] pub fn PyUnicode_CheckExact(op: *mut PyObject) -> c_int; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyUnicode_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_UNICODE_SUBCLASS) } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyUnicode_CheckExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut PyUnicode_Type) as c_int } pub const Py_UNICODE_REPLACEMENT_CHARACTER: Py_UCS4 = 0xFFFD; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromStringAndSize")] pub fn PyUnicode_FromStringAndSize(u: *const c_char, size: Py_ssize_t) -> *mut PyObject; pub fn PyUnicode_FromString(u: *const c_char) -> *mut PyObject; pub fn PyUnicode_Substring( str: *mut PyObject, start: Py_ssize_t, end: Py_ssize_t, ) -> *mut PyObject; pub fn PyUnicode_AsUCS4( unicode: *mut PyObject, buffer: *mut Py_UCS4, buflen: Py_ssize_t, copy_null: c_int, ) -> *mut Py_UCS4; pub fn PyUnicode_AsUCS4Copy(unicode: *mut PyObject) -> *mut Py_UCS4; #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetLength")] pub fn PyUnicode_GetLength(unicode: *mut PyObject) -> Py_ssize_t; #[cfg(not(Py_3_12))] #[deprecated(note = "Removed in Python 3.12")] #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetSize")] pub fn PyUnicode_GetSize(unicode: *mut PyObject) -> Py_ssize_t; pub fn PyUnicode_ReadChar(unicode: *mut PyObject, index: Py_ssize_t) -> Py_UCS4; pub fn PyUnicode_WriteChar( unicode: *mut PyObject, index: Py_ssize_t, character: Py_UCS4, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Resize")] pub fn PyUnicode_Resize(unicode: *mut *mut PyObject, length: Py_ssize_t) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromEncodedObject")] pub fn PyUnicode_FromEncodedObject( obj: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromObject")] pub fn PyUnicode_FromObject(obj: *mut PyObject) -> *mut PyObject; // #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormatV")] // pub fn PyUnicode_FromFormatV(format: *const c_char, vargs: va_list) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormat")] pub fn PyUnicode_FromFormat(format: *const c_char, ...) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_InternInPlace")] pub fn PyUnicode_InternInPlace(arg1: *mut *mut PyObject); #[cfg(not(Py_3_12))] #[cfg_attr(Py_3_10, deprecated(note = "Python 3.10"))] pub fn PyUnicode_InternImmortal(arg1: *mut *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyUnicode_InternFromString")] pub fn PyUnicode_InternFromString(u: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromWideChar")] pub fn PyUnicode_FromWideChar(w: *const wchar_t, size: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsWideChar")] pub fn PyUnicode_AsWideChar( unicode: *mut PyObject, w: *mut wchar_t, size: Py_ssize_t, ) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsWideCharString")] pub fn PyUnicode_AsWideCharString( unicode: *mut PyObject, size: *mut Py_ssize_t, ) -> *mut wchar_t; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromOrdinal")] pub fn PyUnicode_FromOrdinal(ordinal: c_int) -> *mut PyObject; pub fn PyUnicode_ClearFreeList() -> c_int; #[cfg_attr(PyPy, link_name = "PyPyUnicode_GetDefaultEncoding")] pub fn PyUnicode_GetDefaultEncoding() -> *const c_char; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Decode")] pub fn PyUnicode_Decode( s: *const c_char, size: Py_ssize_t, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_AsDecodedObject( unicode: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_AsDecodedUnicode( unicode: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsEncodedObject")] pub fn PyUnicode_AsEncodedObject( unicode: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsEncodedString")] pub fn PyUnicode_AsEncodedString( unicode: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_AsEncodedUnicode( unicode: *mut PyObject, encoding: *const c_char, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_BuildEncodingMap(string: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_DecodeUTF7( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_DecodeUTF7Stateful( string: *const c_char, length: Py_ssize_t, errors: *const c_char, consumed: *mut Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF8")] pub fn PyUnicode_DecodeUTF8( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_DecodeUTF8Stateful( string: *const c_char, length: Py_ssize_t, errors: *const c_char, consumed: *mut Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8String")] pub fn PyUnicode_AsUTF8String(unicode: *mut PyObject) -> *mut PyObject; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF8AndSize")] pub fn PyUnicode_AsUTF8AndSize(unicode: *mut PyObject, size: *mut Py_ssize_t) -> *const c_char; #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF32")] pub fn PyUnicode_DecodeUTF32( string: *const c_char, length: Py_ssize_t, errors: *const c_char, byteorder: *mut c_int, ) -> *mut PyObject; pub fn PyUnicode_DecodeUTF32Stateful( string: *const c_char, length: Py_ssize_t, errors: *const c_char, byteorder: *mut c_int, consumed: *mut Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF32String")] pub fn PyUnicode_AsUTF32String(unicode: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeUTF16")] pub fn PyUnicode_DecodeUTF16( string: *const c_char, length: Py_ssize_t, errors: *const c_char, byteorder: *mut c_int, ) -> *mut PyObject; pub fn PyUnicode_DecodeUTF16Stateful( string: *const c_char, length: Py_ssize_t, errors: *const c_char, byteorder: *mut c_int, consumed: *mut Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUTF16String")] pub fn PyUnicode_AsUTF16String(unicode: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_DecodeUnicodeEscape( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsUnicodeEscapeString")] pub fn PyUnicode_AsUnicodeEscapeString(unicode: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_DecodeRawUnicodeEscape( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_AsRawUnicodeEscapeString(unicode: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeLatin1")] pub fn PyUnicode_DecodeLatin1( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsLatin1String")] pub fn PyUnicode_AsLatin1String(unicode: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeASCII")] pub fn PyUnicode_DecodeASCII( string: *const c_char, length: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_AsASCIIString")] pub fn PyUnicode_AsASCIIString(unicode: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_DecodeCharmap( string: *const c_char, length: Py_ssize_t, mapping: *mut PyObject, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_AsCharmapString( unicode: *mut PyObject, mapping: *mut PyObject, ) -> *mut PyObject; pub fn PyUnicode_DecodeLocaleAndSize( str: *const c_char, len: Py_ssize_t, errors: *const c_char, ) -> *mut PyObject; pub fn PyUnicode_DecodeLocale(str: *const c_char, errors: *const c_char) -> *mut PyObject; pub fn PyUnicode_EncodeLocale(unicode: *mut PyObject, errors: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FSConverter")] pub fn PyUnicode_FSConverter(arg1: *mut PyObject, arg2: *mut c_void) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FSDecoder")] pub fn PyUnicode_FSDecoder(arg1: *mut PyObject, arg2: *mut c_void) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeFSDefault")] pub fn PyUnicode_DecodeFSDefault(s: *const c_char) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_DecodeFSDefaultAndSize")] pub fn PyUnicode_DecodeFSDefaultAndSize(s: *const c_char, size: Py_ssize_t) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_EncodeFSDefault")] pub fn PyUnicode_EncodeFSDefault(unicode: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Concat")] pub fn PyUnicode_Concat(left: *mut PyObject, right: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_Append(pleft: *mut *mut PyObject, right: *mut PyObject); pub fn PyUnicode_AppendAndDel(pleft: *mut *mut PyObject, right: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyUnicode_Split")] pub fn PyUnicode_Split( s: *mut PyObject, sep: *mut PyObject, maxsplit: Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Splitlines")] pub fn PyUnicode_Splitlines(s: *mut PyObject, keepends: c_int) -> *mut PyObject; pub fn PyUnicode_Partition(s: *mut PyObject, sep: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_RPartition(s: *mut PyObject, sep: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_RSplit( s: *mut PyObject, sep: *mut PyObject, maxsplit: Py_ssize_t, ) -> *mut PyObject; pub fn PyUnicode_Translate( str: *mut PyObject, table: *mut PyObject, errors: *const c_char, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Join")] pub fn PyUnicode_Join(separator: *mut PyObject, seq: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Tailmatch")] pub fn PyUnicode_Tailmatch( str: *mut PyObject, substr: *mut PyObject, start: Py_ssize_t, end: Py_ssize_t, direction: c_int, ) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Find")] pub fn PyUnicode_Find( str: *mut PyObject, substr: *mut PyObject, start: Py_ssize_t, end: Py_ssize_t, direction: c_int, ) -> Py_ssize_t; pub fn PyUnicode_FindChar( str: *mut PyObject, ch: Py_UCS4, start: Py_ssize_t, end: Py_ssize_t, direction: c_int, ) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Count")] pub fn PyUnicode_Count( str: *mut PyObject, substr: *mut PyObject, start: Py_ssize_t, end: Py_ssize_t, ) -> Py_ssize_t; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Replace")] pub fn PyUnicode_Replace( str: *mut PyObject, substr: *mut PyObject, replstr: *mut PyObject, maxcount: Py_ssize_t, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Compare")] pub fn PyUnicode_Compare(left: *mut PyObject, right: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyUnicode_CompareWithASCIIString")] pub fn PyUnicode_CompareWithASCIIString(left: *mut PyObject, right: *const c_char) -> c_int; #[cfg(Py_3_13)] pub fn PyUnicode_EqualToUTF8(unicode: *mut PyObject, string: *const c_char) -> c_int; #[cfg(Py_3_13)] pub fn PyUnicode_EqualToUTF8AndSize( unicode: *mut PyObject, string: *const c_char, size: Py_ssize_t, ) -> c_int; pub fn PyUnicode_RichCompare( left: *mut PyObject, right: *mut PyObject, op: c_int, ) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_Format")] pub fn PyUnicode_Format(format: *mut PyObject, args: *mut PyObject) -> *mut PyObject; pub fn PyUnicode_Contains(container: *mut PyObject, element: *mut PyObject) -> c_int; pub fn PyUnicode_IsIdentifier(s: *mut PyObject) -> c_int; } ================================================ FILE: pyo3-ffi/src/warnings.rs ================================================ use crate::object::PyObject; use crate::pyport::Py_ssize_t; use std::ffi::{c_char, c_int}; extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyErr_WarnEx")] pub fn PyErr_WarnEx( category: *mut PyObject, message: *const c_char, stack_level: Py_ssize_t, ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyErr_WarnFormat")] pub fn PyErr_WarnFormat( category: *mut PyObject, stack_level: Py_ssize_t, format: *const c_char, ... ) -> c_int; pub fn PyErr_ResourceWarning( source: *mut PyObject, stack_level: Py_ssize_t, format: *const c_char, ... ) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyErr_WarnExplicit")] pub fn PyErr_WarnExplicit( category: *mut PyObject, message: *const c_char, filename: *const c_char, lineno: c_int, module: *const c_char, registry: *mut PyObject, ) -> c_int; } ================================================ FILE: pyo3-ffi/src/weakrefobject.rs ================================================ use crate::object::*; use std::ffi::c_int; #[cfg(all(not(PyPy), Py_LIMITED_API, not(GraalPy)))] opaque_struct!(pub PyWeakReference); #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(GraalPy)))] pub use crate::_PyWeakReference as PyWeakReference; extern_libpython! { // TODO: PyO3 is depending on this symbol in `reference.rs`, we should change this and // remove the export as this is a private symbol. pub static mut _PyWeakref_RefType: PyTypeObject; static mut _PyWeakref_ProxyType: PyTypeObject; static mut _PyWeakref_CallableProxyType: PyTypeObject; #[cfg(PyPy)] #[link_name = "PyPyWeakref_CheckRef"] pub fn PyWeakref_CheckRef(op: *mut PyObject) -> c_int; #[cfg(PyPy)] #[link_name = "PyPyWeakref_CheckRefExact"] pub fn PyWeakref_CheckRefExact(op: *mut PyObject) -> c_int; #[cfg(PyPy)] #[link_name = "PyPyWeakref_CheckProxy"] pub fn PyWeakref_CheckProxy(op: *mut PyObject) -> c_int; } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyWeakref_CheckRef(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, &raw mut _PyWeakref_RefType) } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyWeakref_CheckRefExact(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &raw mut _PyWeakref_RefType) as c_int } #[inline] #[cfg(not(PyPy))] pub unsafe fn PyWeakref_CheckProxy(op: *mut PyObject) -> c_int { ((Py_TYPE(op) == &raw mut _PyWeakref_ProxyType) || (Py_TYPE(op) == &raw mut _PyWeakref_CallableProxyType)) as c_int } #[inline] pub unsafe fn PyWeakref_Check(op: *mut PyObject) -> c_int { (PyWeakref_CheckRef(op) != 0 || PyWeakref_CheckProxy(op) != 0) as c_int } extern_libpython! { #[cfg_attr(PyPy, link_name = "PyPyWeakref_NewRef")] pub fn PyWeakref_NewRef(ob: *mut PyObject, callback: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyWeakref_NewProxy")] pub fn PyWeakref_NewProxy(ob: *mut PyObject, callback: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyWeakref_GetObject")] #[cfg_attr( Py_3_13, deprecated(note = "deprecated since Python 3.13. Use `PyWeakref_GetRef` instead.") )] pub fn PyWeakref_GetObject(reference: *mut PyObject) -> *mut PyObject; #[cfg(Py_3_13)] #[cfg_attr(PyPy, link_name = "PyPyWeakref_GetRef")] pub fn PyWeakref_GetRef(reference: *mut PyObject, pobj: *mut *mut PyObject) -> c_int; } ================================================ FILE: pyo3-ffi-check/Cargo.toml ================================================ [package] name = "pyo3-ffi-check" version = "0.1.0" edition = "2021" publish = false [dependencies] pyo3-ffi-check-macro = { path = "./macro" } pyo3-ffi = { path = "../pyo3-ffi" } [build-dependencies] bindgen = "0.69.4" pyo3-build-config = { path = "../pyo3-build-config" } [workspace] members = [ "macro" ] ================================================ FILE: pyo3-ffi-check/README.md ================================================ # pyo3-ffi-check This is a simple program which compares ffi definitions from `pyo3-ffi` against those produced by `bindgen`. If any differ in size, these are printed to stdout and a the process will exit nonzero. The main purpose of this program is to be run as part of PyO3's continuous integration pipeline to catch possible errors in PyO3's ffi definitions. ================================================ FILE: pyo3-ffi-check/build.rs ================================================ use std::env; use std::path::PathBuf; #[derive(Debug)] struct ParseCallbacks; impl bindgen::callbacks::ParseCallbacks for ParseCallbacks { // these are anonymous fields and structs in CPython that we needed to // invent names for. Bindgen seems to generate stable names, so we remap the // automatically generated names to the names we invented in the FFI fn item_name(&self, _original_item_name: &str) -> Option { if _original_item_name == "_object__bindgen_ty_1__bindgen_ty_1" { Some("PyObjectObFlagsAndRefcnt".into()) } else if _original_item_name == "_object__bindgen_ty_1" { Some("PyObjectObRefcnt".into()) } else { None } } } fn main() { pyo3_build_config::add_libpython_rpath_link_args(); let config = pyo3_build_config::get(); let python_include_dir = config .run_python_script( "import sysconfig; print(sysconfig.get_config_var('INCLUDEPY'), end='');", ) .expect("failed to get lib dir"); let gil_disabled_on_windows = config .run_python_script( "import sysconfig; import platform; print(sysconfig.get_config_var('Py_GIL_DISABLED') == 1 and platform.system() == 'Windows');", ) .expect("failed to get Py_GIL_DISABLED").trim_end() == "True"; let clang_args = if gil_disabled_on_windows { vec![ format!("-I{python_include_dir}"), "-DPy_GIL_DISABLED".to_string(), ] } else { vec![format!("-I{python_include_dir}")] }; println!("cargo:rerun-if-changed=wrapper.h"); let bindings = bindgen::Builder::default() .header("wrapper.h") .clang_args(clang_args) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .parse_callbacks(Box::new(ParseCallbacks)) // blocklist some values which apparently have conflicting definitions on unix .blocklist_item("FP_NORMAL") .blocklist_item("FP_SUBNORMAL") .blocklist_item("FP_NAN") .blocklist_item("FP_INFINITE") .blocklist_item("FP_INT_UPWARD") .blocklist_item("FP_INT_DOWNWARD") .blocklist_item("FP_INT_TOWARDZERO") .blocklist_item("FP_INT_TONEARESTFROMZERO") .blocklist_item("FP_INT_TONEAREST") .blocklist_item("FP_ZERO") .generate() .expect("Unable to generate bindings"); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings .write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!"); } ================================================ FILE: pyo3-ffi-check/macro/Cargo.toml ================================================ [package] name = "pyo3-ffi-check-macro" version = "0.1.0" edition = "2021" publish = false [lib] proc-macro = true [dependencies] glob = "0.3" quote = "1" proc-macro2 = "1.0.60" scraper = "0.17" pyo3-build-config = { path = "../../pyo3-build-config" } ================================================ FILE: pyo3-ffi-check/macro/src/lib.rs ================================================ use std::{env, fs, path::PathBuf}; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use pyo3_build_config::PythonVersion; use quote::quote; /// Macro which expands to multiple macro calls, one per pyo3-ffi struct. #[proc_macro] pub fn for_all_structs(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: TokenStream = input.into(); let mut input = input.into_iter(); let macro_name = match input.next() { Some(TokenTree::Ident(i)) => i, _ => { return quote!(compile_error!( "for_all_structs!() takes only a single ident as input" )) .into() } }; if input.next().is_some() { return quote!(compile_error!( "for_all_structs!() takes only a single ident as input" )) .into(); } let doc_dir = get_doc_dir(); let structs_glob = format!("{}/doc/pyo3_ffi/struct.*.html", doc_dir.display()); let mut output = TokenStream::new(); for entry in glob::glob(&structs_glob).expect("Failed to read glob pattern") { let entry = entry .unwrap() .file_name() .unwrap() .to_string_lossy() .into_owned(); let struct_name = entry .strip_prefix("struct.") .unwrap() .strip_suffix(".html") .unwrap(); if pyo3_build_config::get().version < PythonVersion::PY315 && struct_name == "PyBytesWriter" { // PyBytesWriter was added in Python 3.15 continue; } let struct_ident = Ident::new(struct_name, Span::call_site()); output.extend(quote!(#macro_name!(#struct_ident);)); } if output.is_empty() { quote!(compile_error!(concat!( "No files found at `", #structs_glob, "`, try running `cargo doc -p pyo3-ffi` first." ))) } else { output } .into() } fn get_doc_dir() -> PathBuf { let path = PathBuf::from(env::var_os("OUT_DIR").unwrap()); path.parent() .unwrap() .parent() .unwrap() .parent() .unwrap() .parent() .unwrap() .to_owned() } /// Macro which expands to multiple macro calls, one per field in a pyo3-ffi /// struct. #[proc_macro] pub fn for_all_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: TokenStream = input.into(); let mut input = input.into_iter(); let struct_name = match input.next() { Some(TokenTree::Ident(i)) => i, _ => { return quote!(compile_error!( "for_all_fields!() takes exactly two idents as input" )) .into() } }; match input.next() { Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => (), _ => { return quote!(compile_error!( "for_all_fields!() takes exactly two idents as input" )) .into() } }; let macro_name = match input.next() { Some(TokenTree::Ident(i)) => i, _ => { return quote!(compile_error!( "for_all_fields!() takes exactly two idents as input" )) .into() } }; if input.next().is_some() { return quote!(compile_error!( "for_all_fields!() takes exactly two idents as input" )) .into(); } let doc_dir = get_doc_dir(); let struct_file = fs::read_to_string(format!( "{}/doc/pyo3_ffi/struct.{}.html", doc_dir.display(), struct_name )) .unwrap(); let html = scraper::Html::parse_document(&struct_file); let selector = scraper::Selector::parse("span.structfield").unwrap(); let mut output = TokenStream::new(); for el in html.select(&selector) { let field_name = el .value() .id() .unwrap() .strip_prefix("structfield.") .unwrap(); let field_ident = Ident::new(field_name, Span::call_site()); let bindgen_field_ident = if (pyo3_build_config::get().version >= PythonVersion::PY312) && struct_name == "PyObject" && field_name == "ob_refcnt" { // PyObject since 3.12 implements ob_refcnt as a union; bindgen creates // an anonymous name for the field Ident::new("__bindgen_anon_1", Span::call_site()) } else if struct_name == "PyMemberDef" && field_name == "type_code" { // the field name in the C API is `type`, but that's a keyword in Rust // so PyO3 picked type_code, bindgen picked type_ Ident::new("type_", Span::call_site()) } else { field_ident.clone() }; output.extend(quote!(#macro_name!(#struct_name, #field_ident, #bindgen_field_ident);)); } output.into() } ================================================ FILE: pyo3-ffi-check/src/main.rs ================================================ use std::{ffi::CStr, process::exit}; fn main() { println!( "comparing pyo3-ffi against headers generated for {}", CStr::from_bytes_with_nul(bindings::PY_VERSION) .unwrap() .to_string_lossy() ); let mut failed = false; macro_rules! check_struct { ($name:ident) => {{ let pyo3_ffi_size = std::mem::size_of::(); let bindgen_size = std::mem::size_of::(); let pyo3_ffi_align = std::mem::align_of::(); let bindgen_align = std::mem::align_of::(); // Check if sizes differ, but ignore zero-sized types (probably "opaque" in pyo3-ffi) if pyo3_ffi_size == 0 { println!( "warning: ignoring zero-sized pyo3_ffi type {}", stringify!($name), ); } else if pyo3_ffi_size != bindgen_size { failed = true; println!( "error: size of {} differs between pyo3_ffi ({}) and bindgen ({})", stringify!($name), pyo3_ffi_size, bindgen_size ); } else if pyo3_ffi_align != bindgen_align { failed = true; println!( "error: alignment of {} differs between pyo3_ffi ({}) and bindgen ({})", stringify!($name), pyo3_ffi_align, bindgen_align ); } pyo3_ffi_check_macro::for_all_fields!($name, check_field); }}; } macro_rules! check_field { ($struct_name:ident, $field:ident, $bindgen_field:ident) => {{ // some struct fields are deprecated but still present in the ABI #[allow(clippy::used_underscore_binding, deprecated)] let pyo3_ffi_offset = std::mem::offset_of!(pyo3_ffi::$struct_name, $field); #[allow(clippy::used_underscore_binding)] let bindgen_offset = std::mem::offset_of!(bindings::$struct_name, $bindgen_field); if pyo3_ffi_offset != bindgen_offset { failed = true; println!( "error: field offset of {}.{} differs between pyo3_ffi ({}) and bindgen ({})", stringify!($struct_name), stringify!($field), pyo3_ffi_offset, bindgen_offset ); } }}; } pyo3_ffi_check_macro::for_all_structs!(check_struct); if failed { exit(1); } else { exit(0); } } #[allow( non_snake_case, non_camel_case_types, non_upper_case_globals, dead_code, improper_ctypes, clippy::all, // clippy fails with lots of errors if this is not set specifically clippy::used_underscore_binding )] mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } ================================================ FILE: pyo3-ffi-check/wrapper.h ================================================ #include "Python.h" #include "datetime.h" #include "frameobject.h" #include "structmember.h" ================================================ FILE: pyo3-introspection/Cargo.toml ================================================ [package] name = "pyo3-introspection" version = "0.28.2" description = "Introspect dynamic libraries built with PyO3 to get metadata about the exported Python types" authors = ["PyO3 Project and Contributors "] homepage = "https://github.com/pyo3/pyo3" repository = "https://github.com/pyo3/pyo3" license = "MIT OR Apache-2.0" edition = "2021" [dependencies] anyhow = "1" goblin = ">=0.9, <0.11" serde = { version = "1", features = ["derive"] } serde_json = "1" [dev-dependencies] tempfile = "3.12.0" [lints] workspace = true ================================================ FILE: pyo3-introspection/src/introspection.rs ================================================ use crate::model::{ Argument, Arguments, Attribute, Class, Constant, Expr, Function, Module, Operator, VariableLengthArgument, }; use anyhow::{anyhow, bail, ensure, Context, Result}; use goblin::elf::section_header::SHN_XINDEX; use goblin::elf::Elf; use goblin::mach::load_command::CommandVariant; use goblin::mach::symbols::{NO_SECT, N_SECT}; use goblin::mach::{Mach, MachO, SingleArch}; use goblin::pe::PE; use goblin::Object; use serde::Deserialize; use std::borrow::Cow; use std::cmp::Ordering; use std::collections::HashMap; use std::path::Path; use std::{fs, str}; /// Introspect a cdylib built with PyO3 and returns the definition of a Python module. /// /// This function currently supports the ELF (most *nix including Linux), Match-O (macOS) and PE (Windows) formats. pub fn introspect_cdylib(library_path: impl AsRef, main_module_name: &str) -> Result { let chunks = find_introspection_chunks_in_binary_object(library_path.as_ref())?; parse_chunks(&chunks, main_module_name) } /// Parses the introspection chunks found in the binary fn parse_chunks(chunks: &[Chunk], main_module_name: &str) -> Result { let mut chunks_by_id = HashMap::<&str, &Chunk>::new(); let mut chunks_by_parent = HashMap::<&str, Vec<&Chunk>>::new(); for chunk in chunks { let (id, parent) = match chunk { Chunk::Module { id, .. } => (Some(id.as_str()), None), Chunk::Class { id, parent, .. } => (Some(id.as_str()), parent.as_deref()), Chunk::Function { id, parent, .. } | Chunk::Attribute { id, parent, .. } => { (id.as_deref(), parent.as_deref()) } }; if let Some(id) = id { chunks_by_id.insert(id, chunk); } if let Some(parent) = parent { chunks_by_parent.entry(parent).or_default().push(chunk); } } // We look for the root chunk for chunk in chunks { if let Chunk::Module { id, name, members, doc, incomplete, } = chunk { if name == main_module_name { let type_hint_for_annotation_id = introspection_id_to_type_hint_for_root_module( chunk, &chunks_by_id, &chunks_by_parent, ); return convert_module( id, name, members, *incomplete, doc.as_deref(), &chunks_by_id, &chunks_by_parent, &type_hint_for_annotation_id, ); } } } bail!("No module named {main_module_name} found") } #[expect(clippy::too_many_arguments)] fn convert_module( id: &str, name: &str, members: &[String], mut incomplete: bool, docstring: Option<&str>, chunks_by_id: &HashMap<&str, &Chunk>, chunks_by_parent: &HashMap<&str, Vec<&Chunk>>, type_hint_for_annotation_id: &HashMap, ) -> Result { let mut member_chunks = chunks_by_parent .get(&id) .into_iter() .flatten() .copied() .collect::>(); for member in members { if let Some(c) = chunks_by_id.get(member.as_str()) { member_chunks.push(*c); } else { incomplete = true; // We don't find an element } } let (modules, classes, functions, attributes) = convert_members( member_chunks, chunks_by_id, chunks_by_parent, type_hint_for_annotation_id, )?; Ok(Module { name: name.into(), modules, classes, functions, attributes, incomplete, docstring: docstring.map(Into::into), }) } type Members = (Vec, Vec, Vec, Vec); /// Convert a list of members of a module or a class fn convert_members<'a>( chunks: impl IntoIterator, chunks_by_id: &HashMap<&str, &Chunk>, chunks_by_parent: &HashMap<&str, Vec<&Chunk>>, type_hint_for_annotation_id: &HashMap, ) -> Result { let mut modules = Vec::new(); let mut classes = Vec::new(); let mut functions = Vec::new(); let mut attributes = Vec::new(); for chunk in chunks { match chunk { Chunk::Module { name, id, members, incomplete, doc, } => { modules.push(convert_module( id, name, members, *incomplete, doc.as_deref(), chunks_by_id, chunks_by_parent, type_hint_for_annotation_id, )?); } Chunk::Class { name, id, bases, decorators, doc, parent: _, } => classes.push(convert_class( id, name, bases, decorators, doc.as_deref(), chunks_by_id, chunks_by_parent, type_hint_for_annotation_id, )?), Chunk::Function { name, id: _, arguments, parent: _, decorators, is_async, returns, doc, } => functions.push(convert_function( name, arguments, decorators, returns, *is_async, doc.as_deref(), type_hint_for_annotation_id, )), Chunk::Attribute { name, id: _, parent: _, value, annotation, doc, } => attributes.push(convert_attribute( name, value, annotation, doc.as_deref(), type_hint_for_annotation_id, )), } } // We sort elements to get a stable output modules.sort_by(|l, r| l.name.cmp(&r.name)); classes.sort_by(|l, r| l.name.cmp(&r.name)); functions.sort_by(|l, r| match l.name.cmp(&r.name) { Ordering::Equal => { fn decorator_expr_key(expr: &Expr) -> (u32, Cow<'_, str>) { // We put plain names before attributes for @property to be before @foo.property match expr { Expr::Name { id, .. } => (0, Cow::Borrowed(id)), Expr::Attribute { value, attr } => { let (c, v) = decorator_expr_key(value); (c + 1, Cow::Owned(format!("{v}.{attr}"))) } _ => (0, Cow::Borrowed("")), // We don't care } } // We pick an ordering based on decorators l.decorators .iter() .map(decorator_expr_key) .cmp(r.decorators.iter().map(decorator_expr_key)) } o => o, }); attributes.sort_by(|l, r| l.name.cmp(&r.name)); Ok((modules, classes, functions, attributes)) } #[expect(clippy::too_many_arguments)] fn convert_class( id: &str, name: &str, bases: &[ChunkExpr], decorators: &[ChunkExpr], docstring: Option<&str>, chunks_by_id: &HashMap<&str, &Chunk>, chunks_by_parent: &HashMap<&str, Vec<&Chunk>>, type_hint_for_annotation_id: &HashMap, ) -> Result { let (nested_modules, nested_classes, methods, attributes) = convert_members( chunks_by_parent.get(&id).into_iter().flatten().copied(), chunks_by_id, chunks_by_parent, type_hint_for_annotation_id, )?; ensure!( nested_modules.is_empty(), "Classes cannot contain nested modules" ); Ok(Class { name: name.into(), bases: bases .iter() .map(|e| convert_expr(e, type_hint_for_annotation_id)) .collect(), methods, attributes, decorators: decorators .iter() .map(|e| convert_expr(e, type_hint_for_annotation_id)) .collect(), inner_classes: nested_classes, docstring: docstring.map(Into::into), }) } fn convert_function( name: &str, arguments: &ChunkArguments, decorators: &[ChunkExpr], returns: &Option, is_async: bool, docstring: Option<&str>, type_hint_for_annotation_id: &HashMap, ) -> Function { Function { name: name.into(), decorators: decorators .iter() .map(|e| convert_expr(e, type_hint_for_annotation_id)) .collect(), arguments: Arguments { positional_only_arguments: arguments .posonlyargs .iter() .map(|a| convert_argument(a, type_hint_for_annotation_id)) .collect(), arguments: arguments .args .iter() .map(|a| convert_argument(a, type_hint_for_annotation_id)) .collect(), vararg: arguments .vararg .as_ref() .map(|a| convert_variable_length_argument(a, type_hint_for_annotation_id)), keyword_only_arguments: arguments .kwonlyargs .iter() .map(|e| convert_argument(e, type_hint_for_annotation_id)) .collect(), kwarg: arguments .kwarg .as_ref() .map(|a| convert_variable_length_argument(a, type_hint_for_annotation_id)), }, returns: returns .as_ref() .map(|a| convert_expr(a, type_hint_for_annotation_id)), is_async, docstring: docstring.map(Into::into), } } fn convert_argument( arg: &ChunkArgument, type_hint_for_annotation_id: &HashMap, ) -> Argument { Argument { name: arg.name.clone(), default_value: arg .default .as_ref() .map(|e| convert_expr(e, type_hint_for_annotation_id)), annotation: arg .annotation .as_ref() .map(|a| convert_expr(a, type_hint_for_annotation_id)), } } fn convert_variable_length_argument( arg: &ChunkArgument, type_hint_for_annotation_id: &HashMap, ) -> VariableLengthArgument { VariableLengthArgument { name: arg.name.clone(), annotation: arg .annotation .as_ref() .map(|a| convert_expr(a, type_hint_for_annotation_id)), } } fn convert_attribute( name: &str, value: &Option, annotation: &Option, docstring: Option<&str>, type_hint_for_annotation_id: &HashMap, ) -> Attribute { Attribute { name: name.into(), value: value .as_ref() .map(|v| convert_expr(v, type_hint_for_annotation_id)), annotation: annotation .as_ref() .map(|a| convert_expr(a, type_hint_for_annotation_id)), docstring: docstring.map(ToString::to_string), } } fn convert_expr(expr: &ChunkExpr, type_hint_for_annotation_id: &HashMap) -> Expr { match expr { ChunkExpr::Name { id } => Expr::Name { id: id.clone() }, ChunkExpr::Attribute { value, attr } => Expr::Attribute { value: Box::new(convert_expr(value, type_hint_for_annotation_id)), attr: attr.clone(), }, ChunkExpr::BinOp { left, op, right } => Expr::BinOp { left: Box::new(convert_expr(left, type_hint_for_annotation_id)), op: match op { ChunkOperator::BitOr => Operator::BitOr, }, right: Box::new(convert_expr(right, type_hint_for_annotation_id)), }, ChunkExpr::Subscript { value, slice } => Expr::Subscript { value: Box::new(convert_expr(value, type_hint_for_annotation_id)), slice: Box::new(convert_expr(slice, type_hint_for_annotation_id)), }, ChunkExpr::Tuple { elts } => Expr::Tuple { elts: elts .iter() .map(|e| convert_expr(e, type_hint_for_annotation_id)) .collect(), }, ChunkExpr::List { elts } => Expr::List { elts: elts .iter() .map(|e| convert_expr(e, type_hint_for_annotation_id)) .collect(), }, ChunkExpr::Constant { value } => Expr::Constant { value: match value { ChunkConstant::None => Constant::None, ChunkConstant::Bool { value } => Constant::Bool(*value), ChunkConstant::Int { value } => Constant::Int(value.clone()), ChunkConstant::Float { value } => Constant::Float(value.clone()), ChunkConstant::Str { value } => Constant::Str(value.clone()), ChunkConstant::Ellipsis => Constant::Ellipsis, }, }, ChunkExpr::Id { id } => { if let Some(expr) = type_hint_for_annotation_id.get(id) { expr.clone() } else { // This is a pyclass not exposed, we fallback to Any Expr::Attribute { value: Box::new(Expr::Name { id: "typing".into(), }), attr: "Any".to_string(), } } } } } /// Returns the type hint for each class introspection id defined in the module and its submodule fn introspection_id_to_type_hint_for_root_module( module_chunk: &Chunk, chunks_by_id: &HashMap<&str, &Chunk>, chunks_by_parent: &HashMap<&str, Vec<&Chunk>>, ) -> HashMap { fn add_introspection_id_to_type_hint_for_module_members( module_id: &str, module_full_name: &str, module_members: &[String], chunks_by_id: &HashMap<&str, &Chunk>, chunks_by_parent: &HashMap<&str, Vec<&Chunk>>, output: &mut HashMap, ) { for member in chunks_by_parent .get(&module_id) .into_iter() .flatten() .chain( module_members .iter() .filter_map(|id| chunks_by_id.get(id.as_str())), ) .copied() { match member { Chunk::Module { name, id, members, .. } => { add_introspection_id_to_type_hint_for_module_members( id, &format!("{}.{}", module_full_name, name), members, chunks_by_id, chunks_by_parent, output, ); } Chunk::Class { id, name, .. } => { output.insert( id.clone(), Expr::Attribute { value: Box::new(Expr::Name { id: module_full_name.into(), }), attr: name.clone(), }, ); add_introspection_id_to_type_hint_for_class_subclasses( id, name, module_full_name, chunks_by_parent, output, ); } _ => (), } } } fn add_introspection_id_to_type_hint_for_class_subclasses( class_id: &str, class_name: &str, class_module: &str, chunks_by_parent: &HashMap<&str, Vec<&Chunk>>, output: &mut HashMap, ) { for member in chunks_by_parent.get(&class_id).into_iter().flatten() { if let Chunk::Class { id, name, .. } = member { let class_name = format!("{}.{}", class_name, name); add_introspection_id_to_type_hint_for_class_subclasses( id, &class_name, class_module, chunks_by_parent, output, ); output.insert( id.clone(), Expr::Attribute { value: Box::new(Expr::Name { id: class_module.into(), }), attr: class_name, }, ); } } } let mut output = HashMap::new(); let Chunk::Module { id, name, members, .. } = module_chunk else { unreachable!("The chunk must be a module") }; add_introspection_id_to_type_hint_for_module_members( id, name, members, chunks_by_id, chunks_by_parent, &mut output, ); output } fn find_introspection_chunks_in_binary_object(path: &Path) -> Result> { let library_content = fs::read(path).with_context(|| format!("Failed to read {}", path.display()))?; match Object::parse(&library_content) .context("The built library is not valid or not supported by our binary parser")? { Object::Elf(elf) => find_introspection_chunks_in_elf(&elf, &library_content), Object::Mach(Mach::Binary(macho)) => { find_introspection_chunks_in_macho(&macho, &library_content) } Object::Mach(Mach::Fat(multi_arch)) => { for arch in &multi_arch { match arch? { SingleArch::MachO(macho) => { return find_introspection_chunks_in_macho(&macho, &library_content) } SingleArch::Archive(_) => (), } } bail!("No Mach-o chunk found in the multi-arch Mach-o container") } Object::PE(pe) => find_introspection_chunks_in_pe(&pe, &library_content), other => { bail!( "Only ELF, Mach-o and PE containers can be introspected, got {other:?} from file {path:?}", path = path.display() ) } } } fn find_introspection_chunks_in_elf(elf: &Elf<'_>, library_content: &[u8]) -> Result> { let mut chunks = Vec::new(); for sym in &elf.syms { if is_introspection_symbol(elf.strtab.get_at(sym.st_name).unwrap_or_default()) { ensure!(u32::try_from(sym.st_shndx)? != SHN_XINDEX, "Section names length is greater than SHN_LORESERVE in ELF, this is not supported by PyO3 yet"); let section_header = &elf.section_headers[sym.st_shndx]; let data_offset = sym.st_value + section_header.sh_offset - section_header.sh_addr; chunks.push(deserialize_chunk( &library_content[usize::try_from(data_offset).context("File offset overflow")?..], elf.little_endian, )?); } } Ok(chunks) } fn find_introspection_chunks_in_macho( macho: &MachO<'_>, library_content: &[u8], ) -> Result> { if !macho.little_endian { bail!("Only little endian Mach-o binaries are supported"); } ensure!( !macho.load_commands.iter().any(|command| { matches!(command.command, CommandVariant::DyldChainedFixups(_)) }), "Mach-O binaries with fixup chains are not supported yet, to avoid using fixup chains, use `--codegen=link-arg=-no_fixup_chains` option." ); let sections = macho .segments .sections() .flatten() .map(|t| t.map(|s| s.0)) .collect::, _>>()?; let mut chunks = Vec::new(); for symbol in macho.symbols() { let (name, nlist) = symbol?; if nlist.is_global() && nlist.get_type() == N_SECT && nlist.n_sect != NO_SECT as usize && is_introspection_symbol(name) { let section = §ions[nlist.n_sect - 1]; // Sections are counted from 1 let data_offset = nlist.n_value + u64::from(section.offset) - section.addr; chunks.push(deserialize_chunk( &library_content[usize::try_from(data_offset).context("File offset overflow")?..], macho.little_endian, )?); } } Ok(chunks) } fn find_introspection_chunks_in_pe(pe: &PE<'_>, library_content: &[u8]) -> Result> { let mut chunks = Vec::new(); for export in &pe.exports { if is_introspection_symbol(export.name.unwrap_or_default()) { chunks.push(deserialize_chunk( &library_content[export.offset.context("No symbol offset")?..], true, )?); } } Ok(chunks) } fn deserialize_chunk( content_with_chunk_at_the_beginning: &[u8], is_little_endian: bool, ) -> Result { let length = content_with_chunk_at_the_beginning .split_at(4) .0 .try_into() .context("The introspection chunk must contain a length")?; let length = if is_little_endian { u32::from_le_bytes(length) } else { u32::from_be_bytes(length) }; let chunk = content_with_chunk_at_the_beginning .get(4..4 + length as usize) .ok_or_else(|| { anyhow!("The introspection chunk length {length} is greater that the binary size") })?; serde_json::from_slice(chunk).with_context(|| { format!( "Failed to parse introspection chunk: {:?}", String::from_utf8_lossy(chunk) ) }) } fn is_introspection_symbol(name: &str) -> bool { name.strip_prefix('_') .unwrap_or(name) .starts_with("PYO3_INTROSPECTION_1_") } #[derive(Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] enum Chunk { Module { id: String, name: String, members: Vec, #[serde(default)] doc: Option, incomplete: bool, }, Class { id: String, name: String, #[serde(default)] bases: Vec, #[serde(default)] decorators: Vec, #[serde(default)] parent: Option, #[serde(default)] doc: Option, }, Function { #[serde(default)] id: Option, name: String, arguments: Box, #[serde(default)] parent: Option, #[serde(default)] decorators: Vec, #[serde(default)] returns: Option, #[serde(default, rename = "async")] is_async: bool, #[serde(default)] doc: Option, }, Attribute { #[serde(default)] id: Option, #[serde(default)] parent: Option, name: String, #[serde(default)] value: Option, #[serde(default)] annotation: Option, #[serde(default)] doc: Option, }, } #[derive(Deserialize)] struct ChunkArguments { #[serde(default)] posonlyargs: Vec, #[serde(default)] args: Vec, #[serde(default)] vararg: Option, #[serde(default)] kwonlyargs: Vec, #[serde(default)] kwarg: Option, } #[derive(Deserialize)] struct ChunkArgument { name: String, #[serde(default)] default: Option, #[serde(default)] annotation: Option, } #[derive(Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] enum ChunkExpr { /// A constant like `None` or `123` Constant { #[serde(flatten)] value: ChunkConstant, }, /// A name Name { id: String }, /// An attribute `value.attr` Attribute { value: Box, attr: String }, /// A binary operator BinOp { left: Box, op: ChunkOperator, right: Box, }, /// A tuple Tuple { elts: Vec }, /// A list List { elts: Vec }, /// A subscript `value[slice]` Subscript { value: Box, slice: Box }, /// An introspection id Id { id: String }, } #[derive(Deserialize)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum ChunkConstant { None, Bool { value: bool }, Int { value: String }, Float { value: String }, Str { value: String }, Ellipsis, } #[derive(Deserialize)] #[serde(rename_all = "lowercase")] pub enum ChunkOperator { BitOr, } ================================================ FILE: pyo3-introspection/src/lib.rs ================================================ //! Utilities to introspect cdylib built using PyO3 and generate [type stubs](https://typing.readthedocs.io/en/latest/source/stubs.html). pub use crate::introspection::introspect_cdylib; pub use crate::stubs::module_stub_files; mod introspection; pub mod model; mod stubs; ================================================ FILE: pyo3-introspection/src/model.rs ================================================ #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Module { pub name: String, pub modules: Vec, pub classes: Vec, pub functions: Vec, pub attributes: Vec, pub incomplete: bool, pub docstring: Option, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Class { pub name: String, pub bases: Vec, pub methods: Vec, pub attributes: Vec, /// decorator like 'typing.final' pub decorators: Vec, pub inner_classes: Vec, pub docstring: Option, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Function { pub name: String, /// decorator like 'property' or 'staticmethod' pub decorators: Vec, pub arguments: Arguments, /// return type pub returns: Option, pub is_async: bool, pub docstring: Option, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Attribute { pub name: String, /// Value as a Python expression if easily expressible pub value: Option, /// Type annotation as a Python expression pub annotation: Option, pub docstring: Option, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Arguments { /// Arguments before / pub positional_only_arguments: Vec, /// Regular arguments (between / and *) pub arguments: Vec, /// *vararg pub vararg: Option, /// Arguments after * pub keyword_only_arguments: Vec, /// **kwarg pub kwarg: Option, } #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct Argument { pub name: String, /// Default value as a Python expression pub default_value: Option, /// Type annotation as a Python expression pub annotation: Option, } /// A variable length argument ie. *vararg or **kwarg #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct VariableLengthArgument { pub name: String, /// Type annotation as a Python expression pub annotation: Option, } /// A python expression /// /// This is the `expr` production of the [Python `ast` module grammar](https://docs.python.org/3/library/ast.html#abstract-grammar) #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum Expr { /// A constant like `None` or `123` Constant { value: Constant }, /// A name Name { id: String }, /// An attribute `value.attr` Attribute { value: Box, attr: String }, /// A binary operator BinOp { left: Box, op: Operator, right: Box, }, /// A tuple Tuple { elts: Vec }, /// A list List { elts: Vec }, /// A subscript `value[slice]` Subscript { value: Box, slice: Box }, } /// A PyO3 extension to the Python AST to know more about [`Expr::Constant`]. /// /// This enables advanced features like escaping. #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum Constant { /// `None` None, /// `True` or `False` Bool(bool), /// An integer in base 10 Int(String), /// A float in base 10 (does not include Inf and NaN) Float(String), /// A string (unescaped!) Str(String), /// `...` Ellipsis, } /// An operator used in [`Expr::BinOp`]. #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] pub enum Operator { /// `|` operator BitOr, } ================================================ FILE: pyo3-introspection/src/stubs.rs ================================================ use crate::model::{ Argument, Arguments, Attribute, Class, Constant, Expr, Function, Module, Operator, VariableLengthArgument, }; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt::Write; use std::iter::once; use std::path::PathBuf; use std::str::FromStr; /// Generates the [type stubs](https://typing.readthedocs.io/en/latest/source/stubs.html) of a given module. /// It returns a map between the file name and the file content. /// The root module stubs will be in the `__init__.pyi` file and the submodules directory /// in files with a relevant name. pub fn module_stub_files(module: &Module) -> HashMap { let mut output_files = HashMap::new(); add_module_stub_files(module, &[], &mut output_files); output_files } fn add_module_stub_files( module: &Module, module_path: &[&str], output_files: &mut HashMap, ) { let mut file_path = PathBuf::new(); for e in module_path { file_path = file_path.join(e); } output_files.insert( file_path.join("__init__.pyi"), module_stubs(module, module_path), ); let mut module_path = module_path.to_vec(); module_path.push(&module.name); for submodule in &module.modules { if submodule.modules.is_empty() { output_files.insert( file_path.join(format!("{}.pyi", submodule.name)), module_stubs(submodule, &module_path), ); } else { add_module_stub_files(submodule, &module_path, output_files); } } } /// Generates the module stubs to a String, not including submodules fn module_stubs(module: &Module, parents: &[&str]) -> String { let imports = Imports::create(module, parents); let mut elements = Vec::new(); for attribute in &module.attributes { elements.push(attribute_stubs(attribute, &imports)); } for class in &module.classes { elements.push(class_stubs(class, &imports)); } for function in &module.functions { elements.push(function_stubs(function, &imports, None)); } // We generate a __getattr__ method to tag incomplete stubs // See https://typing.python.org/en/latest/guides/writing_stubs.html#incomplete-stubs if module.incomplete && !module.functions.iter().any(|f| f.name == "__getattr__") { elements.push(function_stubs( &Function { name: "__getattr__".into(), decorators: Vec::new(), arguments: Arguments { positional_only_arguments: Vec::new(), arguments: vec![Argument { name: "name".to_string(), default_value: None, annotation: Some(Expr::Name { id: "str".into() }), }], vararg: None, keyword_only_arguments: Vec::new(), kwarg: None, }, returns: Some(Expr::Attribute { value: Box::new(Expr::Name { id: "_typeshed".into(), }), attr: "Incomplete".into(), }), is_async: false, docstring: None, }, &imports, None, )); } let mut final_elements = Vec::new(); if let Some(docstring) = &module.docstring { final_elements.push(format!("\"\"\"\n{docstring}\n\"\"\"")); } final_elements.extend(imports.imports); final_elements.extend(elements); let mut output = String::new(); // We insert two line jumps (i.e. empty strings) only above and below multiple line elements (classes with methods, functions with decorators) for element in final_elements { let is_multiline = element.contains('\n'); if is_multiline && !output.is_empty() && !output.ends_with("\n\n") { output.push('\n'); } output.push_str(&element); output.push('\n'); if is_multiline { output.push('\n'); } } // We remove a line jump at the end if they are two if output.ends_with("\n\n") { output.pop(); } output } fn class_stubs(class: &Class, imports: &Imports) -> String { let mut buffer = String::new(); for decorator in &class.decorators { buffer.push('@'); imports.serialize_expr(decorator, &mut buffer); buffer.push('\n'); } buffer.push_str("class "); buffer.push_str(&class.name); if !class.bases.is_empty() { buffer.push('('); for (i, base) in class.bases.iter().enumerate() { if i > 0 { buffer.push_str(", "); } imports.serialize_expr(base, &mut buffer); } buffer.push(')'); } buffer.push(':'); if class.docstring.is_none() && class.methods.is_empty() && class.attributes.is_empty() && class.inner_classes.is_empty() { buffer.push_str(" ..."); } if let Some(docstring) = &class.docstring { buffer.push_str("\n \"\"\""); for line in docstring.lines() { buffer.push_str("\n "); buffer.push_str(line); } buffer.push_str("\n \"\"\""); } for attribute in &class.attributes { // We do the indentation buffer.push_str("\n "); buffer.push_str(&attribute_stubs(attribute, imports).replace('\n', "\n ")); } for method in &class.methods { // We do the indentation buffer.push_str("\n "); buffer .push_str(&function_stubs(method, imports, Some(&class.name)).replace('\n', "\n ")); } for inner_class in &class.inner_classes { // We do the indentation buffer.push_str("\n "); buffer.push_str(&class_stubs(inner_class, imports).replace('\n', "\n ")); } buffer } fn function_stubs(function: &Function, imports: &Imports, class_name: Option<&str>) -> String { // Signature let mut parameters = Vec::new(); for argument in &function.arguments.positional_only_arguments { parameters.push(argument_stub(argument, imports)); } if !function.arguments.positional_only_arguments.is_empty() { parameters.push("/".into()); } for argument in &function.arguments.arguments { parameters.push(argument_stub(argument, imports)); } if let Some(argument) = &function.arguments.vararg { parameters.push(format!( "*{}", variable_length_argument_stub(argument, imports) )); } else if !function.arguments.keyword_only_arguments.is_empty() { parameters.push("*".into()); } for argument in &function.arguments.keyword_only_arguments { parameters.push(argument_stub(argument, imports)); } if let Some(argument) = &function.arguments.kwarg { parameters.push(format!( "**{}", variable_length_argument_stub(argument, imports) )); } let mut buffer = String::new(); for decorator in &function.decorators { buffer.push('@'); // We remove the class name if it's a prefix to get nicer decorators let mut decorator_buffer = String::new(); imports.serialize_expr(decorator, &mut decorator_buffer); if let Some(class_name) = class_name { if let Some(decorator) = decorator_buffer.strip_prefix(&format!("{class_name}.")) { decorator_buffer = decorator.into(); } } buffer.push_str(&decorator_buffer); buffer.push('\n'); } if function.is_async { buffer.push_str("async "); } buffer.push_str("def "); buffer.push_str(&function.name); buffer.push('('); buffer.push_str(¶meters.join(", ")); buffer.push(')'); if let Some(returns) = &function.returns { buffer.push_str(" -> "); imports.serialize_expr(returns, &mut buffer); } if let Some(docstring) = &function.docstring { buffer.push_str(":\n \"\"\""); for line in docstring.lines() { buffer.push_str("\n "); buffer.push_str(line); } buffer.push_str("\n \"\"\""); } else { buffer.push_str(": ..."); } buffer } fn attribute_stubs(attribute: &Attribute, imports: &Imports) -> String { let mut buffer = attribute.name.clone(); if let Some(annotation) = &attribute.annotation { buffer.push_str(": "); imports.serialize_expr(annotation, &mut buffer); } if let Some(value) = &attribute.value { buffer.push_str(" = "); imports.serialize_expr(value, &mut buffer); } if let Some(docstring) = &attribute.docstring { buffer.push_str("\n\"\"\""); for line in docstring.lines() { buffer.push('\n'); buffer.push_str(line); } buffer.push_str("\n\"\"\""); } buffer } fn argument_stub(argument: &Argument, imports: &Imports) -> String { let mut buffer = argument.name.clone(); if let Some(annotation) = &argument.annotation { buffer.push_str(": "); imports.serialize_expr(annotation, &mut buffer); } if let Some(default_value) = &argument.default_value { buffer.push_str(if argument.annotation.is_some() { " = " } else { "=" }); imports.serialize_expr(default_value, &mut buffer); } buffer } fn variable_length_argument_stub(argument: &VariableLengthArgument, imports: &Imports) -> String { let mut buffer = argument.name.clone(); if let Some(annotation) = &argument.annotation { buffer.push_str(": "); imports.serialize_expr(annotation, &mut buffer); } buffer } /// Datastructure to deduplicate, validate and generate imports #[derive(Default)] struct Imports { /// Import lines ready to use imports: Vec, /// Renaming map: from module name and member name return the name to use in type hints renaming: BTreeMap<(String, String), String>, } impl Imports { /// This generates a map from the builtin or module name to the actual alias used in the file /// /// For Python builtins and elements declared by the module the alias is always the actual name. /// /// For other elements, we can alias them using the `from X import Y as Z` syntax. /// So, we first list all builtins and local elements, then iterate on imports /// and create the aliases when needed. fn create(module: &Module, module_parents: &[&str]) -> Self { let mut elements_used_in_annotations = ElementsUsedInAnnotations::new(); elements_used_in_annotations.walk_module(module); let mut imports = Vec::new(); let mut renaming = BTreeMap::new(); let mut local_name_to_module_and_attribute = BTreeMap::new(); // We get the current module full name let current_module_name = module_parents .iter() .copied() .chain(once(module.name.as_str())) .collect::>() .join("."); // We first list local elements, they are never aliased or imported for name in module .classes .iter() .map(|c| c.name.clone()) .chain(module.functions.iter().map(|f| f.name.clone())) .chain(module.attributes.iter().map(|a| a.name.clone())) { local_name_to_module_and_attribute .insert(name.clone(), (current_module_name.clone(), name.clone())); } // We don't process the current module elements, no need to care about them local_name_to_module_and_attribute.remove(¤t_module_name); // We process then imports, normalizing local imports for (module, attrs) in &elements_used_in_annotations.module_to_name { let mut import_for_module = Vec::new(); for attr in attrs { // We split nested classes A.B in "A" (the part that must be imported and can have naming conflicts) and ".B" let (root_attr, attr_path) = attr .split_once('.') .map_or((attr.as_str(), None), |(root, path)| (root, Some(path))); let mut local_name = root_attr.to_owned(); let mut already_imported = false; while let Some((possible_conflict_module, possible_conflict_attr)) = local_name_to_module_and_attribute.get(&local_name) { if possible_conflict_module == module && *possible_conflict_attr == root_attr { // It's the same already_imported = true; break; } // We generate a new local name // TODO: we use currently a format like Foo2. It might be nicer to use something like ModFoo let number_of_digits_at_the_end = local_name .bytes() .rev() .take_while(|b| b.is_ascii_digit()) .count(); let (local_name_prefix, local_name_number) = local_name.split_at(local_name.len() - number_of_digits_at_the_end); local_name = format!( "{local_name_prefix}{}", u64::from_str(local_name_number).unwrap_or(1) + 1 ); } renaming.insert( (module.clone(), attr.clone()), if let Some(attr_path) = attr_path { format!("{local_name}.{attr_path}") } else { local_name.clone() }, ); if !already_imported { local_name_to_module_and_attribute .insert(local_name.clone(), (module.clone(), root_attr.to_owned())); let is_not_aliased_builtin = module == "builtins" && local_name == root_attr; if !is_not_aliased_builtin { import_for_module.push(if local_name == root_attr { local_name } else { format!("{root_attr} as {local_name}") }); } } } if !import_for_module.is_empty() { imports.push(format!( "from {module} import {}", import_for_module.join(", ") )); } } imports.sort(); // We make sure they are sorted Self { imports, renaming } } fn serialize_expr(&self, expr: &Expr, buffer: &mut String) { match expr { Expr::Constant { value } => match value { Constant::None => buffer.push_str("None"), Constant::Bool(value) => buffer.push_str(if *value { "True" } else { "False" }), Constant::Int(value) => buffer.push_str(value), Constant::Float(value) => { buffer.push_str(value); if !value.contains(['.', 'e', 'E']) { buffer.push('.'); // We make sure it's not parsed as an int } } Constant::Str(value) => { buffer.push('"'); for c in value.chars() { match c { '"' => buffer.push_str("\\\""), '\n' => buffer.push_str("\\n"), '\r' => buffer.push_str("\\r"), '\t' => buffer.push_str("\\t"), '\\' => buffer.push_str("\\\\"), '\0' => buffer.push_str("\\0"), c @ '\x00'..'\x20' => { write!(buffer, "\\x{:02x}", u32::from(c)).unwrap() } c => buffer.push(c), } } buffer.push('"'); } Constant::Ellipsis => buffer.push_str("..."), }, Expr::Name { id } => { buffer.push_str( self.renaming .get(&("builtins".into(), id.clone())) .expect("All type hint attributes should have been visited"), ); } Expr::Attribute { value, attr } => { if let Expr::Name { id, .. } = &**value { buffer.push_str( self.renaming .get(&(id.clone(), attr.clone())) .expect("All type hint attributes should have been visited"), ); } else { self.serialize_expr(value, buffer); buffer.push('.'); buffer.push_str(attr); } } Expr::BinOp { left, op, right } => { self.serialize_expr(left, buffer); buffer.push(' '); buffer.push(match op { Operator::BitOr => '|', }); self.serialize_expr(right, buffer); } Expr::Tuple { elts } => { buffer.push('('); self.serialize_elts(elts, buffer); if elts.len() == 1 { buffer.push(','); } buffer.push(')') } Expr::List { elts } => { buffer.push('['); self.serialize_elts(elts, buffer); buffer.push(']') } Expr::Subscript { value, slice } => { self.serialize_expr(value, buffer); buffer.push('['); if let Expr::Tuple { elts } = &**slice { // We don't display the tuple parentheses self.serialize_elts(elts, buffer); } else { self.serialize_expr(slice, buffer); } buffer.push(']'); } } } fn serialize_elts(&self, elts: &[Expr], buffer: &mut String) { for (i, elt) in elts.iter().enumerate() { if i > 0 { buffer.push_str(", "); } self.serialize_expr(elt, buffer); } } } /// Lists all the elements used in annotations struct ElementsUsedInAnnotations { /// module -> name where module is global (from the root of the interpreter). module_to_name: BTreeMap>, } impl ElementsUsedInAnnotations { fn new() -> Self { Self { module_to_name: BTreeMap::new(), } } fn walk_module(&mut self, module: &Module) { for attr in &module.attributes { self.walk_attribute(attr); } for class in &module.classes { self.walk_class(class); } for function in &module.functions { self.walk_function(function); } if module.incomplete { self.module_to_name .entry("builtins".into()) .or_default() .insert("str".into()); self.module_to_name .entry("_typeshed".into()) .or_default() .insert("Incomplete".into()); } } fn walk_class(&mut self, class: &Class) { for base in &class.bases { self.walk_expr(base); } for decorator in &class.decorators { self.walk_expr(decorator); } for method in &class.methods { self.walk_function(method); } for attr in &class.attributes { self.walk_attribute(attr); } for class in &class.inner_classes { self.walk_class(class); } } fn walk_attribute(&mut self, attribute: &Attribute) { if let Some(type_hint) = &attribute.annotation { self.walk_expr(type_hint); } } fn walk_function(&mut self, function: &Function) { for decorator in &function.decorators { self.walk_expr(decorator); } for arg in function .arguments .positional_only_arguments .iter() .chain(&function.arguments.arguments) .chain(&function.arguments.keyword_only_arguments) { if let Some(type_hint) = &arg.annotation { self.walk_expr(type_hint); } } for arg in function .arguments .vararg .as_ref() .iter() .chain(&function.arguments.kwarg.as_ref()) { if let Some(type_hint) = &arg.annotation { self.walk_expr(type_hint); } } if let Some(type_hint) = &function.returns { self.walk_expr(type_hint); } } fn walk_expr(&mut self, expr: &Expr) { match expr { Expr::Name { id } => { self.module_to_name .entry("builtins".into()) .or_default() .insert(id.clone()); } Expr::Attribute { value, attr } => { if let Expr::Name { id } = &**value { self.module_to_name .entry(id.into()) .or_default() .insert(attr.clone()); } else { self.walk_expr(value) } } Expr::BinOp { left, right, .. } => { self.walk_expr(left); self.walk_expr(right); } Expr::Subscript { value, slice } => { self.walk_expr(value); self.walk_expr(slice); } Expr::Tuple { elts } | Expr::List { elts } => { for elt in elts { self.walk_expr(elt) } } Expr::Constant { .. } => (), } } } #[cfg(test)] mod tests { use super::*; use crate::model::Arguments; #[test] fn function_stubs_with_variable_length() { let function = Function { name: "func".into(), decorators: Vec::new(), arguments: Arguments { positional_only_arguments: vec![Argument { name: "posonly".into(), default_value: None, annotation: None, }], arguments: vec![Argument { name: "arg".into(), default_value: None, annotation: None, }], vararg: Some(VariableLengthArgument { name: "varargs".into(), annotation: None, }), keyword_only_arguments: vec![Argument { name: "karg".into(), default_value: None, annotation: Some(Expr::Constant { value: Constant::Str("str".into()), }), }], kwarg: Some(VariableLengthArgument { name: "kwarg".into(), annotation: Some(Expr::Constant { value: Constant::Str("str".into()), }), }), }, returns: Some(Expr::Constant { value: Constant::Str("list[str]".into()), }), is_async: false, docstring: None, }; assert_eq!( "def func(posonly, /, arg, *varargs, karg: \"str\", **kwarg: \"str\") -> \"list[str]\": ...", function_stubs(&function, &Imports::default(), None) ) } #[test] fn function_stubs_without_variable_length() { let function = Function { name: "afunc".into(), decorators: Vec::new(), arguments: Arguments { positional_only_arguments: vec![Argument { name: "posonly".into(), default_value: Some(Expr::Constant { value: Constant::Int("1".into()), }), annotation: None, }], arguments: vec![Argument { name: "arg".into(), default_value: Some(Expr::Constant { value: Constant::Bool(true), }), annotation: None, }], vararg: None, keyword_only_arguments: vec![Argument { name: "karg".into(), default_value: Some(Expr::Constant { value: Constant::Str("foo".into()), }), annotation: Some(Expr::Constant { value: Constant::Str("str".into()), }), }], kwarg: None, }, returns: None, is_async: false, docstring: None, }; assert_eq!( "def afunc(posonly=1, /, arg=True, *, karg: \"str\" = \"foo\"): ...", function_stubs(&function, &Imports::default(), None) ) } #[test] fn test_function_async() { let function = Function { name: "foo".into(), decorators: Vec::new(), arguments: Arguments { positional_only_arguments: Vec::new(), arguments: Vec::new(), vararg: None, keyword_only_arguments: Vec::new(), kwarg: None, }, returns: None, is_async: true, docstring: None, }; assert_eq!( "async def foo(): ...", function_stubs(&function, &Imports::default(), None) ) } #[test] fn test_import() { let big_type = Expr::Subscript { value: Box::new(Expr::Name { id: "dict".into() }), slice: Box::new(Expr::Tuple { elts: vec![ Expr::Attribute { value: Box::new(Expr::Name { id: "foo.bar".into(), }), attr: "A".into(), }, Expr::Tuple { elts: vec![ Expr::Attribute { value: Box::new(Expr::Name { id: "foo".into() }), attr: "A.C".into(), }, Expr::Attribute { value: Box::new(Expr::Attribute { value: Box::new(Expr::Name { id: "foo".into() }), attr: "A".into(), }), attr: "D".into(), }, Expr::Attribute { value: Box::new(Expr::Name { id: "foo".into() }), attr: "B".into(), }, Expr::Attribute { value: Box::new(Expr::Name { id: "bat".into() }), attr: "A".into(), }, Expr::Attribute { value: Box::new(Expr::Name { id: "foo.bar".into(), }), attr: "int".into(), }, Expr::Name { id: "int".into() }, Expr::Name { id: "float".into() }, ], }, ], }), }; let imports = Imports::create( &Module { name: "bar".into(), modules: Vec::new(), classes: vec![ Class { name: "A".into(), bases: vec![Expr::Name { id: "dict".into() }], methods: Vec::new(), attributes: Vec::new(), decorators: vec![Expr::Attribute { value: Box::new(Expr::Name { id: "typing".into(), }), attr: "final".into(), }], inner_classes: Vec::new(), docstring: None, }, Class { name: "int".into(), bases: Vec::new(), methods: Vec::new(), attributes: Vec::new(), decorators: Vec::new(), inner_classes: Vec::new(), docstring: None, }, ], functions: vec![Function { name: String::new(), decorators: Vec::new(), arguments: Arguments { positional_only_arguments: Vec::new(), arguments: Vec::new(), vararg: None, keyword_only_arguments: Vec::new(), kwarg: None, }, returns: Some(big_type.clone()), is_async: false, docstring: None, }], attributes: Vec::new(), incomplete: true, docstring: None, }, &["foo"], ); assert_eq!( &imports.imports, &[ "from _typeshed import Incomplete", "from bat import A as A2", "from builtins import int as int2", "from foo import A as A3, B", "from typing import final" ] ); let mut output = String::new(); imports.serialize_expr(&big_type, &mut output); assert_eq!(output, "dict[A, (A3.C, A3.D, B, A2, int, int2, float)]"); } } ================================================ FILE: pyo3-introspection/tests/test.rs ================================================ use anyhow::{ensure, Result}; use pyo3_introspection::{introspect_cdylib, module_stub_files}; use std::collections::HashMap; use std::io::{Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, fs}; use tempfile::NamedTempFile; #[test] fn pytests_stubs() -> Result<()> { // We run the introspection let binary = env::var_os("PYO3_PYTEST_LIB_PATH") .expect("The PYO3_PYTEST_LIB_PATH constant must be set and target the pyo3-pytests cdylib"); let module = introspect_cdylib(binary, "pyo3_pytests")?; let actual_stubs = module_stub_files(&module); // We read the expected stubs let expected_subs_dir = Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .join("pytests") .join("stubs"); let mut expected_subs = HashMap::new(); add_dir_files( &expected_subs_dir, &expected_subs_dir.canonicalize()?, &mut expected_subs, )?; // We ensure we do not have extra generated files for file_name in actual_stubs.keys() { assert!( expected_subs.contains_key(file_name), "The generated file {} is not in the expected stubs directory pytests/stubs", file_name.display() ); } // We ensure the expected files are generated properly for (file_name, expected_file_content) in &expected_subs { let actual_file_content = actual_stubs.get(file_name).unwrap_or_else(|| { panic!( "The expected stub file {} has not been generated", file_name.display() ) }); let actual_file_content = format_with_ruff(actual_file_content)?; // We normalize line jumps for compatibility with Windows assert_eq!( expected_file_content.replace('\r', ""), actual_file_content.replace('\r', ""), "The content of file {} is different", file_name.display() ) } Ok(()) } fn add_dir_files( dir_path: &Path, base_dir_path: &Path, output: &mut HashMap, ) -> Result<()> { for entry in fs::read_dir(dir_path)? { let entry = entry?; if entry.file_type()?.is_dir() { add_dir_files(&entry.path(), base_dir_path, output)?; } else { output.insert( entry .path() .canonicalize()? .strip_prefix(base_dir_path)? .into(), fs::read_to_string(entry.path())?, ); } } Ok(()) } fn format_with_ruff(code: &str) -> Result { let temp_file = NamedTempFile::with_suffix(".pyi")?; // Write to file { let mut file = temp_file.as_file(); file.write_all(code.as_bytes())?; file.flush()?; file.seek(SeekFrom::Start(0))?; } ensure!( Command::new("ruff") .arg("format") .arg(temp_file.path()) .status()? .success(), "Failed to run ruff" ); let mut content = String::new(); temp_file.as_file().read_to_string(&mut content)?; Ok(content) } ================================================ FILE: pyo3-macros/Cargo.toml ================================================ [package] name = "pyo3-macros" version = "0.28.2" description = "Proc macros for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] homepage = "https://github.com/pyo3/pyo3" repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" rust-version.workspace = true [lib] proc-macro = true [features] multiple-pymethods = [] experimental-async = ["pyo3-macros-backend/experimental-async"] experimental-inspect = ["pyo3-macros-backend/experimental-inspect"] [dependencies] proc-macro2 = { version = "1.0.60", default-features = false } quote = "1" syn = { version = "2", features = ["full", "extra-traits"] } pyo3-macros-backend = { path = "../pyo3-macros-backend", version = "=0.28.2" } [lints] workspace = true ================================================ FILE: pyo3-macros/src/lib.rs ================================================ //! This crate declares only the proc macro attributes, as a crate defining proc macro attributes //! must not contain any other public items. #![cfg_attr(docsrs, feature(doc_cfg))] use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use pyo3_macros_backend::{ build_derive_from_pyobject, build_derive_into_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods, pymodule_function_impl, pymodule_module_impl, PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions, }; use quote::quote; use syn::{parse_macro_input, Item}; /// A proc macro used to implement Python modules. /// /// The name of the module will be taken from the function name, unless `#[pyo3(name = "my_name")]` /// is also annotated on the function to override the name. **Important**: the module name should /// match the `lib.name` setting in `Cargo.toml`, so that Python is able to import the module /// without needing a custom import loader. /// /// Functions annotated with `#[pymodule]` can also be annotated with the following: /// /// | Annotation | Description | /// | :- | :- | /// | `#[pyo3(name = "...")]` | Defines the name of the module in Python. | /// | `#[pyo3(submodule)]` | Skips adding a `PyInit_` FFI symbol to the compiled binary. | /// | `#[pyo3(module = "...")]` | Defines the Python `dotted.path` to the parent module for use in introspection. | /// | `#[pyo3(crate = "pyo3")]` | Defines the path to PyO3 to use code generated by the macro. | /// | `#[pyo3(gil_used = true)]` | Declares the GIL is needed to run this module safely under free-threaded Python. | /// /// For more on creating Python modules see the [module section of the guide][1]. /// /// Due to technical limitations on how `#[pymodule]` is implemented, a function marked /// `#[pymodule]` cannot have a module with the same name in the same scope. (The /// `#[pymodule]` implementation generates a hidden module with the same name containing /// metadata about the module, which is used by `wrap_pymodule!`). /// #[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/module.html")] #[proc_macro_attribute] pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream { let options = parse_macro_input!(args as PyModuleOptions); let mut ast = parse_macro_input!(input as Item); let expanded = match &mut ast { Item::Mod(module) => { match pymodule_module_impl(module, options) { // #[pymodule] on a module will rebuild the original ast, so we don't emit it here Ok(expanded) => return expanded.into(), Err(e) => Err(e), } } Item::Fn(function) => pymodule_function_impl(function, options), unsupported => Err(syn::Error::new_spanned( unsupported, "#[pymodule] only supports modules and functions.", )), } .unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } #[proc_macro_attribute] pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as Item); match item { Item::Struct(struct_) => pyclass_impl(attr, struct_, methods_type()), Item::Enum(enum_) => pyclass_enum_impl(attr, enum_, methods_type()), unsupported => { syn::Error::new_spanned(unsupported, "#[pyclass] only supports structs and enums.") .into_compile_error() .into() } } } /// A proc macro used to expose methods to Python. /// /// Methods within a `#[pymethods]` block can be annotated with as well as the following: /// /// | Annotation | Description | /// | :- | :- | /// | [`#[new]`][4] | Defines the class constructor, like Python's `__new__` method. | /// | [`#[getter]`][5] and [`#[setter]`][5] | These define getters and setters, similar to Python's `@property` decorator. This is useful for getters/setters that require computation or side effects; if that is not the case consider using [`#[pyo3(get, set)]`][12] on the struct's field(s).| /// | [`#[staticmethod]`][6]| Defines the method as a staticmethod, like Python's `@staticmethod` decorator.| /// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.| /// | [`#[classattr]`][9] | Defines a class variable. | /// | [`#[args]`][10] | Deprecated way to define a method's default arguments and allows the function to receive `*args` and `**kwargs`. Use `#[pyo3(signature = (...))]` instead. | /// | [`#[pyo3( | Any of the `#[pyo3]` options supported on [`macro@pyfunction`]. | /// /// For more on creating class methods, /// see the [class section of the guide][1]. /// /// If the [`multiple-pymethods`][2] feature is enabled, it is possible to implement /// multiple `#[pymethods]` blocks for a single `#[pyclass]`. /// This will add a transitive dependency on the [`inventory`][3] crate. /// #[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#instance-methods")] #[doc = concat!("[2]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#multiple-pymethods")] /// [3]: https://docs.rs/inventory/ #[doc = concat!("[4]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#constructor")] #[doc = concat!("[5]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#object-properties-using-getter-and-setter")] #[doc = concat!("[6]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#static-methods")] #[doc = concat!("[7]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#class-methods")] #[doc = concat!("[8]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#callable-objects")] #[doc = concat!("[9]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#class-attributes")] #[doc = concat!("[10]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#method-arguments")] #[doc = concat!("[11]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/function.html#function-options")] #[doc = concat!("[12]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#object-properties-using-pyo3get-set")] #[proc_macro_attribute] pub fn pymethods(attr: TokenStream, input: TokenStream) -> TokenStream { let methods_type = if cfg!(feature = "multiple-pymethods") { PyClassMethodsType::Inventory } else { PyClassMethodsType::Specialization }; pymethods_impl(attr, input, methods_type) } /// A proc macro used to expose Rust functions to Python. /// /// Functions annotated with `#[pyfunction]` can also be annotated with the following `#[pyo3]` /// options: /// /// | Annotation | Description | /// | :- | :- | /// | `#[pyo3(name = "...")]` | Defines the name of the function in Python. | /// | `#[pyo3(text_signature = "...")]` | Defines the `__text_signature__` attribute of the function in Python. | /// | `#[pyo3(pass_module)]` | Passes the module containing the function as a `&PyModule` first argument to the function. | /// | `#[pyo3(warn(message = "...", category = ...))]` | Generate warning given a message and a category | /// /// For more on exposing functions see the [function section of the guide][1]. /// /// Due to technical limitations on how `#[pyfunction]` is implemented, a function marked /// `#[pyfunction]` cannot have a module with the same name in the same scope. (The /// `#[pyfunction]` implementation generates a hidden module with the same name containing /// metadata about the function, which is used by `wrap_pyfunction!`). /// #[doc = concat!("[1]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/function.html")] #[proc_macro_attribute] pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as syn::ItemFn); let options = parse_macro_input!(attr as PyFunctionOptions); let expanded = build_py_function(&mut ast, options).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } #[proc_macro_derive(IntoPyObject, attributes(pyo3))] pub fn derive_into_py_object(item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as syn::DeriveInput); let expanded = build_derive_into_pyobject::(&ast).unwrap_or_compile_error(); quote!( #expanded ) .into() } #[proc_macro_derive(IntoPyObjectRef, attributes(pyo3))] pub fn derive_into_py_object_ref(item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as syn::DeriveInput); let expanded = pyo3_macros_backend::build_derive_into_pyobject::(&ast).unwrap_or_compile_error(); quote!( #expanded ) .into() } #[proc_macro_derive(FromPyObject, attributes(pyo3))] pub fn derive_from_py_object(item: TokenStream) -> TokenStream { let ast = parse_macro_input!(item as syn::DeriveInput); let expanded = build_derive_from_pyobject(&ast).unwrap_or_compile_error(); quote!( #expanded ) .into() } fn pyclass_impl( attrs: TokenStream, mut ast: syn::ItemStruct, methods_type: PyClassMethodsType, ) -> TokenStream { let args = parse_macro_input!(attrs with PyClassArgs::parse_struct_args); let expanded = build_py_class(&mut ast, args, methods_type).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } fn pyclass_enum_impl( attrs: TokenStream, mut ast: syn::ItemEnum, methods_type: PyClassMethodsType, ) -> TokenStream { let args = parse_macro_input!(attrs with PyClassArgs::parse_enum_args); let expanded = build_py_enum(&mut ast, args, methods_type).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } fn pymethods_impl( attr: TokenStream, input: TokenStream, methods_type: PyClassMethodsType, ) -> TokenStream { let mut ast = parse_macro_input!(input as syn::ItemImpl); // Apply all options as a #[pyo3] attribute on the ItemImpl // e.g. #[pymethods(crate = "crate")] impl Foo { } // -> #[pyo3(crate = "crate")] impl Foo { } let attr: TokenStream2 = attr.into(); ast.attrs.push(syn::parse_quote!( #[pyo3(#attr)] )); let expanded = build_py_methods(&mut ast, methods_type).unwrap_or_compile_error(); quote!( #ast #expanded ) .into() } fn methods_type() -> PyClassMethodsType { if cfg!(feature = "multiple-pymethods") { PyClassMethodsType::Inventory } else { PyClassMethodsType::Specialization } } trait UnwrapOrCompileError { fn unwrap_or_compile_error(self) -> TokenStream2; } impl UnwrapOrCompileError for syn::Result { fn unwrap_or_compile_error(self) -> TokenStream2 { self.unwrap_or_else(|e| e.into_compile_error()) } } ================================================ FILE: pyo3-macros-backend/Cargo.toml ================================================ [package] name = "pyo3-macros-backend" version = "0.28.2" description = "Code generation for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] homepage = "https://github.com/pyo3/pyo3" repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" rust-version.workspace = true # Note: we use default-features = false for proc-macro related crates # not to depend on proc-macro itself. # See https://github.com/PyO3/pyo3/pull/810 for more. [dependencies] heck = "0.5" proc-macro2 = { version = "1.0.60", default-features = false } quote = { version = "1.0.37", default-features = false } [dependencies.syn] # 2.0.59 for `LitCStr` version = "2.0.59" default-features = false features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits", "visit-mut"] [lints] workspace = true [features] experimental-async = [] experimental-inspect = [] ================================================ FILE: pyo3-macros-backend/src/attributes.rs ================================================ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::parse::Parser; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Expr, ExprPath, Ident, Index, LitBool, LitStr, Member, Path, Result, Token, }; use crate::combine_errors::CombineErrors; pub mod kw { syn::custom_keyword!(annotation); syn::custom_keyword!(attribute); syn::custom_keyword!(cancel_handle); syn::custom_keyword!(constructor); syn::custom_keyword!(dict); syn::custom_keyword!(eq); syn::custom_keyword!(eq_int); syn::custom_keyword!(extends); syn::custom_keyword!(freelist); syn::custom_keyword!(from_py_with); syn::custom_keyword!(frozen); syn::custom_keyword!(get); syn::custom_keyword!(get_all); syn::custom_keyword!(hash); syn::custom_keyword!(into_py_with); syn::custom_keyword!(item); syn::custom_keyword!(immutable_type); syn::custom_keyword!(from_item_all); syn::custom_keyword!(mapping); syn::custom_keyword!(module); syn::custom_keyword!(name); syn::custom_keyword!(ord); syn::custom_keyword!(pass_module); syn::custom_keyword!(rename_all); syn::custom_keyword!(sequence); syn::custom_keyword!(set); syn::custom_keyword!(set_all); syn::custom_keyword!(new); syn::custom_keyword!(signature); syn::custom_keyword!(str); syn::custom_keyword!(subclass); syn::custom_keyword!(submodule); syn::custom_keyword!(text_signature); syn::custom_keyword!(transparent); syn::custom_keyword!(unsendable); syn::custom_keyword!(weakref); syn::custom_keyword!(generic); syn::custom_keyword!(gil_used); syn::custom_keyword!(warn); syn::custom_keyword!(message); syn::custom_keyword!(category); syn::custom_keyword!(from_py_object); syn::custom_keyword!(skip_from_py_object); } fn take_int(read: &mut &str, tracker: &mut usize) -> String { let mut int = String::new(); for (i, ch) in read.char_indices() { match ch { '0'..='9' => { *tracker += 1; int.push(ch) } _ => { *read = &read[i..]; break; } } } int } fn take_ident(read: &mut &str, tracker: &mut usize) -> Ident { let mut ident = String::new(); if read.starts_with("r#") { ident.push_str("r#"); *tracker += 2; *read = &read[2..]; } for (i, ch) in read.char_indices() { match ch { 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => { *tracker += 1; ident.push(ch) } _ => { *read = &read[i..]; break; } } } Ident::parse_any.parse_str(&ident).unwrap() } // shorthand parsing logic inspiration taken from https://github.com/dtolnay/thiserror/blob/master/impl/src/fmt.rs fn parse_shorthand_format(fmt: LitStr) -> Result<(LitStr, Vec)> { let span = fmt.span(); let token = fmt.token(); let value = fmt.value(); let mut read = value.as_str(); let mut out = String::new(); let mut members = Vec::new(); let mut tracker = 1; while let Some(brace) = read.find('{') { tracker += brace; out += &read[..brace + 1]; read = &read[brace + 1..]; if read.starts_with('{') { out.push('{'); read = &read[1..]; tracker += 2; continue; } let next = match read.chars().next() { Some(next) => next, None => break, }; tracker += 1; let member = match next { '0'..='9' => { let start = tracker; let index = take_int(&mut read, &mut tracker).parse::().unwrap(); let end = tracker; let subspan = token.subspan(start..end).unwrap_or(span); let idx = Index { index, span: subspan, }; Member::Unnamed(idx) } 'a'..='z' | 'A'..='Z' | '_' => { let start = tracker; let mut ident = take_ident(&mut read, &mut tracker); let end = tracker; let subspan = token.subspan(start..end).unwrap_or(span); ident.set_span(subspan); Member::Named(ident) } '}' | ':' => { let start = tracker; tracker += 1; let end = tracker; let subspan = token.subspan(start..end).unwrap_or(span); // we found a closing bracket or formatting ':' without finding a member, we assume the user wants the instance formatted here bail_spanned!(subspan.span() => "No member found, you must provide a named or positionally specified member.") } _ => continue, }; members.push(member); } out += read; Ok((LitStr::new(&out, span), members)) } #[derive(Clone, Debug)] pub struct StringFormatter { pub fmt: LitStr, pub args: Vec, } impl Parse for crate::attributes::StringFormatter { fn parse(input: ParseStream<'_>) -> Result { let (fmt, args) = parse_shorthand_format(input.parse()?)?; Ok(Self { fmt, args }) } } impl ToTokens for crate::attributes::StringFormatter { fn to_tokens(&self, tokens: &mut TokenStream) { self.fmt.to_tokens(tokens); tokens.extend(quote! {self.args}) } } #[derive(Clone, Debug)] pub struct KeywordAttribute { pub kw: K, pub value: V, } #[derive(Clone, Debug)] pub struct OptionalKeywordAttribute { pub kw: K, pub value: Option, } /// A helper type which parses the inner type via a literal string /// e.g. `LitStrValue` -> parses "some::path" in quotes. #[derive(Clone, Debug, PartialEq, Eq)] pub struct LitStrValue(pub T); impl Parse for LitStrValue { fn parse(input: ParseStream<'_>) -> Result { let lit_str: LitStr = input.parse()?; lit_str.parse().map(LitStrValue) } } impl ToTokens for LitStrValue { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens) } } /// A helper type which parses a name via a literal string #[derive(Clone, Debug, PartialEq, Eq)] pub struct NameLitStr(pub Ident); impl Parse for NameLitStr { fn parse(input: ParseStream<'_>) -> Result { let string_literal: LitStr = input.parse()?; if let Ok(ident) = string_literal.parse_with(Ident::parse_any) { Ok(NameLitStr(ident)) } else { bail_spanned!(string_literal.span() => "expected a single identifier in double quotes") } } } impl ToTokens for NameLitStr { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens) } } /// Available renaming rules #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RenamingRule { CamelCase, KebabCase, Lowercase, PascalCase, ScreamingKebabCase, ScreamingSnakeCase, SnakeCase, Uppercase, } /// A helper type which parses a renaming rule via a literal string #[derive(Clone, Debug, PartialEq, Eq)] pub struct RenamingRuleLitStr { pub lit: LitStr, pub rule: RenamingRule, } impl Parse for RenamingRuleLitStr { fn parse(input: ParseStream<'_>) -> Result { let string_literal: LitStr = input.parse()?; let rule = match string_literal.value().as_ref() { "camelCase" => RenamingRule::CamelCase, "kebab-case" => RenamingRule::KebabCase, "lowercase" => RenamingRule::Lowercase, "PascalCase" => RenamingRule::PascalCase, "SCREAMING-KEBAB-CASE" => RenamingRule::ScreamingKebabCase, "SCREAMING_SNAKE_CASE" => RenamingRule::ScreamingSnakeCase, "snake_case" => RenamingRule::SnakeCase, "UPPERCASE" => RenamingRule::Uppercase, _ => { bail_spanned!(string_literal.span() => "expected a valid renaming rule, possible values are: \"camelCase\", \"kebab-case\", \"lowercase\", \"PascalCase\", \"SCREAMING-KEBAB-CASE\", \"SCREAMING_SNAKE_CASE\", \"snake_case\", \"UPPERCASE\"") } }; Ok(Self { lit: string_literal, rule, }) } } impl ToTokens for RenamingRuleLitStr { fn to_tokens(&self, tokens: &mut TokenStream) { self.lit.to_tokens(tokens) } } /// Text signature can be either a literal string or opt-in/out #[derive(Clone, Debug, PartialEq, Eq)] pub enum TextSignatureAttributeValue { Str(LitStr), // `None` ident to disable automatic text signature generation Disabled(Ident), } impl Parse for TextSignatureAttributeValue { fn parse(input: ParseStream<'_>) -> Result { if let Ok(lit_str) = input.parse::() { return Ok(TextSignatureAttributeValue::Str(lit_str)); } let err_span = match input.parse::() { Ok(ident) if ident == "None" => { return Ok(TextSignatureAttributeValue::Disabled(ident)); } Ok(other_ident) => other_ident.span(), Err(e) => e.span(), }; Err(err_spanned!(err_span => "expected a string literal or `None`")) } } impl ToTokens for TextSignatureAttributeValue { fn to_tokens(&self, tokens: &mut TokenStream) { match self { TextSignatureAttributeValue::Str(s) => s.to_tokens(tokens), TextSignatureAttributeValue::Disabled(b) => b.to_tokens(tokens), } } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum NewImplTypeAttributeValue { FromFields, // Future variant for 'default' should go here } impl Parse for NewImplTypeAttributeValue { fn parse(input: ParseStream<'_>) -> Result { let string_literal: LitStr = input.parse()?; if string_literal.value().as_str() == "from_fields" { Ok(NewImplTypeAttributeValue::FromFields) } else { bail_spanned!(string_literal.span() => "expected \"from_fields\"") } } } impl ToTokens for NewImplTypeAttributeValue { fn to_tokens(&self, tokens: &mut TokenStream) { match self { NewImplTypeAttributeValue::FromFields => { tokens.extend(quote! { "from_fields" }); } } } } pub type ExtendsAttribute = KeywordAttribute; pub type FreelistAttribute = KeywordAttribute>; pub type ModuleAttribute = KeywordAttribute; pub type NameAttribute = KeywordAttribute; pub type RenameAllAttribute = KeywordAttribute; pub type StrFormatterAttribute = OptionalKeywordAttribute; pub type TextSignatureAttribute = KeywordAttribute; pub type NewImplTypeAttribute = KeywordAttribute; pub type SubmoduleAttribute = kw::submodule; pub type GILUsedAttribute = KeywordAttribute; impl Parse for KeywordAttribute { fn parse(input: ParseStream<'_>) -> Result { let kw: K = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; Ok(KeywordAttribute { kw, value }) } } impl ToTokens for KeywordAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.kw.to_tokens(tokens); Token![=](self.kw.span()).to_tokens(tokens); self.value.to_tokens(tokens); } } impl Parse for OptionalKeywordAttribute { fn parse(input: ParseStream<'_>) -> Result { let kw: K = input.parse()?; let value = match input.parse::() { Ok(_) => Some(input.parse()?), Err(_) => None, }; Ok(OptionalKeywordAttribute { kw, value }) } } impl ToTokens for OptionalKeywordAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.kw.to_tokens(tokens); if self.value.is_some() { Token![=](self.kw.span()).to_tokens(tokens); self.value.to_tokens(tokens); } } } pub type FromPyWithAttribute = KeywordAttribute; pub type IntoPyWithAttribute = KeywordAttribute; pub type DefaultAttribute = OptionalKeywordAttribute; /// For specifying the path to the pyo3 crate. pub type CrateAttribute = KeywordAttribute>; pub fn get_pyo3_options(attr: &syn::Attribute) -> Result>> { if attr.path().is_ident("pyo3") { attr.parse_args_with(Punctuated::parse_terminated).map(Some) } else { Ok(None) } } /// Takes attributes from an attribute vector. /// /// For each attribute in `attrs`, `extractor` is called. If `extractor` returns `Ok(true)`, then /// the attribute will be removed from the vector. /// /// This is similar to `Vec::retain` except the closure is fallible and the condition is reversed. /// (In `retain`, returning `true` keeps the element, here it removes it.) pub fn take_attributes( attrs: &mut Vec, mut extractor: impl FnMut(&Attribute) -> Result, ) -> Result<()> { *attrs = attrs .drain(..) .filter_map(|attr| { extractor(&attr) .map(move |attribute_handled| if attribute_handled { None } else { Some(attr) }) .transpose() }) .collect::>()?; Ok(()) } pub fn take_pyo3_options(attrs: &mut Vec) -> Result> { let mut out = Vec::new(); take_attributes(attrs, |attr| match get_pyo3_options(attr) { Ok(result) => { if let Some(options) = result { out.extend(options.into_iter().map(|a| Ok(a))); Ok(true) } else { Ok(false) } } Err(err) => { out.push(Err(err)); Ok(true) } })?; let out: Vec = out.into_iter().try_combine_syn_errors()?; Ok(out) } ================================================ FILE: pyo3-macros-backend/src/combine_errors.rs ================================================ pub(crate) trait CombineErrors: Iterator { type Ok; fn try_combine_syn_errors(self) -> syn::Result>; } impl CombineErrors for I where I: Iterator>, { type Ok = T; fn try_combine_syn_errors(self) -> syn::Result> { let mut oks: Vec = Vec::new(); let mut errors: Vec = Vec::new(); for res in self { match res { Ok(val) => oks.push(val), Err(e) => errors.push(e), } } let mut err_iter = errors.into_iter(); let mut err = match err_iter.next() { // There are no errors None => return Ok(oks), Some(e) => e, }; for e in err_iter { err.combine(e); } Err(err) } } ================================================ FILE: pyo3-macros-backend/src/derive_attributes.rs ================================================ use crate::attributes::{ self, get_pyo3_options, CrateAttribute, DefaultAttribute, FromPyWithAttribute, IntoPyWithAttribute, RenameAllAttribute, }; use proc_macro2::Span; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{parenthesized, Attribute, LitStr, Result, Token}; /// Attributes for deriving `FromPyObject`/`IntoPyObject` scoped on containers. pub enum ContainerAttribute { /// Treat the Container as a Wrapper, operate directly on its field Transparent(attributes::kw::transparent), /// Force every field to be extracted from item of source Python object. ItemAll(attributes::kw::from_item_all), /// Change the name of an enum variant in the generated error message. ErrorAnnotation(LitStr), /// Change the path for the pyo3 crate Crate(CrateAttribute), /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction RenameAll(RenameAllAttribute), } impl Parse for ContainerAttribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::transparent) { let kw: attributes::kw::transparent = input.parse()?; Ok(ContainerAttribute::Transparent(kw)) } else if lookahead.peek(attributes::kw::from_item_all) { let kw: attributes::kw::from_item_all = input.parse()?; Ok(ContainerAttribute::ItemAll(kw)) } else if lookahead.peek(attributes::kw::annotation) { let _: attributes::kw::annotation = input.parse()?; let _: Token![=] = input.parse()?; input.parse().map(ContainerAttribute::ErrorAnnotation) } else if lookahead.peek(Token![crate]) { input.parse().map(ContainerAttribute::Crate) } else if lookahead.peek(attributes::kw::rename_all) { input.parse().map(ContainerAttribute::RenameAll) } else { Err(lookahead.error()) } } } #[derive(Default, Clone)] pub struct ContainerAttributes { /// Treat the Container as a Wrapper, operate directly on its field pub transparent: Option, /// Force every field to be extracted from item of source Python object. pub from_item_all: Option, /// Change the name of an enum variant in the generated error message. pub annotation: Option, /// Change the path for the pyo3 crate pub krate: Option, /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction pub rename_all: Option, } impl ContainerAttributes { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut options = ContainerAttributes::default(); for attr in attrs { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { pyo3_attrs .into_iter() .try_for_each(|opt| options.set_option(opt))?; } } Ok(options) } fn set_option(&mut self, option: ContainerAttribute) -> syn::Result<()> { macro_rules! set_option { ($key:ident) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once") ); self.$key = Some($key); } }; } match option { ContainerAttribute::Transparent(transparent) => set_option!(transparent), ContainerAttribute::ItemAll(from_item_all) => set_option!(from_item_all), ContainerAttribute::ErrorAnnotation(annotation) => set_option!(annotation), ContainerAttribute::Crate(krate) => set_option!(krate), ContainerAttribute::RenameAll(rename_all) => set_option!(rename_all), } Ok(()) } } #[derive(Clone, Debug)] pub enum FieldGetter { GetItem(attributes::kw::item, Option), GetAttr(attributes::kw::attribute, Option), } impl FieldGetter { pub fn span(&self) -> Span { match self { FieldGetter::GetItem(item, _) => item.span, FieldGetter::GetAttr(attribute, _) => attribute.span, } } } pub enum FieldAttribute { Getter(FieldGetter), FromPyWith(FromPyWithAttribute), IntoPyWith(IntoPyWithAttribute), Default(DefaultAttribute), } impl Parse for FieldAttribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::attribute) { let attr_kw: attributes::kw::attribute = input.parse()?; if input.peek(syn::token::Paren) { let content; let _ = parenthesized!(content in input); let attr_name: LitStr = content.parse()?; if !content.is_empty() { return Err(content.error( "expected at most one argument: `attribute` or `attribute(\"name\")`", )); } ensure_spanned!( !attr_name.value().is_empty(), attr_name.span() => "attribute name cannot be empty" ); Ok(Self::Getter(FieldGetter::GetAttr(attr_kw, Some(attr_name)))) } else { Ok(Self::Getter(FieldGetter::GetAttr(attr_kw, None))) } } else if lookahead.peek(attributes::kw::item) { let item_kw: attributes::kw::item = input.parse()?; if input.peek(syn::token::Paren) { let content; let _ = parenthesized!(content in input); let key = content.parse()?; if !content.is_empty() { return Err( content.error("expected at most one argument: `item` or `item(key)`") ); } Ok(Self::Getter(FieldGetter::GetItem(item_kw, Some(key)))) } else { Ok(Self::Getter(FieldGetter::GetItem(item_kw, None))) } } else if lookahead.peek(attributes::kw::from_py_with) { input.parse().map(Self::FromPyWith) } else if lookahead.peek(attributes::kw::into_py_with) { input.parse().map(FieldAttribute::IntoPyWith) } else if lookahead.peek(Token![default]) { input.parse().map(Self::Default) } else { Err(lookahead.error()) } } } #[derive(Clone, Debug, Default)] pub struct FieldAttributes { pub getter: Option, pub from_py_with: Option, pub into_py_with: Option, pub default: Option, } impl FieldAttributes { /// Extract the field attributes. pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut options = FieldAttributes::default(); for attr in attrs { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { pyo3_attrs .into_iter() .try_for_each(|opt| options.set_option(opt))?; } } Ok(options) } fn set_option(&mut self, option: FieldAttribute) -> syn::Result<()> { macro_rules! set_option { ($key:ident) => { set_option!($key, concat!("`", stringify!($key), "` may only be specified once")) }; ($key:ident, $msg: expr) => {{ ensure_spanned!( self.$key.is_none(), $key.span() => $msg ); self.$key = Some($key); }} } match option { FieldAttribute::Getter(getter) => { set_option!(getter, "only one of `attribute` or `item` can be provided") } FieldAttribute::FromPyWith(from_py_with) => set_option!(from_py_with), FieldAttribute::IntoPyWith(into_py_with) => set_option!(into_py_with), FieldAttribute::Default(default) => set_option!(default), } Ok(()) } } ================================================ FILE: pyo3-macros-backend/src/frompyobject.rs ================================================ use crate::attributes::{DefaultAttribute, FromPyWithAttribute, RenamingRule}; use crate::derive_attributes::{ContainerAttributes, FieldAttributes, FieldGetter}; #[cfg(feature = "experimental-inspect")] use crate::py_expr::PyExpr; use crate::utils::{self, Ctx}; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ ext::IdentExt, parse_quote, punctuated::Punctuated, spanned::Spanned, DataEnum, DeriveInput, Fields, Ident, Result, Token, }; /// Describes derivation input of an enum. struct Enum<'a> { enum_ident: &'a Ident, variants: Vec>, } impl<'a> Enum<'a> { /// Construct a new enum representation. /// /// `data_enum` is the `syn` representation of the input enum, `ident` is the /// `Identifier` of the enum. fn new( data_enum: &'a DataEnum, ident: &'a Ident, options: ContainerAttributes, ) -> Result { ensure_spanned!( !data_enum.variants.is_empty(), ident.span() => "cannot derive FromPyObject for empty enum" ); let variants = data_enum .variants .iter() .map(|variant| { let mut variant_options = ContainerAttributes::from_attrs(&variant.attrs)?; if let Some(rename_all) = &options.rename_all { ensure_spanned!( variant_options.rename_all.is_none(), variant_options.rename_all.span() => "Useless variant `rename_all` - enum is already annotated with `rename_all" ); variant_options.rename_all = Some(rename_all.clone()); } let var_ident = &variant.ident; Container::new( &variant.fields, parse_quote!(#ident::#var_ident), variant_options, ) }) .collect::>>()?; Ok(Enum { enum_ident: ident, variants, }) } /// Build derivation body for enums. fn build(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let mut var_extracts = Vec::new(); let mut variant_names = Vec::new(); let mut error_names = Vec::new(); for var in &self.variants { let struct_derive = var.build(ctx); let ext = quote!({ let maybe_ret = || -> #pyo3_path::PyResult { #struct_derive }(); match maybe_ret { ok @ ::std::result::Result::Ok(_) => return ok, ::std::result::Result::Err(err) => err } }); var_extracts.push(ext); variant_names.push(var.path.segments.last().unwrap().ident.to_string()); error_names.push(&var.err_name); } let ty_name = self.enum_ident.to_string(); quote!( let errors = [ #(#var_extracts),* ]; ::std::result::Result::Err( #pyo3_path::impl_::frompyobject::failed_to_extract_enum( obj.py(), #ty_name, &[#(#variant_names),*], &[#(#error_names),*], &errors ) ) ) } #[cfg(feature = "experimental-inspect")] fn input_type(&self) -> PyExpr { self.variants .iter() .map(|var| var.input_type()) .reduce(PyExpr::union) .expect("Empty enum") } } struct NamedStructField<'a> { ident: &'a syn::Ident, getter: Option, from_py_with: Option, default: Option, ty: &'a syn::Type, } struct TupleStructField { from_py_with: Option, ty: syn::Type, } /// Container Style /// /// Covers Structs, Tuplestructs and corresponding Newtypes. enum ContainerType<'a> { /// Struct Container, e.g. `struct Foo { a: String }` /// /// Variant contains the list of field identifiers and the corresponding extraction call. Struct(Vec>), /// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }` /// /// The field specified by the identifier is extracted directly from the object. #[cfg_attr(not(feature = "experimental-inspect"), allow(unused))] StructNewtype(&'a syn::Ident, Option, &'a syn::Type), /// Tuple struct, e.g. `struct Foo(String)`. /// /// Variant contains a list of conversion methods for each of the fields that are directly /// extracted from the tuple. Tuple(Vec), /// Tuple newtype, e.g. `#[transparent] struct Foo(String)` /// /// The wrapped field is directly extracted from the object. #[cfg_attr(not(feature = "experimental-inspect"), allow(unused))] TupleNewtype(Option, Box), } /// Data container /// /// Either describes a struct or an enum variant. struct Container<'a> { path: syn::Path, ty: ContainerType<'a>, err_name: String, rename_rule: Option, } impl<'a> Container<'a> { /// Construct a container based on fields, identifier and attributes. /// /// Fails if the variant has no fields or incompatible attributes. fn new(fields: &'a Fields, path: syn::Path, options: ContainerAttributes) -> Result { let style = match fields { Fields::Unnamed(unnamed) if !unnamed.unnamed.is_empty() => { ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is useless on tuple structs and variants." ); let mut tuple_fields = unnamed .unnamed .iter() .map(|field| { let attrs = FieldAttributes::from_attrs(&field.attrs)?; ensure_spanned!( attrs.getter.is_none(), field.span() => "`getter` is not permitted on tuple struct elements." ); ensure_spanned!( attrs.default.is_none(), field.span() => "`default` is not permitted on tuple struct elements." ); Ok(TupleStructField { from_py_with: attrs.from_py_with, ty: field.ty.clone(), }) }) .collect::>>()?; if tuple_fields.len() == 1 { // Always treat a 1-length tuple struct as "transparent", even without the // explicit annotation. let field = tuple_fields.pop().unwrap(); ContainerType::TupleNewtype(field.from_py_with, Box::new(field.ty)) } else if options.transparent.is_some() { bail_spanned!( fields.span() => "transparent structs and variants can only have 1 field" ); } else { ContainerType::Tuple(tuple_fields) } } Fields::Named(named) if !named.named.is_empty() => { let mut struct_fields = named .named .iter() .map(|field| { let ident = field .ident .as_ref() .expect("Named fields should have identifiers"); let mut attrs = FieldAttributes::from_attrs(&field.attrs)?; if let Some(ref from_item_all) = options.from_item_all { if let Some(replaced) = attrs.getter.replace(FieldGetter::GetItem(parse_quote!(item), None)) { match replaced { FieldGetter::GetItem(item, Some(item_name)) => { attrs.getter = Some(FieldGetter::GetItem(item, Some(item_name))); } FieldGetter::GetItem(_, None) => bail_spanned!(from_item_all.span() => "Useless `item` - the struct is already annotated with `from_item_all`"), FieldGetter::GetAttr(_, _) => bail_spanned!( from_item_all.span() => "The struct is already annotated with `from_item_all`, `attribute` is not allowed" ), } } } Ok(NamedStructField { ident, getter: attrs.getter, from_py_with: attrs.from_py_with, default: attrs.default, ty: &field.ty, }) }) .collect::>>()?; if struct_fields.iter().all(|field| field.default.is_some()) { bail_spanned!( fields.span() => "cannot derive FromPyObject for structs and variants with only default values" ) } else if options.transparent.is_some() { ensure_spanned!( struct_fields.len() == 1, fields.span() => "transparent structs and variants can only have 1 field" ); ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is not permitted on `transparent` structs and variants" ); let field = struct_fields.pop().unwrap(); ensure_spanned!( field.getter.is_none(), field.ident.span() => "`transparent` structs may not have a `getter` for the inner field" ); ContainerType::StructNewtype(field.ident, field.from_py_with, field.ty) } else { ContainerType::Struct(struct_fields) } } _ => bail_spanned!( fields.span() => "cannot derive FromPyObject for empty structs and variants" ), }; let err_name = options.annotation.map_or_else( || path.segments.last().unwrap().ident.to_string(), |lit_str| lit_str.value(), ); let v = Container { path, ty: style, err_name, rename_rule: options.rename_all.map(|v| v.value.rule), }; Ok(v) } fn name(&self) -> String { let mut value = String::new(); for segment in &self.path.segments { if !value.is_empty() { value.push_str("::"); } value.push_str(&segment.ident.to_string()); } value } /// Build derivation body for a struct. fn build(&self, ctx: &Ctx) -> TokenStream { match &self.ty { ContainerType::StructNewtype(ident, from_py_with, _) => { self.build_newtype_struct(Some(ident), from_py_with, ctx) } ContainerType::TupleNewtype(from_py_with, _) => { self.build_newtype_struct(None, from_py_with, ctx) } ContainerType::Tuple(tups) => self.build_tuple_struct(tups, ctx), ContainerType::Struct(tups) => self.build_struct(tups, ctx), } } fn build_newtype_struct( &self, field_ident: Option<&Ident>, from_py_with: &Option, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let self_ty = &self.path; let struct_name = self.name(); if let Some(ident) = field_ident { let field_name = ident.to_string(); if let Some(FromPyWithAttribute { kw, value: expr_path, }) = from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! { Ok(#self_ty { #ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#extractor, obj, #struct_name, #field_name)? }) } } else { quote! { Ok(#self_ty { #ident: #pyo3_path::impl_::frompyobject::extract_struct_field(obj, #struct_name, #field_name)? }) } } } else if let Some(FromPyWithAttribute { kw, value: expr_path, }) = from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! { #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#extractor, obj, #struct_name, 0).map(#self_ty) } } else { quote! { #pyo3_path::impl_::frompyobject::extract_tuple_struct_field(obj, #struct_name, 0).map(#self_ty) } } } fn build_tuple_struct(&self, struct_fields: &[TupleStructField], ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let self_ty = &self.path; let struct_name = &self.name(); let field_idents: Vec<_> = (0..struct_fields.len()) .map(|i| format_ident!("arg{}", i)) .collect(); let fields = struct_fields.iter().zip(&field_idents).enumerate().map(|(index, (field, ident))| { if let Some(FromPyWithAttribute { kw, value: expr_path, .. }) = &field.from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! { #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#extractor, &#ident, #struct_name, #index)? } } else { quote!{ #pyo3_path::impl_::frompyobject::extract_tuple_struct_field(&#ident, #struct_name, #index)? }} }); quote!( match #pyo3_path::types::PyAnyMethods::extract(obj) { ::std::result::Result::Ok((#(#field_idents),*)) => ::std::result::Result::Ok(#self_ty(#(#fields),*)), ::std::result::Result::Err(err) => ::std::result::Result::Err(err), } ) } fn build_struct(&self, struct_fields: &[NamedStructField<'_>], ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let self_ty = &self.path; let struct_name = self.name(); let mut fields: Punctuated = Punctuated::new(); for field in struct_fields { let ident = field.ident; let field_name = ident.unraw().to_string(); let getter = match field .getter .as_ref() .unwrap_or(&FieldGetter::GetAttr(parse_quote!(attribute), None)) { FieldGetter::GetAttr(_, Some(name)) => { quote!(#pyo3_path::types::PyAnyMethods::getattr(obj, #pyo3_path::intern!(obj.py(), #name))) } FieldGetter::GetAttr(_, None) => { let name = self .rename_rule .map(|rule| utils::apply_renaming_rule(rule, &field_name)); let name = name.as_deref().unwrap_or(&field_name); quote!(#pyo3_path::types::PyAnyMethods::getattr(obj, #pyo3_path::intern!(obj.py(), #name))) } FieldGetter::GetItem(_, Some(syn::Lit::Str(key))) => { quote!(#pyo3_path::types::PyAnyMethods::get_item(obj, #pyo3_path::intern!(obj.py(), #key))) } FieldGetter::GetItem(_, Some(key)) => { quote!(#pyo3_path::types::PyAnyMethods::get_item(obj, #key)) } FieldGetter::GetItem(_, None) => { let name = self .rename_rule .map(|rule| utils::apply_renaming_rule(rule, &field_name)); let name = name.as_deref().unwrap_or(&field_name); quote!(#pyo3_path::types::PyAnyMethods::get_item(obj, #pyo3_path::intern!(obj.py(), #name))) } }; let extractor = if let Some(FromPyWithAttribute { kw, value: expr_path, }) = &field.from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! (#pyo3_path::impl_::frompyobject::extract_struct_field_with(#extractor, &#getter?, #struct_name, #field_name)?) } else { quote!(#pyo3_path::impl_::frompyobject::extract_struct_field(&value, #struct_name, #field_name)?) }; let extracted = if let Some(default) = &field.default { let default_expr = if let Some(default_expr) = &default.value { default_expr.to_token_stream() } else { quote!(::std::default::Default::default()) }; quote!(if let ::std::result::Result::Ok(value) = #getter { #extractor } else { #default_expr }) } else { quote!({ let value = #getter?; #extractor }) }; fields.push(quote!(#ident: #extracted)); } quote!(::std::result::Result::Ok(#self_ty{#fields})) } #[cfg(feature = "experimental-inspect")] fn input_type(&self) -> PyExpr { match &self.ty { ContainerType::StructNewtype(_, from_py_with, ty) => { Self::field_input_type(from_py_with, ty) } ContainerType::TupleNewtype(from_py_with, ty) => { Self::field_input_type(from_py_with, ty) } ContainerType::Tuple(tups) => PyExpr::subscript( PyExpr::builtin("tuple"), PyExpr::tuple(tups.iter().map(|TupleStructField { from_py_with, ty }| { Self::field_input_type(from_py_with, ty) })), ), ContainerType::Struct(_) => { // TODO: implement using a Protocol? PyExpr::module_attr("_typeshed", "Incomplete") } } } #[cfg(feature = "experimental-inspect")] fn field_input_type(from_py_with: &Option, ty: &syn::Type) -> PyExpr { if from_py_with.is_some() { // We don't know what from_py_with is doing PyExpr::module_attr("_typeshed", "Incomplete") } else { PyExpr::from_from_py_object(ty.clone(), None) } } } fn verify_and_get_lifetime(generics: &syn::Generics) -> Result> { let mut lifetimes = generics.lifetimes(); let lifetime = lifetimes.next(); ensure_spanned!( lifetimes.next().is_none(), generics.span() => "FromPyObject can be derived with at most one lifetime parameter" ); Ok(lifetime) } /// Derive FromPyObject for enums and structs. /// /// * Max 1 lifetime specifier, will be tied to `FromPyObject`'s specifier /// * At least one field, in case of `#[transparent]`, exactly one field /// * At least one variant for enums. /// * Fields of input structs and enums must implement `FromPyObject` or be annotated with `from_py_with` /// * Derivation for structs with generic fields like `struct Foo(T)` /// adds `T: FromPyObject` on the derived implementation. pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { let options = ContainerAttributes::from_attrs(&tokens.attrs)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = &ctx; let (_, ty_generics, _) = tokens.generics.split_for_impl(); let mut trait_generics = tokens.generics.clone(); let lt_param = if let Some(lt) = verify_and_get_lifetime(&trait_generics)? { lt.clone() } else { trait_generics.params.push(parse_quote!('py)); parse_quote!('py) }; let (impl_generics, _, where_clause) = trait_generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where)); for param in trait_generics.type_params() { let gen_ident = ¶m.ident; where_clause .predicates .push(parse_quote!(#gen_ident: #pyo3_path::conversion::FromPyObjectOwned<#lt_param>)) } let derives = match &tokens.data { syn::Data::Enum(en) => { if options.transparent.is_some() || options.annotation.is_some() { bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \ at top level for enums"); } let en = Enum::new(en, &tokens.ident, options.clone())?; en.build(ctx) } syn::Data::Struct(st) => { if let Some(lit_str) = &options.annotation { bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs"); } let ident = &tokens.ident; let st = Container::new(&st.fields, parse_quote!(#ident), options.clone())?; st.build(ctx) } syn::Data::Union(_) => bail_spanned!( tokens.span() => "#[derive(FromPyObject)] is not supported for unions" ), }; #[cfg(feature = "experimental-inspect")] let input_type = { let pyo3_crate_path = &ctx.pyo3_path; let input_type = if tokens .generics .params .iter() .all(|p| matches!(p, syn::GenericParam::Lifetime(_))) { match &tokens.data { syn::Data::Enum(en) => Enum::new(en, &tokens.ident, options)?.input_type(), syn::Data::Struct(st) => { let ident = &tokens.ident; Container::new(&st.fields, parse_quote!(#ident), options.clone())?.input_type() } syn::Data::Union(_) => { // Not supported at this point PyExpr::module_attr("_typeshed", "Incomplete") } } } else { // We don't know how to deal with generic parameters // Blocked by https://github.com/rust-lang/rust/issues/76560 PyExpr::module_attr("_typeshed", "Incomplete") } .to_introspection_token_stream(pyo3_crate_path); quote! { const INPUT_TYPE: #pyo3_crate_path::inspect::PyStaticExpr = #input_type; } }; #[cfg(not(feature = "experimental-inspect"))] let input_type = quote! {}; let ident = &tokens.ident; Ok(quote!( #[automatically_derived] impl #impl_generics #pyo3_path::FromPyObject<'_, #lt_param> for #ident #ty_generics #where_clause { type Error = #pyo3_path::PyErr; fn extract(obj: #pyo3_path::Borrowed<'_, #lt_param, #pyo3_path::PyAny>) -> ::std::result::Result { let obj: &#pyo3_path::Bound<'_, _> = &*obj; #derives } #input_type } )) } ================================================ FILE: pyo3-macros-backend/src/intopyobject.rs ================================================ use crate::attributes::{IntoPyWithAttribute, RenamingRule}; use crate::derive_attributes::{ContainerAttributes, FieldAttributes}; #[cfg(feature = "experimental-inspect")] use crate::py_expr::PyExpr; use crate::utils::{self, Ctx}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::ext::IdentExt; use syn::spanned::Spanned as _; use syn::{parse_quote, DataEnum, DeriveInput, Fields, Ident, Index, Result}; struct ItemOption(Option); enum IntoPyObjectTypes { Transparent(syn::Type), Opaque { target: TokenStream, output: TokenStream, error: TokenStream, }, } struct IntoPyObjectImpl { types: IntoPyObjectTypes, body: TokenStream, } struct NamedStructField<'a> { ident: &'a syn::Ident, field: &'a syn::Field, item: Option, into_py_with: Option, } struct TupleStructField<'a> { field: &'a syn::Field, into_py_with: Option, } /// Container Style /// /// Covers Structs, Tuplestructs and corresponding Newtypes. enum ContainerType<'a> { /// Struct Container, e.g. `struct Foo { a: String }` /// /// Variant contains the list of field identifiers and the corresponding extraction call. Struct(Vec>), /// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }` /// /// The field specified by the identifier is extracted directly from the object. StructNewtype(&'a syn::Field), /// Tuple struct, e.g. `struct Foo(String)`. /// /// Variant contains a list of conversion methods for each of the fields that are directly /// extracted from the tuple. Tuple(Vec>), /// Tuple newtype, e.g. `#[transparent] struct Foo(String)` /// /// The wrapped field is directly extracted from the object. TupleNewtype(&'a syn::Field), } /// Data container /// /// Either describes a struct or an enum variant. struct Container<'a, const REF: bool> { path: syn::Path, receiver: Option, ty: ContainerType<'a>, rename_rule: Option, } /// Construct a container based on fields, identifier and attributes. impl<'a, const REF: bool> Container<'a, REF> { /// /// Fails if the variant has no fields or incompatible attributes. fn new( receiver: Option, fields: &'a Fields, path: syn::Path, options: ContainerAttributes, ) -> Result { let style = match fields { Fields::Unnamed(unnamed) if !unnamed.unnamed.is_empty() => { ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is useless on tuple structs and variants." ); let mut tuple_fields = unnamed .unnamed .iter() .map(|field| { let attrs = FieldAttributes::from_attrs(&field.attrs)?; ensure_spanned!( attrs.getter.is_none(), attrs.getter.unwrap().span() => "`item` and `attribute` are not permitted on tuple struct elements." ); Ok(TupleStructField { field, into_py_with: attrs.into_py_with, }) }) .collect::>>()?; if tuple_fields.len() == 1 { // Always treat a 1-length tuple struct as "transparent", even without the // explicit annotation. let TupleStructField { field, into_py_with, } = tuple_fields.pop().unwrap(); ensure_spanned!( into_py_with.is_none(), into_py_with.span() => "`into_py_with` is not permitted on `transparent` structs" ); ContainerType::TupleNewtype(field) } else if options.transparent.is_some() { bail_spanned!( fields.span() => "transparent structs and variants can only have 1 field" ); } else { ContainerType::Tuple(tuple_fields) } } Fields::Named(named) if !named.named.is_empty() => { if options.transparent.is_some() { ensure_spanned!( named.named.iter().count() == 1, fields.span() => "transparent structs and variants can only have 1 field" ); let field = named.named.iter().next().unwrap(); let attrs = FieldAttributes::from_attrs(&field.attrs)?; ensure_spanned!( attrs.getter.is_none(), attrs.getter.unwrap().span() => "`transparent` structs may not have `item` nor `attribute` for the inner field" ); ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is not permitted on `transparent` structs and variants" ); ensure_spanned!( attrs.into_py_with.is_none(), attrs.into_py_with.span() => "`into_py_with` is not permitted on `transparent` structs or variants" ); ContainerType::StructNewtype(field) } else { let struct_fields = named .named .iter() .map(|field| { let ident = field .ident .as_ref() .expect("Named fields should have identifiers"); let attrs = FieldAttributes::from_attrs(&field.attrs)?; Ok(NamedStructField { ident, field, item: attrs.getter.and_then(|getter| match getter { crate::derive_attributes::FieldGetter::GetItem(_, lit) => { Some(ItemOption(lit)) } crate::derive_attributes::FieldGetter::GetAttr(_, _) => None, }), into_py_with: attrs.into_py_with, }) }) .collect::>>()?; ContainerType::Struct(struct_fields) } } _ => bail_spanned!( fields.span() => "cannot derive `IntoPyObject` for empty structs" ), }; let v = Container { path, receiver, ty: style, rename_rule: options.rename_all.map(|v| v.value.rule), }; Ok(v) } fn match_pattern(&self) -> TokenStream { let path = &self.path; let pattern = match &self.ty { ContainerType::Struct(fields) => fields .iter() .enumerate() .map(|(i, f)| { let ident = f.ident; let new_ident = format_ident!("arg{i}"); quote! {#ident: #new_ident,} }) .collect::(), ContainerType::StructNewtype(field) => { let ident = field.ident.as_ref().unwrap(); quote!(#ident: arg0) } ContainerType::Tuple(fields) => { let i = (0..fields.len()).map(Index::from); let idents = (0..fields.len()).map(|i| format_ident!("arg{i}")); quote! { #(#i: #idents,)* } } ContainerType::TupleNewtype(_) => quote!(0: arg0), }; quote! { #path{ #pattern } } } /// Build derivation body for a struct. fn build(&self, ctx: &Ctx) -> IntoPyObjectImpl { match &self.ty { ContainerType::StructNewtype(field) | ContainerType::TupleNewtype(field) => { self.build_newtype_struct(field, ctx) } ContainerType::Tuple(fields) => self.build_tuple_struct(fields, ctx), ContainerType::Struct(fields) => self.build_struct(fields, ctx), } } fn build_newtype_struct(&self, field: &syn::Field, ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let ty = &field.ty; let unpack = self .receiver .as_ref() .map(|i| { let pattern = self.match_pattern(); quote! { let #pattern = #i;} }) .unwrap_or_default(); IntoPyObjectImpl { types: IntoPyObjectTypes::Transparent(ty.clone()), body: quote_spanned! { ty.span() => #unpack #pyo3_path::conversion::IntoPyObject::into_pyobject(arg0, py) }, } } fn build_struct(&self, fields: &[NamedStructField<'_>], ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let unpack = self .receiver .as_ref() .map(|i| { let pattern = self.match_pattern(); quote! { let #pattern = #i;} }) .unwrap_or_default(); let setter = fields .iter() .enumerate() .map(|(i, f)| { let key = f .item .as_ref() .and_then(|item| item.0.as_ref()) .map(|item| item.into_token_stream()) .unwrap_or_else(|| { let name = f.ident.unraw().to_string(); self.rename_rule.map(|rule| utils::apply_renaming_rule(rule, &name)).unwrap_or(name).into_token_stream() }); let value = Ident::new(&format!("arg{i}"), f.field.ty.span()); if let Some(expr_path) = f.into_py_with.as_ref().map(|i|&i.value) { let cow = if REF { quote!(::std::borrow::Cow::Borrowed(#value)) } else { quote!(::std::borrow::Cow::Owned(#value)) }; quote! { let into_py_with: fn(::std::borrow::Cow<'_, _>, #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::PyAny>> = #expr_path; #pyo3_path::types::PyDictMethods::set_item(&dict, #key, into_py_with(#cow, py)?)?; } } else { quote! { #pyo3_path::types::PyDictMethods::set_item(&dict, #key, #value)?; } } }) .collect::(); IntoPyObjectImpl { types: IntoPyObjectTypes::Opaque { target: quote!(#pyo3_path::types::PyDict), output: quote!(#pyo3_path::Bound<'py, Self::Target>), error: quote!(#pyo3_path::PyErr), }, body: quote! { #unpack let dict = #pyo3_path::types::PyDict::new(py); #setter ::std::result::Result::Ok::<_, Self::Error>(dict) }, } } fn build_tuple_struct(&self, fields: &[TupleStructField<'_>], ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let unpack = self .receiver .as_ref() .map(|i| { let pattern = self.match_pattern(); quote! { let #pattern = #i;} }) .unwrap_or_default(); let setter = fields .iter() .enumerate() .map(|(i, f)| { let ty = &f.field.ty; let value = Ident::new(&format!("arg{i}"), f.field.ty.span()); if let Some(expr_path) = f.into_py_with.as_ref().map(|i|&i.value) { let cow = if REF { quote!(::std::borrow::Cow::Borrowed(#value)) } else { quote!(::std::borrow::Cow::Owned(#value)) }; quote_spanned! { ty.span() => { let into_py_with: fn(::std::borrow::Cow<'_, _>, #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::PyAny>> = #expr_path; into_py_with(#cow, py)? }, } } else { quote_spanned! { ty.span() => #pyo3_path::conversion::IntoPyObject::into_pyobject(#value, py) .map(#pyo3_path::BoundObject::into_any) .map(#pyo3_path::BoundObject::into_bound)?, } } }) .collect::(); IntoPyObjectImpl { types: IntoPyObjectTypes::Opaque { target: quote!(#pyo3_path::types::PyTuple), output: quote!(#pyo3_path::Bound<'py, Self::Target>), error: quote!(#pyo3_path::PyErr), }, body: quote! { #unpack #pyo3_path::types::PyTuple::new(py, [#setter]) }, } } #[cfg(feature = "experimental-inspect")] fn output_type(&self) -> PyExpr { match &self.ty { ContainerType::StructNewtype(field) | ContainerType::TupleNewtype(field) => { Self::field_output_type(&None, &field.ty) } ContainerType::Tuple(tups) => PyExpr::subscript( PyExpr::builtin("tuple"), PyExpr::tuple(tups.iter().map( |TupleStructField { into_py_with, field, }| { Self::field_output_type(into_py_with, &field.ty) }, )), ), ContainerType::Struct(_) => { // TODO: implement using a Protocol? PyExpr::module_attr("_typeshed", "Incomplete") } } } #[cfg(feature = "experimental-inspect")] fn field_output_type(into_py_with: &Option, ty: &syn::Type) -> PyExpr { if into_py_with.is_some() { // We don't know what into_py_with is doing PyExpr::module_attr("_typeshed", "Incomplete") } else { PyExpr::from_into_py_object(ty.clone(), None) } } } /// Describes derivation input of an enum. struct Enum<'a, const REF: bool> { variants: Vec>, } impl<'a, const REF: bool> Enum<'a, REF> { /// Construct a new enum representation. /// /// `data_enum` is the `syn` representation of the input enum, `ident` is the /// `Identifier` of the enum. fn new(data_enum: &'a DataEnum, ident: &'a Ident) -> Result { ensure_spanned!( !data_enum.variants.is_empty(), ident.span() => "cannot derive `IntoPyObject` for empty enum" ); let variants = data_enum .variants .iter() .map(|variant| { let attrs = ContainerAttributes::from_attrs(&variant.attrs)?; let var_ident = &variant.ident; ensure_spanned!( !variant.fields.is_empty(), variant.ident.span() => "cannot derive `IntoPyObject` for empty variants" ); Container::new( None, &variant.fields, parse_quote!(#ident::#var_ident), attrs, ) }) .collect::>>()?; Ok(Enum { variants }) } /// Build derivation body for enums. fn build(&self, ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let variants = self .variants .iter() .map(|v| { let IntoPyObjectImpl { body, .. } = v.build(ctx); let pattern = v.match_pattern(); quote! { #pattern => { {#body} .map(#pyo3_path::BoundObject::into_any) .map(#pyo3_path::BoundObject::into_bound) .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into) } } }) .collect::(); IntoPyObjectImpl { types: IntoPyObjectTypes::Opaque { target: quote!(#pyo3_path::types::PyAny), output: quote!(#pyo3_path::Bound<'py, >::Target>), error: quote!(#pyo3_path::PyErr), }, body: quote! { match self { #variants } }, } } #[cfg(feature = "experimental-inspect")] fn output_type(&self) -> PyExpr { self.variants .iter() .map(|var| var.output_type()) .reduce(PyExpr::union) .expect("Empty enum") } } // if there is a `'py` lifetime, we treat it as the `Python<'py>` lifetime fn verify_and_get_lifetime(generics: &syn::Generics) -> Option<&syn::LifetimeParam> { let mut lifetimes = generics.lifetimes(); lifetimes.find(|l| l.lifetime.ident == "py") } pub fn build_derive_into_pyobject(tokens: &DeriveInput) -> Result { let options = ContainerAttributes::from_attrs(&tokens.attrs)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = &ctx; let (_, ty_generics, _) = tokens.generics.split_for_impl(); let mut trait_generics = tokens.generics.clone(); if REF { trait_generics.params.push(parse_quote!('_a)); } let lt_param = if let Some(lt) = verify_and_get_lifetime(&trait_generics) { lt.clone() } else { trait_generics.params.push(parse_quote!('py)); parse_quote!('py) }; let (impl_generics, _, where_clause) = trait_generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where)); for param in trait_generics.type_params() { let gen_ident = ¶m.ident; where_clause.predicates.push(if REF { parse_quote!(&'_a #gen_ident: #pyo3_path::conversion::IntoPyObject<'py>) } else { parse_quote!(#gen_ident: #pyo3_path::conversion::IntoPyObject<'py>) }) } let IntoPyObjectImpl { types, body } = match &tokens.data { syn::Data::Enum(en) => { if options.transparent.is_some() { bail_spanned!(tokens.span() => "`transparent` is not supported at top level for enums"); } if let Some(rename_all) = options.rename_all { bail_spanned!(rename_all.span() => "`rename_all` is not supported at top level for enums"); } let en = Enum::::new(en, &tokens.ident)?; en.build(ctx) } syn::Data::Struct(st) => { let ident = &tokens.ident; let st = Container::::new( Some(Ident::new("self", Span::call_site())), &st.fields, parse_quote!(#ident), options.clone(), )?; st.build(ctx) } syn::Data::Union(_) => bail_spanned!( tokens.span() => "#[derive(`IntoPyObject`)] is not supported for unions" ), }; let (target, output, error) = match types { IntoPyObjectTypes::Transparent(ty) => { if REF { ( quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Target }, quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Output }, quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Error }, ) } else { ( quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Target }, quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Output }, quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Error }, ) } } IntoPyObjectTypes::Opaque { target, output, error, } => (target, output, error), }; let ident = &tokens.ident; let ident = if REF { quote! { &'_a #ident} } else { quote! { #ident } }; #[cfg(feature = "experimental-inspect")] let output_type = { let pyo3_crate_path = &ctx.pyo3_path; let output_type = if tokens .generics .params .iter() .all(|p| matches!(p, syn::GenericParam::Lifetime(_))) { match &tokens.data { syn::Data::Enum(en) => Enum::::new(en, &tokens.ident)?.output_type(), syn::Data::Struct(st) => { let ident = &tokens.ident; Container::::new( Some(Ident::new("self", Span::call_site())), &st.fields, parse_quote!(#ident), options, )? .output_type() } syn::Data::Union(_) => { // Not supported at this point PyExpr::module_attr("_typeshed", "Incomplete") } } } else { // We don't know how to deal with generic parameters // Blocked by https://github.com/rust-lang/rust/issues/76560 PyExpr::module_attr("_typeshed", "Incomplete") } .to_introspection_token_stream(pyo3_crate_path); quote! { const OUTPUT_TYPE: #pyo3_path::inspect::PyStaticExpr = #output_type; } }; #[cfg(not(feature = "experimental-inspect"))] let output_type = quote! {}; Ok(quote!( #[automatically_derived] impl #impl_generics #pyo3_path::conversion::IntoPyObject<#lt_param> for #ident #ty_generics #where_clause { type Target = #target; type Output = #output; type Error = #error; #output_type fn into_pyobject(self, py: #pyo3_path::Python<#lt_param>) -> ::std::result::Result< >::Output, >::Error, > { #body } } )) } ================================================ FILE: pyo3-macros-backend/src/introspection.rs ================================================ //! Generates introspection data i.e. JSON strings in the .pyo3i0 section. //! //! There is a JSON per PyO3 proc macro (pyclass, pymodule, pyfunction...). //! //! These JSON blobs can refer to each others via the _PYO3_INTROSPECTION_ID constants //! providing unique ids for each element. //! //! The JSON blobs format must be synchronized with the `pyo3_introspection::introspection.rs::Chunk` //! type that is used to parse them. use crate::method::{FnArg, RegularArg}; use crate::py_expr::PyExpr; use crate::pyfunction::FunctionSignature; use crate::utils::{PyO3CratePath, PythonDoc, StrOrExpr}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::borrow::Cow; use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; use std::fmt::Write; use std::hash::{Hash, Hasher}; use std::mem::take; use std::sync::atomic::{AtomicUsize, Ordering}; use syn::{Attribute, Ident, ReturnType, Type, TypePath}; static GLOBAL_COUNTER_FOR_UNIQUE_NAMES: AtomicUsize = AtomicUsize::new(0); pub fn module_introspection_code<'a>( pyo3_crate_path: &PyO3CratePath, name: &str, members: impl IntoIterator, members_cfg_attrs: impl IntoIterator>, doc: Option<&PythonDoc>, incomplete: bool, ) -> TokenStream { let mut desc = HashMap::from([ ("type", IntrospectionNode::String("module".into())), ("id", IntrospectionNode::IntrospectionId(None)), ("name", IntrospectionNode::String(name.into())), ( "members", IntrospectionNode::List( members .into_iter() .zip(members_cfg_attrs) .map(|(member, attributes)| AttributedIntrospectionNode { node: IntrospectionNode::IntrospectionId(Some(ident_to_type(member))), attributes, }) .collect(), ), ), ("incomplete", IntrospectionNode::Bool(incomplete)), ]); if let Some(doc) = doc { desc.insert("doc", IntrospectionNode::Doc(doc)); } IntrospectionNode::Map(desc).emit(pyo3_crate_path) } pub fn class_introspection_code( pyo3_crate_path: &PyO3CratePath, ident: &Ident, name: &str, extends: Option, is_final: bool, parent: Option<&Type>, doc: Option<&PythonDoc>, ) -> TokenStream { let mut desc = HashMap::from([ ("type", IntrospectionNode::String("class".into())), ( "id", IntrospectionNode::IntrospectionId(Some(ident_to_type(ident))), ), ("name", IntrospectionNode::String(name.into())), ]); if let Some(extends) = extends { desc.insert("bases", IntrospectionNode::List(vec![extends.into()])); } if is_final { desc.insert( "decorators", IntrospectionNode::List(vec![PyExpr::module_attr("typing", "final").into()]), ); } if let Some(parent) = parent { desc.insert( "parent", IntrospectionNode::IntrospectionId(Some(Cow::Borrowed(parent))), ); } if let Some(doc) = &doc { desc.insert("doc", IntrospectionNode::Doc(doc)); } IntrospectionNode::Map(desc).emit(pyo3_crate_path) } #[expect(clippy::too_many_arguments)] pub fn function_introspection_code( pyo3_crate_path: &PyO3CratePath, ident: Option<&Ident>, name: &str, signature: &FunctionSignature<'_>, first_argument: Option<&'static str>, returns: ReturnType, decorators: impl IntoIterator, is_async: bool, is_returning_not_implemented_on_extraction_error: bool, doc: Option<&PythonDoc>, parent: Option<&Type>, ) -> TokenStream { let mut desc = HashMap::from([ ("type", IntrospectionNode::String("function".into())), ("name", IntrospectionNode::String(name.into())), ( "arguments", arguments_introspection_data( signature, first_argument, is_returning_not_implemented_on_extraction_error, parent, ), ), ( "returns", if let Some((_, returns)) = signature .attribute .as_ref() .and_then(|attribute| attribute.value.returns.as_ref()) { returns.as_type_hint().into() } else { match returns { ReturnType::Default => PyExpr::builtin("None"), ReturnType::Type(_, ty) => PyExpr::from_return_type(*ty, parent), } .into() }, ), ]); if is_async { desc.insert("async", IntrospectionNode::Bool(true)); } if let Some(ident) = ident { desc.insert( "id", IntrospectionNode::IntrospectionId(Some(ident_to_type(ident))), ); } let decorators = decorators.into_iter().map(|d| d.into()).collect::>(); if !decorators.is_empty() { desc.insert("decorators", IntrospectionNode::List(decorators)); } if let Some(doc) = doc { desc.insert("doc", IntrospectionNode::Doc(doc)); } if let Some(parent) = parent { desc.insert( "parent", IntrospectionNode::IntrospectionId(Some(Cow::Borrowed(parent))), ); } IntrospectionNode::Map(desc).emit(pyo3_crate_path) } pub fn attribute_introspection_code( pyo3_crate_path: &PyO3CratePath, parent: Option<&Type>, name: String, value: PyExpr, rust_type: Type, doc: Option<&PythonDoc>, is_final: bool, ) -> TokenStream { let mut desc = HashMap::from([ ("type", IntrospectionNode::String("attribute".into())), ("name", IntrospectionNode::String(name.into())), ( "parent", IntrospectionNode::IntrospectionId(parent.map(Cow::Borrowed)), ), ]); if value == PyExpr::ellipsis() { // We need to set a type, but not need to set the value to ..., all attributes have a value desc.insert( "annotation", if is_final { PyExpr::subscript( PyExpr::module_attr("typing", "Final"), PyExpr::from_return_type(rust_type, parent), ) .into() } else { PyExpr::from_return_type(rust_type, parent).into() }, ); } else { desc.insert( "annotation", if is_final { // Type checkers can infer the type from the value because it's typing.Literal[value] // So, following stubs best practices, we only write typing.Final and not // typing.Final[typing.literal[value]] PyExpr::module_attr("typing", "Final") } else { PyExpr::from_return_type(rust_type, parent) } .into(), ); desc.insert("value", value.into()); } if let Some(doc) = doc { desc.insert("doc", IntrospectionNode::Doc(doc)); } IntrospectionNode::Map(desc).emit(pyo3_crate_path) } fn arguments_introspection_data<'a>( signature: &'a FunctionSignature<'a>, first_argument: Option<&'a str>, is_returning_not_implemented_on_extraction_error: bool, class_type: Option<&Type>, ) -> IntrospectionNode<'a> { let mut argument_desc = signature.arguments.iter().filter(|arg| { matches!( arg, FnArg::Regular(_) | FnArg::VarArgs(_) | FnArg::KwArgs(_) ) }); let mut posonlyargs = Vec::new(); let mut args = Vec::new(); let mut vararg = None; let mut kwonlyargs = Vec::new(); let mut kwarg = None; if let Some(first_argument) = first_argument { posonlyargs.push( IntrospectionNode::Map( [("name", IntrospectionNode::String(first_argument.into()))].into(), ) .into(), ); } for (i, param) in signature .python_signature .positional_parameters .iter() .enumerate() { let arg_desc = if let Some(FnArg::Regular(arg_desc)) = argument_desc.next() { arg_desc } else { panic!("Less arguments than in python signature"); }; let arg = argument_introspection_data( param, arg_desc, is_returning_not_implemented_on_extraction_error, class_type, ); if i < signature.python_signature.positional_only_parameters { posonlyargs.push(arg); } else { args.push(arg) } } if let Some(param) = &signature.python_signature.varargs { let Some(FnArg::VarArgs(arg_desc)) = argument_desc.next() else { panic!("Fewer arguments than in python signature"); }; let mut params = HashMap::from([("name", IntrospectionNode::String(param.into()))]); if let Some(annotation) = &arg_desc.annotation { params.insert("annotation", annotation.clone().into()); } vararg = Some(IntrospectionNode::Map(params)); } for (param, _) in &signature.python_signature.keyword_only_parameters { let Some(FnArg::Regular(arg_desc)) = argument_desc.next() else { panic!("Less arguments than in python signature"); }; kwonlyargs.push(argument_introspection_data( param, arg_desc, is_returning_not_implemented_on_extraction_error, class_type, )); } if let Some(param) = &signature.python_signature.kwargs { let Some(FnArg::KwArgs(arg_desc)) = argument_desc.next() else { panic!("Less arguments than in python signature"); }; let mut params = HashMap::from([("name", IntrospectionNode::String(param.into()))]); if let Some(annotation) = &arg_desc.annotation { params.insert("annotation", annotation.clone().into()); } kwarg = Some(IntrospectionNode::Map(params)); } let mut map = HashMap::new(); if !posonlyargs.is_empty() { map.insert("posonlyargs", IntrospectionNode::List(posonlyargs)); } if !args.is_empty() { map.insert("args", IntrospectionNode::List(args)); } if let Some(vararg) = vararg { map.insert("vararg", vararg); } if !kwonlyargs.is_empty() { map.insert("kwonlyargs", IntrospectionNode::List(kwonlyargs)); } if let Some(kwarg) = kwarg { map.insert("kwarg", kwarg); } IntrospectionNode::Map(map) } fn argument_introspection_data<'a>( name: &'a str, desc: &'a RegularArg<'_>, is_returning_not_implemented_on_extraction_error: bool, class_type: Option<&Type>, ) -> AttributedIntrospectionNode<'a> { let mut params: HashMap<_, _> = [("name", IntrospectionNode::String(name.into()))].into(); if let Some(expr) = &desc.default_value { params.insert("default", PyExpr::constant_from_expression(expr).into()); } if is_returning_not_implemented_on_extraction_error { // all inputs are allowed, we use `object` params.insert("annotation", PyExpr::builtin("object").into()); } else if let Some(annotation) = &desc.annotation { params.insert("annotation", annotation.clone().into()); } else if desc.from_py_with.is_none() { // If from_py_with is set we don't know anything on the input type params.insert( "annotation", PyExpr::from_argument_type(desc.ty.clone(), class_type).into(), ); } IntrospectionNode::Map(params).into() } enum IntrospectionNode<'a> { String(Cow<'a, str>), Bool(bool), IntrospectionId(Option>), TypeHint(Cow<'a, PyExpr>), Doc(&'a PythonDoc), Map(HashMap<&'static str, IntrospectionNode<'a>>), List(Vec>), } impl IntrospectionNode<'_> { fn emit(self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { let mut content = ConcatenationBuilder::default(); self.add_to_serialization(&mut content, pyo3_crate_path); content.into_static( pyo3_crate_path, format_ident!("PYO3_INTROSPECTION_1_{}", unique_element_id()), ) } fn add_to_serialization( self, content: &mut ConcatenationBuilder, pyo3_crate_path: &PyO3CratePath, ) { match self { Self::String(string) => { content.push_str_to_escape(&string); } Self::Bool(value) => content.push_str(if value { "true" } else { "false" }), Self::IntrospectionId(ident) => { content.push_str("\""); content.push_tokens(if let Some(ident) = ident { quote! { #ident::_PYO3_INTROSPECTION_ID.as_bytes() } } else { quote! { _PYO3_INTROSPECTION_ID.as_bytes() } }); content.push_str("\""); } Self::TypeHint(hint) => { content.push_tokens(serialize_type_hint( hint.to_introspection_token_stream(pyo3_crate_path), pyo3_crate_path, )); } Self::Doc(doc) => { content.push_str("\""); for part in &doc.parts { match part { StrOrExpr::Str {value, ..} => content.push_str(&escape_json_string(value)), StrOrExpr::Expr(value) => content.push_tokens(quote! {{ const DOC: &str = #value; const DOC_LEN: usize = #pyo3_crate_path::impl_::introspection::escaped_json_string_len(&DOC); const DOC_SER: [u8; DOC_LEN] = { let mut result: [u8; DOC_LEN] = [0; DOC_LEN]; #pyo3_crate_path::impl_::introspection::escape_json_string(&DOC, &mut result); result }; &DOC_SER }}), } } content.push_str("\""); } Self::Map(map) => { content.push_str("{"); for (i, (key, value)) in map.into_iter().enumerate() { if i > 0 { content.push_str(","); } content.push_str_to_escape(key); content.push_str(":"); value.add_to_serialization(content, pyo3_crate_path); } content.push_str("}"); } Self::List(list) => { content.push_str("["); for (i, AttributedIntrospectionNode { node, attributes }) in list.into_iter().enumerate() { if attributes.is_empty() { if i > 0 { content.push_str(","); } node.add_to_serialization(content, pyo3_crate_path); } else { // We serialize the element to easily gate it behind the attributes let mut nested_builder = ConcatenationBuilder::default(); if i > 0 { nested_builder.push_str(","); } node.add_to_serialization(&mut nested_builder, pyo3_crate_path); let nested_content = nested_builder.into_token_stream(pyo3_crate_path); content.push_tokens(quote! { #(#attributes)* #nested_content }); } } content.push_str("]"); } } } } impl From for IntrospectionNode<'static> { fn from(element: PyExpr) -> Self { Self::TypeHint(Cow::Owned(element)) } } fn serialize_type_hint(hint: TokenStream, pyo3_crate_path: &PyO3CratePath) -> TokenStream { quote! {{ const TYPE_HINT: #pyo3_crate_path::inspect::PyStaticExpr = #hint; const TYPE_HINT_LEN: usize = #pyo3_crate_path::inspect::serialized_len_for_introspection(&TYPE_HINT); const TYPE_HINT_SER: [u8; TYPE_HINT_LEN] = { let mut result: [u8; TYPE_HINT_LEN] = [0; TYPE_HINT_LEN]; #pyo3_crate_path::inspect::serialize_for_introspection(&TYPE_HINT, &mut result); result }; &TYPE_HINT_SER }} } struct AttributedIntrospectionNode<'a> { node: IntrospectionNode<'a>, attributes: &'a [Attribute], } impl<'a> From> for AttributedIntrospectionNode<'a> { fn from(node: IntrospectionNode<'a>) -> Self { Self { node, attributes: &[], } } } impl<'a> From for AttributedIntrospectionNode<'a> { fn from(node: PyExpr) -> Self { IntrospectionNode::from(node).into() } } #[derive(Default)] pub struct ConcatenationBuilder { elements: Vec, current_string: String, } impl ConcatenationBuilder { pub fn push_tokens(&mut self, token_stream: TokenStream) { if !self.current_string.is_empty() { self.elements.push(ConcatenationBuilderElement::String(take( &mut self.current_string, ))); } self.elements .push(ConcatenationBuilderElement::TokenStream(token_stream)); } pub fn push_str(&mut self, value: &str) { self.current_string.push_str(value); } fn push_str_to_escape(&mut self, value: &str) { self.current_string.push('"'); for c in value.chars() { match c { '\\' => self.current_string.push_str("\\\\"), '"' => self.current_string.push_str("\\\""), c => { if c < char::from(32) { panic!("ASCII chars below 32 are not allowed") } else { self.current_string.push(c); } } } } self.current_string.push('"'); } pub fn into_token_stream(self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { let mut elements = self.elements; if !self.current_string.is_empty() { elements.push(ConcatenationBuilderElement::String(self.current_string)); } if let [ConcatenationBuilderElement::String(string)] = elements.as_slice() { // We avoid the const_concat! macro if there is only a single string return quote! { #string.as_bytes() }; } quote! { { const PIECES: &[&[u8]] = &[#(#elements , )*]; &#pyo3_crate_path::impl_::concat::combine_to_array::<{ #pyo3_crate_path::impl_::concat::combined_len(PIECES) }>(PIECES) } } } fn into_static(self, pyo3_crate_path: &PyO3CratePath, ident: Ident) -> TokenStream { let mut elements = self.elements; if !self.current_string.is_empty() { elements.push(ConcatenationBuilderElement::String(self.current_string)); } // #[no_mangle] is required to make sure some linkers like Linux ones do not mangle the section name too. quote! { const _: () = { const PIECES: &[&[u8]] = &[#(#elements , )*]; const PIECES_LEN: usize = #pyo3_crate_path::impl_::concat::combined_len(PIECES); #[used] #[no_mangle] static #ident: #pyo3_crate_path::impl_::introspection::SerializedIntrospectionFragment = #pyo3_crate_path::impl_::introspection::SerializedIntrospectionFragment { length: PIECES_LEN as u32, fragment: #pyo3_crate_path::impl_::concat::combine_to_array::(PIECES) }; }; } } } enum ConcatenationBuilderElement { String(String), TokenStream(TokenStream), } impl ToTokens for ConcatenationBuilderElement { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::String(s) => quote! { #s.as_bytes() }.to_tokens(tokens), Self::TokenStream(ts) => ts.to_tokens(tokens), } } } /// Generates a new unique identifier for linking introspection objects together pub fn introspection_id_const() -> TokenStream { let id = unique_element_id().to_string(); quote! { #[doc(hidden)] pub const _PYO3_INTROSPECTION_ID: &'static str = #id; } } pub fn unique_element_id() -> u64 { let mut hasher = DefaultHasher::new(); format!("{:?}", Span::call_site()).hash(&mut hasher); // Distinguishes between call sites GLOBAL_COUNTER_FOR_UNIQUE_NAMES .fetch_add(1, Ordering::Relaxed) .hash(&mut hasher); // If there are multiple elements in the same call site hasher.finish() } fn ident_to_type(ident: &Ident) -> Cow<'static, Type> { Cow::Owned( TypePath { path: ident.clone().into(), qself: None, } .into(), ) } fn escape_json_string(value: &str) -> String { let mut output = String::with_capacity(value.len()); for c in value.chars() { match c { '\\' => output.push_str("\\\\"), '"' => output.push_str("\\\""), '\x08' => output.push_str("\\b"), '\x0C' => output.push_str("\\f"), '\n' => output.push_str("\\n"), '\r' => output.push_str("\\r"), '\t' => output.push_str("\\t"), c @ '\0'..='\x1F' => { write!(output, "\\u{:0>4x}", u32::from(c)).unwrap(); } c => output.push(c), } } output } ================================================ FILE: pyo3-macros-backend/src/konst.rs ================================================ use std::borrow::Cow; use std::ffi::CString; use crate::attributes::{self, get_pyo3_options, take_attributes, NameAttribute}; #[cfg(feature = "experimental-inspect")] use crate::utils::PythonDoc; use proc_macro2::{Ident, Span}; use syn::LitCStr; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, Result, }; pub struct ConstSpec { pub rust_ident: Ident, pub attributes: ConstAttributes, #[cfg(feature = "experimental-inspect")] pub expr: Option, #[cfg(feature = "experimental-inspect")] pub ty: syn::Type, #[cfg(feature = "experimental-inspect")] pub doc: Option, } impl ConstSpec { pub fn python_name(&self) -> Cow<'_, Ident> { if let Some(name) = &self.attributes.name { Cow::Borrowed(&name.value.0) } else { Cow::Owned(self.rust_ident.unraw()) } } /// Null-terminated Python name pub fn null_terminated_python_name(&self) -> LitCStr { let name = self.python_name().to_string(); LitCStr::new(&CString::new(name).unwrap(), Span::call_site()) } } pub struct ConstAttributes { pub is_class_attr: bool, pub name: Option, } pub enum PyO3ConstAttribute { Name(NameAttribute), } impl Parse for PyO3ConstAttribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(PyO3ConstAttribute::Name) } else { Err(lookahead.error()) } } } impl ConstAttributes { pub fn from_attrs(attrs: &mut Vec) -> syn::Result { let mut attributes = ConstAttributes { is_class_attr: false, name: None, }; take_attributes(attrs, |attr| { if attr.path().is_ident("classattr") { ensure_spanned!( matches!(attr.meta, syn::Meta::Path(..)), attr.span() => "`#[classattr]` does not take any arguments" ); attributes.is_class_attr = true; Ok(true) } else if let Some(pyo3_attributes) = get_pyo3_options(attr)? { for pyo3_attr in pyo3_attributes { match pyo3_attr { PyO3ConstAttribute::Name(name) => attributes.set_name(name)?, } } Ok(true) } else { Ok(false) } })?; Ok(attributes) } fn set_name(&mut self, name: NameAttribute) -> Result<()> { ensure_spanned!( self.name.is_none(), name.span() => "`name` may only be specified once" ); self.name = Some(name); Ok(()) } } ================================================ FILE: pyo3-macros-backend/src/lib.rs ================================================ //! This crate contains the implementation of the proc macro attributes #![warn(elided_lifetimes_in_paths, unused_lifetimes)] #![cfg_attr(docsrs, feature(doc_cfg))] #![recursion_limit = "1024"] // Listed first so that macros in this module are available in the rest of the crate. #[macro_use] mod utils; mod attributes; mod combine_errors; mod derive_attributes; mod frompyobject; mod intopyobject; #[cfg(feature = "experimental-inspect")] mod introspection; mod konst; mod method; mod module; mod params; #[cfg(feature = "experimental-inspect")] mod py_expr; mod pyclass; mod pyfunction; mod pyimpl; mod pymethod; mod quotes; pub use frompyobject::build_derive_from_pyobject; pub use intopyobject::build_derive_into_pyobject; pub use module::{pymodule_function_impl, pymodule_module_impl, PyModuleOptions}; pub use pyclass::{build_py_class, build_py_enum, PyClassArgs}; pub use pyfunction::{build_py_function, PyFunctionOptions}; pub use pyimpl::{build_py_methods, PyClassMethodsType}; pub use utils::get_doc; ================================================ FILE: pyo3-macros-backend/src/method.rs ================================================ use std::borrow::Cow; use std::ffi::CString; use std::fmt::Display; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::LitCStr; use syn::{ext::IdentExt, spanned::Spanned, Ident, Result}; use crate::params::is_forwarded_args; #[cfg(feature = "experimental-inspect")] use crate::py_expr::PyExpr; use crate::pyfunction::{PyFunctionWarning, WarningFactory}; use crate::utils::Ctx; use crate::{ attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue}, params::{impl_arg_params, Holders}, pyfunction::{ FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute, }, quotes, utils::{self, PythonDoc}, }; #[derive(Clone, Debug)] pub struct RegularArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, pub from_py_with: Option>, pub default_value: Option>, pub option_wrapped_type: Option<&'a syn::Type>, #[cfg(feature = "experimental-inspect")] pub annotation: Option, } /// Pythons *args argument #[derive(Clone, Debug)] pub struct VarargsArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, #[cfg(feature = "experimental-inspect")] pub annotation: Option, } /// Pythons **kwarg argument #[derive(Clone, Debug)] pub struct KwargsArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, #[cfg(feature = "experimental-inspect")] pub annotation: Option, } #[derive(Clone, Debug)] pub struct CancelHandleArg<'a> { pub name: &'a syn::Ident, pub ty: &'a syn::Type, } #[derive(Clone, Debug)] pub struct PyArg<'a> { pub name: &'a syn::Ident, pub ty: &'a syn::Type, } #[derive(Clone, Debug)] pub enum FnArg<'a> { Regular(RegularArg<'a>), VarArgs(VarargsArg<'a>), KwArgs(KwargsArg<'a>), Py(PyArg<'a>), CancelHandle(CancelHandleArg<'a>), } impl<'a> FnArg<'a> { pub fn name(&self) -> &syn::Ident { match self { FnArg::Regular(RegularArg { name, .. }) => name, FnArg::VarArgs(VarargsArg { name, .. }) => name, FnArg::KwArgs(KwargsArg { name, .. }) => name, FnArg::Py(PyArg { name, .. }) => name, FnArg::CancelHandle(CancelHandleArg { name, .. }) => name, } } pub fn ty(&self) -> &'a syn::Type { match self { FnArg::Regular(RegularArg { ty, .. }) => ty, FnArg::VarArgs(VarargsArg { ty, .. }) => ty, FnArg::KwArgs(KwargsArg { ty, .. }) => ty, FnArg::Py(PyArg { ty, .. }) => ty, FnArg::CancelHandle(CancelHandleArg { ty, .. }) => ty, } } #[expect( clippy::wrong_self_convention, reason = "called `from_` but not a constructor" )] pub fn from_py_with(&self) -> Option<&FromPyWithAttribute> { if let FnArg::Regular(RegularArg { from_py_with, .. }) = self { from_py_with.as_deref() } else { None } } pub fn to_varargs_mut(&mut self) -> Result<&mut Self> { if let Self::Regular(RegularArg { name, ty, option_wrapped_type: None, #[cfg(feature = "experimental-inspect")] annotation, .. }) = self { *self = Self::VarArgs(VarargsArg { name: name.clone(), ty, #[cfg(feature = "experimental-inspect")] annotation: annotation.clone(), }); Ok(self) } else { bail_spanned!(self.name().span() => "args cannot be optional") } } pub fn to_kwargs_mut(&mut self) -> Result<&mut Self> { if let Self::Regular(RegularArg { name, ty, option_wrapped_type: Some(..), #[cfg(feature = "experimental-inspect")] annotation, .. }) = self { *self = Self::KwArgs(KwargsArg { name: name.clone(), ty, #[cfg(feature = "experimental-inspect")] annotation: annotation.clone(), }); Ok(self) } else { bail_spanned!(self.name().span() => "kwargs must be Option<_>") } } /// Transforms a rust fn arg parsed with syn into a method::FnArg pub fn parse(arg: &'a mut syn::FnArg) -> Result { match arg { syn::FnArg::Receiver(recv) => { bail_spanned!(recv.span() => "unexpected receiver") } // checked in parse_fn_type syn::FnArg::Typed(cap) => { if let syn::Type::ImplTrait(_) = &*cap.ty { bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR); } let PyFunctionArgPyO3Attributes { from_py_with, cancel_handle, } = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?; let ident = match &*cap.pat { syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident, other => return Err(handle_argument_error(other)), }; if utils::is_python(&cap.ty) { return Ok(Self::Py(PyArg { name: ident, ty: &cap.ty, })); } if cancel_handle.is_some() { // `PyFunctionArgPyO3Attributes::from_attrs` validates that // only compatible attributes are specified, either // `cancel_handle` or `from_py_with`, duplicates and any // combination of the two are already rejected. return Ok(Self::CancelHandle(CancelHandleArg { name: ident, ty: &cap.ty, })); } Ok(Self::Regular(RegularArg { name: Cow::Borrowed(ident), ty: &cap.ty, from_py_with: from_py_with.map(Box::new), default_value: None, option_wrapped_type: utils::option_type_argument(&cap.ty), #[cfg(feature = "experimental-inspect")] annotation: None, })) } } } } fn handle_argument_error(pat: &syn::Pat) -> syn::Error { let span = pat.span(); let msg = match pat { syn::Pat::Wild(_) => "wildcard argument names are not supported", syn::Pat::Struct(_) | syn::Pat::Tuple(_) | syn::Pat::TupleStruct(_) | syn::Pat::Slice(_) => "destructuring in arguments is not supported", _ => "unsupported argument", }; syn::Error::new(span, msg) } /// Represents what kind of a function a pyfunction or pymethod is #[derive(Clone, Debug)] pub enum FnType { /// Represents a pymethod annotated with `#[getter]` Getter(SelfType), /// Represents a pymethod annotated with `#[setter]` Setter(SelfType), /// Represents a pymethod annotated with `#[deleter]` Deleter(SelfType), /// Represents a regular pymethod Fn(SelfType), /// Represents a pymethod annotated with `#[classmethod]`, like a `@classmethod` FnClass(Span), /// Represents a pyfunction or a pymethod annotated with `#[staticmethod]`, like a `@staticmethod` FnStatic, /// Represents a pyfunction annotated with `#[pyo3(pass_module)] FnModule(Span), /// Represents a pymethod or associated constant annotated with `#[classattr]` ClassAttribute, } impl FnType { pub fn skip_first_rust_argument_in_python_signature(&self) -> bool { match self { FnType::Getter(_) | FnType::Setter(_) | FnType::Deleter(_) | FnType::Fn(_) | FnType::FnClass(_) | FnType::FnModule(_) => true, FnType::FnStatic | FnType::ClassAttribute => false, } } pub fn signature_attribute_allowed(&self) -> bool { match self { FnType::Fn(_) | FnType::FnStatic | FnType::FnClass(_) | FnType::FnModule(_) => true, // Getter, Setter and Deleter and ClassAttribute all have fixed signatures (either take 0 or 1 // arguments) so cannot have a `signature = (...)` attribute. FnType::Getter(_) | FnType::Setter(_) | FnType::Deleter(_) | FnType::ClassAttribute => { false } } } pub fn self_arg( &self, cls: Option<&syn::Type>, error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> Option { let Ctx { pyo3_path, .. } = ctx; match self { FnType::Getter(st) | FnType::Setter(st) | FnType::Deleter(st) | FnType::Fn(st) => { Some(st.receiver( cls.expect("no class given for Fn with a \"self\" receiver"), error_mode, holders, ctx, )) } FnType::FnClass(span) => { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf", Span::call_site()); let pyo3_path = pyo3_path.to_tokens_spanned(*span); let ret = quote_spanned! { *span => #[allow(clippy::useless_conversion, reason = "#[classmethod] accepts anything which implements `From>`")] ::std::convert::Into::into( #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(&#slf as *const _ as *const *mut _)) .cast_unchecked::<#pyo3_path::types::PyType>() ) }; Some(quote! { unsafe { #ret } }) } FnType::FnModule(span) => { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf", Span::call_site()); let pyo3_path = pyo3_path.to_tokens_spanned(*span); let ret = quote_spanned! { *span => #[allow(clippy::useless_conversion, reason = "`pass_module` accepts anything which implements `From>`")] ::std::convert::Into::into( #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(&#slf as *const _ as *const *mut _)) .cast_unchecked::<#pyo3_path::types::PyModule>() ) }; Some(quote! { unsafe { #ret } }) } FnType::FnStatic | FnType::ClassAttribute => None, } } } #[derive(Clone, Debug)] pub enum SelfType { Receiver { mutable: bool, non_null: bool, span: Span, }, TryFromBoundRef { span: Span, non_null: bool, }, } #[derive(Clone, Copy)] pub enum ExtractErrorMode { NotImplemented, Raise, } impl ExtractErrorMode { pub fn handle_error(self, extract: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { ExtractErrorMode::Raise => quote! { #extract? }, ExtractErrorMode::NotImplemented => quote! { match #extract { ::std::result::Result::Ok(value) => value, ::std::result::Result::Err(_) => { return #pyo3_path::impl_::callback::convert(py, py.NotImplemented()); }, } }, } } } impl SelfType { pub fn receiver( &self, cls: &syn::Type, error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { // Due to use of quote_spanned in this function, need to bind these idents to the // main macro callsite. let py = syn::Ident::new("py", Span::call_site()); let slf = syn::Ident::new("_slf", Span::call_site()); let Ctx { pyo3_path, .. } = ctx; match self { SelfType::Receiver { span, mutable, non_null, } => { let cast_fn = if *non_null { quote!(cast_non_null_function_argument) } else { quote!(cast_function_argument) }; let arg = quote! { unsafe { #pyo3_path::impl_::extract_argument::#cast_fn(#py, #slf) } }; let method = if *mutable { syn::Ident::new("extract_pyclass_ref_mut", *span) } else { syn::Ident::new("extract_pyclass_ref", *span) }; let holder = holders.push_holder(*span); let pyo3_path = pyo3_path.to_tokens_spanned(*span); error_mode.handle_error( quote_spanned! { *span => #pyo3_path::impl_::extract_argument::#method::<#cls>( #arg, &mut #holder, ) }, ctx, ) } SelfType::TryFromBoundRef { span, non_null } => { let bound_ref = if *non_null { quote! { unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_non_null(#py, &#slf) } } } else { quote! { unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &#slf) } } }; let pyo3_path = pyo3_path.to_tokens_spanned(*span); error_mode.handle_error( quote_spanned! { *span => #bound_ref.cast::<#cls>() .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into) .and_then( #[allow(clippy::unnecessary_fallible_conversions, reason = "anything implementing `TryFrom` is permitted")] |bound| ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) ) }, ctx ) } } } } /// Determines which CPython calling convention a given FnSpec uses. #[derive(Clone, Debug, Copy)] pub enum CallingConvention { Noargs, // METH_NOARGS Varargs, // METH_VARARGS | METH_KEYWORDS Fastcall, // METH_FASTCALL | METH_KEYWORDS } impl CallingConvention { /// Determine default calling convention from an argument signature. /// /// Different other slots (tp_call, tp_new) can have other requirements /// and are set manually (see `parse_fn_type` below). pub fn from_signature(signature: &FunctionSignature<'_>) -> Self { if signature.python_signature.has_no_args() { Self::Noargs } else if signature.python_signature.kwargs.is_none() { Self::Fastcall } else { Self::Varargs } } } #[derive(Clone)] pub struct FnSpec<'a> { pub tp: FnType, // Rust function name pub name: &'a syn::Ident, // Wrapped python name. This should not have any leading r#. // r# can be removed by syn::ext::IdentExt::unraw() pub python_name: syn::Ident, pub signature: FunctionSignature<'a>, pub text_signature: Option, pub asyncness: Option, pub unsafety: Option, pub warnings: Vec, pub output: syn::ReturnType, } pub fn parse_method_receiver(arg: &syn::FnArg, non_null: bool) -> Result { match arg { syn::FnArg::Receiver( recv @ syn::Receiver { reference: None, .. }, ) => { bail_spanned!(recv.span() => RECEIVER_BY_VALUE_ERR); } syn::FnArg::Receiver(recv @ syn::Receiver { mutability, .. }) => Ok(SelfType::Receiver { mutable: mutability.is_some(), span: recv.span(), non_null, }), syn::FnArg::Typed(syn::PatType { ty, .. }) => { if let syn::Type::ImplTrait(_) = &**ty { bail_spanned!(ty.span() => IMPL_TRAIT_ERR); } Ok(SelfType::TryFromBoundRef { span: ty.span(), non_null, }) } } } impl<'a> FnSpec<'a> { /// Parser function signature and function attributes pub fn parse( // Signature is mutable to remove the `Python` argument. sig: &'a mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result> { let PyFunctionOptions { text_signature, name, signature, warnings, .. } = options; let mut python_name = name.map(|name| name.value.0); let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name)?; ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?; let name = &sig.ident; let python_name = python_name.as_ref().unwrap_or(name).unraw(); let arguments: Vec<_> = sig .inputs .iter_mut() .skip(if fn_type.skip_first_rust_argument_in_python_signature() { 1 } else { 0 }) .map(FnArg::parse) .collect::>()?; let signature = if let Some(signature) = signature { FunctionSignature::from_arguments_and_attribute(arguments, signature)? } else { FunctionSignature::from_arguments(arguments) }; Ok(FnSpec { tp: fn_type, name, python_name, signature, text_signature, asyncness: sig.asyncness, unsafety: sig.unsafety, warnings, output: sig.output.clone(), }) } pub fn null_terminated_python_name(&self) -> LitCStr { let name = self.python_name.to_string(); let name = CString::new(name).unwrap(); LitCStr::new(&name, self.python_name.span()) } fn parse_fn_type( sig: &syn::Signature, meth_attrs: &mut Vec, python_name: &mut Option, ) -> Result { let mut method_attributes = parse_method_attributes(meth_attrs)?; let receiver_non_null = method_attributes.iter().any(|attr| { matches!( attr, MethodTypeAttribute::Getter(_, _) | MethodTypeAttribute::Setter(_, _) | MethodTypeAttribute::Deleter(_, _) ) }); let name = &sig.ident; let parse_receiver = |msg: &'static str| { let first_arg = sig .inputs .first() .ok_or_else(|| err_spanned!(sig.span() => msg))?; parse_method_receiver(first_arg, receiver_non_null) }; // strip get_ or set_ let strip_fn_name = |prefix: &'static str| { name.unraw() .to_string() .strip_prefix(prefix) .map(|stripped| syn::Ident::new(stripped, name.span())) }; let mut set_name_to_new = || { if let Some(name) = &python_name { bail_spanned!(name.span() => "`name` not allowed with `#[new]`"); } *python_name = Some(syn::Ident::new("__new__", Span::call_site())); Ok(()) }; let fn_type = match method_attributes.as_mut_slice() { [] => FnType::Fn(parse_receiver( "static method needs #[staticmethod] attribute", )?), [MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic, [MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute, [MethodTypeAttribute::New(_)] => { set_name_to_new()?; FnType::FnStatic } [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)] | [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => { set_name_to_new()?; FnType::FnClass(*span) } [MethodTypeAttribute::ClassMethod(_)] => { // Add a helpful hint if the classmethod doesn't look like a classmethod let span = match sig.inputs.first() { // Don't actually bother checking the type of the first argument, the compiler // will error on incorrect type. Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( sig.paren_token.span.join() => "Expected `&Bound` or `Py` as the first argument to `#[classmethod]`" ), }; FnType::FnClass(span) } [MethodTypeAttribute::Getter(_, name)] => { if let Some(name) = name.take() { ensure_spanned!( python_name.replace(name).is_none(), python_name.span() => "`name` may only be specified once" ); } else if python_name.is_none() { // Strip off "get_" prefix if needed *python_name = strip_fn_name("get_"); } FnType::Getter(parse_receiver("expected receiver for `#[getter]`")?) } [MethodTypeAttribute::Setter(_, name)] => { if let Some(name) = name.take() { ensure_spanned!( python_name.replace(name).is_none(), python_name.span() => "`name` may only be specified once" ); } else if python_name.is_none() { // Strip off "set_" prefix if needed *python_name = strip_fn_name("set_"); } FnType::Setter(parse_receiver("expected receiver for `#[setter]`")?) } [MethodTypeAttribute::Deleter(_, name)] => { if let Some(name) = name.take() { ensure_spanned!( python_name.replace(name).is_none(), python_name.span() => "`name` may only be specified once" ); } else if python_name.is_none() { // Strip off "delete_" prefix if needed *python_name = strip_fn_name("delete_"); } FnType::Deleter(parse_receiver("expected receiver for `#[deleter]`")?) } [first, rest @ .., last] => { // Join as many of the spans together as possible let span = rest .iter() .fold(first.span(), |s, next| s.join(next.span()).unwrap_or(s)); let span = span.join(last.span()).unwrap_or(span); // List all the attributes in the error message let mut msg = format!("`{first}` may not be combined with"); let mut is_first = true; for attr in &*rest { msg.push_str(&format!(" `{attr}`")); if is_first { is_first = false; } else { msg.push(','); } } if !rest.is_empty() { msg.push_str(" and"); } msg.push_str(&format!(" `{last}`")); bail_spanned!(span => msg) } }; Ok(fn_type) } /// Return a C wrapper function for this signature. pub fn get_wrapper_function( &self, ident: &proc_macro2::Ident, cls: Option<&syn::Type>, convention: CallingConvention, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, output_span, } = ctx; let mut cancel_handle_iter = self .signature .arguments .iter() .filter(|arg| matches!(arg, FnArg::CancelHandle(..))); let cancel_handle = cancel_handle_iter.next(); if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle { ensure_spanned!(self.asyncness.is_some(), name.span() => "`cancel_handle` attribute can only be used with `async fn`"); if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle_iter.next() { bail_spanned!(name.span() => "`cancel_handle` may only be specified once"); } } let rust_call = |args: Vec, mut holders: Holders| { let self_arg = self .tp .self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx); let init_holders = holders.init_holders(ctx); // We must assign the output_span to the return value of the call, // but *not* of the call itself otherwise the spans get really weird let ret_ident = Ident::new("ret", *output_span); if self.asyncness.is_some() { // For async functions, we need to build up a coroutine object to return from the initial function call. // // Extraction of the call signature (positional & keyword arguments) happens as part of the initial function // call. The Python objects are then moved into the Rust future that will be executed when the coroutine is // awaited. // // The argument extraction from Python objects to Rust values then happens inside the future, this allows // things like extraction to `&MyClass` which needs a holder (for the class guard) to work properly inside // async code. // // It *might* be possible in the future to do the extraction before the coroutine is created, but that would require // changing argument extraction code to first create holders and then read the values from them later. let (throw_callback, init_throw_callback) = if cancel_handle.is_some() { ( quote! { Some(__throw_callback) }, Some( quote! { let __cancel_handle = #pyo3_path::coroutine::CancelHandle::new(); let __throw_callback = __cancel_handle.throw_callback(); }, ), ) } else { (quote! { None }, None) }; let python_name = &self.python_name; let qualname_prefix = match cls { Some(cls) => quote!(Some(<#cls as #pyo3_path::PyClass>::NAME)), None => quote!(None), }; // copy self arg into async block // slf_py will create the owned value to store in the future // slf_ptr recreates the raw pointer temporarily when building the future let (slf_py, slf_ptr) = if self_arg.is_some() { ( Some( quote! { let _slf = #pyo3_path::Borrowed::from_ptr(py, _slf).to_owned().unbind(); }, ), Some(quote! { let _slf = _slf.as_ptr(); }), ) } else { (None, None) }; // copy extracted arguments into async block // output_py will create the owned arguments to store in the future // output_args recreates the borrowed objects temporarily when building the future let (output_py, output_args) = if !matches!(convention, CallingConvention::Noargs) && !is_forwarded_args(&self.signature) { ( Some(quote! { let output = output.map(|o| o.map(Py::from)); }), Some(quote! { let output = output.each_ref().map(|o| o.as_ref().map(|obj| obj.bind_borrowed(assume_attached.py()))); }), ) } else { (None, None) }; // if *args / **kwargs are present, treat the `Bound<'_, PyTuple>` / `Option>` similarly let (varargs_py, varargs_ptr) = if self.signature.python_signature.varargs.is_some() { ( Some(quote! { let _args = _args.to_owned().unbind(); }), Some(quote! { let _args = _args.bind_borrowed(assume_attached.py()); }), ) } else { (None, None) }; let (kwargs_py, kwargs_ptr) = if self.signature.python_signature.kwargs.is_some() { ( Some(quote! { let _kwargs = _kwargs.map(|k| k.to_owned().unbind()); }), Some(quote! { let _kwargs = _kwargs.as_ref().map(|k| k.bind_borrowed(assume_attached.py())); }), ) } else { (None, None) }; let args = self_arg.into_iter().chain(args); let ok_wrap = quotes::ok_wrap(ret_ident.to_token_stream(), ctx); quote! { { let coroutine = { #slf_py #output_py #varargs_py #kwargs_py #init_throw_callback #pyo3_path::impl_::coroutine::new_coroutine( #pyo3_path::intern!(py, stringify!(#python_name)), #qualname_prefix, #throw_callback, async move { // SAFETY: attached when future is polled (see `Coroutine::poll`) let assume_attached = unsafe { #pyo3_path::impl_::coroutine::AssumeAttachedInCoroutine::new() }; #init_holders let future = { let py = assume_attached.py(); #slf_ptr #output_args #varargs_ptr #kwargs_ptr function(#(#args),*) }; let #ret_ident = future.await; let #ret_ident = #ok_wrap; #pyo3_path::impl_::wrap::converter(&#ret_ident).map_into_pyobject(assume_attached.py(), #ret_ident) }, ) }; #pyo3_path::Py::new(py, coroutine).map(#pyo3_path::Py::into_ptr) } } } else { let args = self_arg.into_iter().chain(args); let return_conversion = quotes::map_result_into_ptr( quotes::ok_wrap(ret_ident.to_token_stream(), ctx), ctx, ); quote! { { #init_holders let #ret_ident = function(#(#args),*); #return_conversion } } } }; let func_name = &self.name; let rust_name = if let Some(cls) = cls { quote!(#cls::#func_name) } else { quote!(#func_name) }; let warnings = self.warnings.build_py_warning(ctx); let mut holders = Holders::new(); Ok(match convention { CallingConvention::Noargs => { let args = self .signature .arguments .iter() .map(|arg| match arg { FnArg::Py(..) => quote!(py), FnArg::CancelHandle(..) => quote!(__cancel_handle), _ => unreachable!("`CallingConvention::Noargs` should not contain any arguments (reaching Python) except for `self`, which is handled below."), }) .collect(); let call = rust_call(args, holders); quote! { unsafe fn #ident<'py>( py: #pyo3_path::Python<'py>, _slf: *mut #pyo3_path::ffi::PyObject, ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 #warnings let result = #call; result } } } CallingConvention::Fastcall => { let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx); let call = rust_call(args, holders); quote! { #pyo3_path::impl_::pymethods::maybe_define_fastcall_function_with_keywords!( #ident, py, _slf, _args, _nargs, _kwargs, { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #warnings let result = #call; result } ); } } CallingConvention::Varargs => { let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx); let call = rust_call(args, holders); quote! { unsafe fn #ident<'py>( py: #pyo3_path::Python<'py>, _slf: *mut #pyo3_path::ffi::PyObject, _args: *mut #pyo3_path::ffi::PyObject, _kwargs: *mut #pyo3_path::ffi::PyObject ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #warnings let result = #call; result } } } }) } /// Return a `PyMethodDef` constructor for this function, matching the selected /// calling convention. pub fn get_methoddef( &self, wrapper: impl ToTokens, doc: Option<&PythonDoc>, convention: CallingConvention, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = self.null_terminated_python_name(); let flags = match self.tp { FnType::FnClass(_) => quote! { .flags(#pyo3_path::ffi::METH_CLASS) }, FnType::FnStatic => quote! { .flags(#pyo3_path::ffi::METH_STATIC) }, _ => quote! {}, }; let trampoline = match convention { CallingConvention::Noargs => Ident::new("noargs", Span::call_site()), CallingConvention::Fastcall => { Ident::new("maybe_fastcall_cfunction_with_keywords", Span::call_site()) } CallingConvention::Varargs => Ident::new("cfunction_with_keywords", Span::call_site()), }; let doc = if let Some(doc) = doc { doc.to_cstr_stream(ctx)? } else { c"".to_token_stream() }; Ok(quote! { #pyo3_path::impl_::pymethods::PyMethodDef::#trampoline( #python_name, #pyo3_path::impl_::trampoline::get_trampoline_function!(#trampoline, #wrapper), #doc, ) #flags }) } /// Forwards to [utils::get_doc] with the text signature of this spec. pub fn get_doc(&self, attrs: &[syn::Attribute]) -> Option { let text_signature = self .text_signature_call_signature() .map(|sig| format!("{}{}", self.python_name, sig)); utils::get_doc(attrs, text_signature) } /// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature /// and/or attributes. Prepend the callable name to make a complete `__text_signature__`. pub fn text_signature_call_signature(&self) -> Option { let self_argument = match &self.tp { // Getters / Setters / deleter / ClassAttribute are not callables on the Python side FnType::Getter(_) | FnType::Setter(_) | FnType::Deleter(_) | FnType::ClassAttribute => { return None } FnType::Fn(_) => Some("self"), FnType::FnModule(_) => Some("module"), FnType::FnClass(_) => Some("cls"), FnType::FnStatic => None, }; match self.text_signature.as_ref().map(|attr| &attr.value) { Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()), None => Some(self.signature.text_signature(self_argument)), Some(TextSignatureAttributeValue::Disabled(_)) => None, } } } enum MethodTypeAttribute { New(Span), ClassMethod(Span), StaticMethod(Span), Getter(Span, Option), Setter(Span, Option), Deleter(Span, Option), ClassAttribute(Span), } impl MethodTypeAttribute { fn span(&self) -> Span { match self { MethodTypeAttribute::New(span) | MethodTypeAttribute::ClassMethod(span) | MethodTypeAttribute::StaticMethod(span) | MethodTypeAttribute::Getter(span, _) | MethodTypeAttribute::Setter(span, _) | MethodTypeAttribute::Deleter(span, _) | MethodTypeAttribute::ClassAttribute(span) => *span, } } /// Attempts to parse a method type attribute. /// /// If the attribute does not match one of the attribute names, returns `Ok(None)`. /// /// Otherwise will either return a parse error or the attribute. fn parse_if_matching_attribute(attr: &syn::Attribute) -> Result> { fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> { match meta { syn::Meta::Path(_) => Ok(()), syn::Meta::List(l) => bail_spanned!( l.span() => format!( "`#[{ident}]` does not take any arguments\n= help: did you mean `#[{ident}] #[pyo3({meta})]`?", ident = ident, meta = l.tokens, ) ), syn::Meta::NameValue(nv) => { bail_spanned!(nv.eq_token.span() => format!( "`#[{}]` does not take any arguments\n= note: this was previously accepted and ignored", ident )) } } } fn extract_name(meta: &syn::Meta, ident: &str) -> Result> { match meta { syn::Meta::Path(_) => Ok(None), syn::Meta::NameValue(nv) => bail_spanned!( nv.eq_token.span() => format!("expected `#[{}(name)]` to set the name", ident) ), syn::Meta::List(l) => { if let Ok(name) = l.parse_args::() { Ok(Some(name)) } else if let Ok(name) = l.parse_args::() { name.parse().map(Some) } else { bail_spanned!(l.tokens.span() => "expected ident or string literal for property name"); } } } } let meta = &attr.meta; let path = meta.path(); if path.is_ident("new") { ensure_no_arguments(meta, "new")?; Ok(Some(MethodTypeAttribute::New(path.span()))) } else if path.is_ident("classmethod") { ensure_no_arguments(meta, "classmethod")?; Ok(Some(MethodTypeAttribute::ClassMethod(path.span()))) } else if path.is_ident("staticmethod") { ensure_no_arguments(meta, "staticmethod")?; Ok(Some(MethodTypeAttribute::StaticMethod(path.span()))) } else if path.is_ident("classattr") { ensure_no_arguments(meta, "classattr")?; Ok(Some(MethodTypeAttribute::ClassAttribute(path.span()))) } else if path.is_ident("getter") { let name = extract_name(meta, "getter")?; Ok(Some(MethodTypeAttribute::Getter(path.span(), name))) } else if path.is_ident("setter") { let name = extract_name(meta, "setter")?; Ok(Some(MethodTypeAttribute::Setter(path.span(), name))) } else if path.is_ident("deleter") { let name = extract_name(meta, "deleter")?; Ok(Some(MethodTypeAttribute::Deleter(path.span(), name))) } else { Ok(None) } } } impl Display for MethodTypeAttribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { MethodTypeAttribute::New(_) => "#[new]", MethodTypeAttribute::ClassMethod(_) => "#[classmethod]", MethodTypeAttribute::StaticMethod(_) => "#[staticmethod]", MethodTypeAttribute::Getter(_, _) => "#[getter]", MethodTypeAttribute::Setter(_, _) => "#[setter]", MethodTypeAttribute::Deleter(_, _) => "#[deleter]", MethodTypeAttribute::ClassAttribute(_) => "#[classattr]", }) } } fn parse_method_attributes(attrs: &mut Vec) -> Result> { let mut new_attrs = Vec::new(); let mut found_attrs = Vec::new(); for attr in attrs.drain(..) { match MethodTypeAttribute::parse_if_matching_attribute(&attr)? { Some(attr) => found_attrs.push(attr), None => new_attrs.push(attr), } } *attrs = new_attrs; Ok(found_attrs) } const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments"; const RECEIVER_BY_VALUE_ERR: &str = "Python objects are shared, so 'self' cannot be moved out of the Python interpreter. Try `&self`, `&mut self, `slf: PyClassGuard<'_, Self>` or `slf: PyClassGuardMut<'_, Self>`."; fn ensure_signatures_on_valid_method( fn_type: &FnType, signature: Option<&SignatureAttribute>, text_signature: Option<&TextSignatureAttribute>, ) -> syn::Result<()> { if let Some(signature) = signature { match fn_type { FnType::Getter(_) => { debug_assert!(!fn_type.signature_attribute_allowed()); bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`") } FnType::Setter(_) => { debug_assert!(!fn_type.signature_attribute_allowed()); bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`") } FnType::Deleter(_) => { debug_assert!(!fn_type.signature_attribute_allowed()); bail_spanned!(signature.kw.span() => "`signature` not allowed with `deleter`") } FnType::ClassAttribute => { debug_assert!(!fn_type.signature_attribute_allowed()); bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`") } _ => debug_assert!(fn_type.signature_attribute_allowed()), } } if let Some(text_signature) = text_signature { match fn_type { FnType::Getter(_) => { bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `getter`") } FnType::Setter(_) => { bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `setter`") } FnType::Deleter(_) => { bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `deleter`") } FnType::ClassAttribute => { bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `classattr`") } _ => {} } } Ok(()) } ================================================ FILE: pyo3-macros-backend/src/module.rs ================================================ //! Code generation for the function that initializes a python module and adds classes and function. #[cfg(feature = "experimental-inspect")] use crate::introspection::{ attribute_introspection_code, introspection_id_const, module_introspection_code, }; #[cfg(feature = "experimental-inspect")] use crate::py_expr::PyExpr; use crate::{ attributes::{ self, kw, take_attributes, take_pyo3_options, CrateAttribute, GILUsedAttribute, ModuleAttribute, NameAttribute, SubmoduleAttribute, }, combine_errors::CombineErrors, get_doc, pyclass::PyClassPyO3Option, pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, utils::{has_attribute, has_attribute_with_namespace, Ctx, IdentOrStr, PythonDoc}, }; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use std::ffi::CString; use syn::LitCStr; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Comma, Item, Meta, Path, Result, }; #[derive(Default)] pub struct PyModuleOptions { krate: Option, name: Option, module: Option, submodule: Option, gil_used: Option, } impl Parse for PyModuleOptions { fn parse(input: ParseStream<'_>) -> syn::Result { let mut options: PyModuleOptions = Default::default(); options.add_attributes( Punctuated::::parse_terminated(input)?, )?; Ok(options) } } impl PyModuleOptions { fn take_pyo3_options(&mut self, attrs: &mut Vec) -> Result<()> { self.add_attributes(take_pyo3_options(attrs)?) } fn add_attributes( &mut self, attrs: impl IntoIterator, ) -> Result<()> { macro_rules! set_option { ($key:ident $(, $extra:literal)?) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once" $(, $extra)?) ); self.$key = Some($key); } }; } attrs .into_iter() .map(|attr| { match attr { PyModulePyO3Option::Crate(krate) => set_option!(krate), PyModulePyO3Option::Name(name) => set_option!(name), PyModulePyO3Option::Module(module) => set_option!(module), PyModulePyO3Option::Submodule(submodule) => set_option!( submodule, " (it is implicitly always specified for nested modules)" ), PyModulePyO3Option::GILUsed(gil_used) => { set_option!(gil_used) } } Ok(()) }) .try_combine_syn_errors()?; Ok(()) } } pub fn pymodule_module_impl( module: &mut syn::ItemMod, mut options: PyModuleOptions, ) -> Result { let syn::ItemMod { attrs, vis, unsafety: _, ident, mod_token, content, semi: _, } = module; let items = if let Some((_, items)) = content { items } else { bail_spanned!(mod_token.span() => "`#[pymodule]` can only be used on inline modules") }; options.take_pyo3_options(attrs)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = ctx; let doc = get_doc(attrs, None); let name = options .name .map_or_else(|| ident.unraw(), |name| name.value.0); let full_name = if let Some(module) = &options.module { format!("{}.{}", module.value.value(), name) } else { name.to_string() }; let mut module_items = Vec::new(); let mut module_items_cfg_attrs = Vec::new(); #[cfg(feature = "experimental-inspect")] let mut introspection_chunks = Vec::new(); #[cfg(not(feature = "experimental-inspect"))] let introspection_chunks = Vec::::new(); fn extract_use_items( source: &syn::UseTree, cfg_attrs: &[syn::Attribute], target_items: &mut Vec, target_cfg_attrs: &mut Vec>, ) -> Result<()> { match source { syn::UseTree::Name(name) => { target_items.push(name.ident.clone()); target_cfg_attrs.push(cfg_attrs.to_vec()); } syn::UseTree::Path(path) => { extract_use_items(&path.tree, cfg_attrs, target_items, target_cfg_attrs)? } syn::UseTree::Group(group) => { for tree in &group.items { extract_use_items(tree, cfg_attrs, target_items, target_cfg_attrs)? } } syn::UseTree::Glob(glob) => { bail_spanned!(glob.span() => "#[pymodule] cannot import glob statements") } syn::UseTree::Rename(rename) => { target_items.push(rename.rename.clone()); target_cfg_attrs.push(cfg_attrs.to_vec()); } } Ok(()) } let mut pymodule_init = None; let mut module_consts = Vec::new(); let mut module_consts_cfg_attrs = Vec::new(); let _: Vec<()> = (*items).iter_mut().map(|item|{ match item { Item::Use(item_use) => { let is_pymodule_export = find_and_remove_attribute(&mut item_use.attrs, "pymodule_export"); if is_pymodule_export { let cfg_attrs = get_cfg_attributes(&item_use.attrs); extract_use_items( &item_use.tree, &cfg_attrs, &mut module_items, &mut module_items_cfg_attrs, )?; } } Item::Fn(item_fn) => { ensure_spanned!( !has_attribute(&item_fn.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); let is_pymodule_init = find_and_remove_attribute(&mut item_fn.attrs, "pymodule_init"); let ident = &item_fn.sig.ident; if is_pymodule_init { ensure_spanned!( !has_attribute(&item_fn.attrs, "pyfunction"), item_fn.span() => "`#[pyfunction]` cannot be used alongside `#[pymodule_init]`" ); ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one `#[pymodule_init]` may be specified"); pymodule_init = Some(quote! { #ident(module)?; }); } else if has_attribute(&item_fn.attrs, "pyfunction") || has_attribute_with_namespace( &item_fn.attrs, Some(pyo3_path), &["pyfunction"], ) || has_attribute_with_namespace( &item_fn.attrs, Some(pyo3_path), &["prelude", "pyfunction"], ) { module_items.push(ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_fn.attrs)); } } Item::Struct(item_struct) => { ensure_spanned!( !has_attribute(&item_struct.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); if has_attribute(&item_struct.attrs, "pyclass") || has_attribute_with_namespace( &item_struct.attrs, Some(pyo3_path), &["pyclass"], ) || has_attribute_with_namespace( &item_struct.attrs, Some(pyo3_path), &["prelude", "pyclass"], ) { module_items.push(item_struct.ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_struct.attrs)); if !has_pyo3_module_declared::( &item_struct.attrs, "pyclass", |option| matches!(option, PyClassPyO3Option::Module(_)), )? { set_module_attribute(&mut item_struct.attrs, &full_name); } } } Item::Enum(item_enum) => { ensure_spanned!( !has_attribute(&item_enum.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); if has_attribute(&item_enum.attrs, "pyclass") || has_attribute_with_namespace(&item_enum.attrs, Some(pyo3_path), &["pyclass"]) || has_attribute_with_namespace( &item_enum.attrs, Some(pyo3_path), &["prelude", "pyclass"], ) { module_items.push(item_enum.ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_enum.attrs)); if !has_pyo3_module_declared::( &item_enum.attrs, "pyclass", |option| matches!(option, PyClassPyO3Option::Module(_)), )? { set_module_attribute(&mut item_enum.attrs, &full_name); } } } Item::Mod(item_mod) => { ensure_spanned!( !has_attribute(&item_mod.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); if has_attribute(&item_mod.attrs, "pymodule") || has_attribute_with_namespace(&item_mod.attrs, Some(pyo3_path), &["pymodule"]) || has_attribute_with_namespace( &item_mod.attrs, Some(pyo3_path), &["prelude", "pymodule"], ) { module_items.push(item_mod.ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_mod.attrs)); if !has_pyo3_module_declared::( &item_mod.attrs, "pymodule", |option| matches!(option, PyModulePyO3Option::Module(_)), )? { set_module_attribute(&mut item_mod.attrs, &full_name); } item_mod .attrs .push(parse_quote_spanned!(item_mod.mod_token.span()=> #[pyo3(submodule)])); } } Item::ForeignMod(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Trait(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Const(item) => { if !find_and_remove_attribute(&mut item.attrs, "pymodule_export") { return Ok(()); } module_consts.push(item.ident.clone()); module_consts_cfg_attrs.push(get_cfg_attributes(&item.attrs)); #[cfg(feature = "experimental-inspect")] { let cfg_attrs = get_cfg_attributes(&item.attrs); let chunk = attribute_introspection_code( pyo3_path, None, item.ident.unraw().to_string(), PyExpr::constant_from_expression(&item.expr), (*item.ty).clone(), get_doc(&item.attrs, None).as_ref(), true, ); introspection_chunks.push(quote! { #(#cfg_attrs)* #chunk }); } } Item::Static(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Macro(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::ExternCrate(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Impl(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::TraitAlias(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Type(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Union(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } _ => (), } Ok(()) }).try_combine_syn_errors()?; #[cfg(feature = "experimental-inspect")] let introspection = module_introspection_code( pyo3_path, &name.to_string(), &module_items, &module_items_cfg_attrs, doc.as_ref(), pymodule_init.is_some(), ); #[cfg(not(feature = "experimental-inspect"))] let introspection = quote! {}; #[cfg(feature = "experimental-inspect")] let introspection_id = introspection_id_const(); #[cfg(not(feature = "experimental-inspect"))] let introspection_id = quote! {}; let gil_used = options.gil_used.is_some_and(|op| op.value.value); let initialization = module_initialization( &full_name, &name, ctx, quote! { __pyo3_pymodule }, options.submodule.is_some(), gil_used, doc.as_ref(), )?; let module_consts_names = module_consts.iter().map(|i| i.unraw().to_string()); Ok(quote!( #(#attrs)* #vis #mod_token #ident { #(#items)* #initialization #introspection #introspection_id #(#introspection_chunks)* fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> { use #pyo3_path::impl_::pymodule::PyAddToModule; #( #(#module_items_cfg_attrs)* #module_items::_PYO3_DEF.add_to_module(module)?; )* #( #(#module_consts_cfg_attrs)* #pyo3_path::types::PyModuleMethods::add(module, #module_consts_names, #module_consts)?; )* #pymodule_init ::std::result::Result::Ok(()) } } )) } /// Generates the function that is called by the python interpreter to initialize the native /// module pub fn pymodule_function_impl( function: &mut syn::ItemFn, mut options: PyModuleOptions, ) -> Result { options.take_pyo3_options(&mut function.attrs)?; process_functions_in_module(&options, function)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = ctx; let ident = &function.sig.ident; let name = options .name .map_or_else(|| ident.unraw(), |name| name.value.0); let vis = &function.vis; let doc = get_doc(&function.attrs, None); let gil_used = options.gil_used.is_some_and(|op| op.value.value); let initialization = module_initialization( &name.to_string(), &name, ctx, quote! { ModuleExec::__pyo3_module_exec }, false, gil_used, doc.as_ref(), )?; #[cfg(feature = "experimental-inspect")] let introspection = module_introspection_code( pyo3_path, &name.unraw().to_string(), &[], &[], doc.as_ref(), true, ); #[cfg(not(feature = "experimental-inspect"))] let introspection = quote! {}; #[cfg(feature = "experimental-inspect")] let introspection_id = introspection_id_const(); #[cfg(not(feature = "experimental-inspect"))] let introspection_id = quote! {}; // Module function called with optional Python<'_> marker as first arg, followed by the module. let mut module_args = Vec::new(); if function.sig.inputs.len() == 2 { module_args.push(quote!(module.py())); } module_args .push(quote!(::std::convert::Into::into(#pyo3_path::impl_::pymethods::BoundRef(module)))); Ok(quote! { #[doc(hidden)] #vis mod #ident { #initialization #introspection #introspection_id } // Generate the definition inside an anonymous function in the same scope as the original function - // this avoids complications around the fact that the generated module has a different scope // (and `super` doesn't always refer to the outer scope, e.g. if the `#[pymodule] is // inside a function body) #[allow(unknown_lints, non_local_definitions)] impl #ident::ModuleExec { fn __pyo3_module_exec(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> { #ident(#(#module_args),*) } } }) } fn module_initialization( full_name: &str, name: &syn::Ident, ctx: &Ctx, module_exec: TokenStream, is_submodule: bool, gil_used: bool, doc: Option<&PythonDoc>, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let pyinit_symbol = format!("PyInit_{name}"); let pymodexport_symbol = format!("PyModExport_{name}"); let pyo3_name = LitCStr::new(&CString::new(full_name).unwrap(), Span::call_site()); let doc = if let Some(doc) = doc { doc.to_cstr_stream(ctx)? } else { c"".into_token_stream() }; let mut result = quote! { #[doc(hidden)] pub const __PYO3_NAME: &'static ::std::ffi::CStr = #pyo3_name; // This structure exists for `fn` modules declared within `fn` bodies, where due to the hidden // module (used for importing) the `fn` to initialize the module cannot be seen from the #module_def // declaration just below. #[doc(hidden)] pub(super) struct ModuleExec; #[doc(hidden)] pub static _PYO3_DEF: #pyo3_path::impl_::pymodule::ModuleDef = { use #pyo3_path::impl_::pymodule as impl_; unsafe extern "C" fn __pyo3_module_exec(module: *mut #pyo3_path::ffi::PyObject) -> ::std::os::raw::c_int { #pyo3_path::impl_::trampoline::module_exec(module, #module_exec) } // The full slots, used for the PyModExport initializaiton static SLOTS: impl_::PyModuleSlots = impl_::PyModuleSlotsBuilder::new() .with_mod_exec(__pyo3_module_exec) .with_abi_info() .with_gil_used(#gil_used) .with_name(__PYO3_NAME) .with_doc(#doc) .build(); // Since the macros need to be written agnostic to the Python version // we need to explicitly pass the name and docstring for PyModuleDef // initializaiton. impl_::ModuleDef::new(__PYO3_NAME, #doc, &SLOTS) }; }; if !is_submodule { result.extend(quote! { /// This autogenerated function is called by the python interpreter when importing /// the module on Python 3.14 and older. #[doc(hidden)] #[export_name = #pyinit_symbol] pub unsafe extern "C" fn __pyo3_init() -> *mut #pyo3_path::ffi::PyObject { _PYO3_DEF.init_multi_phase() } /// This autogenerated function is called by the python interpreter when importing /// the module on Python 3.15 and newer. #[doc(hidden)] #[export_name = #pymodexport_symbol] pub unsafe extern "C" fn __pyo3_export() -> *mut #pyo3_path::ffi::PyModuleDef_Slot { _PYO3_DEF.get_slots() } }); } Ok(result) } /// Finds and takes care of the #[pyfn(...)] in `#[pymodule]` fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn) -> Result<()> { let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = ctx; let mut stmts: Vec = Vec::new(); for mut stmt in func.block.stmts.drain(..) { if let syn::Stmt::Item(Item::Fn(func)) = &mut stmt { if let Some((pyfn_span, pyfn_args)) = get_pyfn_attr(&mut func.attrs)? { let module_name = pyfn_args.modname; let wrapped_function = impl_wrap_pyfunction(func, pyfn_args.options)?; let name = &func.sig.ident; let statements: Vec = syn::parse_quote_spanned! { pyfn_span => #wrapped_function { use #pyo3_path::types::PyModuleMethods; #module_name.add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?; #[deprecated(note = "`pyfn` will be removed in a future PyO3 version, use declarative `#[pymodule]` with `mod` instead")] #[allow(dead_code)] const PYFN_ATTRIBUTE: () = (); const _: () = PYFN_ATTRIBUTE; } }; stmts.extend(statements); } }; stmts.push(stmt); } func.block.stmts = stmts; Ok(()) } pub struct PyFnArgs { modname: Path, options: PyFunctionOptions, } impl Parse for PyFnArgs { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let modname = input.parse().map_err( |e| err_spanned!(e.span() => "expected module as first argument to #[pyfn()]"), )?; if input.is_empty() { return Ok(Self { modname, options: Default::default(), }); } let _: Comma = input.parse()?; Ok(Self { modname, options: input.parse()?, }) } } /// Extracts the data from the #[pyfn(...)] attribute of a function fn get_pyfn_attr(attrs: &mut Vec) -> syn::Result> { let mut pyfn_args: Option<(Span, PyFnArgs)> = None; take_attributes(attrs, |attr| { if attr.path().is_ident("pyfn") { ensure_spanned!( pyfn_args.is_none(), attr.span() => "`#[pyfn] may only be specified once" ); pyfn_args = Some((attr.path().span(), attr.parse_args()?)); Ok(true) } else { Ok(false) } })?; if let Some((_, pyfn_args)) = &mut pyfn_args { pyfn_args .options .add_attributes(take_pyo3_options(attrs)?)?; } Ok(pyfn_args) } fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec { attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) .cloned() .collect() } fn find_and_remove_attribute(attrs: &mut Vec, ident: &str) -> bool { let mut found = false; attrs.retain(|attr| { if attr.path().is_ident(ident) { found = true; false } else { true } }); found } impl PartialEq for IdentOrStr<'_> { fn eq(&self, other: &syn::Ident) -> bool { match self { IdentOrStr::Str(s) => other == s, IdentOrStr::Ident(i) => other == i, } } } fn set_module_attribute(attrs: &mut Vec, module_name: &str) { attrs.push(parse_quote!(#[pyo3(module = #module_name)])); } fn has_pyo3_module_declared( attrs: &[syn::Attribute], root_attribute_name: &str, is_module_option: impl Fn(&T) -> bool + Copy, ) -> Result { for attr in attrs { if (attr.path().is_ident("pyo3") || attr.path().is_ident(root_attribute_name)) && matches!(attr.meta, Meta::List(_)) { for option in &attr.parse_args_with(Punctuated::::parse_terminated)? { if is_module_option(option) { return Ok(true); } } } } Ok(false) } enum PyModulePyO3Option { Submodule(SubmoduleAttribute), Crate(CrateAttribute), Name(NameAttribute), Module(ModuleAttribute), GILUsed(GILUsedAttribute), } impl Parse for PyModulePyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(PyModulePyO3Option::Name) } else if lookahead.peek(syn::Token![crate]) { input.parse().map(PyModulePyO3Option::Crate) } else if lookahead.peek(attributes::kw::module) { input.parse().map(PyModulePyO3Option::Module) } else if lookahead.peek(attributes::kw::submodule) { input.parse().map(PyModulePyO3Option::Submodule) } else if lookahead.peek(attributes::kw::gil_used) { input.parse().map(PyModulePyO3Option::GILUsed) } else { Err(lookahead.error()) } } } ================================================ FILE: pyo3-macros-backend/src/params.rs ================================================ use crate::utils::Ctx; use crate::{ attributes::FromPyWithAttribute, method::{FnArg, FnSpec, RegularArg}, pyfunction::FunctionSignature, quotes::some_wrap, }; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::spanned::Spanned; pub struct Holders { holders: Vec, } impl Holders { pub fn new() -> Self { Holders { holders: Vec::new(), } } pub fn push_holder(&mut self, span: Span) -> syn::Ident { let holder = syn::Ident::new(&format!("holder_{}", self.holders.len()), span); self.holders.push(holder.clone()); holder } pub fn init_holders(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let holders = &self.holders; quote! { #[allow(clippy::let_unit_value, reason = "many holders are just `()`")] #(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)* } } } /// Return true if the argument list is simply (*args, **kwds). pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool { matches!( signature.arguments.as_slice(), [FnArg::VarArgs(..), FnArg::KwArgs(..),] ) } pub fn impl_arg_params( spec: &FnSpec<'_>, self_: Option<&syn::Type>, fastcall: bool, holders: &mut Holders, ctx: &Ctx, ) -> (TokenStream, Vec) { let args_array = syn::Ident::new("output", Span::call_site()); let Ctx { pyo3_path, .. } = ctx; let from_py_with = spec .signature .arguments .iter() .enumerate() .filter_map(|(i, arg)| { let from_py_with = &arg.from_py_with()?.value; let from_py_with_holder = format_ident!("from_py_with_{}", i); Some(quote_spanned! { from_py_with.span() => let #from_py_with_holder = #from_py_with; }) }) .collect::(); if !fastcall && is_forwarded_args(&spec.signature) { // In the varargs convention, we can just pass though if the signature // is (*args, **kwds). let arg_convert = spec .signature .arguments .iter() .enumerate() .map(|(i, arg)| impl_arg_param(arg, i, &mut 0, holders, ctx)) .collect(); return ( quote! { let _args = unsafe { #pyo3_path::impl_::extract_argument::cast_function_argument(py, _args) }; let _kwargs = unsafe { #pyo3_path::impl_::extract_argument::cast_optional_function_argument(py, _kwargs) }; #from_py_with }, arg_convert, ); }; let positional_parameter_names = &spec.signature.python_signature.positional_parameters; let positional_only_parameters = &spec.signature.python_signature.positional_only_parameters; let required_positional_parameters = spec .signature .python_signature .required_positional_parameters(); let keyword_only_parameters = spec .signature .python_signature .keyword_only_parameters .iter() .map(|(name, default_value)| { let required = default_value.is_none(); quote! { #pyo3_path::impl_::extract_argument::KeywordOnlyParameterDescription { name: #name, required: #required, } } }); let num_params = positional_parameter_names.len() + keyword_only_parameters.len(); let mut option_pos = 0usize; let param_conversion = spec .signature .arguments .iter() .enumerate() .map(|(i, arg)| impl_arg_param(arg, i, &mut option_pos, holders, ctx)) .collect(); let args_handler = if spec.signature.python_signature.varargs.is_some() { quote! { #pyo3_path::impl_::extract_argument::TupleVarargs } } else { quote! { #pyo3_path::impl_::extract_argument::NoVarargs } }; let kwargs_handler = if spec.signature.python_signature.kwargs.is_some() { quote! { #pyo3_path::impl_::extract_argument::DictVarkeywords } } else { quote! { #pyo3_path::impl_::extract_argument::NoVarkeywords } }; let cls_name = if let Some(cls) = self_ { quote! { ::std::option::Option::Some(<#cls as #pyo3_path::PyClass>::NAME) } } else { quote! { ::std::option::Option::None } }; let python_name = &spec.python_name; let extract_expression = if fastcall { quote! { #pyo3_path::impl_::pymethods::maybe_extract_arguments_fastcall!( DESCRIPTION, py, _args, _nargs, _kwargs, #args_array, #args_handler, #kwargs_handler )? } } else { quote! { DESCRIPTION.extract_arguments_tuple_dict::<#args_handler, #kwargs_handler>( py, _args, _kwargs, &mut #args_array )? } }; // create array of arguments, and then parse ( quote! { const DESCRIPTION: #pyo3_path::impl_::extract_argument::FunctionDescription = #pyo3_path::impl_::extract_argument::FunctionDescription { cls_name: #cls_name, func_name: stringify!(#python_name), positional_parameter_names: &[#(#positional_parameter_names),*], positional_only_parameters: #positional_only_parameters, required_positional_parameters: #required_positional_parameters, keyword_only_parameters: &[#(#keyword_only_parameters),*], }; let mut #args_array = [::std::option::Option::None; #num_params]; let (_args, _kwargs) = #extract_expression; #from_py_with }, param_conversion, ) } fn impl_arg_param( arg: &FnArg<'_>, pos: usize, option_pos: &mut usize, holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let args_array = syn::Ident::new("output", Span::call_site()); match arg { FnArg::Regular(arg) => { let from_py_with = format_ident!("from_py_with_{}", pos); let arg_value = quote!(#args_array[#option_pos]); *option_pos += 1; impl_regular_arg_param(arg, from_py_with, arg_value, holders, ctx) } FnArg::VarArgs(arg) => { let span = Span::call_site().located_at(arg.ty.span()); let holder = holders.push_holder(span); let name_str = arg.name.to_string(); quote_spanned! { span => #pyo3_path::impl_::extract_argument::extract_argument( _args.as_any().as_borrowed(), &mut #holder, #name_str )? } } FnArg::KwArgs(arg) => { let span = Span::call_site().located_at(arg.ty.span()); let holder = holders.push_holder(span); let name_str = arg.name.to_string(); quote_spanned! { span => #pyo3_path::impl_::extract_argument::extract_argument_with_default( _kwargs.as_ref().map(|d| d.as_any().as_borrowed()), &mut #holder, #name_str, || ::std::option::Option::None )? } } FnArg::Py(..) => quote! { py }, FnArg::CancelHandle(..) => quote! { __cancel_handle }, } } /// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument /// index and the index in option diverge when using py: Python pub(crate) fn impl_regular_arg_param( arg: &RegularArg<'_>, from_py_with: syn::Ident, arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>> holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(arg.ty.span()); // Use this macro inside this function, to ensure that all code generated here is associated // with the function argument let use_probe = quote! { #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_path::impl_::pyclass::Probe as _; }; macro_rules! quote_arg_span { ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => { #use_probe $($tokens)* }) } } let name_str = arg.name.to_string(); let mut default = arg.default_value.as_ref().map(|expr| quote!(#expr)); // Option arguments have special treatment: the default should be specified _without_ the // Some() wrapper. Maybe this should be changed in future?! if arg.option_wrapped_type.is_some() { default = default.map(|tokens| some_wrap(tokens, ctx)); } if let Some(FromPyWithAttribute { kw, .. }) = &arg.from_py_with.as_deref() { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #from_py_with; from_py_with } }; if let Some(default) = default { quote_arg_span! { #pyo3_path::impl_::extract_argument::from_py_with_with_default( #arg_value.as_deref(), #name_str, #extractor, #[allow(clippy::redundant_closure, reason = "wrapping user-provided default expression")] { || #default } )? } } else { let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument_bound(#arg_value.as_deref()) }}; quote_arg_span! { #pyo3_path::impl_::extract_argument::from_py_with( #unwrap, #name_str, #extractor, )? } } } else if let Some(default) = default { let holder = holders.push_holder(arg.name.span()); quote_arg_span! { #pyo3_path::impl_::extract_argument::extract_argument_with_default( #arg_value, &mut #holder, #name_str, #[allow(clippy::redundant_closure, reason = "wrapping user-provided default expression")] { || #default } )? } } else { let holder = holders.push_holder(arg.name.span()); let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) }}; quote_arg_span! { #pyo3_path::impl_::extract_argument::extract_argument( #unwrap, &mut #holder, #name_str )? } } } ================================================ FILE: pyo3-macros-backend/src/py_expr.rs ================================================ //! Define a data structure for Python type hints, mixing static data from macros and call to Pyo3 constants. use crate::utils::PyO3CratePath; use proc_macro2::TokenStream; use quote::quote; use std::borrow::Cow; use syn::visit_mut::{visit_type_mut, VisitMut}; use syn::{Expr, ExprLit, ExprPath, Lifetime, Lit, Type}; /// A Python expression /// /// Please do not construct directly but use the constructor methods that normalize the expression #[derive(Clone, Debug, PartialEq, Eq)] pub enum PyExpr { /// The Python type hint of a FromPyObject implementation FromPyObjectType(Type), /// The Python type hint of a IntoPyObject implementation IntoPyObjectType(Type), /// The Python type matching the given Rust type given as a function argument ArgumentType(Type), /// The Python type matching the given Rust type given as a function returned value ReturnType(Type), /// The Python type matching the given Rust type Type(Type), /// A name Name { id: Cow<'static, str> }, /// An attribute `value.attr` Attribute { value: Box, attr: Cow<'static, str>, }, /// A binary operator BinOp { left: Box, op: PyOperator, right: Box, }, /// A tuple Tuple { elts: Vec }, /// A subscript `value[slice]` Subscript { value: Box, slice: Box }, /// A constant Constant(PyConstant), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PyOperator { /// `|` operator BitOr, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum PyConstant { /// None None, /// The `True` and `False` booleans Bool(bool), /// `int` value written in base 10 ([+-]?[0-9]+) Int(String), /// `float` value written in base-10 ([+-]?[0-9]*(.[0-9]*)*([eE])[0-9]*), not including Inf and NaN Float(String), /// `str` value unescaped and without quotes Str(String), /// `...` Ellipsis, } impl PyExpr { /// Build from a builtins name like `None` pub fn builtin(name: impl Into>) -> Self { Self::Name { id: name.into() } } /// Build from a module and a name like `collections.abc` and `Sequence` pub fn module_attr( module: impl Into>, name: impl Into>, ) -> Self { Self::attribute(Self::Name { id: module.into() }, name) } /// The type hint of a `FromPyObject` implementation as a function argument /// /// If self_type is set, self_type will replace Self in the given type pub fn from_from_py_object(t: Type, self_type: Option<&Type>) -> Self { Self::FromPyObjectType(clean_type(t, self_type)) } /// The type hint of a `IntoPyObject` implementation as a function argument /// /// If self_type is set, self_type will replace Self in the given type pub fn from_into_py_object(t: Type, self_type: Option<&Type>) -> Self { Self::IntoPyObjectType(clean_type(t, self_type)) } /// The type hint of the Rust type used as a function argument /// /// If self_type is set, self_type will replace Self in the given type pub fn from_argument_type(t: Type, self_type: Option<&Type>) -> Self { Self::ArgumentType(clean_type(t, self_type)) } /// The type hint of the Rust type used as a function output type /// /// If self_type is set, self_type will replace Self in the given type pub fn from_return_type(t: Type, self_type: Option<&Type>) -> Self { Self::ReturnType(clean_type(t, self_type)) } /// The type hint of the Rust type `PyTypeCheck` trait. /// /// If self_type is set, self_type will replace Self in the given type pub fn from_type(t: Type, self_type: Option<&Type>) -> Self { Self::Type(clean_type(t, self_type)) } /// An attribute of a given value: `value.attr` pub fn attribute(value: Self, attr: impl Into>) -> Self { Self::Attribute { value: Box::new(value), attr: attr.into(), } } /// Build the union of the different element pub fn union(left: Self, right: Self) -> Self { Self::BinOp { left: Box::new(left), op: PyOperator::BitOr, right: Box::new(right), } } /// Build the subscripted type value[slice] pub fn subscript(value: Self, slice: Self) -> Self { Self::Subscript { value: Box::new(value), slice: Box::new(slice), } } /// Build a tuple pub fn tuple(elts: impl IntoIterator) -> Self { Self::Tuple { elts: elts.into_iter().collect(), } } pub fn constant_from_expression(expr: &Expr) -> Self { Self::Constant(match expr { Expr::Lit(ExprLit { lit, .. }) => match lit { Lit::Str(s) => PyConstant::Str(s.value()), Lit::Char(c) => PyConstant::Str(c.value().into()), Lit::Int(i) => PyConstant::Int(i.base10_digits().into()), Lit::Float(f) => PyConstant::Float(f.base10_digits().into()), Lit::Bool(b) => PyConstant::Bool(b.value()), _ => PyConstant::Ellipsis, // TODO: implement ByteStr and CStr }, Expr::Path(ExprPath { qself, path, .. }) if qself.is_none() && path.is_ident("None") => { PyConstant::None } _ => PyConstant::Ellipsis, }) } pub fn str_constant(value: impl Into) -> Self { Self::Constant(PyConstant::Str(value.into())) } /// `...` pub fn ellipsis() -> Self { Self::Constant(PyConstant::Ellipsis) } pub fn to_introspection_token_stream(&self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { match self { Self::FromPyObjectType(t) => { quote! { <#t as #pyo3_crate_path::FromPyObject<'_, '_>>::INPUT_TYPE } } Self::IntoPyObjectType(t) => { quote! { <#t as #pyo3_crate_path::IntoPyObject<'_>>::OUTPUT_TYPE } } Self::ArgumentType(t) => { quote! { <#t as #pyo3_crate_path::impl_::extract_argument::PyFunctionArgument< { #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_crate_path::impl_::pyclass::Probe as _; #pyo3_crate_path::impl_::pyclass::IsFromPyObject::<#t>::VALUE } >>::INPUT_TYPE } } Self::ReturnType(t) => { quote! {{ #[allow(unused_imports)] use #pyo3_crate_path::impl_::pyclass::Probe as _; const TYPE: #pyo3_crate_path::inspect::PyStaticExpr = if #pyo3_crate_path::impl_::pyclass::IsReturningEmptyTuple::<#t>::VALUE { <#pyo3_crate_path::types::PyNone as #pyo3_crate_path::type_object::PyTypeInfo>::TYPE_HINT } else { <#t as #pyo3_crate_path::impl_::introspection::PyReturnType>::OUTPUT_TYPE }; TYPE }} } Self::Type(t) => { quote! { <#t as #pyo3_crate_path::type_object::PyTypeCheck>::TYPE_HINT } } Self::Name { id } => { quote! { #pyo3_crate_path::inspect::PyStaticExpr::Name { id: #id } } } Self::Attribute { value, attr } => { let value = value.to_introspection_token_stream(pyo3_crate_path); quote! { #pyo3_crate_path::inspect::PyStaticExpr::Attribute { value: &#value, attr: #attr } } } Self::BinOp { left, op, right } => { let left = left.to_introspection_token_stream(pyo3_crate_path); let op = match op { PyOperator::BitOr => quote!(#pyo3_crate_path::inspect::PyStaticOperator::BitOr), }; let right = right.to_introspection_token_stream(pyo3_crate_path); quote! { #pyo3_crate_path::inspect::PyStaticExpr::BinOp { left: &#left, op: #op, right: &#right, } } } Self::Subscript { value, slice } => { let value = value.to_introspection_token_stream(pyo3_crate_path); let slice = slice.to_introspection_token_stream(pyo3_crate_path); quote! { #pyo3_crate_path::inspect::PyStaticExpr::Subscript { value: &#value, slice: &#slice } } } Self::Tuple { elts } => { let elts = elts .iter() .map(|e| e.to_introspection_token_stream(pyo3_crate_path)); quote! { #pyo3_crate_path::inspect::PyStaticExpr::Tuple { elts: &[#(#elts),*] } } } Self::Constant(c) => match c { PyConstant::None => { quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #pyo3_crate_path::inspect::PyStaticConstant::None } } } PyConstant::Bool(v) => { quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #pyo3_crate_path::inspect::PyStaticConstant::Bool(#v) } } } PyConstant::Int(v) => { quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #pyo3_crate_path::inspect::PyStaticConstant::Int(#v) } } } PyConstant::Float(v) => { quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #pyo3_crate_path::inspect::PyStaticConstant::Float(#v) } } } PyConstant::Str(v) => { quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #pyo3_crate_path::inspect::PyStaticConstant::Str(#v) } } } PyConstant::Ellipsis => { quote! { #pyo3_crate_path::inspect::PyStaticExpr::Constant { value: #pyo3_crate_path::inspect::PyStaticConstant::Ellipsis } } } }, } } } fn clean_type(mut t: Type, self_type: Option<&Type>) -> Type { if let Some(self_type) = self_type { replace_self(&mut t, self_type); } elide_lifetimes(&mut t); t } /// Replaces all explicit lifetimes in `self` with elided (`'_`) lifetimes /// /// This is useful if `Self` is used in `const` context, where explicit /// lifetimes are not allowed (yet). fn elide_lifetimes(ty: &mut Type) { struct ElideLifetimesVisitor; impl VisitMut for ElideLifetimesVisitor { fn visit_lifetime_mut(&mut self, l: &mut Lifetime) { *l = Lifetime::new("'_", l.span()); } } ElideLifetimesVisitor.visit_type_mut(ty); } // Replace Self in types with the given type fn replace_self(ty: &mut Type, self_target: &Type) { struct SelfReplacementVisitor<'a> { self_target: &'a Type, } impl VisitMut for SelfReplacementVisitor<'_> { fn visit_type_mut(&mut self, ty: &mut Type) { if let Type::Path(type_path) = ty { if type_path.qself.is_none() && type_path.path.segments.len() == 1 && type_path.path.segments[0].ident == "Self" && type_path.path.segments[0].arguments.is_empty() { // It is Self *ty = self.self_target.clone(); return; } } visit_type_mut(self, ty); } } SelfReplacementVisitor { self_target }.visit_type_mut(ty); } ================================================ FILE: pyo3-macros-backend/src/pyclass.rs ================================================ use std::borrow::Cow; use std::fmt::Debug; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{parse_quote, parse_quote_spanned, spanned::Spanned, ImplItemFn, Result, Token}; use crate::attributes::kw::frozen; use crate::attributes::{ self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute, ModuleAttribute, NameAttribute, NameLitStr, NewImplTypeAttribute, NewImplTypeAttributeValue, RenameAllAttribute, StrFormatterAttribute, }; use crate::combine_errors::CombineErrors; #[cfg(feature = "experimental-inspect")] use crate::introspection::{ attribute_introspection_code, class_introspection_code, function_introspection_code, introspection_id_const, }; use crate::konst::{ConstAttributes, ConstSpec}; use crate::method::{FnArg, FnSpec, PyArg, RegularArg}; #[cfg(feature = "experimental-inspect")] use crate::py_expr::PyExpr; use crate::pyfunction::{ConstructorAttribute, FunctionSignature}; #[cfg(feature = "experimental-inspect")] use crate::pyimpl::method_introspection_code; use crate::pyimpl::{gen_py_const, get_cfg_attributes, PyClassMethodsType}; #[cfg(feature = "experimental-inspect")] use crate::pymethod::field_python_name; use crate::pymethod::{ impl_py_class_attribute, impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __NEW__, __REPR__, __RICHCMP__, __STR__, }; use crate::utils::{self, apply_renaming_rule, get_doc, locate_tokens_at, Ctx, PythonDoc}; use crate::PyFunctionOptions; /// If the class is derived from a Rust `struct` or `enum`. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PyClassKind { Struct, Enum, } /// The parsed arguments of the pyclass macro #[derive(Clone)] pub struct PyClassArgs { pub class_kind: PyClassKind, pub options: PyClassPyO3Options, } impl PyClassArgs { fn parse(input: ParseStream<'_>, kind: PyClassKind) -> Result { Ok(PyClassArgs { class_kind: kind, options: PyClassPyO3Options::parse(input)?, }) } pub fn parse_struct_args(input: ParseStream<'_>) -> syn::Result { Self::parse(input, PyClassKind::Struct) } pub fn parse_enum_args(input: ParseStream<'_>) -> syn::Result { Self::parse(input, PyClassKind::Enum) } } #[derive(Clone, Default)] pub struct PyClassPyO3Options { pub krate: Option, pub dict: Option, pub eq: Option, pub eq_int: Option, pub extends: Option, pub get_all: Option, pub freelist: Option, pub frozen: Option, pub hash: Option, pub immutable_type: Option, pub mapping: Option, pub module: Option, pub name: Option, pub ord: Option, pub rename_all: Option, pub sequence: Option, pub set_all: Option, pub new: Option, pub str: Option, pub subclass: Option, pub unsendable: Option, pub weakref: Option, pub generic: Option, pub from_py_object: Option, pub skip_from_py_object: Option, } pub enum PyClassPyO3Option { Crate(CrateAttribute), Dict(kw::dict), Eq(kw::eq), EqInt(kw::eq_int), Extends(ExtendsAttribute), Freelist(FreelistAttribute), Frozen(kw::frozen), GetAll(kw::get_all), Hash(kw::hash), ImmutableType(kw::immutable_type), Mapping(kw::mapping), Module(ModuleAttribute), Name(NameAttribute), Ord(kw::ord), RenameAll(RenameAllAttribute), Sequence(kw::sequence), SetAll(kw::set_all), New(NewImplTypeAttribute), Str(StrFormatterAttribute), Subclass(kw::subclass), Unsendable(kw::unsendable), Weakref(kw::weakref), Generic(kw::generic), FromPyObject(kw::from_py_object), SkipFromPyObject(kw::skip_from_py_object), } impl Parse for PyClassPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(Token![crate]) { input.parse().map(PyClassPyO3Option::Crate) } else if lookahead.peek(kw::dict) { input.parse().map(PyClassPyO3Option::Dict) } else if lookahead.peek(kw::eq) { input.parse().map(PyClassPyO3Option::Eq) } else if lookahead.peek(kw::eq_int) { input.parse().map(PyClassPyO3Option::EqInt) } else if lookahead.peek(kw::extends) { input.parse().map(PyClassPyO3Option::Extends) } else if lookahead.peek(attributes::kw::freelist) { input.parse().map(PyClassPyO3Option::Freelist) } else if lookahead.peek(attributes::kw::frozen) { input.parse().map(PyClassPyO3Option::Frozen) } else if lookahead.peek(attributes::kw::get_all) { input.parse().map(PyClassPyO3Option::GetAll) } else if lookahead.peek(attributes::kw::hash) { input.parse().map(PyClassPyO3Option::Hash) } else if lookahead.peek(attributes::kw::immutable_type) { input.parse().map(PyClassPyO3Option::ImmutableType) } else if lookahead.peek(attributes::kw::mapping) { input.parse().map(PyClassPyO3Option::Mapping) } else if lookahead.peek(attributes::kw::module) { input.parse().map(PyClassPyO3Option::Module) } else if lookahead.peek(kw::name) { input.parse().map(PyClassPyO3Option::Name) } else if lookahead.peek(attributes::kw::ord) { input.parse().map(PyClassPyO3Option::Ord) } else if lookahead.peek(kw::rename_all) { input.parse().map(PyClassPyO3Option::RenameAll) } else if lookahead.peek(attributes::kw::sequence) { input.parse().map(PyClassPyO3Option::Sequence) } else if lookahead.peek(attributes::kw::set_all) { input.parse().map(PyClassPyO3Option::SetAll) } else if lookahead.peek(attributes::kw::new) { input.parse().map(PyClassPyO3Option::New) } else if lookahead.peek(attributes::kw::str) { input.parse().map(PyClassPyO3Option::Str) } else if lookahead.peek(attributes::kw::subclass) { input.parse().map(PyClassPyO3Option::Subclass) } else if lookahead.peek(attributes::kw::unsendable) { input.parse().map(PyClassPyO3Option::Unsendable) } else if lookahead.peek(attributes::kw::weakref) { input.parse().map(PyClassPyO3Option::Weakref) } else if lookahead.peek(attributes::kw::generic) { input.parse().map(PyClassPyO3Option::Generic) } else if lookahead.peek(attributes::kw::from_py_object) { input.parse().map(PyClassPyO3Option::FromPyObject) } else if lookahead.peek(attributes::kw::skip_from_py_object) { input.parse().map(PyClassPyO3Option::SkipFromPyObject) } else { Err(lookahead.error()) } } } impl Parse for PyClassPyO3Options { fn parse(input: ParseStream<'_>) -> syn::Result { let mut options: PyClassPyO3Options = Default::default(); for option in Punctuated::::parse_terminated(input)? { options.set_option(option)?; } Ok(options) } } impl PyClassPyO3Options { pub fn take_pyo3_options(&mut self, attrs: &mut Vec) -> syn::Result<()> { take_pyo3_options(attrs)? .into_iter() .try_for_each(|option| self.set_option(option)) } fn set_option(&mut self, option: PyClassPyO3Option) -> syn::Result<()> { macro_rules! set_option { ($key:ident) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once") ); self.$key = Some($key); } }; } match option { PyClassPyO3Option::Crate(krate) => set_option!(krate), PyClassPyO3Option::Dict(dict) => set_option!(dict), PyClassPyO3Option::Eq(eq) => set_option!(eq), PyClassPyO3Option::EqInt(eq_int) => set_option!(eq_int), PyClassPyO3Option::Extends(extends) => set_option!(extends), PyClassPyO3Option::Freelist(freelist) => set_option!(freelist), PyClassPyO3Option::Frozen(frozen) => set_option!(frozen), PyClassPyO3Option::GetAll(get_all) => set_option!(get_all), PyClassPyO3Option::ImmutableType(immutable_type) => { set_option!(immutable_type) } PyClassPyO3Option::Hash(hash) => set_option!(hash), PyClassPyO3Option::Mapping(mapping) => set_option!(mapping), PyClassPyO3Option::Module(module) => set_option!(module), PyClassPyO3Option::Name(name) => set_option!(name), PyClassPyO3Option::Ord(ord) => set_option!(ord), PyClassPyO3Option::RenameAll(rename_all) => set_option!(rename_all), PyClassPyO3Option::Sequence(sequence) => set_option!(sequence), PyClassPyO3Option::SetAll(set_all) => set_option!(set_all), PyClassPyO3Option::New(new) => set_option!(new), PyClassPyO3Option::Str(str) => set_option!(str), PyClassPyO3Option::Subclass(subclass) => set_option!(subclass), PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable), PyClassPyO3Option::Weakref(weakref) => set_option!(weakref), PyClassPyO3Option::Generic(generic) => set_option!(generic), PyClassPyO3Option::SkipFromPyObject(skip_from_py_object) => { ensure_spanned!( self.from_py_object.is_none(), skip_from_py_object.span() => "`skip_from_py_object` and `from_py_object` are mutually exclusive" ); set_option!(skip_from_py_object) } PyClassPyO3Option::FromPyObject(from_py_object) => { ensure_spanned!( self.skip_from_py_object.is_none(), from_py_object.span() => "`skip_from_py_object` and `from_py_object` are mutually exclusive" ); set_option!(from_py_object) } } Ok(()) } } pub fn build_py_class( class: &mut syn::ItemStruct, mut args: PyClassArgs, methods_type: PyClassMethodsType, ) -> syn::Result { args.options.take_pyo3_options(&mut class.attrs)?; let ctx = &Ctx::new(&args.options.krate, None); let doc = utils::get_doc(&class.attrs, None); if let Some(lt) = class.generics.lifetimes().next() { bail_spanned!( lt.span() => concat!( "#[pyclass] cannot have lifetime parameters. For an explanation, see \ https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#no-lifetime-parameters" ) ); } ensure_spanned!( class.generics.params.is_empty(), class.generics.span() => concat!( "#[pyclass] cannot have generic parameters. For an explanation, see \ https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#no-generic-parameters" ) ); let mut field_options: Vec<(&syn::Field, FieldPyO3Options)> = match &mut class.fields { syn::Fields::Named(fields) => fields .named .iter_mut() .map( |field| match FieldPyO3Options::take_pyo3_options(&mut field.attrs) { Ok(options) => Ok((&*field, options)), Err(e) => Err(e), }, ) .collect::>(), syn::Fields::Unnamed(fields) => fields .unnamed .iter_mut() .map( |field| match FieldPyO3Options::take_pyo3_options(&mut field.attrs) { Ok(options) => Ok((&*field, options)), Err(e) => Err(e), }, ) .collect::>(), syn::Fields::Unit => { let mut results = Vec::new(); if let Some(attr) = args.options.set_all { results.push(Err(syn::Error::new_spanned(attr, UNIT_SET))); }; if let Some(attr) = args.options.get_all { results.push(Err(syn::Error::new_spanned(attr, UNIT_GET))); }; results } } .into_iter() .try_combine_syn_errors()?; if let Some(attr) = args.options.get_all { for (_, FieldPyO3Options { get, .. }) in &mut field_options { if let Some(old_get) = get.replace(Annotated::Struct(attr)) { return Err(syn::Error::new(old_get.span(), DUPE_GET)); } } } if let Some(attr) = args.options.set_all { for (_, FieldPyO3Options { set, .. }) in &mut field_options { if let Some(old_set) = set.replace(Annotated::Struct(attr)) { return Err(syn::Error::new(old_set.span(), DUPE_SET)); } } } impl_class(&class.ident, &args, doc, field_options, methods_type, ctx) } enum Annotated { Field(X), Struct(Y), } impl Annotated { fn span(&self) -> Span { match self { Self::Field(x) => x.span(), Self::Struct(y) => y.span(), } } } /// `#[pyo3()]` options for pyclass fields struct FieldPyO3Options { get: Option>, set: Option>, name: Option, } enum FieldPyO3Option { Get(attributes::kw::get), Set(attributes::kw::set), Name(NameAttribute), } impl Parse for FieldPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::get) { input.parse().map(FieldPyO3Option::Get) } else if lookahead.peek(attributes::kw::set) { input.parse().map(FieldPyO3Option::Set) } else if lookahead.peek(attributes::kw::name) { input.parse().map(FieldPyO3Option::Name) } else { Err(lookahead.error()) } } } impl FieldPyO3Options { fn take_pyo3_options(attrs: &mut Vec) -> Result { let mut options = FieldPyO3Options { get: None, set: None, name: None, }; for option in take_pyo3_options(attrs)? { match option { FieldPyO3Option::Get(kw) => { if options.get.replace(Annotated::Field(kw)).is_some() { return Err(syn::Error::new(kw.span(), UNIQUE_GET)); } } FieldPyO3Option::Set(kw) => { if options.set.replace(Annotated::Field(kw)).is_some() { return Err(syn::Error::new(kw.span(), UNIQUE_SET)); } } FieldPyO3Option::Name(name) => { if options.name.replace(name).is_some() { return Err(syn::Error::new(options.name.span(), UNIQUE_NAME)); } } } } Ok(options) } } fn get_class_python_name<'a>(cls: &'a Ident, args: &'a PyClassArgs) -> Cow<'a, Ident> { args.options .name .as_ref() .map(|name_attr| Cow::Borrowed(&name_attr.value.0)) .unwrap_or_else(|| Cow::Owned(cls.unraw())) } #[cfg(feature = "experimental-inspect")] fn get_class_type_hint(cls: &Ident, args: &PyClassArgs, ctx: &Ctx) -> TokenStream { let pyo3_path = &ctx.pyo3_path; let name = get_class_python_name(cls, args).to_string(); if let Some(module) = &args.options.module { let module = module.value.value(); quote! { #pyo3_path::inspect::PyStaticExpr::PyClass(#pyo3_path::inspect::PyClassNameStaticExpr::new( &#pyo3_path::inspect::PyStaticExpr::Attribute { value: &#pyo3_path::inspect::PyStaticExpr::Name { id: #module }, attr: #name }, Self::_PYO3_INTROSPECTION_ID )) } } else { quote! { #pyo3_path::inspect::PyStaticExpr::PyClass(#pyo3_path::inspect::PyClassNameStaticExpr::new( &#pyo3_path::inspect::PyStaticExpr::Name { id: #name }, Self::_PYO3_INTROSPECTION_ID )) } } } fn impl_class( cls: &Ident, args: &PyClassArgs, doc: Option, field_options: Vec<(&syn::Field, FieldPyO3Options)>, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let pytypeinfo_impl = impl_pytypeinfo(cls, args, ctx); if let Some(str) = &args.options.str { if str.value.is_some() { // check if any renaming is present let no_naming_conflict = field_options.iter().all(|x| x.1.name.is_none()) & args.options.name.is_none() & args.options.rename_all.is_none(); ensure_spanned!(no_naming_conflict, str.value.span() => "The format string syntax is incompatible with any renaming via `name` or `rename_all`"); } } let (default_new, default_new_slot) = pyclass_new_impl( &args.options, &syn::parse_quote!(#cls), field_options.iter().map(|(f, _)| f), ctx, )?; let mut default_methods = descriptors_to_items( cls, args.options.rename_all.as_ref(), args.options.frozen, field_options, ctx, )?; let (default_class_getitem, default_class_getitem_method) = pyclass_class_getitem(&args.options, &syn::parse_quote!(#cls), ctx)?; if let Some(default_class_getitem_method) = default_class_getitem_method { default_methods.push(default_class_getitem_method); } let (default_str, default_str_slot) = implement_pyclass_str(&args.options, &syn::parse_quote!(#cls), ctx); let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &syn::parse_quote!(#cls), ctx)?; let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &syn::parse_quote!(#cls), ctx)?; let mut slots = Vec::new(); slots.extend(default_richcmp_slot); slots.extend(default_hash_slot); slots.extend(default_str_slot); slots.extend(default_new_slot); let mut impl_builder = PyClassImplsBuilder::new(cls, cls, args, methods_type, default_methods, slots); if let Some(doc) = doc { impl_builder = impl_builder.doc(doc); } let py_class_impl: TokenStream = [ impl_builder.impl_pyclass(ctx), impl_builder.impl_into_py(ctx), impl_builder.impl_pyclassimpl(ctx)?, impl_builder.impl_add_to_module(ctx), impl_builder.impl_freelist(ctx), impl_builder.impl_introspection(ctx, None), ] .into_iter() .collect(); Ok(quote! { impl #pyo3_path::types::DerefToPyAny for #cls {} #pytypeinfo_impl #py_class_impl #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_richcmp #default_hash #default_str #default_new #default_class_getitem } }) } enum PyClassEnum<'a> { Simple(PyClassSimpleEnum<'a>), Complex(PyClassComplexEnum<'a>), } impl<'a> PyClassEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let has_only_unit_variants = enum_ .variants .iter() .all(|variant| matches!(variant.fields, syn::Fields::Unit)); Ok(if has_only_unit_variants { let simple_enum = PyClassSimpleEnum::new(enum_)?; Self::Simple(simple_enum) } else { let complex_enum = PyClassComplexEnum::new(enum_)?; Self::Complex(complex_enum) }) } } pub fn build_py_enum( enum_: &mut syn::ItemEnum, mut args: PyClassArgs, method_type: PyClassMethodsType, ) -> syn::Result { args.options.take_pyo3_options(&mut enum_.attrs)?; let ctx = &Ctx::new(&args.options.krate, None); if let Some(extends) = &args.options.extends { bail_spanned!(extends.span() => "enums can't extend from other classes"); } else if let Some(subclass) = &args.options.subclass { bail_spanned!(subclass.span() => "enums can't be inherited by other classes"); } else if enum_.variants.is_empty() { bail_spanned!(enum_.brace_token.span.join() => "#[pyclass] can't be used on enums without any variants"); } if let Some(generic) = &args.options.generic { bail_spanned!(generic.span() => "enums do not support #[pyclass(generic)]"); } let doc = utils::get_doc(&enum_.attrs, None); let enum_ = PyClassEnum::new(enum_)?; impl_enum(enum_, &args, doc, method_type, ctx) } struct PyClassSimpleEnum<'a> { ident: &'a syn::Ident, // The underlying #[repr] of the enum, used to implement __int__ and __richcmp__. // This matters when the underlying representation may not fit in `isize`. repr_type: syn::Ident, variants: Vec>, } impl<'a> PyClassSimpleEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { fn is_numeric_type(t: &syn::Ident) -> bool { [ "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize", "isize", ] .iter() .any(|&s| t == s) } fn extract_unit_variant_data( variant: &mut syn::Variant, ) -> syn::Result> { use syn::Fields; let ident = match &variant.fields { Fields::Unit => &variant.ident, _ => bail_spanned!(variant.span() => "Must be a unit variant."), }; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; Ok(PyClassEnumUnitVariant { ident, options, attrs: variant.attrs.clone(), }) } let ident = &enum_.ident; // According to the [reference](https://doc.rust-lang.org/reference/items/enumerations.html), // "Under the default representation, the specified discriminant is interpreted as an isize // value", so `isize` should be enough by default. let mut repr_type = syn::Ident::new("isize", proc_macro2::Span::call_site()); if let Some(attr) = enum_.attrs.iter().find(|attr| attr.path().is_ident("repr")) { let args = attr.parse_args_with(Punctuated::::parse_terminated)?; if let Some(ident) = args .into_iter() .filter_map(|ts| syn::parse2::(ts).ok()) .find(is_numeric_type) { repr_type = ident; } } let variants: Vec<_> = enum_ .variants .iter_mut() .map(extract_unit_variant_data) .collect::>()?; Ok(Self { ident, repr_type, variants, }) } } struct PyClassComplexEnum<'a> { ident: &'a syn::Ident, variants: Vec>, } impl<'a> PyClassComplexEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let witness = enum_ .variants .iter() .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) .expect("complex enum has a non-unit variant") .ident .to_owned(); let extract_variant_data = |variant: &'a mut syn::Variant| -> syn::Result> { use syn::Fields; let ident = &variant.ident; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; let variant = match &variant.fields { Fields::Unit => { bail_spanned!(variant.span() => format!( "Unit variant `{ident}` is not yet supported in a complex enum\n\ = help: change to an empty tuple variant instead: `{ident}()`\n\ = note: the enum is complex because of non-unit variant `{witness}`", ident=ident, witness=witness)) } Fields::Named(fields) => { let fields = fields .named .iter() .map(|field| PyClassEnumVariantNamedField { ident: field.ident.as_ref().expect("named field has an identifier"), ty: &field.ty, attrs: &field.attrs, span: field.span(), }) .collect(); PyClassEnumVariant::Struct(PyClassEnumStructVariant { ident, fields, options, attrs: variant.attrs.clone(), }) } Fields::Unnamed(types) => { let fields = types .unnamed .iter() .map(|field| PyClassEnumVariantUnnamedField { ty: &field.ty, span: field.span(), attrs: &field.attrs, }) .collect(); PyClassEnumVariant::Tuple(PyClassEnumTupleVariant { ident, fields, options, attrs: variant.attrs.clone(), }) } }; Ok(variant) }; let ident = &enum_.ident; let variants: Vec<_> = enum_ .variants .iter_mut() .map(extract_variant_data) .collect::>()?; Ok(Self { ident, variants }) } } enum PyClassEnumVariant<'a> { // TODO(mkovaxx): Unit(PyClassEnumUnitVariant<'a>), Struct(PyClassEnumStructVariant<'a>), Tuple(PyClassEnumTupleVariant<'a>), } trait EnumVariant { fn get_ident(&self) -> &syn::Ident; fn get_options(&self) -> &EnumVariantPyO3Options; fn get_attrs(&self) -> &[syn::Attribute]; fn get_python_name(&self, args: &PyClassArgs) -> Cow<'_, syn::Ident> { self.get_options() .name .as_ref() .map(|name_attr| Cow::Borrowed(&name_attr.value.0)) .unwrap_or_else(|| { let name = self.get_ident().unraw(); if let Some(attr) = &args.options.rename_all { let new_name = apply_renaming_rule(attr.value.rule, &name.to_string()); Cow::Owned(Ident::new(&new_name, Span::call_site())) } else { Cow::Owned(name) } }) } } impl EnumVariant for PyClassEnumVariant<'_> { fn get_ident(&self) -> &syn::Ident { match self { Self::Struct(struct_variant) => struct_variant.ident, Self::Tuple(tuple_variant) => tuple_variant.ident, } } fn get_options(&self) -> &EnumVariantPyO3Options { match self { Self::Struct(struct_variant) => &struct_variant.options, Self::Tuple(tuple_variant) => &tuple_variant.options, } } fn get_attrs(&self) -> &[syn::Attribute] { match self { Self::Struct(struct_variant) => &struct_variant.attrs, Self::Tuple(tuple_variant) => &tuple_variant.attrs, } } } /// A unit variant has no fields struct PyClassEnumUnitVariant<'a> { ident: &'a syn::Ident, options: EnumVariantPyO3Options, attrs: Vec, } impl EnumVariant for PyClassEnumUnitVariant<'_> { fn get_ident(&self) -> &syn::Ident { self.ident } fn get_options(&self) -> &EnumVariantPyO3Options { &self.options } fn get_attrs(&self) -> &[syn::Attribute] { &self.attrs } } /// A struct variant has named fields struct PyClassEnumStructVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, attrs: Vec, } impl PyClassEnumStructVariant<'_> { fn python_name(&self) -> Cow<'_, syn::Ident> { self.options .name .as_ref() .map(|name_attr| Cow::Borrowed(&name_attr.value.0)) .unwrap_or_else(|| Cow::Owned(self.ident.unraw())) } } struct PyClassEnumTupleVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, attrs: Vec, } impl PyClassEnumTupleVariant<'_> { fn python_name(&self) -> Cow<'_, syn::Ident> { self.options .name .as_ref() .map(|name_attr| Cow::Borrowed(&name_attr.value.0)) .unwrap_or_else(|| Cow::Owned(self.ident.unraw())) } } struct PyClassEnumVariantNamedField<'a> { ident: &'a syn::Ident, ty: &'a syn::Type, attrs: &'a [syn::Attribute], span: Span, } struct PyClassEnumVariantUnnamedField<'a> { ty: &'a syn::Type, attrs: &'a [syn::Attribute], span: Span, } /// `#[pyo3()]` options for pyclass enum variants #[derive(Clone, Default)] struct EnumVariantPyO3Options { name: Option, constructor: Option, } enum EnumVariantPyO3Option { Name(NameAttribute), Constructor(ConstructorAttribute), } impl Parse for EnumVariantPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(EnumVariantPyO3Option::Name) } else if lookahead.peek(attributes::kw::constructor) { input.parse().map(EnumVariantPyO3Option::Constructor) } else { Err(lookahead.error()) } } } impl EnumVariantPyO3Options { fn take_pyo3_options(attrs: &mut Vec) -> Result { let mut options = EnumVariantPyO3Options::default(); take_pyo3_options(attrs)? .into_iter() .try_for_each(|option| options.set_option(option))?; Ok(options) } fn set_option(&mut self, option: EnumVariantPyO3Option) -> syn::Result<()> { macro_rules! set_option { ($key:ident) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once") ); self.$key = Some($key); } }; } match option { EnumVariantPyO3Option::Constructor(constructor) => set_option!(constructor), EnumVariantPyO3Option::Name(name) => set_option!(name), } Ok(()) } } // todo(remove this dead code allowance once __repr__ is implemented #[allow(dead_code)] pub enum PyFmtName { Str, Repr, } fn implement_py_formatting( ty: &syn::Type, ctx: &Ctx, option: &StrFormatterAttribute, ) -> (ImplItemFn, MethodAndSlotDef) { let mut fmt_impl = match &option.value { Some(opt) => { let fmt = &opt.fmt; let args = &opt .args .iter() .map(|member| quote! {self.#member}) .collect::>(); let fmt_impl: ImplItemFn = syn::parse_quote! { fn __pyo3__generated____str__(&self) -> ::std::string::String { ::std::format!(#fmt, #(#args, )*) } }; fmt_impl } None => { let fmt_impl: syn::ImplItemFn = syn::parse_quote! { fn __pyo3__generated____str__(&self) -> ::std::string::String { ::std::format!("{}", &self) } }; fmt_impl } }; let fmt_slot = generate_protocol_slot( ty, &mut fmt_impl, &__STR__, "__str__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__str__"], arguments: Vec::new(), returns: parse_quote! { ::std::string::String }, is_returning_not_implemented_on_extraction_error: false, }, ctx, ) .unwrap(); (fmt_impl, fmt_slot) } fn implement_pyclass_str( options: &PyClassPyO3Options, ty: &syn::Type, ctx: &Ctx, ) -> (Option, Option) { match &options.str { Some(option) => { let (default_str, default_str_slot) = implement_py_formatting(ty, ctx, option); (Some(default_str), Some(default_str_slot)) } _ => (None, None), } } fn impl_enum( enum_: PyClassEnum<'_>, args: &PyClassArgs, doc: Option, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { if let Some(str_fmt) = &args.options.str { ensure_spanned!(str_fmt.value.is_none(), str_fmt.value.span() => "The format string syntax cannot be used with enums") } match enum_ { PyClassEnum::Simple(simple_enum) => { impl_simple_enum(simple_enum, args, doc, methods_type, ctx) } PyClassEnum::Complex(complex_enum) => { impl_complex_enum(complex_enum, args, doc, methods_type, ctx) } } } fn impl_simple_enum( simple_enum: PyClassSimpleEnum<'_>, args: &PyClassArgs, doc: Option, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let cls = simple_enum.ident; let ty: syn::Type = syn::parse_quote!(#cls); let variants = simple_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, args, ctx); for variant in &variants { ensure_spanned!(variant.options.constructor.is_none(), variant.options.constructor.span() => "`constructor` can't be used on a simple enum variant"); } let variant_cfg_check = generate_cfg_check(&variants, cls); let (default_repr, default_repr_slot) = { let variants_repr = variants.iter().map(|variant| { let variant_name = variant.ident; let cfg_attrs = get_cfg_attributes(&variant.attrs); // Assuming all variants are unit variants because they are the only type we support. let repr = format!( "{}.{}", get_class_python_name(cls, args), variant.get_python_name(args), ); quote! { #(#cfg_attrs)* #cls::#variant_name => #repr, } }); let mut repr_impl: syn::ImplItemFn = syn::parse_quote! { fn __pyo3__repr__(&self) -> &'static str { match *self { #(#variants_repr)* } } }; let repr_slot = generate_default_protocol_slot( &ty, &mut repr_impl, &__REPR__, #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__repr__"], arguments: Vec::new(), returns: parse_quote! { &'static str }, is_returning_not_implemented_on_extraction_error: false, }, ctx, )?; (repr_impl, repr_slot) }; let (default_str, default_str_slot) = implement_pyclass_str(&args.options, &ty, ctx); let repr_type = &simple_enum.repr_type; let (default_int, default_int_slot) = { // This implementation allows us to convert &T to #repr_type without implementing `Copy` let variants_to_int = variants.iter().map(|variant| { let variant_name = variant.ident; let cfg_attrs = get_cfg_attributes(&variant.attrs); quote! { #(#cfg_attrs)* #cls::#variant_name => #cls::#variant_name as #repr_type, } }); let mut int_impl: syn::ImplItemFn = syn::parse_quote! { fn __pyo3__int__(&self) -> #repr_type { match *self { #(#variants_to_int)* } } }; let int_slot = generate_default_protocol_slot( &ty, &mut int_impl, &__INT__, #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__int__"], arguments: Vec::new(), returns: parse_quote!(#repr_type), is_returning_not_implemented_on_extraction_error: false, }, ctx, )?; (int_impl, int_slot) }; let (default_richcmp, default_richcmp_slot) = pyclass_richcmp_simple_enum(&args.options, &ty, repr_type, ctx)?; let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?; let mut default_slots = vec![default_repr_slot, default_int_slot]; default_slots.extend(default_richcmp_slot); default_slots.extend(default_hash_slot); default_slots.extend(default_str_slot); let mut impl_builder = PyClassImplsBuilder::new( cls, cls, args, methods_type, simple_enum_default_methods( cls, variants .iter() .map(|v| (v.ident, v.get_python_name(args), v.attrs.as_slice())), ctx, ), default_slots, ); if let Some(doc) = doc { impl_builder = impl_builder.doc(doc); } let enum_into_pyobject_impl = { let output_type = get_conversion_type_hint(ctx, &format_ident!("OUTPUT_TYPE"), cls); let num = variants.len(); let i = (0..num).map(proc_macro2::Literal::usize_unsuffixed); let variant_idents = variants.iter().map(|v| v.ident); let cfgs = variants.iter().map(|v| get_cfg_attributes(&v.attrs)); quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; type Output = #pyo3_path::Bound<'py, >::Target>; type Error = #pyo3_path::PyErr; #output_type fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< >::Output, >::Error, > { // TODO(icxolu): switch this to lookup the variants on the type object, once that is immutable static SINGLETON: [#pyo3_path::sync::PyOnceLock<#pyo3_path::Py<#cls>>; #num] = [const { #pyo3_path::sync::PyOnceLock::<#pyo3_path::Py<#cls>>::new() }; #num]; let idx: usize = match self { #( #(#cfgs)* Self::#variant_idents => #i, )* }; #[allow(unreachable_code)] SINGLETON[idx].get_or_try_init(py, || { #pyo3_path::Py::new(py, self) }).map(|obj| ::std::clone::Clone::clone(obj.bind(py))) } } } }; let pyclass_impls: TokenStream = [ impl_builder.impl_pyclass(ctx), enum_into_pyobject_impl, impl_builder.impl_pyclassimpl(ctx)?, impl_builder.impl_add_to_module(ctx), impl_builder.impl_freelist(ctx), impl_builder.impl_introspection(ctx, None), ] .into_iter() .collect(); Ok(quote! { #variant_cfg_check #pytypeinfo #pyclass_impls #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_repr #default_int #default_richcmp #default_hash #default_str } }) } fn impl_complex_enum( complex_enum: PyClassComplexEnum<'_>, args: &PyClassArgs, doc: Option, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let cls = complex_enum.ident; let ty: syn::Type = syn::parse_quote!(#cls); // Need to rig the enum PyClass options let args = { let mut rigged_args = args.clone(); // Needs to be frozen to disallow `&mut self` methods, which could break a runtime invariant rigged_args.options.frozen = parse_quote!(frozen); // Needs to be subclassable by the variant PyClasses rigged_args.options.subclass = parse_quote!(subclass); rigged_args }; let ctx = &Ctx::new(&args.options.krate, None); let cls = complex_enum.ident; let variants = complex_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, &args, ctx); let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &ty, ctx)?; let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?; let (default_str, default_str_slot) = implement_pyclass_str(&args.options, &ty, ctx); let mut default_slots = vec![]; default_slots.extend(default_richcmp_slot); default_slots.extend(default_hash_slot); default_slots.extend(default_str_slot); let mut impl_builder = PyClassImplsBuilder::new( cls, cls, &args, methods_type, complex_enum_default_methods( cls, variants .iter() .map(|v| (v.get_ident(), v.get_python_name(&args), v.get_attrs())), ctx, ), default_slots, ); if let Some(doc) = doc { impl_builder = impl_builder.doc(doc); } let enum_into_pyobject_impl = { let match_arms = variants .iter() .map(|variant| { let variant_ident = variant.get_ident(); let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident()); quote! { #cls::#variant_ident { .. } => { let pyclass_init = <#pyo3_path::PyClassInitializer as ::std::convert::From>::from(self).add_subclass(#variant_cls); unsafe { #pyo3_path::Bound::new(py, pyclass_init).map(|b| b.cast_into_unchecked()) } } } }); let output_type = get_conversion_type_hint(ctx, &format_ident!("OUTPUT_TYPE"), cls); quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; type Output = #pyo3_path::Bound<'py, >::Target>; type Error = #pyo3_path::PyErr; #output_type fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< ::Output, ::Error, > { match self { #(#match_arms)* } } } } }; let pyclass_impls: TokenStream = [ impl_builder.impl_pyclass(ctx), enum_into_pyobject_impl, impl_builder.impl_pyclassimpl(ctx)?, impl_builder.impl_add_to_module(ctx), impl_builder.impl_freelist(ctx), impl_builder.impl_introspection(ctx, None), ] .into_iter() .collect(); let mut variant_cls_zsts = vec![]; let mut variant_cls_pytypeinfos = vec![]; let mut variant_cls_pyclass_impls = vec![]; let mut variant_cls_impls = vec![]; for variant in variants { let variant_name = variant.get_ident().clone(); let variant_cls = gen_complex_enum_variant_class_ident(cls, &variant_name); let variant_cls_zst = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] struct #variant_cls; }; variant_cls_zsts.push(variant_cls_zst); let variant_args = PyClassArgs { class_kind: PyClassKind::Struct, // TODO(mkovaxx): propagate variant.options options: { let mut rigged_options: PyClassPyO3Options = parse_quote!(extends = #cls, frozen); // If a specific module was given to the base class, use it for all variants. rigged_options.module.clone_from(&args.options.module); rigged_options }, }; let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, ctx); variant_cls_pytypeinfos.push(variant_cls_pytypeinfo); let (variant_cls_impl, field_getters, mut slots) = impl_complex_enum_variant_cls(cls, &args, &variant, ctx)?; variant_cls_impls.push(variant_cls_impl); let variant_doc = get_doc(variant.get_attrs(), None); let variant_new = complex_enum_variant_new(cls, variant, ctx)?; slots.push(variant_new); let mut impl_builder = PyClassImplsBuilder::new( &variant_cls, &variant_name, &variant_args, methods_type, field_getters, slots, ); if let Some(doc) = variant_doc { impl_builder = impl_builder.doc(doc); } let pyclass_impl: TokenStream = [ impl_builder.impl_pyclass(ctx), impl_builder.impl_into_py(ctx), impl_builder.impl_pyclassimpl(ctx)?, impl_builder.impl_add_to_module(ctx), impl_builder.impl_freelist(ctx), impl_builder.impl_introspection(ctx, Some(cls)), ] .into_iter() .collect(); variant_cls_pyclass_impls.push(pyclass_impl); } Ok(quote! { #pytypeinfo #pyclass_impls #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_richcmp #default_hash #default_str } #(#variant_cls_zsts)* #(#variant_cls_pytypeinfos)* #(#variant_cls_pyclass_impls)* #(#variant_cls_impls)* }) } fn impl_complex_enum_variant_cls( enum_name: &syn::Ident, args: &PyClassArgs, variant: &PyClassEnumVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { match variant { PyClassEnumVariant::Struct(struct_variant) => { impl_complex_enum_struct_variant_cls(enum_name, args, struct_variant, ctx) } PyClassEnumVariant::Tuple(tuple_variant) => { impl_complex_enum_tuple_variant_cls(enum_name, args, tuple_variant, ctx) } } } fn impl_complex_enum_variant_match_args( ctx @ Ctx { pyo3_path, .. }: &Ctx, variant_cls_type: &syn::Type, field_names: &[Ident], ) -> syn::Result<(MethodAndMethodDef, syn::ImplItemFn)> { let ident = format_ident!("__match_args__"); let field_names_unraw = field_names.iter().map(|name| name.unraw()); let mut match_args_impl: syn::ImplItemFn = { parse_quote! { #[classattr] fn #ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'_, #pyo3_path::types::PyTuple>> { #pyo3_path::types::PyTuple::new::<&str, _>(py, [ #(stringify!(#field_names_unraw),)* ]) } } }; let spec = FnSpec::parse( &mut match_args_impl.sig, &mut match_args_impl.attrs, Default::default(), )?; #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut variant_match_args = impl_py_class_attribute(variant_cls_type, &spec, ctx)?; #[cfg(feature = "experimental-inspect")] variant_match_args.add_introspection(attribute_introspection_code( pyo3_path, Some(variant_cls_type), "__match_args__".into(), PyExpr::tuple( field_names .iter() .map(|name| PyExpr::str_constant(name.unraw().to_string())), ), syn::Type::Tuple(syn::TypeTuple { paren_token: syn::token::Paren::default(), elems: field_names .iter() .map(|_| { let t: syn::Type = parse_quote!(&'static str); t }) .collect(), }), None, true, )); Ok((variant_match_args, match_args_impl)) } fn impl_complex_enum_struct_variant_cls( enum_name: &syn::Ident, args: &PyClassArgs, variant: &PyClassEnumStructVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { let Ctx { pyo3_path, .. } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); let mut field_names: Vec = vec![]; let mut fields_with_types: Vec = vec![]; let mut field_getters = vec![]; let mut field_getter_impls: Vec = vec![]; for field in &variant.fields { let field_name = field.ident; let field_type = field.ty; let field_with_type = quote! { #field_name: #field_type }; let field_getter = complex_enum_variant_field_getter( &variant_cls_type, field_name, field_type, field.attrs, field.span, ctx, )?; let field_getter_impl = quote! { fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; match &*slf.into_super() { #enum_name::#variant_ident { #field_name, .. } => #pyo3_path::impl_::pyclass::ConvertField::< { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE }, >::convert_field::<#field_type>(#field_name, py), _ => ::core::unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), } } }; field_names.push(field_name.clone()); fields_with_types.push(field_with_type); field_getters.push(field_getter); field_getter_impls.push(field_getter_impl); } let (qualname, qualname_impl) = impl_complex_enum_variant_qualname( &get_class_python_name(enum_name, args), &variant.python_name(), &variant_cls_type, ctx, )?; field_getters.push(qualname); let (variant_match_args, match_args_const_impl) = impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &field_names)?; field_getters.push(variant_match_args); let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] impl #variant_cls { #[allow(clippy::too_many_arguments)] fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#fields_with_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> { let base_value = #enum_name::#variant_ident { #(#field_names,)* }; <#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls) } #match_args_const_impl #qualname_impl #(#field_getter_impls)* } }; Ok((cls_impl, field_getters, Vec::new())) } fn impl_complex_enum_tuple_variant_field_getters( ctx: &Ctx, variant: &PyClassEnumTupleVariant<'_>, enum_name: &syn::Ident, variant_cls_type: &syn::Type, variant_ident: &&Ident, field_names: &mut Vec, fields_types: &mut Vec, ) -> Result<(Vec, Vec)> { let Ctx { pyo3_path, .. } = ctx; let mut field_getters = vec![]; let mut field_getter_impls = vec![]; for (index, field) in variant.fields.iter().enumerate() { let field_name = format_ident!("_{}", index); let field_type = field.ty; let field_getter = complex_enum_variant_field_getter( variant_cls_type, &field_name, field_type, field.attrs, field.span, ctx, )?; // Generate the match arms needed to destructure the tuple and access the specific field let field_access_tokens: Vec<_> = (0..variant.fields.len()) .map(|i| { if i == index { quote! { val } } else { quote! { _ } } }) .collect(); let field_getter_impl: syn::ImplItemFn = parse_quote! { fn #field_name(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; match &*slf.into_super() { #enum_name::#variant_ident ( #(#field_access_tokens), *) => #pyo3_path::impl_::pyclass::ConvertField::< { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE }, >::convert_field::<#field_type>(val, py), _ => ::core::unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), } } }; field_names.push(field_name); fields_types.push(field_type.clone()); field_getters.push(field_getter); field_getter_impls.push(field_getter_impl); } Ok((field_getters, field_getter_impls)) } fn impl_complex_enum_tuple_variant_len( ctx: &Ctx, variant_cls_type: &syn::Type, num_fields: usize, ) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { let Ctx { pyo3_path, .. } = ctx; let mut len_method_impl: syn::ImplItemFn = parse_quote! { fn __len__(slf: #pyo3_path::PyClassGuard<'_, Self>) -> #pyo3_path::PyResult { ::std::result::Result::Ok(#num_fields) } }; let variant_len = generate_default_protocol_slot( variant_cls_type, &mut len_method_impl, &__LEN__, #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__len__"], arguments: Vec::new(), returns: parse_quote! { ::std::primitive::usize }, is_returning_not_implemented_on_extraction_error: false, }, ctx, )?; Ok((variant_len, len_method_impl)) } fn impl_complex_enum_tuple_variant_getitem( ctx: &Ctx, variant_cls: &syn::Ident, variant_cls_type: &syn::Type, num_fields: usize, ) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { let Ctx { pyo3_path, .. } = ctx; let match_arms: Vec<_> = (0..num_fields) .map(|i| { let field_access = format_ident!("_{}", i); quote! { #i => #pyo3_path::IntoPyObjectExt::into_py_any(#variant_cls::#field_access(slf, py)?, py) } }) .collect(); let mut get_item_method_impl: syn::ImplItemFn = parse_quote! { fn __getitem__(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::Py<#pyo3_path::PyAny>> { match idx { #( #match_arms, )* _ => ::std::result::Result::Err(#pyo3_path::exceptions::PyIndexError::new_err("tuple index out of range")), } } }; let variant_getitem = generate_default_protocol_slot( variant_cls_type, &mut get_item_method_impl, &__GETITEM__, #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__getitem__"], arguments: vec![FnArg::Regular(RegularArg { name: Cow::Owned(Ident::new("key", variant_cls.span())), ty: &parse_quote! { ::std::primitive::usize }, from_py_with: None, default_value: None, option_wrapped_type: None, annotation: None, })], returns: parse_quote! { #pyo3_path::Py<#pyo3_path::PyAny> }, // TODO: figure out correct type is_returning_not_implemented_on_extraction_error: false, }, ctx, )?; Ok((variant_getitem, get_item_method_impl)) } fn impl_complex_enum_tuple_variant_cls( enum_name: &syn::Ident, args: &PyClassArgs, variant: &PyClassEnumTupleVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { let Ctx { pyo3_path, .. } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); let mut slots = vec![]; // represents the index of the field let mut field_names: Vec = vec![]; let mut field_types: Vec = vec![]; let (mut field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters( ctx, variant, enum_name, &variant_cls_type, variant_ident, &mut field_names, &mut field_types, )?; let (qualname, qualname_impl) = impl_complex_enum_variant_qualname( &get_class_python_name(enum_name, args), &variant.python_name(), &variant_cls_type, ctx, )?; field_getters.push(qualname); let num_fields = variant.fields.len(); let (variant_len, len_method_impl) = impl_complex_enum_tuple_variant_len(ctx, &variant_cls_type, num_fields)?; slots.push(variant_len); let (variant_getitem, getitem_method_impl) = impl_complex_enum_tuple_variant_getitem(ctx, &variant_cls, &variant_cls_type, num_fields)?; slots.push(variant_getitem); let (variant_match_args, match_args_method_impl) = impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &field_names)?; field_getters.push(variant_match_args); let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] impl #variant_cls { #[allow(clippy::too_many_arguments)] fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#field_names : #field_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> { let base_value = #enum_name::#variant_ident ( #(#field_names,)* ); <#pyo3_path::PyClassInitializer<#enum_name> as ::std::convert::From<#enum_name>>::from(base_value).add_subclass(#variant_cls) } #len_method_impl #getitem_method_impl #match_args_method_impl #qualname_impl #(#field_getter_impls)* } }; Ok((cls_impl, field_getters, slots)) } fn gen_complex_enum_variant_class_ident(enum_: &Ident, variant: &Ident) -> Ident { format_ident!("{}_{}", enum_, variant) } fn impl_complex_enum_variant_qualname( enum_name: &syn::Ident, variant_ident: &syn::Ident, variant_cls_type: &syn::Type, ctx: &Ctx, ) -> syn::Result<(MethodAndMethodDef, syn::ImplItemFn)> { let Ctx { pyo3_path, .. } = ctx; let qualname = format!("{}.{}", enum_name, variant_ident); let mut qualname_impl: syn::ImplItemFn = { parse_quote! { #[classattr] fn __qualname__(py: #pyo3_path::Python<'_>) -> &'static str { #qualname } } }; let spec = FnSpec::parse( &mut qualname_impl.sig, &mut qualname_impl.attrs, Default::default(), )?; // NB: Deliberately add no introspection here, this is __qualname__ let qualname = impl_py_class_attribute(variant_cls_type, &spec, ctx)?; Ok((qualname, qualname_impl)) } #[cfg(feature = "experimental-inspect")] struct FunctionIntrospectionData<'a> { names: &'a [&'a str], arguments: Vec>, is_returning_not_implemented_on_extraction_error: bool, returns: syn::Type, } #[cfg(feature = "experimental-inspect")] impl FunctionIntrospectionData<'_> { fn generate(self, ctx: &Ctx, cls: &syn::Type) -> TokenStream { let signature = FunctionSignature::from_arguments(self.arguments); let returns = self.returns; self.names .iter() .flat_map(|name| { function_introspection_code( &ctx.pyo3_path, None, name, &signature, Some("self"), parse_quote!(-> #returns), [], false, self.is_returning_not_implemented_on_extraction_error, None, Some(cls), ) }) .collect() } } fn generate_protocol_slot( cls: &syn::Type, method: &mut syn::ImplItemFn, slot: &SlotDef, name: &str, #[cfg(feature = "experimental-inspect")] introspection_data: FunctionIntrospectionData<'_>, ctx: &Ctx, ) -> syn::Result { let spec = FnSpec::parse( &mut method.sig, &mut method.attrs, PyFunctionOptions::default(), )?; #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut def = slot.generate_type_slot(cls, &spec, name, ctx)?; #[cfg(feature = "experimental-inspect")] def.add_introspection(introspection_data.generate(ctx, cls)); Ok(def) } fn generate_default_protocol_slot( cls: &syn::Type, method: &mut syn::ImplItemFn, slot: &SlotDef, #[cfg(feature = "experimental-inspect")] introspection_data: FunctionIntrospectionData<'_>, ctx: &Ctx, ) -> syn::Result { let spec = FnSpec::parse( &mut method.sig, &mut Vec::new(), PyFunctionOptions::default(), )?; let name = spec.name.to_string(); #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut def = slot.generate_type_slot( &syn::parse_quote!(#cls), &spec, &format!("__default_{name}__"), ctx, )?; #[cfg(feature = "experimental-inspect")] def.add_introspection(introspection_data.generate(ctx, cls)); Ok(def) } fn simple_enum_default_methods<'a>( cls: &'a syn::Ident, unit_variant_names: impl IntoIterator< Item = (&'a syn::Ident, Cow<'a, syn::Ident>, &'a [syn::Attribute]), >, ctx: &Ctx, ) -> Vec { let cls_type: syn::Type = syn::parse_quote!(#cls); unit_variant_names .into_iter() .map(|(var, py_name, attrs)| { let spec = ConstSpec { rust_ident: var.clone(), attributes: ConstAttributes { is_class_attr: true, name: Some(NameAttribute { kw: syn::parse_quote! { name }, value: NameLitStr(py_name.into_owned()), }), }, #[cfg(feature = "experimental-inspect")] expr: None, #[cfg(feature = "experimental-inspect")] ty: cls_type.clone(), #[cfg(feature = "experimental-inspect")] doc: get_doc(attrs, None), }; let method = gen_py_const(&cls_type, &spec, ctx); let associated_method_tokens = method.associated_method; let method_def_tokens = method.method_def; let cfg_attrs = get_cfg_attributes(attrs); let associated_method = quote! { #(#cfg_attrs)* #associated_method_tokens }; let method_def = quote! { #(#cfg_attrs)* #method_def_tokens }; MethodAndMethodDef { associated_method, method_def, } }) .collect() } #[cfg_attr(not(feature = "experimental-inspect"), expect(unused_variables))] fn complex_enum_default_methods<'a>( cls: &'a Ident, variant_names: impl IntoIterator, &'a [syn::Attribute])>, ctx: &Ctx, ) -> Vec { let cls_type = syn::parse_quote!(#cls); variant_names .into_iter() .map(|(var, py_name, attrs)| { #[cfg(feature = "experimental-inspect")] let variant_cls = gen_complex_enum_variant_class_ident(cls, &py_name); let spec = ConstSpec { rust_ident: var.clone(), attributes: ConstAttributes { is_class_attr: true, name: Some(NameAttribute { kw: syn::parse_quote! { name }, value: NameLitStr(py_name.into_owned()), }), }, #[cfg(feature = "experimental-inspect")] expr: None, #[cfg(feature = "experimental-inspect")] ty: parse_quote!(#variant_cls), #[cfg(feature = "experimental-inspect")] doc: get_doc(attrs, None), }; gen_complex_enum_variant_attr(cls, &cls_type, &spec, ctx) }) .collect() } pub fn gen_complex_enum_variant_attr( cls: &syn::Ident, cls_type: &syn::Type, spec: &ConstSpec, ctx: &Ctx, ) -> MethodAndMethodDef { let Ctx { pyo3_path, .. } = ctx; let member = &spec.rust_ident; let wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member); let python_name = spec.null_terminated_python_name(); let variant_cls = gen_complex_enum_variant_class_ident(cls, member); let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { ::std::result::Result::Ok(py.get_type::<#variant_cls>().into_any().unbind()) } }; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({ #pyo3_path::impl_::pymethods::PyClassAttributeDef::new( #python_name, #cls_type::#wrapper_ident ) }) }; MethodAndMethodDef { associated_method, method_def, } } fn complex_enum_variant_new<'a>( cls: &'a syn::Ident, variant: PyClassEnumVariant<'a>, ctx: &Ctx, ) -> Result { match variant { PyClassEnumVariant::Struct(struct_variant) => { complex_enum_struct_variant_new(cls, struct_variant, ctx) } PyClassEnumVariant::Tuple(tuple_variant) => { complex_enum_tuple_variant_new(cls, tuple_variant, ctx) } } } fn complex_enum_struct_variant_new<'a>( cls: &'a syn::Ident, variant: PyClassEnumStructVariant<'a>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); let arg_py_ident: syn::Ident = parse_quote!(py); let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); let args = { let mut args = vec![ // py: Python<'_> FnArg::Py(PyArg { name: &arg_py_ident, ty: &arg_py_type, }), ]; for field in &variant.fields { args.push(FnArg::Regular(RegularArg { name: Cow::Borrowed(field.ident), ty: field.ty, from_py_with: None, default_value: None, option_wrapped_type: None, #[cfg(feature = "experimental-inspect")] annotation: None, })); } args }; let signature = if let Some(constructor) = variant.options.constructor { FunctionSignature::from_arguments_and_attribute(args, constructor.into_signature())? } else { FunctionSignature::from_arguments(args) }; let spec = FnSpec { tp: crate::method::FnType::FnStatic, name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, text_signature: None, asyncness: None, unsafety: None, warnings: vec![], output: syn::ReturnType::Default, }; #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut def = __NEW__.generate_type_slot(&variant_cls_type, &spec, "__default___new____", ctx)?; #[cfg(feature = "experimental-inspect")] def.add_introspection(method_introspection_code( &spec, &[], &variant_cls_type, false, ctx, )); Ok(def) } fn complex_enum_tuple_variant_new<'a>( cls: &'a syn::Ident, variant: PyClassEnumTupleVariant<'a>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); let arg_py_ident: syn::Ident = parse_quote!(py); let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); let args = { let mut args = vec![FnArg::Py(PyArg { name: &arg_py_ident, ty: &arg_py_type, })]; for (i, field) in variant.fields.iter().enumerate() { args.push(FnArg::Regular(RegularArg { name: std::borrow::Cow::Owned(format_ident!("_{}", i)), ty: field.ty, from_py_with: None, default_value: None, option_wrapped_type: None, #[cfg(feature = "experimental-inspect")] annotation: None, })); } args }; let signature = if let Some(constructor) = variant.options.constructor { FunctionSignature::from_arguments_and_attribute(args, constructor.into_signature())? } else { FunctionSignature::from_arguments(args) }; let spec = FnSpec { tp: crate::method::FnType::FnStatic, name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, text_signature: None, asyncness: None, unsafety: None, warnings: vec![], output: syn::ReturnType::Default, }; #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut def = __NEW__.generate_type_slot(&variant_cls_type, &spec, "__default___new____", ctx)?; #[cfg(feature = "experimental-inspect")] def.add_introspection(method_introspection_code( &spec, &[], &variant_cls_type, false, ctx, )); Ok(def) } fn complex_enum_variant_field_getter( variant_cls_type: &syn::Type, field_name: &Ident, field_type: &syn::Type, field_attrs: &[syn::Attribute], field_span: Span, ctx: &Ctx, ) -> Result { let mut arg = parse_quote!(py: Python<'_>); let py = FnArg::parse(&mut arg)?; let signature = FunctionSignature::from_arguments(vec![py]); let self_type = crate::method::SelfType::TryFromBoundRef { span: field_span, non_null: true, }; let spec = FnSpec { tp: crate::method::FnType::Getter(self_type.clone()), name: field_name, python_name: field_name.unraw(), signature, text_signature: None, asyncness: None, unsafety: None, warnings: vec![], output: parse_quote!(-> #field_type), }; let property_type = PropertyType::Function { self_type: &self_type, spec: &spec, doc: get_doc(field_attrs, None), }; #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut getter = impl_py_getter_def(variant_cls_type, property_type, ctx)?; #[cfg(feature = "experimental-inspect")] getter.add_introspection(method_introspection_code( &spec, field_attrs, variant_cls_type, false, ctx, )); Ok(getter) } fn descriptors_to_items( cls: &Ident, rename_all: Option<&RenameAllAttribute>, frozen: Option, field_options: Vec<(&syn::Field, FieldPyO3Options)>, ctx: &Ctx, ) -> Result> { let ty = syn::parse_quote!(#cls); let mut items = Vec::new(); for (field_index, (field, options)) in field_options.into_iter().enumerate() { if let FieldPyO3Options { name: Some(name), get: None, set: None, } = options { return Err(syn::Error::new_spanned(name, USELESS_NAME)); } if options.get.is_some() { let renaming_rule = rename_all.map(|rename_all| rename_all.value.rule); #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut getter = impl_py_getter_def( &ty, PropertyType::Descriptor { field_index, field, python_name: options.name.as_ref(), renaming_rule, }, ctx, )?; #[cfg(feature = "experimental-inspect")] { // We generate introspection data let return_type = &field.ty; getter.add_introspection(function_introspection_code( &ctx.pyo3_path, None, &field_python_name(field, options.name.as_ref(), renaming_rule)?, &FunctionSignature::from_arguments(vec![]), Some("self"), parse_quote!(-> #return_type), vec![PyExpr::builtin("property")], false, false, utils::get_doc(&field.attrs, None).as_ref(), Some(&parse_quote!(#cls)), )); } items.push(getter); } if let Some(set) = options.set { ensure_spanned!(frozen.is_none(), set.span() => "cannot use `#[pyo3(set)]` on a `frozen` class"); let renaming_rule = rename_all.map(|rename_all| rename_all.value.rule); #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut setter = impl_py_setter_def( &ty, PropertyType::Descriptor { field_index, field, python_name: options.name.as_ref(), renaming_rule, }, ctx, )?; #[cfg(feature = "experimental-inspect")] { // We generate introspection data let name = field_python_name(field, options.name.as_ref(), renaming_rule)?; setter.add_introspection(function_introspection_code( &ctx.pyo3_path, None, &name, &FunctionSignature::from_arguments(vec![FnArg::Regular(RegularArg { name: Cow::Owned(format_ident!("value")), ty: &field.ty, from_py_with: None, default_value: None, option_wrapped_type: None, annotation: None, })]), Some("self"), syn::ReturnType::Default, vec![PyExpr::attribute( PyExpr::attribute( PyExpr::from_type( syn::TypePath { qself: None, path: cls.clone().into(), } .into(), None, ), name.clone(), ), "setter", )], false, false, get_doc(&field.attrs, None).as_ref(), Some(&parse_quote!(#cls)), )); } items.push(setter); }; } Ok(items) } fn impl_pytypeinfo(cls: &Ident, attr: &PyClassArgs, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; #[cfg(feature = "experimental-inspect")] let type_hint = { let type_hint = get_class_type_hint(cls, attr, ctx); quote! { const TYPE_HINT: #pyo3_path::inspect::PyStaticExpr = #type_hint; } }; #[cfg(not(feature = "experimental-inspect"))] let type_hint = quote! {}; #[cfg(not(feature = "experimental-inspect"))] let _ = attr; quote! { unsafe impl #pyo3_path::type_object::PyTypeInfo for #cls { const NAME: &str = ::NAME; const MODULE: ::std::option::Option<&str> = ::MODULE; #type_hint #[inline] fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject { use #pyo3_path::prelude::PyTypeMethods; <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object() .get_or_try_init(py) .unwrap_or_else(|e| #pyo3_path::impl_::pyclass::type_object_init_failed( py, e, ::NAME )) .as_type_ptr() } } } } fn pyclass_richcmp_arms( options: &PyClassPyO3Options, ctx: &Ctx, ) -> std::result::Result { let Ctx { pyo3_path, .. } = ctx; let eq_arms = options .eq .map(|eq| eq.span) .or(options.eq_int.map(|eq_int| eq_int.span)) .map(|span| { quote_spanned! { span => #pyo3_path::pyclass::CompareOp::Eq => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val == other, py) }, #pyo3_path::pyclass::CompareOp::Ne => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val != other, py) }, } }) .unwrap_or_default(); if let Some(ord) = options.ord { ensure_spanned!(options.eq.is_some(), ord.span() => "The `ord` option requires the `eq` option."); } let ord_arms = options .ord .map(|ord| { quote_spanned! { ord.span() => #pyo3_path::pyclass::CompareOp::Gt => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val > other, py) }, #pyo3_path::pyclass::CompareOp::Lt => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val < other, py) }, #pyo3_path::pyclass::CompareOp::Le => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val <= other, py) }, #pyo3_path::pyclass::CompareOp::Ge => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val >= other, py) }, } }) .unwrap_or_else(|| quote! { _ => ::std::result::Result::Ok(py.NotImplemented()) }); Ok(quote! { #eq_arms #ord_arms }) } fn pyclass_richcmp_simple_enum( options: &PyClassPyO3Options, cls: &syn::Type, repr_type: &syn::Ident, ctx: &Ctx, ) -> Result<(Option, Option)> { let Ctx { pyo3_path, .. } = ctx; if let Some(eq_int) = options.eq_int { ensure_spanned!(options.eq.is_some(), eq_int.span() => "The `eq_int` option requires the `eq` option."); } if options.eq.is_none() && options.eq_int.is_none() { return Ok((None, None)); } let arms = pyclass_richcmp_arms(options, ctx)?; let eq = options.eq.map(|eq| { quote_spanned! { eq.span() => let self_val = self; if let ::std::result::Result::Ok(other) = other.cast::() { let other = &*other.borrow(); return match op { #arms } } } }); let eq_int = options.eq_int.map(|eq_int| { quote_spanned! { eq_int.span() => let self_val = self.__pyo3__int__(); if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::extract::<#repr_type>(other).or_else(|_| { other.cast::().map(|o| o.borrow().__pyo3__int__()) }) { return match op { #arms } } } }); let mut richcmp_impl = parse_quote! { fn __pyo3__generated____richcmp__( &self, py: #pyo3_path::Python, other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>, op: #pyo3_path::pyclass::CompareOp ) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #eq #eq_int ::std::result::Result::Ok(py.NotImplemented()) } }; #[cfg(feature = "experimental-inspect")] let any = parse_quote!(#pyo3_path::Py<#pyo3_path::PyAny>); #[cfg(feature = "experimental-inspect")] let introspection = FunctionIntrospectionData { names: &["__eq__", "__ne__"], arguments: vec![FnArg::Regular(RegularArg { name: Cow::Owned(format_ident!("other")), ty: &any, from_py_with: None, default_value: None, option_wrapped_type: None, annotation: None, })], returns: parse_quote!(::std::primitive::bool), is_returning_not_implemented_on_extraction_error: true, }; let richcmp_slot = if options.eq.is_some() { generate_protocol_slot( cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", #[cfg(feature = "experimental-inspect")] introspection, ctx, )? } else { generate_default_protocol_slot( cls, &mut richcmp_impl, &__RICHCMP__, #[cfg(feature = "experimental-inspect")] introspection, ctx, )? }; Ok((Some(richcmp_impl), Some(richcmp_slot))) } fn pyclass_richcmp( options: &PyClassPyO3Options, cls: &syn::Type, ctx: &Ctx, ) -> Result<(Option, Option)> { let Ctx { pyo3_path, .. } = ctx; if let Some(eq_int) = options.eq_int { bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.") } let arms = pyclass_richcmp_arms(options, ctx)?; if options.eq.is_some() { let mut richcmp_impl = parse_quote! { fn __pyo3__generated____richcmp__( &self, py: #pyo3_path::Python, other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>, op: #pyo3_path::pyclass::CompareOp ) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { let self_val = self; if let ::std::result::Result::Ok(other) = other.cast::() { let other = &*other.borrow(); match op { #arms } } else { ::std::result::Result::Ok(py.NotImplemented()) } } }; let richcmp_slot = generate_protocol_slot( cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: if options.ord.is_some() { &["__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__"] } else { &["__eq__", "__ne__"] }, arguments: vec![FnArg::Regular(RegularArg { name: Cow::Owned(format_ident!("other")), ty: &parse_quote!(&#cls), from_py_with: None, default_value: None, option_wrapped_type: None, annotation: None, })], returns: parse_quote! { ::std::primitive::bool }, is_returning_not_implemented_on_extraction_error: true, }, ctx, )?; Ok((Some(richcmp_impl), Some(richcmp_slot))) } else { Ok((None, None)) } } fn pyclass_hash( options: &PyClassPyO3Options, cls: &syn::Type, ctx: &Ctx, ) -> Result<(Option, Option)> { if options.hash.is_some() { ensure_spanned!( options.frozen.is_some(), options.hash.span() => "The `hash` option requires the `frozen` option."; options.eq.is_some(), options.hash.span() => "The `hash` option requires the `eq` option."; ); } match options.hash { Some(opt) => { let mut hash_impl = parse_quote_spanned! { opt.span() => fn __pyo3__generated____hash__(&self) -> u64 { let mut s = ::std::collections::hash_map::DefaultHasher::new(); ::std::hash::Hash::hash(self, &mut s); ::std::hash::Hasher::finish(&s) } }; let hash_slot = generate_protocol_slot( cls, &mut hash_impl, &__HASH__, "__hash__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__hash__"], arguments: Vec::new(), returns: parse_quote! { ::std::primitive::u64 }, is_returning_not_implemented_on_extraction_error: false, }, ctx, )?; Ok((Some(hash_impl), Some(hash_slot))) } None => Ok((None, None)), } } fn pyclass_new_impl<'a>( options: &PyClassPyO3Options, ty: &syn::Type, fields: impl Iterator, ctx: &Ctx, ) -> Result<(Option, Option)> { if options .new .as_ref() .is_some_and(|o| matches!(o.value, NewImplTypeAttributeValue::FromFields)) { ensure_spanned!( options.extends.is_none(), options.new.span() => "The `new=\"from_fields\"` option cannot be used with `extends`."; ); } let mut tuple_struct: bool = false; match &options.new { Some(opt) => { let mut field_idents = vec![]; let mut field_types = vec![]; for (idx, field) in fields.enumerate() { tuple_struct = field.ident.is_none(); field_idents.push( field .ident .clone() .unwrap_or_else(|| format_ident!("_{}", idx)), ); field_types.push(&field.ty); } let mut new_impl = if tuple_struct { parse_quote_spanned! { opt.span() => #[new] fn __pyo3_generated____new__( #( #field_idents : #field_types ),* ) -> Self { Self ( #( #field_idents, )* ) } } } else { parse_quote_spanned! { opt.span() => #[new] fn __pyo3_generated____new__( #( #field_idents : #field_types ),* ) -> Self { Self { #( #field_idents, )* } } } }; let new_slot = generate_protocol_slot( ty, &mut new_impl, &__NEW__, "__new__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__new__"], arguments: field_idents .iter() .zip(field_types.iter()) .map(|(ident, ty)| { FnArg::Regular(RegularArg { name: Cow::Owned(ident.clone()), ty, from_py_with: None, default_value: None, option_wrapped_type: None, annotation: None, }) }) .collect(), returns: ty.clone(), is_returning_not_implemented_on_extraction_error: false, }, ctx, ) .unwrap(); Ok((Some(new_impl), Some(new_slot))) } None => Ok((None, None)), } } fn pyclass_class_getitem( options: &PyClassPyO3Options, cls: &syn::Type, ctx: &Ctx, ) -> Result<(Option, Option)> { let Ctx { pyo3_path, .. } = ctx; match options.generic { Some(_) => { let ident = format_ident!("__class_getitem__"); let mut class_getitem_impl: syn::ImplItemFn = { parse_quote! { #[classmethod] fn #ident<'py>( cls: &#pyo3_path::Bound<'py, #pyo3_path::types::PyType>, key: &#pyo3_path::Bound<'py, #pyo3_path::types::PyAny> ) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::types::PyGenericAlias>> { #pyo3_path::types::PyGenericAlias::new(cls.py(), cls.as_any(), key) } } }; let spec = FnSpec::parse( &mut class_getitem_impl.sig, &mut class_getitem_impl.attrs, Default::default(), )?; let class_getitem_method = crate::pymethod::impl_py_method_def( cls, &spec, spec.get_doc(&class_getitem_impl.attrs).as_ref(), ctx, )?; Ok((Some(class_getitem_impl), Some(class_getitem_method))) } None => Ok((None, None)), } } /// Implements most traits used by `#[pyclass]`. /// /// Specifically, it implements traits that only depend on class name, /// and attributes of `#[pyclass]`, and docstrings. /// Therefore it doesn't implement traits that depends on struct fields and enum variants. struct PyClassImplsBuilder<'a> { /// Identifier of the class Rust struct cls_ident: &'a Ident, /// Name of the class in Python cls_name: &'a Ident, attr: &'a PyClassArgs, methods_type: PyClassMethodsType, default_methods: Vec, default_slots: Vec, doc: Option, } impl<'a> PyClassImplsBuilder<'a> { fn new( cls_ident: &'a Ident, cls_name: &'a Ident, attr: &'a PyClassArgs, methods_type: PyClassMethodsType, default_methods: Vec, default_slots: Vec, ) -> Self { Self { cls_ident, cls_name, attr, methods_type, default_methods, default_slots, doc: None, } } fn doc(self, doc: PythonDoc) -> Self { Self { doc: Some(doc), ..self } } fn impl_pyclass(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls_ident; let cls_name = get_class_python_name(self.cls_name, self.attr).to_string(); let frozen = if self.attr.options.frozen.is_some() { quote! { #pyo3_path::pyclass::boolean_struct::True } } else { quote! { #pyo3_path::pyclass::boolean_struct::False } }; quote! { impl #pyo3_path::PyClass for #cls { const NAME: &str = #cls_name; type Frozen = #frozen; } } } fn impl_into_py(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls_ident; let attr = self.attr; // If #cls is not extended type, we allow Self->PyObject conversion if attr.options.extends.is_none() { let output_type = get_conversion_type_hint(ctx, &format_ident!("OUTPUT_TYPE"), cls); quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; type Output = #pyo3_path::Bound<'py, >::Target>; type Error = #pyo3_path::PyErr; #output_type fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< ::Output, ::Error, > { #pyo3_path::Bound::new(py, self) } } } } else { quote! {} } } fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls_ident; let doc = if let Some(doc) = &self.doc { doc.to_cstr_stream(ctx)? } else { c"".to_token_stream() }; let module = if let Some(ModuleAttribute { value, .. }) = &self.attr.options.module { quote! { ::core::option::Option::Some(#value) } } else { quote! { ::core::option::Option::None } }; let is_basetype = self.attr.options.subclass.is_some(); let base = match &self.attr.options.extends { Some(extends_attr) => extends_attr.value.clone(), None => parse_quote! { #pyo3_path::PyAny }, }; let is_subclass = self.attr.options.extends.is_some(); let is_mapping: bool = self.attr.options.mapping.is_some(); let is_sequence: bool = self.attr.options.sequence.is_some(); let is_immutable_type = self.attr.options.immutable_type.is_some(); ensure_spanned!( !(is_mapping && is_sequence), cls.span() => "a `#[pyclass]` cannot be both a `mapping` and a `sequence`" ); let dict_offset = if self.attr.options.dict.is_some() { quote! { fn dict_offset() -> ::std::option::Option<#pyo3_path::impl_::pyclass::PyObjectOffset> { ::std::option::Option::Some(#pyo3_path::impl_::pyclass::dict_offset::()) } } } else { TokenStream::new() }; let weaklist_offset = if self.attr.options.weakref.is_some() { quote! { fn weaklist_offset() -> ::std::option::Option<#pyo3_path::impl_::pyclass::PyObjectOffset> { ::std::option::Option::Some(#pyo3_path::impl_::pyclass::weaklist_offset::()) } } } else { TokenStream::new() }; let thread_checker = if self.attr.options.unsendable.is_some() { quote! { #pyo3_path::impl_::pyclass::ThreadCheckerImpl } } else { quote! { #pyo3_path::impl_::pyclass::NoopThreadChecker } }; let (pymethods_items, inventory, inventory_class) = match self.methods_type { PyClassMethodsType::Specialization => ( quote! {{ use #pyo3_path::impl_::pyclass::PyMethods as _; collector.py_methods() }}, None, None, ), PyClassMethodsType::Inventory => { // To allow multiple #[pymethods] block, we define inventory types. let inventory_class_name = syn::Ident::new( &format!("Pyo3MethodsInventoryFor{}", cls.unraw()), Span::call_site(), ); ( quote! { ::std::boxed::Box::new( ::std::iter::Iterator::map( #pyo3_path::inventory::iter::<::Inventory>(), #pyo3_path::impl_::pyclass::PyClassInventory::items ) ) }, Some(quote! { type Inventory = #inventory_class_name; }), Some(define_inventory_class(&inventory_class_name, ctx)), ) } }; let default_methods = self .default_methods .iter() .map(|meth| &meth.associated_method) .chain( self.default_slots .iter() .map(|meth| &meth.associated_method), ); let default_method_defs = self.default_methods.iter().map(|meth| &meth.method_def); let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def); let freelist_slots = self.freelist_slots(ctx); let class_mutability = if self.attr.options.frozen.is_some() { quote! { ImmutableChild } } else { quote! { MutableChild } }; let attr = self.attr; let dict = if attr.options.dict.is_some() { quote! { #pyo3_path::impl_::pyclass::PyClassDictSlot } } else { quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot } }; // insert space for weak ref let weakref = if attr.options.weakref.is_some() { quote! { #pyo3_path::impl_::pyclass::PyClassWeakRefSlot } } else { quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot } }; let base_nativetype = if attr.options.extends.is_some() { quote! { ::BaseNativeType } } else { quote! { #pyo3_path::PyAny } }; let pyclass_base_type_impl = attr.options.subclass.map(|subclass| { quote_spanned! { subclass.span() => impl #pyo3_path::impl_::pyclass::PyClassBaseType for #cls { type LayoutAsBase = ::Layout; type BaseNativeType = ::BaseNativeType; type Initializer = #pyo3_path::pyclass_init::PyClassInitializer; type PyClassMutability = ::PyClassMutability; type Layout = ::Layout; } } }); let mut assertions = TokenStream::new(); // Classes must implement send / sync, unless `#[pyclass(unsendable)]` is used if attr.options.unsendable.is_none() { let pyo3_path = locate_tokens_at(pyo3_path.to_token_stream(), cls.span()); assertions.extend(quote_spanned! { cls.span() => #pyo3_path::impl_::pyclass::assert_pyclass_send_sync::<#cls>(); }); }; if let Some(kw) = &attr.options.dict { let pyo3_path = locate_tokens_at(pyo3_path.to_token_stream(), kw.span()); assertions.extend(quote_spanned! { kw.span() => const ASSERT_DICT_SUPPORTED: () = #pyo3_path::impl_::pyclass::assert_dict_supported(); }); } if let Some(kw) = &attr.options.weakref { let pyo3_path = locate_tokens_at(pyo3_path.to_token_stream(), kw.span()); assertions.extend(quote_spanned! { kw.span() => { const ASSERT_WEAKREF_SUPPORTED: () = #pyo3_path::impl_::pyclass::assert_weakref_supported(); }; }); } if let Some(kw) = &attr.options.immutable_type { let pyo3_path = locate_tokens_at(pyo3_path.to_token_stream(), kw.span()); assertions.extend(quote_spanned! { kw.span() => { const ASSERT_IMMUTABLE_SUPPORTED: () = #pyo3_path::impl_::pyclass::assert_immutable_type_supported(); }; }); } let deprecation = if self.attr.options.skip_from_py_object.is_none() && self.attr.options.from_py_object.is_none() { quote! { const _: () = { #[allow(unused_import)] use #pyo3_path::impl_::pyclass::Probe as _; #pyo3_path::impl_::deprecated::HasAutomaticFromPyObject::<{ #pyo3_path::impl_::pyclass::IsClone::<#cls>::VALUE }>::MSG }; } } else { TokenStream::new() }; let extract_pyclass_with_clone = if let Some(from_py_object) = self.attr.options.from_py_object { let input_type = get_conversion_type_hint(ctx, &format_ident!("INPUT_TYPE"), cls); quote_spanned! { from_py_object.span() => impl<'a, 'py> #pyo3_path::FromPyObject<'a, 'py> for #cls where Self: ::std::clone::Clone, { type Error = #pyo3_path::pyclass::PyClassGuardError<'a, 'py>; #input_type fn extract(obj: #pyo3_path::Borrowed<'a, 'py, #pyo3_path::PyAny>) -> ::std::result::Result>::Error> { ::std::result::Result::Ok(::std::clone::Clone::clone(&*obj.extract::<#pyo3_path::PyClassGuard<'_, #cls>>()?)) } } } } else if self.attr.options.skip_from_py_object.is_none() { quote!( impl #pyo3_path::impl_::pyclass::ExtractPyClassWithClone for #cls {} ) } else { TokenStream::new() }; Ok(quote! { #deprecation #extract_pyclass_with_clone #[allow(dead_code)] const _: () ={ #assertions }; #pyclass_base_type_impl impl #pyo3_path::impl_::pyclass::PyClassImpl for #cls { const MODULE: ::std::option::Option<&str> = #module; const IS_BASETYPE: bool = #is_basetype; const IS_SUBCLASS: bool = #is_subclass; const IS_MAPPING: bool = #is_mapping; const IS_SEQUENCE: bool = #is_sequence; const IS_IMMUTABLE_TYPE: bool = #is_immutable_type; type Layout = ::Layout; type BaseType = #base; type ThreadChecker = #thread_checker; #inventory type PyClassMutability = <<#base as #pyo3_path::impl_::pyclass::PyClassBaseType>::PyClassMutability as #pyo3_path::impl_::pycell::PyClassMutability>::#class_mutability; type Dict = #dict; type WeakRef = #weakref; type BaseNativeType = #base_nativetype; fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter { let collector = #pyo3_path::impl_::pyclass::PyClassImplCollector::::new(); static INTRINSIC_ITEMS: #pyo3_path::impl_::pyclass::PyClassItems = #pyo3_path::impl_::pyclass::PyClassItems { methods: &[#(#default_method_defs),*], slots: &[#(#default_slot_defs),* #(#freelist_slots),*], }; #pyo3_path::impl_::pyclass::PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items) } const RAW_DOC: &'static ::std::ffi::CStr = #doc; const DOC: &'static ::std::ffi::CStr = { use #pyo3_path::impl_ as impl_; use impl_::pyclass::Probe as _; const DOC_PIECES: &'static [&'static [u8]] = impl_::pyclass::doc::PyClassDocGenerator::< #cls, { impl_::pyclass::HasNewTextSignature::<#cls>::VALUE } >::DOC_PIECES; const LEN: usize = impl_::concat::combined_len(DOC_PIECES); const DOC: &'static [u8] = &impl_::concat::combine_to_array::(DOC_PIECES); impl_::pyclass::doc::doc_bytes_as_cstr(DOC) }; #dict_offset #weaklist_offset fn lazy_type_object() -> &'static #pyo3_path::impl_::pyclass::LazyTypeObject { use #pyo3_path::impl_::pyclass::LazyTypeObject; static TYPE_OBJECT: LazyTypeObject<#cls> = LazyTypeObject::new(); &TYPE_OBJECT } } #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #(#default_methods)* } #inventory_class }) } fn impl_add_to_module(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls_ident; quote! { impl #cls { #[doc(hidden)] pub const _PYO3_DEF: #pyo3_path::impl_::pymodule::AddClassToModule = #pyo3_path::impl_::pymodule::AddClassToModule::new(); } } } fn impl_freelist(&self, ctx: &Ctx) -> TokenStream { let cls = self.cls_ident; let Ctx { pyo3_path, .. } = ctx; self.attr.options.freelist.as_ref().map_or(quote! {}, |freelist| { let freelist = &freelist.value; quote! { impl #pyo3_path::impl_::pyclass::PyClassWithFreeList for #cls { #[inline] fn get_free_list(py: #pyo3_path::Python<'_>) -> &'static ::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList> { static FREELIST: #pyo3_path::sync::PyOnceLock<::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList>> = #pyo3_path::sync::PyOnceLock::new(); &FREELIST.get_or_init(py, || ::std::sync::Mutex::new(#pyo3_path::impl_::freelist::PyObjectFreeList::with_capacity(#freelist))) } } } }) } fn freelist_slots(&self, ctx: &Ctx) -> Vec { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls_ident; if self.attr.options.freelist.is_some() { vec![ quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_alloc, pfunc: #pyo3_path::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _, } }, quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_free, pfunc: #pyo3_path::impl_::pyclass::free_with_freelist::<#cls> as *mut _, } }, ] } else { Vec::new() } } #[cfg(feature = "experimental-inspect")] fn impl_introspection(&self, ctx: &Ctx, parent: Option<&Ident>) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let name = get_class_python_name(self.cls_name, self.attr).to_string(); let ident = self.cls_ident; let static_introspection = class_introspection_code( pyo3_path, ident, &name, self.attr.options.extends.as_ref().map(|attr| { PyExpr::from_type( syn::TypePath { qself: None, path: attr.value.clone(), } .into(), None, ) }), self.attr.options.subclass.is_none(), parent.map(|p| parse_quote!(#p)).as_ref(), self.doc.as_ref(), ); let introspection_id = introspection_id_const(); quote! { #static_introspection impl #ident { #introspection_id } } } #[cfg(not(feature = "experimental-inspect"))] fn impl_introspection(&self, _ctx: &Ctx, _parent: Option<&Ident>) -> TokenStream { quote! {} } } fn define_inventory_class(inventory_class_name: &syn::Ident, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #[doc(hidden)] pub struct #inventory_class_name { items: #pyo3_path::impl_::pyclass::PyClassItems, } impl #inventory_class_name { pub const fn new(items: #pyo3_path::impl_::pyclass::PyClassItems) -> Self { Self { items } } } impl #pyo3_path::impl_::pyclass::PyClassInventory for #inventory_class_name { fn items(&self) -> &#pyo3_path::impl_::pyclass::PyClassItems { &self.items } } #pyo3_path::inventory::collect!(#inventory_class_name); } } fn generate_cfg_check(variants: &[PyClassEnumUnitVariant<'_>], cls: &syn::Ident) -> TokenStream { if variants.is_empty() { return quote! {}; } let mut conditions = Vec::new(); for variant in variants { let cfg_attrs = get_cfg_attributes(&variant.attrs); if cfg_attrs.is_empty() { // There's at least one variant of the enum without cfg attributes, // so the check is not necessary return quote! {}; } for attr in cfg_attrs { if let syn::Meta::List(meta) = &attr.meta { let cfg_tokens = &meta.tokens; conditions.push(quote! { not(#cfg_tokens) }); } } } quote_spanned! { cls.span() => #[cfg(all(#(#conditions),*))] ::core::compile_error!(concat!("#[pyclass] can't be used on enums without any variants - all variants of enum `", stringify!(#cls), "` have been configured out by cfg attributes")); } } fn get_conversion_type_hint( Ctx { pyo3_path, .. }: &Ctx, konst: &Ident, cls: &Ident, ) -> TokenStream { if cfg!(feature = "experimental-inspect") { quote!(const #konst: #pyo3_path::inspect::PyStaticExpr = <#cls as #pyo3_path::PyTypeInfo>::TYPE_HINT;) } else { TokenStream::new() } } const UNIQUE_GET: &str = "`get` may only be specified once"; const UNIQUE_SET: &str = "`set` may only be specified once"; const UNIQUE_NAME: &str = "`name` may only be specified once"; const DUPE_SET: &str = "useless `set` - the struct is already annotated with `set_all`"; const DUPE_GET: &str = "useless `get` - the struct is already annotated with `get_all`"; const UNIT_GET: &str = "`get_all` on an unit struct does nothing, because unit structs have no fields"; const UNIT_SET: &str = "`set_all` on an unit struct does nothing, because unit structs have no fields"; const USELESS_NAME: &str = "`name` is useless without `get` or `set`"; ================================================ FILE: pyo3-macros-backend/src/pyfunction/signature.rs ================================================ #[cfg(feature = "experimental-inspect")] use crate::py_expr::PyExpr; use crate::{ attributes::{kw, KeywordAttribute}, method::FnArg, utils::expr_to_python, }; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, Expr, Token, }; #[derive(Clone)] pub struct Signature { paren_token: syn::token::Paren, pub items: Punctuated, pub returns: Option<(Token![->], PyTypeAnnotation)>, } impl Parse for Signature { fn parse(input: ParseStream<'_>) -> syn::Result { let content; let paren_token = syn::parenthesized!(content in input); let items = content.parse_terminated(SignatureItem::parse, Token![,])?; let returns = if input.peek(Token![->]) { Some((input.parse()?, input.parse()?)) } else { None }; Ok(Signature { paren_token, items, returns, }) } } impl ToTokens for Signature { fn to_tokens(&self, tokens: &mut TokenStream) { self.paren_token .surround(tokens, |tokens| self.items.to_tokens(tokens)); if let Some((arrow, returns)) = &self.returns { arrow.to_tokens(tokens); returns.to_tokens(tokens); } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemArgument { pub ident: syn::Ident, pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>, pub eq_and_default: Option<(Token![=], syn::Expr)>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemPosargsSep { pub slash: Token![/], } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemVarargsSep { pub asterisk: Token![*], } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemVarargs { pub sep: SignatureItemVarargsSep, pub ident: syn::Ident, pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemKwargs { pub asterisks: (Token![*], Token![*]), pub ident: syn::Ident, pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum SignatureItem { Argument(Box), PosargsSep(SignatureItemPosargsSep), VarargsSep(SignatureItemVarargsSep), Varargs(SignatureItemVarargs), Kwargs(SignatureItemKwargs), } impl Parse for SignatureItem { fn parse(input: ParseStream<'_>) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(Token![*]) { if input.peek2(Token![*]) { input.parse().map(SignatureItem::Kwargs) } else { let sep = input.parse()?; if input.is_empty() || input.peek(Token![,]) { Ok(SignatureItem::VarargsSep(sep)) } else { Ok(SignatureItem::Varargs(SignatureItemVarargs { sep, ident: input.parse()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, })) } } } else if lookahead.peek(Token![/]) { input.parse().map(SignatureItem::PosargsSep) } else { input.parse().map(SignatureItem::Argument) } } } impl ToTokens for SignatureItem { fn to_tokens(&self, tokens: &mut TokenStream) { match self { SignatureItem::Argument(arg) => arg.to_tokens(tokens), SignatureItem::Varargs(varargs) => varargs.to_tokens(tokens), SignatureItem::VarargsSep(sep) => sep.to_tokens(tokens), SignatureItem::Kwargs(kwargs) => kwargs.to_tokens(tokens), SignatureItem::PosargsSep(sep) => sep.to_tokens(tokens), } } } impl Parse for SignatureItemArgument { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { ident: input.parse()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, eq_and_default: if input.peek(Token![=]) { Some((input.parse()?, input.parse()?)) } else { None }, }) } } impl ToTokens for SignatureItemArgument { fn to_tokens(&self, tokens: &mut TokenStream) { self.ident.to_tokens(tokens); if let Some((colon, annotation)) = &self.colon_and_annotation { colon.to_tokens(tokens); annotation.to_tokens(tokens); } if let Some((eq, default)) = &self.eq_and_default { eq.to_tokens(tokens); default.to_tokens(tokens); } } } impl Parse for SignatureItemVarargsSep { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { asterisk: input.parse()?, }) } } impl ToTokens for SignatureItemVarargsSep { fn to_tokens(&self, tokens: &mut TokenStream) { self.asterisk.to_tokens(tokens); } } impl Parse for SignatureItemVarargs { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { sep: input.parse()?, ident: input.parse()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, }) } } impl ToTokens for SignatureItemVarargs { fn to_tokens(&self, tokens: &mut TokenStream) { self.sep.to_tokens(tokens); self.ident.to_tokens(tokens); } } impl Parse for SignatureItemKwargs { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { asterisks: (input.parse()?, input.parse()?), ident: input.parse()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, }) } } impl ToTokens for SignatureItemKwargs { fn to_tokens(&self, tokens: &mut TokenStream) { self.asterisks.0.to_tokens(tokens); self.asterisks.1.to_tokens(tokens); self.ident.to_tokens(tokens); } } impl Parse for SignatureItemPosargsSep { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { slash: input.parse()?, }) } } impl ToTokens for SignatureItemPosargsSep { fn to_tokens(&self, tokens: &mut TokenStream) { self.slash.to_tokens(tokens); } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PyTypeAnnotation(syn::LitStr); impl Parse for PyTypeAnnotation { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self(input.parse()?)) } } impl ToTokens for PyTypeAnnotation { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens); } } impl PyTypeAnnotation { #[cfg(feature = "experimental-inspect")] pub fn as_type_hint(&self) -> PyExpr { PyExpr::str_constant(self.0.value()) } } pub type SignatureAttribute = KeywordAttribute; pub type ConstructorAttribute = KeywordAttribute; impl ConstructorAttribute { pub fn into_signature(self) -> SignatureAttribute { SignatureAttribute { kw: kw::signature(self.kw.span), value: self.value, } } } #[derive(Default, Clone)] pub struct PythonSignature { pub positional_parameters: Vec, pub positional_only_parameters: usize, /// Vector of expressions representing positional defaults pub default_positional_parameters: Vec, pub varargs: Option, // Tuples of keyword name and optional default value pub keyword_only_parameters: Vec<(String, Option)>, pub kwargs: Option, } impl PythonSignature { pub fn has_no_args(&self) -> bool { self.positional_parameters.is_empty() && self.keyword_only_parameters.is_empty() && self.varargs.is_none() && self.kwargs.is_none() } pub fn required_positional_parameters(&self) -> usize { self.positional_parameters .len() .checked_sub(self.default_positional_parameters.len()) .expect("should always have positional defaults <= positional parameters") } } #[derive(Clone)] pub struct FunctionSignature<'a> { pub arguments: Vec>, pub python_signature: PythonSignature, pub attribute: Option, } pub enum ParseState { /// Accepting positional parameters, which might be positional only Positional, /// Accepting positional parameters after '/' PositionalAfterPosargs, /// Accepting keyword-only parameters after '*' or '*args' Keywords, /// After `**kwargs` nothing is allowed Done, } impl ParseState { fn add_argument( &mut self, signature: &mut PythonSignature, name: String, default_value: Option, span: Span, ) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs => { signature.positional_parameters.push(name); if let Some(default_value) = default_value { signature.default_positional_parameters.push(default_value); // Now all subsequent positional parameters must also have defaults } else if !signature.default_positional_parameters.is_empty() { bail_spanned!(span => "cannot have required positional parameter after an optional parameter") } Ok(()) } ParseState::Keywords => { signature .keyword_only_parameters .push((name, default_value)); Ok(()) } ParseState::Done => { bail_spanned!(span => format!("no more arguments are allowed after `**{}`", signature.kwargs.as_deref().unwrap_or(""))) } } } fn add_varargs( &mut self, signature: &mut PythonSignature, varargs: &SignatureItemVarargs, ) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs => { signature.varargs = Some(varargs.ident.to_string()); *self = ParseState::Keywords; Ok(()) } ParseState::Keywords => { bail_spanned!(varargs.span() => format!("`*{}` not allowed after `*{}`", varargs.ident, signature.varargs.as_deref().unwrap_or(""))) } ParseState::Done => { bail_spanned!(varargs.span() => format!("`*{}` not allowed after `**{}`", varargs.ident, signature.kwargs.as_deref().unwrap_or(""))) } } } fn add_kwargs( &mut self, signature: &mut PythonSignature, kwargs: &SignatureItemKwargs, ) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs | ParseState::Keywords => { signature.kwargs = Some(kwargs.ident.to_string()); *self = ParseState::Done; Ok(()) } ParseState::Done => { bail_spanned!(kwargs.span() => format!("`**{}` not allowed after `**{}`", kwargs.ident, signature.kwargs.as_deref().unwrap_or(""))) } } } fn finish_pos_only_args( &mut self, signature: &mut PythonSignature, span: Span, ) -> syn::Result<()> { match self { ParseState::Positional => { signature.positional_only_parameters = signature.positional_parameters.len(); *self = ParseState::PositionalAfterPosargs; Ok(()) } ParseState::PositionalAfterPosargs => { bail_spanned!(span => "`/` not allowed after `/`") } ParseState::Keywords => { bail_spanned!(span => format!("`/` not allowed after `*{}`", signature.varargs.as_deref().unwrap_or(""))) } ParseState::Done => { bail_spanned!(span => format!("`/` not allowed after `**{}`", signature.kwargs.as_deref().unwrap_or(""))) } } } fn finish_pos_args(&mut self, signature: &PythonSignature, span: Span) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs => { *self = ParseState::Keywords; Ok(()) } ParseState::Keywords => { bail_spanned!(span => format!("`*` not allowed after `*{}`", signature.varargs.as_deref().unwrap_or(""))) } ParseState::Done => { bail_spanned!(span => format!("`*` not allowed after `**{}`", signature.kwargs.as_deref().unwrap_or(""))) } } } } impl<'a> FunctionSignature<'a> { pub fn from_arguments_and_attribute( mut arguments: Vec>, attribute: SignatureAttribute, ) -> syn::Result { let mut parse_state = ParseState::Positional; let mut python_signature = PythonSignature::default(); let mut args_iter = arguments.iter_mut(); let mut next_non_py_argument_checked = |name: &syn::Ident| { for fn_arg in args_iter.by_ref() { match fn_arg { FnArg::Py(..) => { // If the user incorrectly tried to include py: Python in the // signature, give a useful error as a hint. ensure_spanned!( name != fn_arg.name(), name.span() => "arguments of type `Python` must not be part of the signature" ); // Otherwise try next argument. continue; } FnArg::CancelHandle(..) => { // If the user incorrectly tried to include cancel: CoroutineCancel in the // signature, give a useful error as a hint. ensure_spanned!( name != fn_arg.name(), name.span() => "`cancel_handle` argument must not be part of the signature" ); // Otherwise try next argument. continue; } _ => { ensure_spanned!( name == fn_arg.name(), name.span() => format!( "expected argument from function definition `{}` but got argument `{}`", fn_arg.name().unraw(), name.unraw(), ) ); return Ok(fn_arg); } } } bail_spanned!( name.span() => "signature entry does not have a corresponding function argument" ) }; if let Some(returns) = &attribute.value.returns { ensure_spanned!( cfg!(feature = "experimental-inspect"), returns.1.span() => "Return type annotation in the signature is only supported with the `experimental-inspect` feature" ); } for item in &attribute.value.items { match item { SignatureItem::Argument(arg) => { let fn_arg = next_non_py_argument_checked(&arg.ident)?; parse_state.add_argument( &mut python_signature, arg.ident.unraw().to_string(), arg.eq_and_default .as_ref() .map(|(_, default)| default.clone()), arg.span(), )?; let FnArg::Regular(fn_arg) = fn_arg else { unreachable!( "`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \ parsed and transformed below. Because the have to come last and are only allowed \ once, this has to be a regular argument." ); }; if let Some((_, default)) = &arg.eq_and_default { fn_arg.default_value = Some(Box::new(default.clone())); } if let Some((_, annotation)) = &arg.colon_and_annotation { ensure_spanned!( cfg!(feature = "experimental-inspect"), annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature" ); #[cfg(feature = "experimental-inspect")] { fn_arg.annotation = Some(annotation.as_type_hint()); } } } SignatureItem::VarargsSep(sep) => { parse_state.finish_pos_args(&python_signature, sep.span())? } SignatureItem::Varargs(varargs) => { let fn_arg = next_non_py_argument_checked(&varargs.ident)?; fn_arg.to_varargs_mut()?; parse_state.add_varargs(&mut python_signature, varargs)?; if let Some((_, annotation)) = &varargs.colon_and_annotation { ensure_spanned!( cfg!(feature = "experimental-inspect"), annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature" ); #[cfg(feature = "experimental-inspect")] { let FnArg::VarArgs(fn_arg) = fn_arg else { unreachable!( "`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \ parsed and transformed below. Because the have to come last and are only allowed \ once, this has to be a regular argument." ); }; fn_arg.annotation = Some(annotation.as_type_hint()); } } } SignatureItem::Kwargs(kwargs) => { let fn_arg = next_non_py_argument_checked(&kwargs.ident)?; fn_arg.to_kwargs_mut()?; parse_state.add_kwargs(&mut python_signature, kwargs)?; if let Some((_, annotation)) = &kwargs.colon_and_annotation { ensure_spanned!( cfg!(feature = "experimental-inspect"), annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature" ); #[cfg(feature = "experimental-inspect")] { let FnArg::KwArgs(fn_arg) = fn_arg else { unreachable!( "`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \ parsed and transformed below. Because the have to come last and are only allowed \ once, this has to be a regular argument." ); }; fn_arg.annotation = Some(annotation.as_type_hint()); } } } SignatureItem::PosargsSep(sep) => { parse_state.finish_pos_only_args(&mut python_signature, sep.span())? } }; } // Ensure no non-py arguments remain if let Some(arg) = args_iter.find(|arg| !matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..))) { bail_spanned!( attribute.kw.span() => format!("missing signature entry for argument `{}`", arg.name()) ); } Ok(FunctionSignature { arguments, python_signature, attribute: Some(attribute), }) } /// Without `#[pyo3(signature)]` or `#[args]` - just take the Rust function arguments as positional. pub fn from_arguments(arguments: Vec>) -> Self { let mut python_signature = PythonSignature::default(); for arg in &arguments { // Python<'_> arguments don't show in Python signature if matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)) { continue; } python_signature .positional_parameters .push(arg.name().unraw().to_string()); } Self { arguments, python_signature, attribute: None, } } pub fn text_signature(&self, self_argument: Option<&str>) -> String { let mut output = String::new(); output.push('('); if let Some(arg) = self_argument { output.push('$'); output.push_str(arg); } let mut maybe_push_comma = { let mut first = self_argument.is_none(); move |output: &mut String| { if !first { output.push_str(", "); } else { first = false; } } }; let py_sig = &self.python_signature; let defaults = std::iter::repeat_n(None, py_sig.required_positional_parameters()) .chain(py_sig.default_positional_parameters.iter().map(Some)); for (i, (parameter, default)) in std::iter::zip(&py_sig.positional_parameters, defaults).enumerate() { maybe_push_comma(&mut output); output.push_str(parameter); if let Some(expr) = default { output.push('='); output.push_str(&expr_to_python(expr)); } if py_sig.positional_only_parameters > 0 && i + 1 == py_sig.positional_only_parameters { output.push_str(", /") } } if let Some(varargs) = &py_sig.varargs { maybe_push_comma(&mut output); output.push('*'); output.push_str(varargs); } else if !py_sig.keyword_only_parameters.is_empty() { maybe_push_comma(&mut output); output.push('*'); } for (parameter, default) in &py_sig.keyword_only_parameters { maybe_push_comma(&mut output); output.push_str(parameter); if let Some(expr) = default { output.push('='); output.push_str(&expr_to_python(expr)); } } if let Some(kwargs) = &py_sig.kwargs { maybe_push_comma(&mut output); output.push_str("**"); output.push_str(kwargs); } output.push(')'); output } } ================================================ FILE: pyo3-macros-backend/src/pyfunction.rs ================================================ use crate::attributes::KeywordAttribute; use crate::combine_errors::CombineErrors; #[cfg(feature = "experimental-inspect")] use crate::introspection::{function_introspection_code, introspection_id_const}; #[cfg(feature = "experimental-inspect")] use crate::utils::get_doc; use crate::utils::Ctx; use crate::{ attributes::{ self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute, FromPyWithAttribute, NameAttribute, TextSignatureAttribute, }, method::{self, CallingConvention, FnArg}, pymethod::check_generic, }; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::cmp::PartialEq; use std::ffi::CString; #[cfg(feature = "experimental-inspect")] use std::iter::empty; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::LitCStr; use syn::{ext::IdentExt, spanned::Spanned, LitStr, Path, Result, Token}; mod signature; pub use self::signature::{ConstructorAttribute, FunctionSignature, SignatureAttribute}; #[derive(Clone, Debug)] pub struct PyFunctionArgPyO3Attributes { pub from_py_with: Option, pub cancel_handle: Option, } enum PyFunctionArgPyO3Attribute { FromPyWith(FromPyWithAttribute), CancelHandle(attributes::kw::cancel_handle), } impl Parse for PyFunctionArgPyO3Attribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::cancel_handle) { input.parse().map(PyFunctionArgPyO3Attribute::CancelHandle) } else if lookahead.peek(attributes::kw::from_py_with) { input.parse().map(PyFunctionArgPyO3Attribute::FromPyWith) } else { Err(lookahead.error()) } } } impl PyFunctionArgPyO3Attributes { /// Parses #[pyo3(from_python_with = "func")] pub fn from_attrs(attrs: &mut Vec) -> syn::Result { let mut attributes = PyFunctionArgPyO3Attributes { from_py_with: None, cancel_handle: None, }; take_attributes(attrs, |attr| { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { for attr in pyo3_attrs { match attr { PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => { ensure_spanned!( attributes.from_py_with.is_none(), from_py_with.span() => "`from_py_with` may only be specified once per argument" ); attributes.from_py_with = Some(from_py_with); } PyFunctionArgPyO3Attribute::CancelHandle(cancel_handle) => { ensure_spanned!( attributes.cancel_handle.is_none(), cancel_handle.span() => "`cancel_handle` may only be specified once per argument" ); attributes.cancel_handle = Some(cancel_handle); } } ensure_spanned!( attributes.from_py_with.is_none() || attributes.cancel_handle.is_none(), attributes.cancel_handle.unwrap().span() => "`from_py_with` and `cancel_handle` cannot be specified together" ); } Ok(true) } else { Ok(false) } })?; Ok(attributes) } } type PyFunctionWarningMessageAttribute = KeywordAttribute; type PyFunctionWarningCategoryAttribute = KeywordAttribute; pub struct PyFunctionWarningAttribute { pub message: PyFunctionWarningMessageAttribute, pub category: Option, pub span: Span, } #[derive(PartialEq, Clone)] pub enum PyFunctionWarningCategory { Path(Path), UserWarning, DeprecationWarning, // TODO: unused for now, intended for pyo3(deprecated) special-case } #[derive(Clone)] pub struct PyFunctionWarning { pub message: LitStr, pub category: PyFunctionWarningCategory, pub span: Span, } impl From for PyFunctionWarning { fn from(value: PyFunctionWarningAttribute) -> Self { Self { message: value.message.value, category: value .category .map_or(PyFunctionWarningCategory::UserWarning, |cat| { PyFunctionWarningCategory::Path(cat.value) }), span: value.span, } } } pub trait WarningFactory { fn build_py_warning(&self, ctx: &Ctx) -> TokenStream; fn span(&self) -> Span; } impl WarningFactory for PyFunctionWarning { fn build_py_warning(&self, ctx: &Ctx) -> TokenStream { let message = &self.message.value(); let c_message = LitCStr::new( &CString::new(message.clone()).unwrap(), Spanned::span(&message), ); let pyo3_path = &ctx.pyo3_path; let category = match &self.category { PyFunctionWarningCategory::Path(path) => quote! {#path}, PyFunctionWarningCategory::UserWarning => { quote! {#pyo3_path::exceptions::PyUserWarning} } PyFunctionWarningCategory::DeprecationWarning => { quote! {#pyo3_path::exceptions::PyDeprecationWarning} } }; quote! { #pyo3_path::PyErr::warn(py, &<#category as #pyo3_path::PyTypeInfo>::type_object(py), #c_message, 1)?; } } fn span(&self) -> Span { self.span } } impl WarningFactory for Vec { fn build_py_warning(&self, ctx: &Ctx) -> TokenStream { let warnings = self.iter().map(|warning| warning.build_py_warning(ctx)); quote! { #(#warnings)* } } fn span(&self) -> Span { self.iter() .map(|val| val.span()) .reduce(|acc, span| acc.join(span).unwrap_or(acc)) .unwrap() } } impl Parse for PyFunctionWarningAttribute { fn parse(input: ParseStream<'_>) -> Result { let mut message: Option = None; let mut category: Option = None; let span = input.parse::()?.span(); let content; syn::parenthesized!(content in input); while !content.is_empty() { let lookahead = content.lookahead1(); if lookahead.peek(attributes::kw::message) { message = content .parse::() .map(Some)?; } else if lookahead.peek(attributes::kw::category) { category = content .parse::() .map(Some)?; } else { return Err(lookahead.error()); } if content.peek(Token![,]) { content.parse::()?; } } Ok(PyFunctionWarningAttribute { message: message.ok_or(syn::Error::new( content.span(), "missing `message` in `warn` attribute", ))?, category, span, }) } } impl ToTokens for PyFunctionWarningAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { let message_tokens = self.message.to_token_stream(); let category_tokens = self .category .as_ref() .map_or(quote! {}, |cat| cat.to_token_stream()); let token_stream = quote! { warn(#message_tokens, #category_tokens) }; tokens.extend(token_stream); } } #[derive(Default)] pub struct PyFunctionOptions { pub pass_module: Option, pub name: Option, pub signature: Option, pub text_signature: Option, pub krate: Option, pub warnings: Vec, } impl Parse for PyFunctionOptions { fn parse(input: ParseStream<'_>) -> Result { let mut options = PyFunctionOptions::default(); let attrs = Punctuated::::parse_terminated(input)?; options.add_attributes(attrs)?; Ok(options) } } pub enum PyFunctionOption { Name(NameAttribute), PassModule(attributes::kw::pass_module), Signature(SignatureAttribute), TextSignature(TextSignatureAttribute), Crate(CrateAttribute), Warning(PyFunctionWarningAttribute), } impl Parse for PyFunctionOption { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(PyFunctionOption::Name) } else if lookahead.peek(attributes::kw::pass_module) { input.parse().map(PyFunctionOption::PassModule) } else if lookahead.peek(attributes::kw::signature) { input.parse().map(PyFunctionOption::Signature) } else if lookahead.peek(attributes::kw::text_signature) { input.parse().map(PyFunctionOption::TextSignature) } else if lookahead.peek(syn::Token![crate]) { input.parse().map(PyFunctionOption::Crate) } else if lookahead.peek(attributes::kw::warn) { input.parse().map(PyFunctionOption::Warning) } else { Err(lookahead.error()) } } } impl PyFunctionOptions { pub fn from_attrs(attrs: &mut Vec) -> syn::Result { let mut options = PyFunctionOptions::default(); options.add_attributes(take_pyo3_options(attrs)?)?; Ok(options) } pub fn add_attributes( &mut self, attrs: impl IntoIterator, ) -> Result<()> { macro_rules! set_option { ($key:ident) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once") ); self.$key = Some($key); } }; } for attr in attrs { match attr { PyFunctionOption::Name(name) => set_option!(name), PyFunctionOption::PassModule(pass_module) => set_option!(pass_module), PyFunctionOption::Signature(signature) => set_option!(signature), PyFunctionOption::TextSignature(text_signature) => set_option!(text_signature), PyFunctionOption::Crate(krate) => set_option!(krate), PyFunctionOption::Warning(warning) => { self.warnings.push(warning.into()); } } } Ok(()) } } pub fn build_py_function( ast: &mut syn::ItemFn, mut options: PyFunctionOptions, ) -> syn::Result { options.add_attributes(take_pyo3_options(&mut ast.attrs)?)?; impl_wrap_pyfunction(ast, options) } /// Generates python wrapper over a function that allows adding it to a python module as a python /// function pub fn impl_wrap_pyfunction( func: &mut syn::ItemFn, options: PyFunctionOptions, ) -> syn::Result { check_generic(&func.sig)?; let PyFunctionOptions { pass_module, name, signature, text_signature, krate, warnings, } = options; let ctx = &Ctx::new(&krate, Some(&func.sig)); let Ctx { pyo3_path, .. } = &ctx; let python_name = name .as_ref() .map_or_else(|| &func.sig.ident, |name| &name.value.0) .unraw(); let tp = if pass_module.is_some() { let span = match func.sig.inputs.first() { Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( func.sig.paren_token.span.join() => "expected `&PyModule` or `Py` as first argument with `pass_module`" ), }; method::FnType::FnModule(span) } else { method::FnType::FnStatic }; let arguments = func .sig .inputs .iter_mut() .skip(if tp.skip_first_rust_argument_in_python_signature() { 1 } else { 0 }) .map(FnArg::parse) .try_combine_syn_errors()?; let signature = if let Some(signature) = signature { FunctionSignature::from_arguments_and_attribute(arguments, signature)? } else { FunctionSignature::from_arguments(arguments) }; let spec = method::FnSpec { tp, name: &func.sig.ident, python_name, signature, text_signature, asyncness: func.sig.asyncness, unsafety: func.sig.unsafety, warnings, output: func.sig.output.clone(), }; let vis = &func.vis; let name = &func.sig.ident; #[cfg(feature = "experimental-inspect")] let introspection = function_introspection_code( pyo3_path, Some(name), &name.to_string(), &spec.signature, None, func.sig.output.clone(), empty(), func.sig.asyncness.is_some(), false, get_doc(&func.attrs, None).as_ref(), None, ); #[cfg(not(feature = "experimental-inspect"))] let introspection = quote! {}; #[cfg(feature = "experimental-inspect")] let introspection_id = introspection_id_const(); #[cfg(not(feature = "experimental-inspect"))] let introspection_id = quote! {}; let wrapper_ident = format_ident!("__pyfunction_{}", spec.name); if spec.asyncness.is_some() { ensure_spanned!( cfg!(feature = "experimental-async"), spec.asyncness.span() => "async functions are only supported with the `experimental-async` feature" ); } let calling_convention = CallingConvention::from_signature(&spec.signature); let wrapper = spec.get_wrapper_function(&wrapper_ident, None, calling_convention, ctx)?; let methoddef = spec.get_methoddef( wrapper_ident, spec.get_doc(&func.attrs).as_ref(), calling_convention, ctx, )?; let wrapped_pyfunction = quote! { // Create a module with the same name as the `#[pyfunction]` - this way `use ` // will actually bring both the module and the function into scope. #[doc(hidden)] #vis mod #name { pub(crate) struct MakeDef; pub static _PYO3_DEF: #pyo3_path::impl_::pyfunction::PyFunctionDef = MakeDef::_PYO3_DEF; #introspection_id } // Generate the definition in the same scope as the original function - // this avoids complications around the fact that the generated module has a different scope // (and `super` doesn't always refer to the outer scope, e.g. if the `#[pyfunction] is // inside a function body) #[allow(unknown_lints, non_local_definitions)] impl #name::MakeDef { // We're using this to initialize a static, so it's fine. #[allow(clippy::declare_interior_mutable_const)] const _PYO3_DEF: #pyo3_path::impl_::pyfunction::PyFunctionDef = #pyo3_path::impl_::pyfunction::PyFunctionDef::from_method_def(#methoddef); } #[allow(non_snake_case)] #wrapper #introspection }; Ok(wrapped_pyfunction) } ================================================ FILE: pyo3-macros-backend/src/pyimpl.rs ================================================ use std::collections::HashSet; use crate::combine_errors::CombineErrors; #[cfg(feature = "experimental-inspect")] use crate::get_doc; #[cfg(feature = "experimental-inspect")] use crate::introspection::{attribute_introspection_code, function_introspection_code}; #[cfg(feature = "experimental-inspect")] use crate::method::{FnSpec, FnType}; #[cfg(feature = "experimental-inspect")] use crate::py_expr::PyExpr; use crate::utils::{has_attribute, has_attribute_with_namespace, Ctx, PyO3CratePath}; use crate::{ attributes::{take_pyo3_options, CrateAttribute}, konst::{ConstAttributes, ConstSpec}, pyfunction::PyFunctionOptions, pymethod::{ self, is_proto_method, GeneratedPyMethod, MethodAndMethodDef, MethodAndSlotDef, PyMethod, }, }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, ImplItemFn, Result, }; #[cfg(feature = "experimental-inspect")] use syn::{parse_quote, Ident, ReturnType}; /// The mechanism used to collect `#[pymethods]` into the type object #[derive(Copy, Clone)] pub enum PyClassMethodsType { Specialization, Inventory, } enum PyImplPyO3Option { Crate(CrateAttribute), } impl Parse for PyImplPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(syn::Token![crate]) { input.parse().map(PyImplPyO3Option::Crate) } else { Err(lookahead.error()) } } } #[derive(Default)] pub struct PyImplOptions { krate: Option, } impl PyImplOptions { pub fn from_attrs(attrs: &mut Vec) -> Result { let mut options: PyImplOptions = Default::default(); for option in take_pyo3_options(attrs)? { match option { PyImplPyO3Option::Crate(path) => options.set_crate(path)?, } } Ok(options) } fn set_crate(&mut self, path: CrateAttribute) -> Result<()> { ensure_spanned!( self.krate.is_none(), path.span() => "`crate` may only be specified once" ); self.krate = Some(path); Ok(()) } } pub fn build_py_methods( ast: &mut syn::ItemImpl, methods_type: PyClassMethodsType, ) -> syn::Result { if let Some((_, path, _)) = &ast.trait_ { bail_spanned!(path.span() => "#[pymethods] cannot be used on trait impl blocks"); } else if ast.generics != Default::default() { bail_spanned!( ast.generics.span() => "#[pymethods] cannot be used with lifetime parameters or generics" ); } else { let options = PyImplOptions::from_attrs(&mut ast.attrs)?; impl_methods(&ast.self_ty, &mut ast.items, methods_type, options) } } fn check_pyfunction(pyo3_path: &PyO3CratePath, meth: &mut ImplItemFn) -> syn::Result<()> { let mut error = None; meth.attrs.retain(|attr| { let attrs = [attr.clone()]; if has_attribute(&attrs, "pyfunction") || has_attribute_with_namespace(&attrs, Some(pyo3_path), &["pyfunction"]) || has_attribute_with_namespace(&attrs, Some(pyo3_path), &["prelude", "pyfunction"]) { error = Some(err_spanned!(meth.sig.span() => "functions inside #[pymethods] do not need to be annotated with #[pyfunction]")); false } else { true } }); error.map_or(Ok(()), Err) } pub fn impl_methods( ty: &syn::Type, impls: &mut [syn::ImplItem], methods_type: PyClassMethodsType, options: PyImplOptions, ) -> syn::Result { let mut extra_fragments = Vec::new(); let mut proto_impls = Vec::new(); let mut methods = Vec::new(); let mut associated_methods = Vec::new(); let mut implemented_proto_fragments = HashSet::new(); let _: Vec<()> = impls .iter_mut() .map(|iimpl| { match iimpl { syn::ImplItem::Fn(meth) => { let ctx = &Ctx::new(&options.krate, Some(&meth.sig)); let mut fun_options = PyFunctionOptions::from_attrs(&mut meth.attrs)?; fun_options.krate = fun_options.krate.or_else(|| options.krate.clone()); check_pyfunction(&ctx.pyo3_path, meth)?; let method = PyMethod::parse(&mut meth.sig, &mut meth.attrs, fun_options)?; #[cfg(feature = "experimental-inspect")] extra_fragments.push(method_introspection_code( &method.spec, &meth.attrs, ty, method.is_returning_not_implemented_on_extraction_error(), ctx, )); match pymethod::gen_py_method(ty, method, &meth.attrs, ctx)? { GeneratedPyMethod::Method(MethodAndMethodDef { associated_method, method_def, }) => { let attrs = get_cfg_attributes(&meth.attrs); associated_methods.push(quote!(#(#attrs)* #associated_method)); methods.push(quote!(#(#attrs)* #method_def)); } GeneratedPyMethod::SlotTraitImpl(method_name, token_stream) => { implemented_proto_fragments.insert(method_name); let attrs = get_cfg_attributes(&meth.attrs); extra_fragments.push(quote!(#(#attrs)* #token_stream)); } GeneratedPyMethod::Proto(MethodAndSlotDef { associated_method, slot_def, }) => { let attrs = get_cfg_attributes(&meth.attrs); proto_impls.push(quote!(#(#attrs)* #slot_def)); associated_methods.push(quote!(#(#attrs)* #associated_method)); } } } syn::ImplItem::Const(konst) => { let ctx = &Ctx::new(&options.krate, None); #[cfg(feature = "experimental-inspect")] let doc = get_doc(&konst.attrs, None); let attributes = ConstAttributes::from_attrs(&mut konst.attrs)?; if attributes.is_class_attr { let spec = ConstSpec { rust_ident: konst.ident.clone(), attributes, #[cfg(feature = "experimental-inspect")] expr: Some(konst.expr.clone()), #[cfg(feature = "experimental-inspect")] ty: konst.ty.clone(), #[cfg(feature = "experimental-inspect")] doc, }; let attrs = get_cfg_attributes(&konst.attrs); let MethodAndMethodDef { associated_method, method_def, } = gen_py_const(ty, &spec, ctx); methods.push(quote!(#(#attrs)* #method_def)); associated_methods.push(quote!(#(#attrs)* #associated_method)); if is_proto_method(&spec.python_name().to_string()) { // If this is a known protocol method e.g. __contains__, then allow this // symbol even though it's not an uppercase constant. konst .attrs .push(syn::parse_quote!(#[allow(non_upper_case_globals)])); } } } syn::ImplItem::Macro(m) => bail_spanned!( m.span() => "macros cannot be used as items in `#[pymethods]` impl blocks\n\ = note: this was previously accepted and ignored" ), _ => {} } Ok(()) }) .try_combine_syn_errors()?; let ctx = &Ctx::new(&options.krate, None); add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments, ctx); let items = match methods_type { PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls, ctx), PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls, ctx), }; Ok(quote! { #(#extra_fragments)* #items #[doc(hidden)] #[allow(non_snake_case)] impl #ty { #(#associated_methods)* } }) } pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec, ctx: &Ctx) -> MethodAndMethodDef { let member = &spec.rust_ident; let wrapper_ident = format_ident!("__pymethod_{}__", member); let python_name = spec.null_terminated_python_name(); let Ctx { pyo3_path, .. } = ctx; let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #pyo3_path::IntoPyObjectExt::into_py_any(#cls::#member, py) } }; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({ #pyo3_path::impl_::pymethods::PyClassAttributeDef::new( #python_name, #cls::#wrapper_ident ) }) }; #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut def = MethodAndMethodDef { associated_method, method_def, }; #[cfg(feature = "experimental-inspect")] def.add_introspection(attribute_introspection_code( &ctx.pyo3_path, Some(cls), spec.python_name().to_string(), spec.expr .as_ref() .map_or_else(PyExpr::ellipsis, PyExpr::constant_from_expression), spec.ty.clone(), spec.doc.as_ref(), true, )); def } fn impl_py_methods( ty: &syn::Type, methods: Vec, proto_impls: Vec, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #[allow(unknown_lints, non_local_definitions)] impl #pyo3_path::impl_::pyclass::PyMethods<#ty> for #pyo3_path::impl_::pyclass::PyClassImplCollector<#ty> { fn py_methods(self) -> &'static #pyo3_path::impl_::pyclass::PyClassItems { static ITEMS: #pyo3_path::impl_::pyclass::PyClassItems = #pyo3_path::impl_::pyclass::PyClassItems { methods: &[#(#methods),*], slots: &[#(#proto_impls),*] }; &ITEMS } } } } fn add_shared_proto_slots( ty: &syn::Type, proto_impls: &mut Vec, mut implemented_proto_fragments: HashSet, ctx: &Ctx, ) { let Ctx { pyo3_path, .. } = ctx; macro_rules! try_add_shared_slot { ($slot:ident, $($fragments:literal),*) => {{ let mut implemented = false; $(implemented |= implemented_proto_fragments.remove($fragments));*; if implemented { proto_impls.push(quote! { #pyo3_path::impl_::pyclass::$slot!(#ty) }) } }}; } try_add_shared_slot!( generate_pyclass_getattro_slot, "__getattribute__", "__getattr__" ); try_add_shared_slot!(generate_pyclass_setattr_slot, "__setattr__", "__delattr__"); try_add_shared_slot!(generate_pyclass_setdescr_slot, "__set__", "__delete__"); try_add_shared_slot!(generate_pyclass_setitem_slot, "__setitem__", "__delitem__"); try_add_shared_slot!(generate_pyclass_add_slot, "__add__", "__radd__"); try_add_shared_slot!(generate_pyclass_sub_slot, "__sub__", "__rsub__"); try_add_shared_slot!(generate_pyclass_mul_slot, "__mul__", "__rmul__"); try_add_shared_slot!(generate_pyclass_mod_slot, "__mod__", "__rmod__"); try_add_shared_slot!(generate_pyclass_divmod_slot, "__divmod__", "__rdivmod__"); try_add_shared_slot!(generate_pyclass_lshift_slot, "__lshift__", "__rlshift__"); try_add_shared_slot!(generate_pyclass_rshift_slot, "__rshift__", "__rrshift__"); try_add_shared_slot!(generate_pyclass_and_slot, "__and__", "__rand__"); try_add_shared_slot!(generate_pyclass_or_slot, "__or__", "__ror__"); try_add_shared_slot!(generate_pyclass_xor_slot, "__xor__", "__rxor__"); try_add_shared_slot!(generate_pyclass_matmul_slot, "__matmul__", "__rmatmul__"); try_add_shared_slot!(generate_pyclass_truediv_slot, "__truediv__", "__rtruediv__"); try_add_shared_slot!( generate_pyclass_floordiv_slot, "__floordiv__", "__rfloordiv__" ); try_add_shared_slot!(generate_pyclass_pow_slot, "__pow__", "__rpow__"); try_add_shared_slot!( generate_pyclass_richcompare_slot, "__lt__", "__le__", "__eq__", "__ne__", "__gt__", "__ge__" ); // if this assertion trips, a slot fragment has been implemented which has not been added in the // list above assert!(implemented_proto_fragments.is_empty()); } fn submit_methods_inventory( ty: &syn::Type, methods: Vec, proto_impls: Vec, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::inventory::submit! { type Inventory = <#ty as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory; Inventory::new(#pyo3_path::impl_::pyclass::PyClassItems { methods: &[#(#methods),*], slots: &[#(#proto_impls),*] }) } } } pub(crate) fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<&syn::Attribute> { attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) .collect() } #[cfg(feature = "experimental-inspect")] pub fn method_introspection_code( spec: &FnSpec<'_>, attrs: &[syn::Attribute], parent: &syn::Type, is_returning_not_implemented_on_extraction_error: bool, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let name = spec.python_name.to_string(); // __richcmp__ special case if name == "__richcmp__" { // We expend into each individual method return ["__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__"] .into_iter() .map(|method_name| { let mut spec = (*spec).clone(); spec.python_name = Ident::new(method_name, spec.python_name.span()); // We remove the CompareOp arg, this is safe because the signature is always the same // First the other value to compare with then the CompareOp // We cant to keep the first argument type, hence this hack spec.signature.arguments.pop(); spec.signature.python_signature.positional_parameters.pop(); method_introspection_code( &spec, attrs, parent, is_returning_not_implemented_on_extraction_error, ctx, ) }) .collect(); } // We map or ignore some magic methods // TODO: this might create a naming conflict let name = match name.as_str() { "__concat__" => "__add__".into(), "__repeat__" => "__mul__".into(), "__inplace_concat__" => "__iadd__".into(), "__inplace_repeat__" => "__imul__".into(), "__getbuffer__" | "__releasebuffer__" | "__traverse__" | "__clear__" => return quote! {}, _ => name, }; // We introduce self/cls argument and setup decorators let mut first_argument = None; let mut decorators = Vec::new(); match &spec.tp { FnType::Getter(_) => { first_argument = Some("self"); decorators.push(PyExpr::builtin("property")); } FnType::Setter(_) => { first_argument = Some("self"); decorators.push(PyExpr::attribute( PyExpr::attribute(PyExpr::from_type(parent.clone(), None), name.clone()), "setter", )); } FnType::Deleter(_) => { first_argument = Some("self"); decorators.push(PyExpr::attribute( PyExpr::attribute(PyExpr::from_type(parent.clone(), None), name.clone()), "deleter", )); } FnType::Fn(_) => { first_argument = Some("self"); } FnType::FnClass(_) => { first_argument = Some("cls"); if spec.python_name != "__new__" { // special case __new__ - does not get the decorator decorators.push(PyExpr::builtin("classmethod")); } } FnType::FnStatic => { if spec.python_name != "__new__" { decorators.push(PyExpr::builtin("staticmethod")); } else { // special case __new__ - does not get the decorator and gets first argument first_argument = Some("cls"); } } FnType::FnModule(_) => (), // TODO: not sure this can happen FnType::ClassAttribute => { // We return an attribute because there is no decorator for this case return attribute_introspection_code( pyo3_path, Some(parent), name, PyExpr::ellipsis(), if let ReturnType::Type(_, t) = &spec.output { (**t).clone() } else { parse_quote!(#pyo3_path::Py<#pyo3_path::types::PyNone>) }, get_doc(attrs, None).as_ref(), true, ); } } let return_type = if spec.python_name == "__new__" { // Hack to return Self while implementing IntoPyObject parse_quote!(-> #pyo3_path::PyRef) } else { spec.output.clone() }; function_introspection_code( pyo3_path, None, &name, &spec.signature, first_argument, return_type, decorators, spec.asyncness.is_some(), is_returning_not_implemented_on_extraction_error, get_doc(attrs, None).as_ref(), Some(parent), ) } ================================================ FILE: pyo3-macros-backend/src/pymethod.rs ================================================ use std::borrow::Cow; use std::ffi::CString; use crate::attributes::{FromPyWithAttribute, NameAttribute, RenamingRule}; #[cfg(feature = "experimental-inspect")] use crate::introspection::unique_element_id; use crate::method::{CallingConvention, ExtractErrorMode, PyArg}; use crate::params::{impl_arg_params, impl_regular_arg_param, Holders}; use crate::pyfunction::WarningFactory; use crate::utils::PythonDoc; use crate::utils::{Ctx, StaticIdent}; use crate::{ method::{FnArg, FnSpec, FnType, SelfType}, pyfunction::PyFunctionOptions, }; use crate::{quotes, utils}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ext::IdentExt, spanned::Spanned, Field, Ident, Result}; use syn::{parse_quote, LitCStr}; /// Generated code for a single pymethod item. pub struct MethodAndMethodDef { /// The implementation of the Python wrapper for the pymethod pub associated_method: TokenStream, /// The method def which will be used to register this pymethod pub method_def: TokenStream, } #[cfg(feature = "experimental-inspect")] impl MethodAndMethodDef { pub fn add_introspection(&mut self, data: TokenStream) { let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here self.associated_method.extend(quote! { const #const_name: () = { #data }; }); } } /// Generated code for a single pymethod item which is registered by a slot. pub struct MethodAndSlotDef { /// The implementation of the Python wrapper for the pymethod pub associated_method: TokenStream, /// The slot def which will be used to register this pymethod pub slot_def: TokenStream, } #[cfg(feature = "experimental-inspect")] impl MethodAndSlotDef { pub fn add_introspection(&mut self, data: TokenStream) { let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here self.associated_method.extend(quote! { const #const_name: () = { #data }; }); } } pub enum GeneratedPyMethod { Method(MethodAndMethodDef), Proto(MethodAndSlotDef), SlotTraitImpl(String, TokenStream), } pub struct PyMethod<'a> { kind: PyMethodKind, method_name: String, pub spec: FnSpec<'a>, } enum PyMethodKind { Fn, Proto(PyMethodProtoKind), } impl PyMethodKind { fn from_name(name: &str) -> Self { match name { // Protocol implemented through slots "__new__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEW__)), "__init__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INIT__)), "__str__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__STR__)), "__repr__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPR__)), "__hash__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__HASH__)), "__richcmp__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RICHCMP__)), "__get__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GET__)), "__iter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITER__)), "__next__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEXT__)), "__await__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AWAIT__)), "__aiter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AITER__)), "__anext__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ANEXT__)), "__len__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__LEN__)), "__contains__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONTAINS__)), "__concat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONCAT__)), "__repeat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPEAT__)), "__inplace_concat__" => { PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_CONCAT__)) } "__inplace_repeat__" => { PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_REPEAT__)) } "__getitem__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETITEM__)), "__pos__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__POS__)), "__neg__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEG__)), "__abs__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ABS__)), "__invert__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INVERT__)), "__index__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INDEX__)), "__int__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INT__)), "__float__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__FLOAT__)), "__bool__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__BOOL__)), "__iadd__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IADD__)), "__isub__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ISUB__)), "__imul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMUL__)), "__imatmul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMATMUL__)), "__itruediv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITRUEDIV__)), "__ifloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IFLOORDIV__)), "__imod__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMOD__)), "__ipow__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IPOW__)), "__ilshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ILSHIFT__)), "__irshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IRSHIFT__)), "__iand__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IAND__)), "__ixor__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IXOR__)), "__ior__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IOR__)), "__getbuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETBUFFER__)), "__releasebuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RELEASEBUFFER__)), // Protocols implemented through traits "__getattribute__" => { PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTRIBUTE__)) } "__getattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTR__)), "__setattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETATTR__)), "__delattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELATTR__)), "__set__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SET__)), "__delete__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELETE__)), "__setitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETITEM__)), "__delitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELITEM__)), "__add__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ADD__)), "__radd__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RADD__)), "__sub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SUB__)), "__rsub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSUB__)), "__mul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MUL__)), "__rmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMUL__)), "__matmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MATMUL__)), "__rmatmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMATMUL__)), "__floordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__FLOORDIV__)), "__rfloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RFLOORDIV__)), "__truediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__TRUEDIV__)), "__rtruediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RTRUEDIV__)), "__divmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DIVMOD__)), "__rdivmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RDIVMOD__)), "__mod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MOD__)), "__rmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMOD__)), "__lshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LSHIFT__)), "__rlshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RLSHIFT__)), "__rshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSHIFT__)), "__rrshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RRSHIFT__)), "__and__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__AND__)), "__rand__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RAND__)), "__xor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__XOR__)), "__rxor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RXOR__)), "__or__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__OR__)), "__ror__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ROR__)), "__pow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__POW__)), "__rpow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RPOW__)), "__lt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LT__)), "__le__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LE__)), "__eq__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__EQ__)), "__ne__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__NE__)), "__gt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GT__)), "__ge__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GE__)), // Some tricky protocols which don't fit the pattern of the rest "__call__" => PyMethodKind::Proto(PyMethodProtoKind::Call), "__traverse__" => PyMethodKind::Proto(PyMethodProtoKind::Traverse), "__clear__" => PyMethodKind::Proto(PyMethodProtoKind::Clear), // Not a proto _ => PyMethodKind::Fn, } } } enum PyMethodProtoKind { Slot(&'static SlotDef), Call, Traverse, Clear, SlotFragment(&'static SlotFragmentDef), } impl<'a> PyMethod<'a> { pub fn parse( sig: &'a mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result { check_generic(sig)?; ensure_function_options_valid(&options)?; let spec = FnSpec::parse(sig, meth_attrs, options)?; let method_name = spec.python_name.to_string(); let kind = PyMethodKind::from_name(&method_name); Ok(Self { kind, method_name, spec, }) } #[cfg(feature = "experimental-inspect")] pub fn is_returning_not_implemented_on_extraction_error(&self) -> bool { match &self.kind { PyMethodKind::Fn => false, PyMethodKind::Proto(proto) => match proto { PyMethodProtoKind::Slot(slot) => { matches!(slot.extract_error_mode, ExtractErrorMode::NotImplemented) } PyMethodProtoKind::SlotFragment(slot) => { matches!(slot.extract_error_mode, ExtractErrorMode::NotImplemented) } PyMethodProtoKind::Call | PyMethodProtoKind::Traverse | PyMethodProtoKind::Clear => false, }, } } } pub fn is_proto_method(name: &str) -> bool { match PyMethodKind::from_name(name) { PyMethodKind::Fn => false, PyMethodKind::Proto(_) => true, } } pub fn gen_py_method( cls: &syn::Type, method: PyMethod<'_>, meth_attrs: &[syn::Attribute], ctx: &Ctx, ) -> Result { let spec = &method.spec; if spec.asyncness.is_some() { ensure_spanned!( cfg!(feature = "experimental-async"), spec.asyncness.span() => "async functions are only supported with the `experimental-async` feature" ); } Ok(match (method.kind, &spec.tp) { // Class attributes go before protos so that class attributes can be used to set proto // method to None. (_, FnType::ClassAttribute) => { GeneratedPyMethod::Method(impl_py_class_attribute(cls, spec, ctx)?) } (PyMethodKind::Proto(proto_kind), _) => { ensure_no_forbidden_protocol_attributes(&proto_kind, spec, &method.method_name)?; match proto_kind { PyMethodProtoKind::Slot(slot_def) => { let slot = slot_def.generate_type_slot(cls, spec, &method.method_name, ctx)?; GeneratedPyMethod::Proto(slot) } PyMethodProtoKind::Call => { GeneratedPyMethod::Proto(impl_call_slot(cls, spec, ctx)?) } PyMethodProtoKind::Traverse => { GeneratedPyMethod::Proto(impl_traverse_slot(cls, spec, ctx)?) } PyMethodProtoKind::Clear => { GeneratedPyMethod::Proto(impl_clear_slot(cls, spec, ctx)?) } PyMethodProtoKind::SlotFragment(slot_fragment_def) => { let proto = slot_fragment_def.generate_pyproto_fragment(cls, spec, ctx)?; GeneratedPyMethod::SlotTraitImpl(method.method_name, proto) } } } // ordinary functions (with some specialties) (_, FnType::Fn(_) | FnType::FnClass(_) | FnType::FnStatic) => GeneratedPyMethod::Method( impl_py_method_def(cls, spec, spec.get_doc(meth_attrs).as_ref(), ctx)?, ), (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs), }, ctx, )?), (_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs), }, ctx, )?), (_, FnType::Deleter(self_type)) => GeneratedPyMethod::Method(impl_py_deleter_def( cls, self_type, spec, spec.get_doc(meth_attrs), ctx, )?), (_, FnType::FnModule(_)) => { unreachable!("methods cannot be FnModule") } }) } pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> { let err_msg = |typ| format!("Python functions cannot have generic {typ} parameters"); for param in &sig.generics.params { match param { syn::GenericParam::Lifetime(_) => {} syn::GenericParam::Type(_) => bail_spanned!(param.span() => err_msg("type")), syn::GenericParam::Const(_) => bail_spanned!(param.span() => err_msg("const")), } } Ok(()) } fn ensure_function_options_valid(options: &PyFunctionOptions) -> syn::Result<()> { if let Some(pass_module) = &options.pass_module { bail_spanned!(pass_module.span() => "`pass_module` cannot be used on Python methods"); } Ok(()) } fn ensure_no_forbidden_protocol_attributes( proto_kind: &PyMethodProtoKind, spec: &FnSpec<'_>, method_name: &str, ) -> syn::Result<()> { if let Some(signature) = &spec.signature.attribute { // __new__, __init__ and __call__ are allowed to have a signature, but nothing else is. if !matches!( proto_kind, PyMethodProtoKind::Slot(SlotDef { calling_convention: SlotCallingConvention::TpNew | SlotCallingConvention::TpInit, .. }) ) && !matches!(proto_kind, PyMethodProtoKind::Call) { bail_spanned!(signature.kw.span() => format!("`signature` cannot be used with magic method `{}`", method_name)); } } if let Some(text_signature) = &spec.text_signature { // __new__ is also allowed a text_signature (no other proto method is) if !matches!( proto_kind, PyMethodProtoKind::Slot(SlotDef { calling_convention: SlotCallingConvention::TpNew, .. }) ) { bail_spanned!(text_signature.kw.span() => format!("`text_signature` cannot be used with magic method `{}`", method_name)); } } Ok(()) } pub fn impl_py_method_def( cls: &syn::Type, spec: &FnSpec<'_>, doc: Option<&PythonDoc>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name); let calling_convention = CallingConvention::from_signature(&spec.signature); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), calling_convention, ctx)?; let methoddef = spec.get_methoddef( quote! { #cls::#wrapper_ident }, doc, calling_convention, ctx, )?; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::Method(#methoddef) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> Result { let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = syn::Ident::new("__pymethod___call____", Span::call_site()); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), CallingConvention::Varargs, ctx)?; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_call, pfunc: #pyo3_path::impl_::trampoline::get_trampoline_function!(ternaryfunc, #cls::#wrapper_ident) as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_traverse_slot( cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) { return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \ Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \ should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \ prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic.")); } // check that the receiver does not try to smuggle an (implicit) `Python` token into here if let FnType::Fn(SelfType::TryFromBoundRef { span, .. }) | FnType::Fn(SelfType::Receiver { mutable: true, span, .. }) = spec.tp { bail_spanned! { span => "__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \ `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \ should do nothing but calls to `visit.call`. Most importantly, safe access to the Python interpreter is \ prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic." } } ensure_spanned!( spec.warnings.is_empty(), spec.warnings.span() => "__traverse__ cannot be used with #[pyo3(warn)]" ); let rust_fn_ident = spec.name; let associated_method = quote! { pub unsafe extern "C" fn __pymethod_traverse__( slf: *mut #pyo3_path::ffi::PyObject, visit: #pyo3_path::ffi::visitproc, arg: *mut ::std::ffi::c_void, ) -> ::std::ffi::c_int { #pyo3_path::impl_::pymethods::_call_traverse::<#cls>(slf, #cls::#rust_fn_ident, visit, arg, #cls::__pymethod_traverse__) } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_traverse, pfunc: #cls::__pymethod_traverse__ as #pyo3_path::ffi::traverseproc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_clear_slot(cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let self_type = match &spec.tp { FnType::Fn(self_type) => self_type, _ => bail_spanned!(spec.name.span() => "expected instance method for `__clear__` function"), }; let mut holders = Holders::new(); let slf = self_type.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx); if let [arg, ..] = args { bail_spanned!(arg.ty().span() => "`__clear__` function expected to have no arguments"); } let name = &spec.name; let holders = holders.init_holders(ctx); let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py)) } else { quote!(#cls::#name(#slf)) }; let associated_method = quote! { pub unsafe extern "C" fn __pymethod___clear____( _slf: *mut #pyo3_path::ffi::PyObject, ) -> ::std::ffi::c_int { #pyo3_path::impl_::pymethods::_call_clear(_slf, |py, _slf| { #holders let result = #fncall; let result = #pyo3_path::impl_::wrap::converter(&result).wrap(result)?; ::std::result::Result::Ok(result) }, #cls::__pymethod___clear____) } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_clear, pfunc: #cls::__pymethod___clear____ as #pyo3_path::ffi::inquiry as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } pub(crate) fn impl_py_class_attribute( cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); ensure_spanned!( args.is_empty(), args[0].ty().span() => "#[classattr] can only have one argument (of type pyo3::Python)" ); ensure_spanned!( spec.warnings.is_empty(), spec.warnings.span() => "#[classattr] cannot be used with #[pyo3(warn)]" ); let name = &spec.name; let fncall = if py_arg.is_some() { quote!(function(py)) } else { quote!(function()) }; let wrapper_ident = format_ident!("__pymethod_{}__", name); let python_name = spec.null_terminated_python_name(); let body = quotes::ok_wrap(fncall, ctx); let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { let function = #cls::#name; // Shadow the method name to avoid #3017 let result = #body; #pyo3_path::impl_::wrap::converter(&result).map_into_pyobject(py, result) } }; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({ #pyo3_path::impl_::pymethods::PyClassAttributeDef::new( #python_name, #cls::#wrapper_ident ) }) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_setter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx); if args.is_empty() { bail_spanned!(spec.name.span() => "setter function expected to have one argument"); } else if args.len() > 1 { bail_spanned!( args[1].ty().span() => "setter function can have at most two arguments ([pyo3::Python,] and value)" ); } let name = &spec.name; let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py, _val)) } else { quote!(#cls::#name(#slf, _val)) }; Ok(fncall) } // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_setter_def( cls: &syn::Type, property_type: PropertyType<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name()?; let doc = property_type.doc(); let mut holders = Holders::new(); let setter_impl = match property_type { PropertyType::Descriptor { field_index, field, .. } => { let slf = SelfType::Receiver { mutable: true, span: Span::call_site(), non_null: true, } .receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx); if let Some(ident) = &field.ident { // named struct field quote!({ #slf.#ident = _val; }) } else { // tuple struct field let index = syn::Index::from(field_index); quote!({ #slf.#index = _val; }) } } PropertyType::Function { spec, self_type, .. } => impl_call_setter(cls, spec, self_type, &mut holders, ctx)?, }; let wrapper_ident = match property_type { PropertyType::Descriptor { field: syn::Field { ident: Some(ident), .. }, .. } => { format_ident!("__pymethod_set_{}__", ident) } PropertyType::Descriptor { field_index, .. } => { format_ident!("__pymethod_set_field_{}__", field_index) } PropertyType::Function { spec, .. } => { format_ident!("__pymethod_set_{}__", spec.name) } }; let extract = match &property_type { PropertyType::Function { spec, .. } => { let (_, args) = split_off_python_arg(&spec.signature.arguments); let value_arg = &args[0]; let (from_py_with, ident) = if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) { let ident = syn::Ident::new("from_py_with", from_py_with.span()); ( quote_spanned! { from_py_with.span() => let #ident = #from_py_with; }, ident, ) } else { (quote!(), syn::Ident::new("dummy", Span::call_site())) }; let arg = if let FnArg::Regular(arg) = &value_arg { arg } else { bail_spanned!(value_arg.name().span() => "The #[setter] value argument can't be *args, **kwargs or `cancel_handle`."); }; let extract = impl_regular_arg_param( arg, ident, quote!(::std::option::Option::Some(_value)), &mut holders, ctx, ); quote! { #from_py_with let _val = #extract; } } PropertyType::Descriptor { field, .. } => { let span = field.ty.span(); let name = field .ident .as_ref() .map(|i| i.to_string()) .unwrap_or_default(); let holder = holders.push_holder(span); quote! { #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_path::impl_::pyclass::Probe as _; let _val = #pyo3_path::impl_::extract_argument::extract_argument(_value, &mut #holder, #name)?; } } }; let mut cfg_attrs = TokenStream::new(); if let PropertyType::Descriptor { field, .. } = &property_type { for attr in field .attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) { attr.to_tokens(&mut cfg_attrs); } } let warnings = if let PropertyType::Function { spec, .. } = &property_type { spec.warnings.build_py_warning(ctx) } else { quote!() }; let init_holders = holders.init_holders(ctx); let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject>, _value: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject>, ) -> #pyo3_path::PyResult<::std::ffi::c_int> { use ::std::convert::Into; let _value = #pyo3_path::impl_::extract_argument::cast_non_null_function_argument(py, _value); #init_holders #extract #warnings let result = #setter_impl; #pyo3_path::impl_::callback::convert(py, result) } }; let doc = doc_to_optional_cstr(doc.as_deref(), ctx)?; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pymethods::PyMethodDefType::Setter( #pyo3_path::impl_::pymethods::PySetterDef::new( #python_name, #cls::#wrapper_ident, #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_getter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx); ensure_spanned!( args.is_empty(), args[0].ty().span() => "getter function can only have one argument (of type pyo3::Python)" ); let name = &spec.name; let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py)) } else { quote!(#cls::#name(#slf)) }; Ok(fncall) } // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_getter_def( cls: &syn::Type, property_type: PropertyType<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name()?; let doc = property_type.doc(); let mut cfg_attrs = TokenStream::new(); if let PropertyType::Descriptor { field, .. } = &property_type { for attr in field .attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) { attr.to_tokens(&mut cfg_attrs); } } let mut holders = Holders::new(); match property_type { PropertyType::Descriptor { field_index, field, .. } => { let ty = &field.ty; let field = if let Some(ident) = &field.ident { ident.to_token_stream() } else { syn::Index::from(field_index).to_token_stream() }; let doc = doc_to_optional_cstr(doc.as_deref(), ctx)?; let generator = quote_spanned! { ty.span() => GENERATOR.generate(#python_name, #doc) }; // This is separate from `generator` so that the unsafe below does not inherit the span and thus does not // trigger the `unsafe_code` lint let method_def = quote! { #cfg_attrs { #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_path::impl_::pyclass::Probe as _; const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::< #cls, #ty, { ::std::mem::offset_of!(#cls, #field) }, { #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#ty>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#ty>::VALUE }, > = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() }; #generator } }; Ok(MethodAndMethodDef { associated_method: quote! {}, method_def, }) } // Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results. PropertyType::Function { spec, self_type, .. } => { let wrapper_ident = format_ident!("__pymethod_get_{}__", spec.name); let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?; let body = quote! { #pyo3_path::impl_::callback::convert(py, #call) }; let init_holders = holders.init_holders(ctx); let warnings = spec.warnings.build_py_warning(ctx); let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { #init_holders #warnings let result = #body; result } }; let doc = doc_to_optional_cstr(doc.as_deref(), ctx)?; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pymethods::PyMethodDefType::Getter( #pyo3_path::impl_::pymethods::PyGetterDef::new( #python_name, #cls::#wrapper_ident, #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } } } pub fn impl_py_deleter_def( cls: &syn::Type, self_type: &SelfType, spec: &FnSpec<'_>, doc: Option, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = spec.null_terminated_python_name(); let mut holders = Holders::new(); let deleter_impl = impl_call_deleter(cls, spec, self_type, &mut holders, ctx)?; let wrapper_ident = format_ident!("__pymethod_delete_{}__", spec.name); let warnings = spec.warnings.build_py_warning(ctx); let init_holders = holders.init_holders(ctx); let doc = doc_to_optional_cstr(doc.as_ref(), ctx)?; let associated_method = quote! { unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: ::std::ptr::NonNull<#pyo3_path::ffi::PyObject>, ) -> #pyo3_path::PyResult<::std::ffi::c_int> { #init_holders #warnings let result = #deleter_impl; #pyo3_path::impl_::callback::convert(py, result) } }; let method_def = quote! { #pyo3_path::impl_::pymethods::PyMethodDefType::Deleter( #pyo3_path::impl_::pymethods::PyDeleterDef::new( #python_name, #cls::#wrapper_ident, #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_deleter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx); if !args.is_empty() { bail_spanned!(spec.name.span() => "deleter function can have at most one argument ([pyo3::Python,])" ); } let name = &spec.name; let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py)) } else { quote!(#cls::#name(#slf)) }; Ok(fncall) } /// Split an argument of pyo3::Python from the front of the arg list, if present fn split_off_python_arg<'a, 'b>(args: &'a [FnArg<'b>]) -> (Option<&'a PyArg<'b>>, &'a [FnArg<'b>]) { match args { [FnArg::Py(py), args @ ..] => (Some(py), args), args => (None, args), } } pub enum PropertyType<'a> { Descriptor { field_index: usize, field: &'a Field, python_name: Option<&'a NameAttribute>, renaming_rule: Option, }, Function { self_type: &'a SelfType, spec: &'a FnSpec<'a>, doc: Option, }, } impl PropertyType<'_> { fn null_terminated_python_name(&self) -> Result { match self { PropertyType::Descriptor { field, python_name, renaming_rule, .. } => { let name = field_python_name(field, *python_name, *renaming_rule)?; let name = CString::new(name).unwrap(); Ok(LitCStr::new(&name, field.span())) } PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()), } } fn doc(&self) -> Option> { Some(match self { PropertyType::Descriptor { field, .. } => { Cow::Owned(utils::get_doc(&field.attrs, None)?) } PropertyType::Function { doc, .. } => Cow::Borrowed(doc.as_ref()?), }) } } pub const __NEW__: SlotDef = SlotDef::new("Py_tp_new", "newfunc"); pub const __INIT__: SlotDef = SlotDef::new("Py_tp_init", "initproc"); pub const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc"); pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc"); pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc").return_conversion(TokenGenerator( |Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::impl_::callback::HashCallbackOutput }, )); pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc") .extract_error_mode(ExtractErrorMode::NotImplemented); const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc"); const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc"); const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc") .return_specialized_conversion( TokenGenerator(|_| quote! { IterBaseKind, IterOptionKind, IterResultOptionKind }), TokenGenerator(|_| quote! { iter_tag }), ); const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc"); const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc"); const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_specialized_conversion( TokenGenerator( |_| quote! { AsyncIterBaseKind, AsyncIterOptionKind, AsyncIterResultOptionKind }, ), TokenGenerator(|_| quote! { async_iter_tag }), ); pub const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc"); const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc"); const __CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc"); const __REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc"); const __INPLACE_CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc"); const __INPLACE_REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc"); pub const __GETITEM__: SlotDef = SlotDef::new("Py_mp_subscript", "binaryfunc"); const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc"); const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc"); const __ABS__: SlotDef = SlotDef::new("Py_nb_absolute", "unaryfunc"); const __INVERT__: SlotDef = SlotDef::new("Py_nb_invert", "unaryfunc"); const __INDEX__: SlotDef = SlotDef::new("Py_nb_index", "unaryfunc"); pub const __INT__: SlotDef = SlotDef::new("Py_nb_int", "unaryfunc"); const __FLOAT__: SlotDef = SlotDef::new("Py_nb_float", "unaryfunc"); const __BOOL__: SlotDef = SlotDef::new("Py_nb_bool", "inquiry"); const __IADD__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_add"); const __ISUB__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_subtract"); const __IMUL__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_multiply"); const __IMATMUL__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_matrix_multiply"); const __ITRUEDIV__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_true_divide"); const __IFLOORDIV__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_floor_divide"); const __IMOD__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_remainder"); const __ILSHIFT__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_lshift"); const __IRSHIFT__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_rshift"); const __IAND__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_and"); const __IXOR__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_xor"); const __IOR__: SlotDef = SlotDef::binary_inplace_operator("Py_nb_inplace_or"); const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ipowfunc") .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __GETBUFFER__: SlotDef = SlotDef::new("Py_bf_getbuffer", "getbufferproc").require_unsafe(); const __RELEASEBUFFER__: SlotDef = SlotDef::new("Py_bf_releasebuffer", "releasebufferproc").require_unsafe(); const __CLEAR__: SlotDef = SlotDef::new("Py_tp_clear", "inquiry"); #[derive(Clone, Copy)] enum Ty { Object, MaybeNullObject, NonNullObject, IPowModulo, CompareOp, Int, PyHashT, PySsizeT, Void, PyBuffer, } impl Ty { fn ffi_type(self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); match self { Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject }, Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> }, Ty::IPowModulo => quote! { #pyo3_path::impl_::pymethods::IPowModulo }, Ty::Int | Ty::CompareOp => quote! { ::std::ffi::c_int }, Ty::PyHashT => quote! { #pyo3_path::ffi::Py_hash_t }, Ty::PySsizeT => quote! { #pyo3_path::ffi::Py_ssize_t }, Ty::Void => quote! { () }, Ty::PyBuffer => quote! { *mut #pyo3_path::ffi::Py_buffer }, } } fn extract( self, ident: &syn::Ident, arg: &FnArg<'_>, extract_error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { Ty::Object => extract_object( extract_error_mode, holders, arg, REF_FROM_PTR, CAST_FUNCTION_ARGUMENT, quote! { #ident }, ctx ), Ty::MaybeNullObject => extract_object( extract_error_mode, holders, arg, REF_FROM_PTR, CAST_FUNCTION_ARGUMENT, quote! { if #ident.is_null() { #pyo3_path::ffi::Py_None() } else { #ident } }, ctx ), Ty::NonNullObject => extract_object( extract_error_mode, holders, arg, REF_FROM_NON_NULL, CAST_NON_NULL_FUNCTION_ARGUMENT, quote! { #ident }, ctx ), Ty::IPowModulo => extract_object( extract_error_mode, holders, arg, REF_FROM_PTR, CAST_FUNCTION_ARGUMENT, quote! { #ident.as_ptr() }, ctx ), Ty::CompareOp => extract_error_mode.handle_error( quote! { #pyo3_path::class::basic::CompareOp::from_raw(#ident) .ok_or_else(|| #pyo3_path::exceptions::PyValueError::new_err("invalid comparison operator")) }, ctx ), Ty::PySsizeT => { let ty = arg.ty(); extract_error_mode.handle_error( quote! { ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string())) }, ctx ) } // Just pass other types through unmodified Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! { #ident }, } } } const REF_FROM_PTR: StaticIdent = StaticIdent::new("ref_from_ptr"); const REF_FROM_NON_NULL: StaticIdent = StaticIdent::new("ref_from_non_null"); const CAST_FUNCTION_ARGUMENT: StaticIdent = StaticIdent::new("cast_function_argument"); const CAST_NON_NULL_FUNCTION_ARGUMENT: StaticIdent = StaticIdent::new("cast_non_null_function_argument"); fn extract_object( extract_error_mode: ExtractErrorMode, holders: &mut Holders, arg: &FnArg<'_>, ref_from_method: StaticIdent, cast_method: StaticIdent, source_ptr: TokenStream, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let name = arg.name().unraw().to_string(); let extract = if let Some(FromPyWithAttribute { kw, value: extractor, }) = arg.from_py_with() { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #extractor; from_py_with } }; quote! { #pyo3_path::impl_::extract_argument::from_py_with( unsafe { #pyo3_path::impl_::pymethods::BoundRef::#ref_from_method(py, &#source_ptr).0 }, #name, #extractor, ) } } else { let holder = holders.push_holder(Span::call_site()); quote! {{ #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] use #pyo3_path::impl_::pyclass::Probe as _; #pyo3_path::impl_::extract_argument::extract_argument( unsafe { #pyo3_path::impl_::extract_argument::#cast_method(py, #source_ptr) }, &mut #holder, #name ) }} }; let extracted = extract_error_mode.handle_error(extract, ctx); quote!(#extracted) } enum ReturnMode { ReturnSelf, Conversion(TokenGenerator), SpecializedConversion(TokenGenerator, TokenGenerator), } impl ReturnMode { fn return_call_output(&self, call: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { ReturnMode::Conversion(conversion) => { let conversion = TokenGeneratorCtx(*conversion, ctx); quote! { let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::impl_::callback::convert(py, #call); #pyo3_path::impl_::callback::convert(py, _result) } } ReturnMode::SpecializedConversion(traits, tag) => { let traits = TokenGeneratorCtx(*traits, ctx); let tag = TokenGeneratorCtx(*tag, ctx); quote! { let _result = #call; use #pyo3_path::impl_::pymethods::{#traits}; (&_result).#tag().convert(py, _result) } } ReturnMode::ReturnSelf => quote! { let _result: #pyo3_path::PyResult<()> = #pyo3_path::impl_::callback::convert(py, #call); _result?; #pyo3_path::ffi::Py_XINCREF(_slf); ::std::result::Result::Ok(_slf) }, } } } pub struct SlotDef { slot: StaticIdent, func_ty: StaticIdent, calling_convention: SlotCallingConvention, ret_ty: Ty, extract_error_mode: ExtractErrorMode, return_mode: Option, require_unsafe: bool, } enum SlotCallingConvention { /// Specific set of arguments for the slot function FixedArguments(&'static [Ty]), /// Arbitrary arguments for `__new__` from the signature (extracted from args / kwargs) TpNew, TpInit, } impl SlotDef { const fn new(slot: &'static str, func_ty: &'static str) -> Self { // The FFI function pointer type determines the arguments and return type let (calling_convention, ret_ty) = match func_ty.as_bytes() { b"newfunc" => (SlotCallingConvention::TpNew, Ty::Object), b"initproc" => (SlotCallingConvention::TpInit, Ty::Int), b"reprfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"hashfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::PyHashT), b"richcmpfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object, Ty::CompareOp]), Ty::Object, ), b"descrgetfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]), Ty::Object, ), b"getiterfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"iternextfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"unaryfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::Object), b"lenfunc" => (SlotCallingConvention::FixedArguments(&[]), Ty::PySsizeT), b"objobjproc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object]), Ty::Int, ), b"binaryfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object]), Ty::Object, ), b"inquiry" => (SlotCallingConvention::FixedArguments(&[]), Ty::Int), b"ssizeargfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::PySsizeT]), Ty::Object, ), b"getbufferproc" => ( SlotCallingConvention::FixedArguments(&[Ty::PyBuffer, Ty::Int]), Ty::Int, ), b"releasebufferproc" => ( SlotCallingConvention::FixedArguments(&[Ty::PyBuffer]), Ty::Void, ), b"ipowfunc" => ( SlotCallingConvention::FixedArguments(&[Ty::Object, Ty::IPowModulo]), Ty::Object, ), _ => panic!("don't know calling convention for func_ty"), }; SlotDef { slot: StaticIdent::new(slot), func_ty: StaticIdent::new(func_ty), calling_convention, ret_ty, extract_error_mode: ExtractErrorMode::Raise, return_mode: None, require_unsafe: false, } } /// Specialized constructor for binary inplace operators const fn binary_inplace_operator(slot: &'static str) -> Self { SlotDef::new(slot, "binaryfunc") .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self() } const fn return_conversion(mut self, return_conversion: TokenGenerator) -> Self { self.return_mode = Some(ReturnMode::Conversion(return_conversion)); self } const fn return_specialized_conversion( mut self, traits: TokenGenerator, tag: TokenGenerator, ) -> Self { self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag)); self } const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self } const fn return_self(mut self) -> Self { self.return_mode = Some(ReturnMode::ReturnSelf); self } const fn require_unsafe(mut self) -> Self { self.require_unsafe = true; self } pub fn generate_type_slot( &self, cls: &syn::Type, spec: &FnSpec<'_>, method_name: &str, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let SlotDef { slot, func_ty, calling_convention, extract_error_mode, ret_ty, return_mode, require_unsafe, } = self; if *require_unsafe { ensure_spanned!( spec.unsafety.is_some(), spec.name.span() => format!("`{}` must be `unsafe fn`", method_name) ); } let wrapper_ident = format_ident!("__pymethod_{}__", method_name); let ret_ty = ret_ty.ffi_type(ctx); let mut holders = Holders::new(); let MethodBody { arg_idents, arg_types, body, } = generate_method_body( cls, spec, calling_convention, *extract_error_mode, &mut holders, return_mode.as_ref(), ctx, )?; let name = spec.name; let holders = holders.init_holders(ctx); let associated_method = quote! { #[allow(non_snake_case)] unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { let function = #cls::#name; // Shadow the method name to avoid #3017 #holders #body } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::#slot, pfunc: #pyo3_path::impl_::trampoline::get_trampoline_function!(#func_ty, #cls::#wrapper_ident) as #pyo3_path::ffi::#func_ty as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } } fn generate_method_body( cls: &syn::Type, spec: &FnSpec<'_>, calling_convention: &SlotCallingConvention, extract_error_mode: ExtractErrorMode, holders: &mut Holders, // NB ignored if calling_convention is SlotCallingConvention::TpNew, possibly should merge into that enum return_mode: Option<&ReturnMode>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, output_span, } = ctx; let self_arg = spec .tp .self_arg(Some(cls), extract_error_mode, holders, ctx); let rust_name = spec.name; let warnings = spec.warnings.build_py_warning(ctx); let (arg_idents, arg_types, body) = match calling_convention { SlotCallingConvention::TpNew => { let arg_idents = vec![ format_ident!("_slf"), format_ident!("_args"), format_ident!("_kwargs"), ]; let arg_types = vec![ quote! { *mut #pyo3_path::ffi::PyTypeObject }, quote! { *mut #pyo3_path::ffi::PyObject }, quote! { *mut #pyo3_path::ffi::PyObject }, ]; let (arg_convert, args) = impl_arg_params(spec, Some(cls), false, holders, ctx); let args = self_arg.into_iter().chain(args); let call = quote_spanned! {*output_span=> #cls::#rust_name(#(#args),*) }; // Use just the text_signature_call_signature() because the class' Python name // isn't known to `#[pymethods]` - that has to be attached at runtime from the PyClassImpl // trait implementation created by `#[pyclass]`. let text_signature_impl = spec.text_signature_call_signature().map(|text_signature| { quote! { #[allow(unknown_lints, non_local_definitions)] impl #pyo3_path::impl_::pyclass::doc::PyClassNewTextSignature for #cls { const TEXT_SIGNATURE: &'static str = #text_signature; } } }); let output = if let syn::ReturnType::Type(_, ty) = &spec.output { ty } else { &parse_quote!(()) }; let body = quote! { #text_signature_impl use #pyo3_path::impl_::pyclass::Probe as _; #warnings #arg_convert let result = #call; #pyo3_path::impl_::pymethods::tp_new_impl::< _, { #pyo3_path::impl_::pyclass::IsPyClass::<#output>::VALUE }, { #pyo3_path::impl_::pyclass::IsInitializerTuple::<#output>::VALUE } >(py, result, _slf) }; (arg_idents, arg_types, body) } SlotCallingConvention::TpInit => { let arg_idents = vec![ format_ident!("_slf"), format_ident!("_args"), format_ident!("_kwargs"), ]; let arg_types = vec![ quote! { *mut #pyo3_path::ffi::PyObject }, quote! { *mut #pyo3_path::ffi::PyObject }, quote! { *mut #pyo3_path::ffi::PyObject }, ]; let (arg_convert, args) = impl_arg_params(spec, Some(cls), false, holders, ctx); let args = self_arg.into_iter().chain(args); let call = quote! {{ let r = #cls::#rust_name(#(#args),*); #pyo3_path::impl_::wrap::converter(&r) .wrap(r) .map_err(::core::convert::Into::<#pyo3_path::PyErr>::into)? }}; let output = quote_spanned! { *output_span => result.convert(py) }; let body = quote! { use #pyo3_path::impl_::callback::IntoPyCallbackOutput; #warnings #arg_convert let result = #call; #output }; (arg_idents, arg_types, body) } SlotCallingConvention::FixedArguments(arguments) => { let arg_idents: Vec<_> = std::iter::once(format_ident!("_slf")) .chain((0..arguments.len()).map(|i| format_ident!("arg{}", i))) .collect(); let arg_types: Vec<_> = std::iter::once(quote! { *mut #pyo3_path::ffi::PyObject }) .chain(arguments.iter().map(|arg| arg.ffi_type(ctx))) .collect(); let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?; let args = self_arg.into_iter().chain(args); let call = quote! { #cls::#rust_name(#(#args),*) }; let result = if let Some(return_mode) = return_mode { return_mode.return_call_output(call, ctx) } else { quote! { let result = #call; #pyo3_path::impl_::callback::convert(py, result) } }; let body = quote! { #warnings #result }; (arg_idents, arg_types, body) } }; Ok(MethodBody { arg_idents, arg_types, body, }) } struct SlotFragmentDef { fragment: &'static str, arguments: &'static [Ty], extract_error_mode: ExtractErrorMode, ret_ty: Ty, } impl SlotFragmentDef { const fn new(fragment: &'static str, arguments: &'static [Ty]) -> Self { SlotFragmentDef { fragment, arguments, extract_error_mode: ExtractErrorMode::Raise, ret_ty: Ty::Void, } } /// Specialized constructor for binary operators (which are a common pattern) const fn binary_operator(fragment: &'static str) -> Self { SlotFragmentDef { fragment, arguments: &[Ty::Object], extract_error_mode: ExtractErrorMode::NotImplemented, ret_ty: Ty::Object, } } const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self } const fn ret_ty(mut self, ret_ty: Ty) -> Self { self.ret_ty = ret_ty; self } fn generate_pyproto_fragment( &self, cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let SlotFragmentDef { fragment, arguments, extract_error_mode, ret_ty, } = self; let fragment_trait = format_ident!("PyClass{}SlotFragment", fragment); let method = syn::Ident::new(fragment, Span::call_site()); let wrapper_ident = format_ident!("__pymethod_{}__", fragment); let mut holders = Holders::new(); let MethodBody { arg_idents, arg_types, body, } = generate_method_body( cls, spec, &SlotCallingConvention::FixedArguments(arguments), *extract_error_mode, &mut holders, None, ctx, )?; let ret_ty = ret_ty.ffi_type(ctx); let holders = holders.init_holders(ctx); Ok(quote! { impl #cls { #[allow(non_snake_case)] unsafe fn #wrapper_ident( py: #pyo3_path::Python, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { #holders #body } } impl #pyo3_path::impl_::pyclass::#fragment_trait<#cls> for #pyo3_path::impl_::pyclass::PyClassImplCollector<#cls> { #[inline] unsafe fn #method( self, py: #pyo3_path::Python, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { #cls::#wrapper_ident(py, #(#arg_idents),*) } } }) } } /// The reusable components of a method body. pub struct MethodBody { pub arg_idents: Vec, pub arg_types: Vec, pub body: TokenStream, } const __GETATTRIBUTE__: SlotFragmentDef = SlotFragmentDef::new("__getattribute__", &[Ty::Object]).ret_ty(Ty::Object); const __GETATTR__: SlotFragmentDef = SlotFragmentDef::new("__getattr__", &[Ty::Object]).ret_ty(Ty::Object); const __SETATTR__: SlotFragmentDef = SlotFragmentDef::new("__setattr__", &[Ty::Object, Ty::NonNullObject]); const __DELATTR__: SlotFragmentDef = SlotFragmentDef::new("__delattr__", &[Ty::Object]); const __SET__: SlotFragmentDef = SlotFragmentDef::new("__set__", &[Ty::Object, Ty::NonNullObject]); const __DELETE__: SlotFragmentDef = SlotFragmentDef::new("__delete__", &[Ty::Object]); const __SETITEM__: SlotFragmentDef = SlotFragmentDef::new("__setitem__", &[Ty::Object, Ty::NonNullObject]); const __DELITEM__: SlotFragmentDef = SlotFragmentDef::new("__delitem__", &[Ty::Object]); const __ADD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__add__"); const __RADD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__radd__"); const __SUB__: SlotFragmentDef = SlotFragmentDef::binary_operator("__sub__"); const __RSUB__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rsub__"); const __MUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__mul__"); const __RMUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rmul__"); const __MATMUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__matmul__"); const __RMATMUL__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rmatmul__"); const __FLOORDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__floordiv__"); const __RFLOORDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rfloordiv__"); const __TRUEDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__truediv__"); const __RTRUEDIV__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rtruediv__"); const __DIVMOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__divmod__"); const __RDIVMOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rdivmod__"); const __MOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__mod__"); const __RMOD__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rmod__"); const __LSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__lshift__"); const __RLSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rlshift__"); const __RSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rshift__"); const __RRSHIFT__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rrshift__"); const __AND__: SlotFragmentDef = SlotFragmentDef::binary_operator("__and__"); const __RAND__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rand__"); const __XOR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__xor__"); const __RXOR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__rxor__"); const __OR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__or__"); const __ROR__: SlotFragmentDef = SlotFragmentDef::binary_operator("__ror__"); const __POW__: SlotFragmentDef = SlotFragmentDef::new("__pow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __RPOW__: SlotFragmentDef = SlotFragmentDef::new("__rpow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __LT__: SlotFragmentDef = SlotFragmentDef::new("__lt__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __LE__: SlotFragmentDef = SlotFragmentDef::new("__le__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __EQ__: SlotFragmentDef = SlotFragmentDef::new("__eq__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __NE__: SlotFragmentDef = SlotFragmentDef::new("__ne__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __GT__: SlotFragmentDef = SlotFragmentDef::new("__gt__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __GE__: SlotFragmentDef = SlotFragmentDef::new("__ge__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); fn extract_proto_arguments( spec: &FnSpec<'_>, proto_args: &[Ty], extract_error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> Result> { let mut args = Vec::with_capacity(spec.signature.arguments.len()); let mut non_python_args = 0; for arg in &spec.signature.arguments { if let FnArg::Py(..) = arg { args.push(quote! { py }); } else { let ident = syn::Ident::new(&format!("arg{non_python_args}"), Span::call_site()); let conversions = proto_args.get(non_python_args) .ok_or_else(|| err_spanned!(arg.ty().span() => format!("Expected at most {} non-python arguments", proto_args.len())))? .extract(&ident, arg, extract_error_mode, holders, ctx); non_python_args += 1; args.push(conversions); } } if non_python_args != proto_args.len() { bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", proto_args.len(), non_python_args)); } Ok(args) } #[derive(Clone, Copy)] struct TokenGenerator(fn(&Ctx) -> TokenStream); struct TokenGeneratorCtx<'ctx>(TokenGenerator, &'ctx Ctx); impl ToTokens for TokenGeneratorCtx<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let Self(TokenGenerator(gen), ctx) = self; (gen)(ctx).to_tokens(tokens) } } pub fn field_python_name( field: &Field, name_attr: Option<&NameAttribute>, renaming_rule: Option, ) -> Result { if let Some(name_attr) = name_attr { return Ok(name_attr.value.0.to_string()); } let Some(ident) = &field.ident else { bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`"); }; let mut name = ident.unraw().to_string(); if let Some(rule) = renaming_rule { name = utils::apply_renaming_rule(rule, &name); } Ok(name) } fn doc_to_optional_cstr(doc: Option<&PythonDoc>, ctx: &Ctx) -> Result { Ok(if let Some(doc) = doc { let doc = doc.to_cstr_stream(ctx)?; quote!(::std::option::Option::Some(#doc)) } else { quote!(::std::option::Option::None) }) } ================================================ FILE: pyo3-macros-backend/src/quotes.rs ================================================ use crate::utils::Ctx; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; pub(crate) fn some_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::impl_::wrap::SomeWrap::wrap(#obj) } } pub(crate) fn ok_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); quote_spanned! { *output_span => { let obj = #obj; #[allow(clippy::useless_conversion, reason = "needed for Into conversion, may be redundant")] #pyo3_path::impl_::wrap::converter(&obj).wrap(obj).map_err(::core::convert::Into::<#pyo3_path::PyErr>::into) }} } pub(crate) fn map_result_into_ptr(result: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); let py = syn::Ident::new("py", proc_macro2::Span::call_site()); quote_spanned! { *output_span => { let result = #result; #pyo3_path::impl_::wrap::converter(&result).map_into_ptr(#py, result) }} } ================================================ FILE: pyo3-macros-backend/src/utils.rs ================================================ use crate::attributes::{CrateAttribute, RenamingRule}; use proc_macro2::{Span, TokenStream, TokenTree}; use quote::{quote, ToTokens, TokenStreamExt}; use std::ffi::CString; use std::mem::take; use syn::spanned::Spanned; use syn::{Expr, ExprLit, Lit, LitCStr}; /// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span. macro_rules! err_spanned { ($span:expr => $msg:expr) => { syn::Error::new($span, $msg) }; } /// Macro inspired by `anyhow::bail!` to return a compiler error with the given span. macro_rules! bail_spanned { ($span:expr => $msg:expr) => { return Err(err_spanned!($span => $msg)) }; } /// Macro inspired by `anyhow::ensure!` to return a compiler error with the given span if the /// specified condition is not met. macro_rules! ensure_spanned { ($condition:expr, $span:expr => $msg:expr) => { if !($condition) { bail_spanned!($span => $msg); } }; ($($condition:expr, $span:expr => $msg:expr;)*) => { if let Some(e) = [$( (!($condition)).then(|| err_spanned!($span => $msg)), )*] .into_iter() .flatten() .reduce(|mut acc, e| { acc.combine(e); acc }) { return Err(e); } }; } /// Check if the given type `ty` is `pyo3::Python`. pub fn is_python(ty: &syn::Type) -> bool { match unwrap_ty_group(ty) { syn::Type::Path(typath) => typath .path .segments .last() .map(|seg| seg.ident == "Python") .unwrap_or(false), _ => false, } } /// If `ty` is `Option`, return `Some(T)`, else `None`. pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> { if let syn::Type::Path(syn::TypePath { path, .. }) = ty { let seg = path.segments.last().filter(|s| s.ident == "Option")?; if let syn::PathArguments::AngleBracketed(params) = &seg.arguments { if let syn::GenericArgument::Type(ty) = params.args.first()? { return Some(ty); } } } None } /// A syntax tree which evaluates to a string for Python. /// /// Typically, the tokens will just be that string, but if the original docs included macro /// expressions then the tokens will be a concatenation expression of the strings and /// macro parts. contents such as parse the string contents. #[derive(Clone)] pub struct PythonDoc { pub parts: Vec, } /// Collects all #[doc = "..."] attributes into a TokenStream evaluating to a string. /// /// If this doc is for a callable, the provided `text_signature` can be passed to prepend /// this to the documentation suitable for Python to extract this into the `__text_signature__` /// attribute. pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option) -> Option { // insert special divider between `__text_signature__` and doc // (assume text_signature is itself well-formed) if let Some(text_signature) = &mut text_signature { text_signature.push_str("\n--\n\n"); } let mut parts = Vec::new(); let mut first = true; let mut current_part = text_signature.unwrap_or_default(); let mut current_part_span: Option = None; for attr in attrs { if attr.path().is_ident("doc") { if let Ok(nv) = attr.meta.require_name_value() { if !first { current_part.push('\n'); } else { first = false; } if let Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) = &nv.value { // Strip single left space from literal strings, if needed. // e.g. `/// Hello world` expands to #[doc = " Hello world"] let doc_line = lit_str.value(); current_part.push_str(doc_line.strip_prefix(' ').unwrap_or(&doc_line)); current_part_span = current_part_span .map_or_else(|| Some(lit_str.span()), |s| s.join(lit_str.span())); } else { // This is probably a macro doc from Rust 1.54, e.g. #[doc = include_str!(...)] // Reset the string buffer, write that part, and then push this macro part too. if !current_part.is_empty() { parts.push(StrOrExpr::Str { value: take(&mut current_part), span: take(&mut current_part_span), }); } parts.push(StrOrExpr::Expr(nv.value.clone())); } } } } if !current_part.is_empty() { parts.push(StrOrExpr::Str { value: current_part, span: current_part_span, }); } if parts.is_empty() { None } else { Some(PythonDoc { parts }) } } impl PythonDoc { pub fn to_cstr_stream(&self, ctx: &Ctx) -> syn::Result { let parts = &self.parts; if let [StrOrExpr::Str { value, span }] = &parts[..] { // Simple case, a single string. We serialize as such return match CString::new(value.clone()) { Ok(null_terminated_value) => Ok(LitCStr::new( &null_terminated_value, span.unwrap_or_else(Span::call_site), ) .into_token_stream()), Err(e) => Err(syn::Error::new( span.unwrap_or_else(Span::call_site), format!( "Python doc may not contain nul byte, found nul at position {}", e.nul_position() ), )), }; } let Ctx { pyo3_path, .. } = ctx; Ok(quote!(#pyo3_path::ffi::c_str!(concat!(#(#parts),*)))) } } /// A plain string or an expression #[derive(Clone)] pub enum StrOrExpr { Str { value: String, span: Option }, Expr(Expr), } impl ToTokens for StrOrExpr { fn to_tokens(&self, tokens: &mut TokenStream) { match self { StrOrExpr::Str { value, .. } => value.to_tokens(tokens), StrOrExpr::Expr(expr) => expr.to_tokens(tokens), } } } pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type { while let syn::Type::Group(g) = ty { ty = &*g.elem; } ty } pub struct Ctx { /// Where we can find the pyo3 crate pub pyo3_path: PyO3CratePath, /// If we are in a pymethod or pyfunction, /// this will be the span of the return type pub output_span: Span, } impl Ctx { pub(crate) fn new(attr: &Option, signature: Option<&syn::Signature>) -> Self { let pyo3_path = match attr { Some(attr) => PyO3CratePath::Given(attr.value.0.clone()), None => PyO3CratePath::Default, }; let output_span = if let Some(syn::Signature { output: syn::ReturnType::Type(_, output_type), .. }) = &signature { output_type.span() } else { Span::call_site() }; Self { pyo3_path, output_span, } } } #[derive(Clone)] pub enum PyO3CratePath { Given(syn::Path), Default, } impl PyO3CratePath { pub fn to_tokens_spanned(&self, span: Span) -> TokenStream { match self { Self::Given(path) => quote::quote_spanned! { span => #path }, Self::Default => quote::quote_spanned! { span => ::pyo3 }, } } } impl quote::ToTokens for PyO3CratePath { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Given(path) => path.to_tokens(tokens), Self::Default => quote::quote! { ::pyo3 }.to_tokens(tokens), } } } pub fn apply_renaming_rule(rule: RenamingRule, name: &str) -> String { use heck::*; match rule { RenamingRule::CamelCase => name.to_lower_camel_case(), RenamingRule::KebabCase => name.to_kebab_case(), RenamingRule::Lowercase => name.to_lowercase(), RenamingRule::PascalCase => name.to_upper_camel_case(), RenamingRule::ScreamingKebabCase => name.to_shouty_kebab_case(), RenamingRule::ScreamingSnakeCase => name.to_shouty_snake_case(), RenamingRule::SnakeCase => name.to_snake_case(), RenamingRule::Uppercase => name.to_uppercase(), } } pub(crate) enum IdentOrStr<'a> { Str(&'a str), Ident(syn::Ident), } pub(crate) fn has_attribute(attrs: &[syn::Attribute], ident: &str) -> bool { has_attribute_with_namespace(attrs, None, &[ident]) } pub(crate) fn has_attribute_with_namespace( attrs: &[syn::Attribute], crate_path: Option<&PyO3CratePath>, idents: &[&str], ) -> bool { let mut segments = vec![]; if let Some(c) = crate_path { match c { PyO3CratePath::Given(paths) => { for p in &paths.segments { segments.push(IdentOrStr::Ident(p.ident.clone())); } } PyO3CratePath::Default => segments.push(IdentOrStr::Str("pyo3")), } }; for i in idents { segments.push(IdentOrStr::Str(i)); } attrs.iter().any(|attr| { segments .iter() .eq(attr.path().segments.iter().map(|v| &v.ident)) }) } pub fn expr_to_python(expr: &syn::Expr) -> String { match expr { // literal values syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit { syn::Lit::Str(s) => s.token().to_string(), syn::Lit::Char(c) => c.token().to_string(), syn::Lit::Int(i) => i.base10_digits().to_string(), syn::Lit::Float(f) => f.base10_digits().to_string(), syn::Lit::Bool(b) => { if b.value() { "True".to_string() } else { "False".to_string() } } _ => "...".to_string(), }, // None syn::Expr::Path(syn::ExprPath { qself, path, .. }) if qself.is_none() && path.is_ident("None") => { "None".to_string() } // others, unsupported yet so defaults to `...` _ => "...".to_string(), } } /// Helper struct for hard-coded identifiers used in the macro code. #[derive(Clone, Copy)] pub struct StaticIdent(&'static str); impl StaticIdent { pub const fn new(name: &'static str) -> Self { Self(name) } } impl ToTokens for StaticIdent { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.append(syn::Ident::new(self.0, Span::call_site())); } } /// Adjusts a tokes stream so that the location for the stream comes from `Span`. /// /// This affects where error messages will arise in the compiler output. pub(crate) fn locate_tokens_at(tokens: TokenStream, span: Span) -> TokenStream { fn set_span_recursively(tokens: TokenStream, span: Span) -> TokenStream { tokens .into_iter() .map(|tt| match tt { TokenTree::Group(g) => { let inner = set_span_recursively(g.stream(), span); let mut new_group = proc_macro2::Group::new(g.delimiter(), inner); new_group.set_span(span); TokenTree::Group(new_group) } TokenTree::Ident(mut ident) => { ident.set_span(span); TokenTree::Ident(ident) } TokenTree::Punct(mut punct) => { punct.set_span(span); TokenTree::Punct(punct) } TokenTree::Literal(mut lit) => { lit.set_span(span); TokenTree::Literal(lit) } }) .collect() } let output_span = tokens.span().located_at(span); set_span_recursively(tokens, output_span) } ================================================ FILE: pyo3-runtime/README.md ================================================ Coming soon! ================================================ FILE: pyo3-runtime/pyproject.toml ================================================ [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "pyo3-runtime" dynamic = ["version"] description = '' readme = "README.md" requires-python = ">=3.7" license = "MIT OR Apache-2.0" keywords = [] authors = [ { name = "David Hewitt", email = "1939362+davidhewitt@users.noreply.github.com" }, ] classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [] [project.urls] Homepage = "https://github.com/PyO3/pyo3" [tool.hatch.version] path = "src/pyo3_runtime/__init__.py" ================================================ FILE: pyo3-runtime/src/pyo3_runtime/__init__.py ================================================ __version__ = "0.0.1" ================================================ FILE: pyo3-runtime/tests/__init__.py ================================================ ================================================ FILE: pyproject.toml ================================================ [project] requires-python = ">=3.7" name = "pyo3" dynamic = ["version"] [tool.ruff.per-file-target-version] # experimental-inspect generates .pyi files that use positional-only `/` marker "*.pyi" = "py38" # uses `match` statement "pytests/tests/test_enums_match.py" = "py310" [tool.ruff.lint.extend-per-file-ignores] "__init__.py" = ["F403"] [tool.towncrier] filename = "CHANGELOG.md" version = "0.28.2" start_string = "\n" template = ".towncrier.template.md" title_format = "## [{version}] - {project_date}" issue_format = "[#{issue}](https://github.com/PyO3/pyo3/pull/{issue})" # Note PyO3 shows pulls, not issues, in the CHANGELOG underlines = ["", "", ""] directory = "newsfragments" [tool.towncrier.fragment.packaging] name = "Packaging" [tool.towncrier.fragment.added] name = "Added" [tool.towncrier.fragment.changed] name = "Changed" [tool.towncrier.fragment.removed] name = "Removed" [tool.towncrier.fragment.fixed] name = "Fixed" [tool.rumdl] disable = [ # TODO: what to do about inline HTML, probably allow? "MD033", # TODO: {{#PYO3_DOCS_URL}} placeholder confuses rumdl, change syntax perhaps? "MD051" ] exclude = [ # just has an include to the top-level files "guide/src/changelog.md", "guide/src/contributing.md" ] [tool.rumdl.per-file-ignores] "guide/pyclass-parameters.md" = ["MD041"] [tool.rumdl.MD004] style = "dash" [tool.rumdl.MD013] line_length = 0 code_blocks = false tables = false headings = false reflow = true reflow_mode = "sentence-per-line" [tool.uv] package = false [dependency-groups] dev = [ "rumdl", ] ================================================ FILE: pytests/Cargo.toml ================================================ [package] authors = ["PyO3 Authors"] name = "pyo3-pytests" version = "0.1.0" description = "Python-based tests for PyO3" edition = "2021" publish = false rust-version = "1.83" [features] experimental-async = ["pyo3/experimental-async"] experimental-inspect = ["pyo3/experimental-inspect"] [dependencies] pyo3.path = "../" [build-dependencies] pyo3-build-config = { path = "../pyo3-build-config" } [lib] name = "pyo3_pytests" crate-type = ["cdylib"] [lints] workspace = true ================================================ FILE: pytests/MANIFEST.in ================================================ include pyproject.toml Cargo.toml recursive-include src * ================================================ FILE: pytests/MODULE_DOC.md ================================================ This is documentation for the main module of PyO3 integration tests It provides multiple modules to do tests ================================================ FILE: pytests/README.md ================================================ # pyo3-pytests An extension module built using PyO3, used to test and benchmark PyO3 from Python. The `stubs` directory contains Python stubs used to test the automated stubs introspection. To test them run `nox -s test-introspection`. ## Testing This package is intended to be built using `maturin`. Once built, you can run the tests using `pytest`: ```shell pip install maturin maturin develop pytest ``` Alternatively, install nox and run the tests inside an isolated environment: ```shell nox ``` ## Running benchmarks You can install the module in your Python environment and then run the benchmarks with pytest: ```shell pip install . pytest --benchmark-enable ``` Or with nox: ```shell nox -s bench ``` ================================================ FILE: pytests/build.rs ================================================ fn main() { pyo3_build_config::use_pyo3_cfgs(); pyo3_build_config::add_extension_module_link_args(); } ================================================ FILE: pytests/conftest.py ================================================ import sysconfig import sys import pytest FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) gil_enabled_at_start = True if FREE_THREADED_BUILD: gil_enabled_at_start = sys._is_gil_enabled() def pytest_terminal_summary(terminalreporter, exitstatus, config): if FREE_THREADED_BUILD and not gil_enabled_at_start and sys._is_gil_enabled(): tr = terminalreporter tr.ensure_newline() tr.section("GIL re-enabled", sep="=", red=True, bold=True) tr.line("The GIL was re-enabled at runtime during the tests.") tr.line("") tr.line("Please ensure all new modules declare support for running") tr.line("without the GIL. Any new tests that intentionally imports ") tr.line("code that re-enables the GIL should do so in a subprocess.") pytest.exit("GIL re-enabled during tests", returncode=1) ================================================ FILE: pytests/noxfile.py ================================================ import shutil from pathlib import Path import nox import sys from nox.command import CommandFailed nox.options.sessions = ["test"] @nox.session def test(session: nox.Session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.install("-v", ".[dev]") def try_install_binary(package: str, constraint: str): try: session.install("--only-binary=:all:", f"{package}{constraint}") except CommandFailed: # No binary wheel available on this platform pass try_install_binary("numpy", ">=1.16") # https://github.com/zopefoundation/zope.interface/issues/316 # - is a dependency of gevent try_install_binary("zope.interface", "<7") try_install_binary("gevent", ">=22.10.2") ignored_paths = [] if sys.version_info < (3, 10): # Match syntax is only available in Python >= 3.10 ignored_paths.append("tests/test_enums_match.py") ignore_args = [f"--ignore={path}" for path in ignored_paths] session.run("pytest", *ignore_args, *session.posargs) @nox.session def bench(session: nox.Session): session.install(".[dev]") session.run("pytest", "--benchmark-enable", "--benchmark-only", *session.posargs) @nox.session def mypy(session: nox.Session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" try: # We move the stubs where maturin is expecting them to be shutil.copytree("stubs", "pyo3_pytests") (Path("pyo3_pytests") / "py.typed").touch() session.install(".[dev]") session.run_always( "python", "-m", "mypy", "tests", ) # TODO: enable stubtest when previously listed errors will be fixed session.run_always("python", "-m", "mypy.stubtest", "pyo3_pytests") finally: shutil.rmtree("pyo3_pytests") ================================================ FILE: pytests/pyproject.toml ================================================ [build-system] requires = ["maturin>=1.9.4,<2"] build-backend = "maturin" [tool.pytest.ini_options] addopts = "--benchmark-disable" [project] name = "pyo3_pytests" version = "0.1.0" classifiers = [ "License :: OSI Approved :: MIT License", "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Rust", "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", ] [project.optional-dependencies] dev = [ "hypothesis>=3.55", "mypy~=1.0", "pytest-asyncio>=0.21,<2", "pytest-benchmark>=3.4", "pytest>=7", "typing_extensions>=4.0.0" ] ================================================ FILE: pytests/src/awaitable.rs ================================================ //! The following classes are examples of objects which implement Python's //! awaitable protocol. //! //! Both IterAwaitable and FutureAwaitable will return a value immediately //! when awaited, see guide examples related to pyo3-async-runtimes for ways //! to suspend tasks and await results. use pyo3::prelude::*; #[pymodule] pub mod awaitable { use pyo3::exceptions::PyStopIteration; use pyo3::prelude::*; #[pyclass] #[derive(Debug)] pub(crate) struct IterAwaitable { result: Option>>, } #[pymethods] impl IterAwaitable { #[new] fn new(result: Py) -> Self { IterAwaitable { result: Some(Ok(result)), } } fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> { pyself } fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> { pyself } fn __next__(&mut self, py: Python<'_>) -> PyResult> { match self.result.take() { Some(res) => match res { Ok(v) => Err(PyStopIteration::new_err(v)), Err(err) => Err(err), }, _ => Ok(py.None()), } } } #[pyclass] pub(crate) struct FutureAwaitable { #[pyo3(get, set, name = "_asyncio_future_blocking")] py_block: bool, result: Option>>, } #[pymethods] impl FutureAwaitable { #[new] fn new(result: Py) -> Self { FutureAwaitable { py_block: false, result: Some(Ok(result)), } } fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> { pyself } fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> { pyself } fn __next__(mut pyself: PyRefMut<'_, Self>) -> PyResult> { match pyself.result { Some(_) => match pyself.result.take().unwrap() { Ok(v) => Err(PyStopIteration::new_err(v)), Err(err) => Err(err), }, _ => Ok(pyself), } } } } ================================================ FILE: pytests/src/buf_and_str.rs ================================================ #![cfg(any(not(Py_LIMITED_API), Py_3_11))] use pyo3::prelude::*; /// Objects related to PyBuffer and PyStr #[pymodule] pub mod buf_and_str { use pyo3::buffer::PyBuffer; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyMemoryView, PyString}; use std::borrow::Cow; /// This is for confirming that PyBuffer does not cause memory leak #[pyclass] struct BytesExtractor {} #[pymethods] impl BytesExtractor { #[new] pub fn __new__() -> Self { BytesExtractor {} } #[staticmethod] pub fn from_bytes(bytes: &Bound<'_, PyBytes>) -> PyResult { let byte_vec: Vec = bytes.extract()?; Ok(byte_vec.len()) } #[staticmethod] pub fn from_str(string: &Bound<'_, PyString>) -> PyResult { let rust_string: String = string.extract()?; Ok(rust_string.len()) } #[staticmethod] pub fn from_str_lossy(string: &Bound<'_, PyString>) -> usize { let rust_string_lossy: String = string.to_string_lossy().to_string(); rust_string_lossy.len() } #[staticmethod] pub fn from_buffer(buf: &Bound<'_, PyAny>) -> PyResult { let buf = PyBuffer::::get(buf)?; Ok(buf.item_count()) } } #[pyfunction] fn return_memoryview(py: Python<'_>) -> PyResult> { let bytes = PyBytes::new(py, b"hello world"); PyMemoryView::from(&bytes) } #[pyfunction] fn map_byte_slice(bytes: &[u8]) -> &[u8] { bytes } #[pyfunction] fn map_byte_cow(bytes: Cow<'_, [u8]>) -> Cow<'_, [u8]> { bytes } #[pyfunction] fn map_byte_vec(bytes: Vec) -> Vec { bytes } } ================================================ FILE: pytests/src/comparisons.rs ================================================ use pyo3::basic::CompareOp; use pyo3::prelude::*; use std::fmt; #[pyclass(frozen)] struct Eq(i64); #[pymethods] impl Eq { #[new] fn new(value: i64) -> Self { Self(value) } fn __eq__(&self, other: &Self) -> bool { self.0 == other.0 } fn __ne__(&self, other: &Self) -> bool { self.0 != other.0 } } #[pyclass(frozen)] struct EqDefaultNe(i64); #[pymethods] impl EqDefaultNe { #[new] fn new(value: i64) -> Self { Self(value) } fn __eq__(&self, other: &Self) -> bool { self.0 == other.0 } } #[pyclass(eq, frozen)] #[derive(PartialEq, Eq)] struct EqDerived(i64); #[pymethods] impl EqDerived { #[new] fn new(value: i64) -> Self { Self(value) } } #[pyclass(frozen)] struct Ordered(i64); #[pymethods] impl Ordered { #[new] fn new(value: i64) -> Self { Self(value) } fn __lt__(&self, other: &Self) -> bool { self.0 < other.0 } fn __le__(&self, other: &Self) -> bool { self.0 <= other.0 } fn __eq__(&self, other: &Self) -> bool { self.0 == other.0 } fn __ne__(&self, other: &Self) -> bool { self.0 != other.0 } fn __gt__(&self, other: &Self) -> bool { self.0 > other.0 } fn __ge__(&self, other: &Self) -> bool { self.0 >= other.0 } } #[pyclass(frozen)] struct OrderedRichCmp(i64); #[pymethods] impl OrderedRichCmp { #[new] fn new(value: i64) -> Self { Self(value) } fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { op.matches(self.0.cmp(&other.0)) } } #[pyclass(eq, ord, hash, str, frozen)] #[derive(PartialEq, Eq, Ord, PartialOrd, Hash)] struct OrderedDerived(i64); impl fmt::Display for OrderedDerived { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } #[pymethods] impl OrderedDerived { #[new] fn new(value: i64) -> Self { Self(value) } } #[pyclass(frozen)] struct OrderedDefaultNe(i64); #[pymethods] impl OrderedDefaultNe { #[new] fn new(value: i64) -> Self { Self(value) } fn __lt__(&self, other: &Self) -> bool { self.0 < other.0 } fn __le__(&self, other: &Self) -> bool { self.0 <= other.0 } fn __eq__(&self, other: &Self) -> bool { self.0 == other.0 } fn __gt__(&self, other: &Self) -> bool { self.0 > other.0 } fn __ge__(&self, other: &Self) -> bool { self.0 >= other.0 } } #[pymodule] pub mod comparisons { #[pymodule_export] use super::{ Eq, EqDefaultNe, EqDerived, Ordered, OrderedDefaultNe, OrderedDerived, OrderedRichCmp, }; } ================================================ FILE: pytests/src/consts.rs ================================================ use pyo3::pymodule; #[pymodule] pub mod consts { use pyo3::{pyclass, pymethods}; /// Exports PI constant as part of the module #[pymodule_export] pub const PI: f64 = std::f64::consts::PI; /// We experiment with "escaping" #[pymodule_export] pub const ESCAPING: &str = "S\0\x01\t\n\r\"'\\"; #[pyclass] struct ClassWithConst {} #[pymethods] impl ClassWithConst { /// A constant #[classattr] const INSTANCE: Self = ClassWithConst {}; } } ================================================ FILE: pytests/src/datetime.rs ================================================ use pyo3::prelude::*; use pyo3::types::{ PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple, PyTzInfo, PyTzInfoAccess, }; #[pyfunction] fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult> { PyDate::new(py, year, month, day) } #[pyfunction] fn get_date_tuple<'py>(d: &Bound<'py, PyDate>) -> PyResult> { PyTuple::new( d.py(), [d.get_year(), d.get_month() as i32, d.get_day() as i32], ) } #[pyfunction] fn date_from_timestamp(py: Python<'_>, timestamp: f64) -> PyResult> { PyDate::from_timestamp(py, timestamp) } #[pyfunction] #[pyo3(signature=(hour, minute, second, microsecond, tzinfo=None))] fn make_time<'py>( py: Python<'py>, hour: u8, minute: u8, second: u8, microsecond: u32, tzinfo: Option<&Bound<'py, PyTzInfo>>, ) -> PyResult> { PyTime::new(py, hour, minute, second, microsecond, tzinfo) } #[pyfunction] #[pyo3(signature = (hour, minute, second, microsecond, tzinfo, fold))] fn time_with_fold<'py>( py: Python<'py>, hour: u8, minute: u8, second: u8, microsecond: u32, tzinfo: Option<&Bound<'py, PyTzInfo>>, fold: bool, ) -> PyResult> { PyTime::new_with_fold(py, hour, minute, second, microsecond, tzinfo, fold) } #[pyfunction] fn get_time_tuple<'py>(dt: &Bound<'py, PyTime>) -> PyResult> { PyTuple::new( dt.py(), [ dt.get_hour() as u32, dt.get_minute() as u32, dt.get_second() as u32, dt.get_microsecond(), ], ) } #[pyfunction] fn get_time_tuple_fold<'py>(dt: &Bound<'py, PyTime>) -> PyResult> { PyTuple::new( dt.py(), [ dt.get_hour() as u32, dt.get_minute() as u32, dt.get_second() as u32, dt.get_microsecond(), dt.get_fold() as u32, ], ) } #[pyfunction] fn make_delta( py: Python<'_>, days: i32, seconds: i32, microseconds: i32, ) -> PyResult> { PyDelta::new(py, days, seconds, microseconds, true) } #[pyfunction] fn get_delta_tuple<'py>(delta: &Bound<'py, PyDelta>) -> PyResult> { PyTuple::new( delta.py(), [ delta.get_days(), delta.get_seconds(), delta.get_microseconds(), ], ) } #[expect(clippy::too_many_arguments)] #[pyfunction] #[pyo3(signature=(year, month, day, hour, minute, second, microsecond, tzinfo=None))] fn make_datetime<'py>( py: Python<'py>, year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8, microsecond: u32, tzinfo: Option<&Bound<'py, PyTzInfo>>, ) -> PyResult> { PyDateTime::new( py, year, month, day, hour, minute, second, microsecond, tzinfo, ) } #[pyfunction] fn get_datetime_tuple<'py>(dt: &Bound<'py, PyDateTime>) -> PyResult> { PyTuple::new( dt.py(), [ dt.get_year(), dt.get_month() as i32, dt.get_day() as i32, dt.get_hour() as i32, dt.get_minute() as i32, dt.get_second() as i32, dt.get_microsecond() as i32, ], ) } #[pyfunction] fn get_datetime_tuple_fold<'py>(dt: &Bound<'py, PyDateTime>) -> PyResult> { PyTuple::new( dt.py(), [ dt.get_year(), dt.get_month() as i32, dt.get_day() as i32, dt.get_hour() as i32, dt.get_minute() as i32, dt.get_second() as i32, dt.get_microsecond() as i32, dt.get_fold() as i32, ], ) } #[pyfunction] #[pyo3(signature=(ts, tz=None))] fn datetime_from_timestamp<'py>( py: Python<'py>, ts: f64, tz: Option<&Bound<'py, PyTzInfo>>, ) -> PyResult> { PyDateTime::from_timestamp(py, ts, tz) } #[pyfunction] fn get_datetime_tzinfo<'py>(dt: &Bound<'py, PyDateTime>) -> Option> { dt.get_tzinfo() } #[pyfunction] fn get_time_tzinfo<'py>(dt: &Bound<'py, PyTime>) -> Option> { dt.get_tzinfo() } #[pyclass(extends=PyTzInfo)] pub struct TzClass {} #[pymethods] impl TzClass { #[new] fn new() -> Self { TzClass {} } #[pyo3(signature = (_dt, /))] fn utcoffset<'py>( &self, _dt: Option<&Bound<'_, PyDateTime>>, py: Python<'py>, ) -> PyResult> { PyDelta::new(py, 0, 3600, 0, true) } #[pyo3(signature = (_dt, /))] fn tzname(&self, _dt: Option<&Bound<'_, PyDateTime>>) -> String { String::from("+01:00") } #[pyo3(signature = (_dt, /))] fn dst(&self, _dt: Option<&Bound<'_, PyDateTime>>) -> Option> { None } } #[pymodule] pub mod datetime { #[pymodule_export] use super::{ date_from_timestamp, datetime_from_timestamp, get_date_tuple, get_datetime_tuple, get_datetime_tuple_fold, get_datetime_tzinfo, get_delta_tuple, get_time_tuple, get_time_tuple_fold, get_time_tzinfo, make_date, make_datetime, make_delta, make_time, time_with_fold, TzClass, }; } ================================================ FILE: pytests/src/dict_iter.rs ================================================ use pyo3::prelude::*; #[pymodule] pub mod dict_iter { use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use pyo3::types::PyDict; #[pyclass] pub struct DictSize { expected: u32, } #[pymethods] impl DictSize { #[new] fn new(expected: u32) -> Self { DictSize { expected } } fn iter_dict(&mut self, _py: Python<'_>, dict: &Bound<'_, PyDict>) -> PyResult { let mut seen = 0u32; for (sym, values) in dict { seen += 1; println!( "{:4}/{:4} iterations:{}=>{}", seen, self.expected, sym, values ); } if seen == self.expected { Ok(seen) } else { Err(PyErr::new::(format!( "Expected {} iterations - performed {}", self.expected, seen ))) } } } } ================================================ FILE: pytests/src/enums.rs ================================================ use pyo3::{pyclass, pyfunction, pymodule}; #[pymodule] pub mod enums { #[pymodule_export] use super::{ do_complex_stuff, do_mixed_complex_stuff, do_simple_stuff, do_tuple_stuff, ComplexEnum, MixedComplexEnum, SimpleEnum, SimpleEnumWithoutDerive, SimpleTupleEnum, TupleEnum, }; } #[pyclass(eq, eq_int)] #[derive(PartialEq)] pub enum SimpleEnum { /// A variant Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, } #[pyclass] pub enum SimpleEnumWithoutDerive { A, B, } #[pyfunction] pub fn do_simple_stuff(thing: &SimpleEnum) -> SimpleEnum { match thing { SimpleEnum::Sunday => SimpleEnum::Monday, SimpleEnum::Monday => SimpleEnum::Tuesday, SimpleEnum::Tuesday => SimpleEnum::Wednesday, SimpleEnum::Wednesday => SimpleEnum::Thursday, SimpleEnum::Thursday => SimpleEnum::Friday, SimpleEnum::Friday => SimpleEnum::Saturday, SimpleEnum::Saturday => SimpleEnum::Sunday, } } #[pyclass] pub enum ComplexEnum { /// A struct variant Int { /// An integer i: i32, }, Float { f: f64, }, Str { s: String, }, EmptyStruct {}, MultiFieldStruct { a: i32, b: f64, c: bool, }, #[pyo3(constructor = (a = 42, b = None))] VariantWithDefault { a: i32, b: Option, }, } #[pyfunction] pub fn do_complex_stuff(thing: &ComplexEnum) -> ComplexEnum { match thing { ComplexEnum::Int { i } => ComplexEnum::Str { s: i.to_string() }, ComplexEnum::Float { f } => ComplexEnum::Float { f: f * f }, ComplexEnum::Str { s } => ComplexEnum::Int { i: s.len() as i32 }, ComplexEnum::EmptyStruct {} => ComplexEnum::EmptyStruct {}, ComplexEnum::MultiFieldStruct { a, b, c } => ComplexEnum::MultiFieldStruct { a: *a, b: *b, c: *c, }, ComplexEnum::VariantWithDefault { a, b } => ComplexEnum::VariantWithDefault { a: 2 * a, b: b.as_ref().map(|s| s.to_uppercase()), }, } } #[pyclass] enum SimpleTupleEnum { Int(i32), Str(String), } #[pyclass] pub enum TupleEnum { /// A tuple variant #[pyo3(constructor = (_0 = 1, _1 = 1.0, _2 = true))] FullWithDefault(i32, f64, bool), Full(i32, f64, bool), EmptyTuple(), } #[pyfunction] pub fn do_tuple_stuff(thing: &TupleEnum) -> TupleEnum { match thing { TupleEnum::FullWithDefault(a, b, c) => TupleEnum::FullWithDefault(*a, *b, *c), TupleEnum::Full(a, b, c) => TupleEnum::Full(*a, *b, *c), TupleEnum::EmptyTuple() => TupleEnum::EmptyTuple(), } } #[pyclass] pub enum MixedComplexEnum { Nothing {}, Empty(), } #[pyfunction] pub fn do_mixed_complex_stuff(thing: &MixedComplexEnum) -> MixedComplexEnum { match thing { MixedComplexEnum::Nothing {} => MixedComplexEnum::Empty(), MixedComplexEnum::Empty() => MixedComplexEnum::Nothing {}, } } ================================================ FILE: pytests/src/exception.rs ================================================ use pyo3::create_exception; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; create_exception!(pytests.exception, MyValueError, PyValueError); #[pymodule(gil_used = false)] pub mod exception { use pyo3::exceptions::PyValueError; use pyo3::prelude::*; #[pymodule_export] use super::MyValueError; #[pyfunction] fn raise_my_value_error() -> PyResult<()> { Err(MyValueError::new_err("error")) } #[pyfunction] fn return_value_error<'py>(py: Python<'py>) -> PyResult> { Ok(PyValueError::new_err("error") .into_pyobject(py)? .cast_into()?) } #[pyfunction] fn return_my_value_error<'py>(py: Python<'py>) -> PyResult> { Ok(MyValueError::new_err("error") .into_pyobject(py)? .cast_into()?) } #[pyfunction] fn return_pyerr() -> PyErr { MyValueError::new_err("error") } } ================================================ FILE: pytests/src/lib.rs ================================================ use pyo3::prelude::*; use pyo3::types::PyDict; mod awaitable; mod buf_and_str; mod comparisons; mod consts; #[cfg(not(Py_LIMITED_API))] mod datetime; mod dict_iter; mod enums; mod exception; mod misc; mod objstore; mod othermod; mod path; mod pyclasses; mod pyfunctions; mod sequence; mod subclassing; #[doc = include_str!("../MODULE_DOC.md")] #[pymodule] mod pyo3_pytests { use super::*; #[cfg(any(not(Py_LIMITED_API), Py_3_11))] #[pymodule_export] use buf_and_str::buf_and_str; #[cfg(not(Py_LIMITED_API))] #[pymodule_export] use datetime::datetime; #[pymodule_export] use { awaitable::awaitable, comparisons::comparisons, consts::consts, dict_iter::dict_iter, enums::enums, exception::exception, misc::misc, objstore::objstore, othermod::othermod, path::path, pyclasses::pyclasses, pyfunctions::pyfunctions, sequence::sequence, subclassing::subclassing, }; // Inserting to sys.modules allows importing submodules nicely from Python // e.g. import pyo3_pytests.buf_and_str as bas #[pymodule_init] fn init(m: &Bound<'_, PyModule>) -> PyResult<()> { let sys = PyModule::import(m.py(), "sys")?; let sys_modules = sys.getattr("modules")?.cast_into::()?; sys_modules.set_item("pyo3_pytests.awaitable", m.getattr("awaitable")?)?; sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; sys_modules.set_item("pyo3_pytests.dict_iter", m.getattr("dict_iter")?)?; sys_modules.set_item("pyo3_pytests.enums", m.getattr("enums")?)?; sys_modules.set_item("pyo3_pytests.misc", m.getattr("misc")?)?; sys_modules.set_item("pyo3_pytests.objstore", m.getattr("objstore")?)?; sys_modules.set_item("pyo3_pytests.othermod", m.getattr("othermod")?)?; sys_modules.set_item("pyo3_pytests.path", m.getattr("path")?)?; sys_modules.set_item("pyo3_pytests.pyclasses", m.getattr("pyclasses")?)?; sys_modules.set_item("pyo3_pytests.pyfunctions", m.getattr("pyfunctions")?)?; sys_modules.set_item("pyo3_pytests.sequence", m.getattr("sequence")?)?; sys_modules.set_item("pyo3_pytests.subclassing", m.getattr("subclassing")?)?; Ok(()) } } ================================================ FILE: pytests/src/misc.rs ================================================ use pyo3::{ prelude::*, types::{PyDict, PyString}, }; #[pyfunction] fn issue_219() { // issue 219: attaching inside #[pyfunction] deadlocks. Python::attach(|_| {}); } #[pyclass] struct LockHolder { #[expect(unused, reason = "used to block until sender is dropped")] sender: std::sync::mpsc::Sender<()>, } // This will repeatedly attach and detach from the Python interpreter // once the LockHolder is dropped. #[pyfunction] fn hammer_attaching_in_thread() -> LockHolder { let (sender, receiver) = std::sync::mpsc::channel(); std::thread::spawn(move || { receiver.recv().ok(); // now the interpreter has shut down, so hammer the attach API. In buggy // versions of PyO3 this will cause a crash. loop { Python::try_attach(|_py| ()); } }); LockHolder { sender } } #[pyfunction] fn get_type_fully_qualified_name<'py>(obj: &Bound<'py, PyAny>) -> PyResult> { obj.get_type().fully_qualified_name() } #[pyfunction] fn accepts_bool(val: bool) -> bool { val } #[pyfunction] fn get_item_and_run_callback(dict: Bound<'_, PyDict>, callback: Bound<'_, PyAny>) -> PyResult<()> { // This function gives the opportunity to run a pure-Python callback so that // gevent can instigate a context switch. This had problematic interactions // with PyO3's removed "GIL Pool". // For context, see https://github.com/PyO3/pyo3/issues/3668 let item = dict.get_item("key")?.expect("key not found in dict"); let string = item.to_string(); callback.call0()?; assert_eq!(item.to_string(), string); Ok(()) } #[pymodule] pub mod misc { #[pymodule_export] use super::{ accepts_bool, get_item_and_run_callback, get_type_fully_qualified_name, hammer_attaching_in_thread, issue_219, }; } ================================================ FILE: pytests/src/objstore.rs ================================================ use pyo3::prelude::*; #[pymodule] pub mod objstore { use pyo3::prelude::*; #[pyclass] #[derive(Default)] pub struct ObjStore { obj: Vec>, } #[pymethods] impl ObjStore { #[new] fn new() -> Self { ObjStore::default() } fn push(&mut self, obj: &Bound<'_, PyAny>) { self.obj.push(obj.clone().unbind()); } } } ================================================ FILE: pytests/src/othermod.rs ================================================ //! //! //! The code below just tries to use the most important code generation paths use pyo3::prelude::*; #[pymodule] pub mod othermod { use pyo3::prelude::*; #[pyclass] pub struct ModClass { _somefield: String, } #[pymethods] impl ModClass { #[new] fn new() -> Self { ModClass { _somefield: String::from("contents"), } } fn noop(&self, x: usize) -> usize { x } } #[pyfunction] fn double(x: i32) -> i32 { x * 2 } #[pymodule_export] pub const USIZE_MIN: usize = usize::MIN; #[pymodule_export] pub const USIZE_MAX: usize = usize::MAX; } ================================================ FILE: pytests/src/path.rs ================================================ use pyo3::prelude::*; #[pymodule] pub mod path { use pyo3::prelude::*; use std::path::{Path, PathBuf}; #[pyfunction] fn make_path() -> PathBuf { Path::new("/root").to_owned() } #[pyfunction] fn take_pathbuf(path: PathBuf) -> PathBuf { path } } ================================================ FILE: pytests/src/pyclasses.rs ================================================ use std::{thread, time}; use pyo3::basic::CompareOp; use pyo3::exceptions::{PyAttributeError, PyStopIteration, PyValueError}; use pyo3::prelude::*; use pyo3::types::{PyComplex, PyType}; #[cfg(not(any(Py_LIMITED_API, GraalPy)))] use pyo3::types::{PyDict, PyTuple}; #[pyclass(from_py_object)] #[derive(Clone, Default)] struct EmptyClass {} #[pymethods] impl EmptyClass { #[new] fn new() -> Self { EmptyClass {} } fn method(&self) {} fn __len__(&self) -> usize { 0 } } /// This is for demonstrating how to return a value from __next__ #[pyclass] #[derive(Default)] struct PyClassIter { count: usize, } #[pymethods] impl PyClassIter { /// A constructor #[new] pub fn new() -> Self { Default::default() } fn __next__(&mut self) -> PyResult { if self.count < 5 { self.count += 1; Ok(self.count) } else { Err(PyStopIteration::new_err("Ended")) } } } #[pyclass] #[derive(Default)] struct PyClassThreadIter { count: usize, } #[pymethods] impl PyClassThreadIter { #[new] pub fn new() -> Self { Default::default() } fn __next__(&mut self, py: Python<'_>) -> usize { let current_count = self.count; self.count += 1; if current_count == 0 { py.detach(|| thread::sleep(time::Duration::from_millis(100))); } self.count } } /// Demonstrates a base class which can operate on the relevant subclass in its constructor. #[pyclass(subclass, skip_from_py_object)] #[derive(Clone, Debug)] struct AssertingBaseClass; #[pymethods] impl AssertingBaseClass { #[new] #[classmethod] fn new(cls: &Bound<'_, PyType>, expected_type: Bound<'_, PyType>) -> PyResult { if !cls.is(&expected_type) { return Err(PyValueError::new_err(format!( "{cls:?} != {expected_type:?}" ))); } Ok(Self) } } #[pyclass] struct ClassWithoutConstructor; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[pyclass(dict)] struct ClassWithDict; #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[pymethods] impl ClassWithDict { #[new] fn new() -> Self { ClassWithDict } } #[cfg(not(any(Py_LIMITED_API, GraalPy)))] // Can't subclass native types on abi3 yet #[pyclass(extends = PyDict)] struct SubClassWithInit; #[cfg(not(any(Py_LIMITED_API, GraalPy)))] #[pymethods] impl SubClassWithInit { #[new] #[pyo3(signature = (*args, **kwargs))] #[allow(unused_variables)] fn __new__(args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>) -> Self { Self } #[pyo3(signature = (*args, **kwargs))] fn __init__( self_: &Bound<'_, Self>, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult<()> { self_ .py_super()? .call_method("__init__", args.to_owned(), kwargs)?; self_.as_super().set_item("__init__", true)?; Ok(()) } } #[pyclass(skip_from_py_object)] #[derive(Clone)] struct ClassWithDecorators { attr: Option, } #[pymethods] impl ClassWithDecorators { #[new] #[classmethod] fn new(_cls: Bound<'_, PyType>) -> Self { Self { attr: Some(0) } } /// A getter #[getter] fn get_attr(&self) -> PyResult { self.attr .ok_or_else(|| PyAttributeError::new_err("attr is not set")) } /// A setter #[setter] fn set_attr(&mut self, value: usize) { self.attr = Some(value); } /// A deleter #[deleter] fn delete_attr(&mut self) { self.attr = None; } /// A class method #[classmethod] fn cls_method(_cls: &Bound<'_, PyType>) -> usize { 1 } /// A static method #[staticmethod] fn static_method() -> usize { 2 } /// A class attribute #[classattr] fn cls_attribute() -> usize { 3 } } #[pyclass(get_all, set_all)] struct PlainObject { /// Foo foo: String, /// Bar bar: usize, } #[derive(FromPyObject, IntoPyObject)] enum AClass { NewType(EmptyClass), Tuple(EmptyClass, EmptyClass), Struct { f: EmptyClass, #[pyo3(item(42))] g: EmptyClass, #[pyo3(default)] h: EmptyClass, }, } #[pyfunction] fn map_a_class(cls: AClass) -> AClass { cls } #[pyclass] struct Number(u64); // TODO: Implementations are just for the example and often not correct #[pymethods] impl Number { #[new] fn new(value: u64) -> Self { Self(value) } fn __str__(&self) -> String { self.0.to_string() } fn __repr__(&self) -> String { format!("{:?}", self.0) } fn __hash__(&self) -> u64 { self.0 } fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { op.matches(self.0.cmp(&other.0)) } fn __add__(&self, other: &Self) -> Self { Self(self.0 + other.0) } fn __sub__(&self, other: &Self) -> Self { Self(self.0 - other.0) } fn __mul__(&self, other: &Self) -> Self { Self(self.0 * other.0) } fn __matmul__(&self, other: &Self) -> Self { Self(self.0 * other.0) } fn __truediv__(&self, other: &Self) -> Self { Self(self.0 / other.0) } fn __floordiv__(&self, other: &Self) -> Self { Self(self.0 / other.0) } fn __mod__(&self, other: &Self) -> Self { Self(self.0 % other.0) } fn __divmod__(&self, other: &Self) -> (Self, Self) { (Self(self.0 / other.0), Self(self.0 % other.0)) } fn __pow__(&self, other: &Self, modulo: Option<&Self>) -> Self { Self(self.0.pow(other.0 as u32) % modulo.map_or(1, |m| m.0)) } fn __rshift__(&self, other: &Self) -> Self { Self(self.0 >> other.0) } fn __lshift__(&self, other: &Self) -> Self { Self(self.0 << other.0) } fn __and__(&self, other: &Self) -> Self { Self(self.0 & other.0) } fn __or__(&self, other: &Self) -> Self { Self(self.0 | other.0) } fn __xor__(&self, other: &Self) -> Self { Self(self.0 ^ other.0) } fn __pos__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __neg__(&self) -> PyResult { if self.0 == 0 { Ok(Self(0)) } else { Err(PyValueError::new_err("not zero")) } } fn __abs__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __invert__(&self) -> Self { Self(!self.0) } fn __int__(&self) -> u64 { self.0 } fn __float__(&self) -> f64 { self.0 as f64 } fn __complex__<'py>(&self, py: Python<'py>) -> Bound<'py, PyComplex> { PyComplex::from_doubles(py, self.0 as f64, 0.) } } #[pymodule] pub mod pyclasses { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] #[pymodule_export] use super::ClassWithDict; #[cfg(not(any(Py_LIMITED_API, GraalPy)))] #[pymodule_export] use super::SubClassWithInit; #[pymodule_export] use super::{ map_a_class, AssertingBaseClass, ClassWithDecorators, ClassWithoutConstructor, EmptyClass, Number, PlainObject, PyClassIter, PyClassThreadIter, }; } ================================================ FILE: pytests/src/pyfunctions.rs ================================================ use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; #[pyfunction(signature = ())] fn none() {} type Any<'py> = Bound<'py, PyAny>; type Dict<'py> = Bound<'py, PyDict>; type Tuple<'py> = Bound<'py, PyTuple>; #[pyfunction(signature = (a, b = None, *, c = None))] fn simple<'py>( a: Any<'py>, b: Option>, c: Option>, ) -> (Any<'py>, Option>, Option>) { (a, b, c) } #[pyfunction(signature = (a, b = None, *args, c = None))] fn simple_args<'py>( a: Any<'py>, b: Option>, args: Tuple<'py>, c: Option>, ) -> (Any<'py>, Option>, Tuple<'py>, Option>) { (a, b, args, c) } #[pyfunction(signature = (a, b = None, c = None, **kwargs))] fn simple_kwargs<'py>( a: Any<'py>, b: Option>, c: Option>, kwargs: Option>, ) -> ( Any<'py>, Option>, Option>, Option>, ) { (a, b, c, kwargs) } #[pyfunction(signature = (a, b = None, *args, c = None, **kwargs))] fn simple_args_kwargs<'py>( a: Any<'py>, b: Option>, args: Tuple<'py>, c: Option>, kwargs: Option>, ) -> ( Any<'py>, Option>, Tuple<'py>, Option>, Option>, ) { (a, b, args, c, kwargs) } #[pyfunction(signature = (*args, **kwargs))] fn args_kwargs<'py>( args: Tuple<'py>, kwargs: Option>, ) -> (Tuple<'py>, Option>) { (args, kwargs) } #[pyfunction(signature = (a, /, b))] fn positional_only<'py>(a: Any<'py>, b: Any<'py>) -> (Any<'py>, Any<'py>) { (a, b) } #[pyfunction(signature = (a = false, b = 0, c = 0.0, d = ""))] fn with_typed_args(a: bool, b: u64, c: f64, d: &str) -> (bool, u64, f64, &str) { (a, b, c, d) } #[cfg(feature = "experimental-inspect")] #[pyfunction(signature = (a: "int", *_args: "str", _b: "int | None" = None, **_kwargs: "bool") -> "int")] fn with_custom_type_annotations<'py>( a: Any<'py>, _args: Tuple<'py>, _b: Option>, _kwargs: Option>, ) -> Any<'py> { a } #[cfg(feature = "experimental-async")] #[pyfunction] async fn with_async() {} #[expect(clippy::too_many_arguments)] #[pyfunction( signature = ( *, ant = None, bear = None, cat = None, dog = None, elephant = None, fox = None, goat = None, horse = None, iguana = None, jaguar = None, koala = None, lion = None, monkey = None, newt = None, owl = None, penguin = None ) )] fn many_keyword_arguments<'py>( ant: Option<&'_ Bound<'py, PyAny>>, bear: Option<&'_ Bound<'py, PyAny>>, cat: Option<&'_ Bound<'py, PyAny>>, dog: Option<&'_ Bound<'py, PyAny>>, elephant: Option<&'_ Bound<'py, PyAny>>, fox: Option<&'_ Bound<'py, PyAny>>, goat: Option<&'_ Bound<'py, PyAny>>, horse: Option<&'_ Bound<'py, PyAny>>, iguana: Option<&'_ Bound<'py, PyAny>>, jaguar: Option<&'_ Bound<'py, PyAny>>, koala: Option<&'_ Bound<'py, PyAny>>, lion: Option<&'_ Bound<'py, PyAny>>, monkey: Option<&'_ Bound<'py, PyAny>>, newt: Option<&'_ Bound<'py, PyAny>>, owl: Option<&'_ Bound<'py, PyAny>>, penguin: Option<&'_ Bound<'py, PyAny>>, ) { std::hint::black_box(( ant, bear, cat, dog, elephant, fox, goat, horse, iguana, jaguar, koala, lion, monkey, newt, owl, penguin, )); } #[pymodule] pub mod pyfunctions { #[cfg(feature = "experimental-async")] #[pymodule_export] use super::with_async; #[cfg(feature = "experimental-inspect")] #[pymodule_export] use super::with_custom_type_annotations; #[pymodule_export] use super::{ args_kwargs, many_keyword_arguments, none, positional_only, simple, simple_args, simple_args_kwargs, simple_kwargs, with_typed_args, }; } ================================================ FILE: pytests/src/sequence.rs ================================================ use pyo3::prelude::*; #[pymodule] pub mod sequence { use pyo3::prelude::*; use pyo3::types::PyString; #[pyfunction] fn vec_to_vec_i32(vec: Vec) -> Vec { vec } #[pyfunction] fn array_to_array_i32(arr: [i32; 3]) -> [i32; 3] { arr } #[pyfunction] fn vec_to_vec_pystring(vec: Vec>) -> Vec> { vec } } ================================================ FILE: pytests/src/subclassing.rs ================================================ //! Test for [#220](https://github.com/PyO3/pyo3/issues/220) use pyo3::prelude::*; #[pymodule(gil_used = false)] pub mod subclassing { use pyo3::prelude::*; #[cfg(not(any(Py_LIMITED_API, GraalPy)))] use pyo3::types::PyDict; #[pyclass(subclass)] pub struct Subclassable {} #[pymethods] impl Subclassable { #[new] fn new() -> Self { Subclassable {} } fn __str__(&self) -> &'static str { "Subclassable" } } #[pyclass(extends = Subclassable)] pub struct Subclass {} #[pymethods] impl Subclass { #[new] fn new() -> (Self, Subclassable) { (Subclass {}, Subclassable::new()) } fn __str__(&self) -> &'static str { "Subclass" } } #[cfg(not(any(Py_LIMITED_API, GraalPy)))] #[pyclass(extends = PyDict)] pub struct SubDict {} #[cfg(not(any(Py_LIMITED_API, GraalPy)))] #[pymethods] impl SubDict { #[new] fn new() -> Self { Self {} } fn __str__(&self) -> &'static str { "SubDict" } } } ================================================ FILE: pytests/stubs/__init__.pyi ================================================ """ This is documentation for the main module of PyO3 integration tests It provides multiple modules to do tests """ from _typeshed import Incomplete def __getattr__(name: str) -> Incomplete: ... ================================================ FILE: pytests/stubs/awaitable.pyi ================================================ from typing import Any, final @final class FutureAwaitable: def __await__(self, /) -> FutureAwaitable: ... def __iter__(self, /) -> FutureAwaitable: ... def __new__(cls, /, result: Any) -> FutureAwaitable: ... def __next__(self, /) -> FutureAwaitable: ... @property def _asyncio_future_blocking(self, /) -> bool: ... @_asyncio_future_blocking.setter def _asyncio_future_blocking(self, /, value: bool) -> None: ... @final class IterAwaitable: def __await__(self, /) -> IterAwaitable: ... def __iter__(self, /) -> IterAwaitable: ... def __new__(cls, /, result: Any) -> IterAwaitable: ... def __next__(self, /) -> Any: ... ================================================ FILE: pytests/stubs/buf_and_str.pyi ================================================ """ Objects related to PyBuffer and PyStr """ from collections.abc import Sequence from typing import Any, final @final class BytesExtractor: """ This is for confirming that PyBuffer does not cause memory leak """ def __new__(cls, /) -> BytesExtractor: ... @staticmethod def from_buffer(buf: Any) -> int: ... @staticmethod def from_bytes(bytes: bytes) -> int: ... @staticmethod def from_str(string: str) -> int: ... @staticmethod def from_str_lossy(string: str) -> int: ... def map_byte_cow(bytes: Sequence[int]) -> bytes: ... def map_byte_slice(bytes: bytes) -> bytes: ... def map_byte_vec(bytes: Sequence[int]) -> bytes: ... def return_memoryview() -> memoryview: ... ================================================ FILE: pytests/stubs/comparisons.pyi ================================================ from typing import final @final class Eq: def __eq__(self, /, other: object) -> bool: ... def __ne__(self, /, other: object) -> bool: ... def __new__(cls, /, value: int) -> Eq: ... @final class EqDefaultNe: def __eq__(self, /, other: object) -> bool: ... def __new__(cls, /, value: int) -> EqDefaultNe: ... @final class EqDerived: def __eq__(self, /, other: object) -> bool: ... def __ne__(self, /, other: object) -> bool: ... def __new__(cls, /, value: int) -> EqDerived: ... @final class Ordered: def __eq__(self, /, other: object) -> bool: ... def __ge__(self, /, other: object) -> bool: ... def __gt__(self, /, other: object) -> bool: ... def __le__(self, /, other: object) -> bool: ... def __lt__(self, /, other: object) -> bool: ... def __ne__(self, /, other: object) -> bool: ... def __new__(cls, /, value: int) -> Ordered: ... @final class OrderedDefaultNe: def __eq__(self, /, other: object) -> bool: ... def __ge__(self, /, other: object) -> bool: ... def __gt__(self, /, other: object) -> bool: ... def __le__(self, /, other: object) -> bool: ... def __lt__(self, /, other: object) -> bool: ... def __new__(cls, /, value: int) -> OrderedDefaultNe: ... @final class OrderedDerived: def __eq__(self, /, other: object) -> bool: ... def __ge__(self, /, other: object) -> bool: ... def __gt__(self, /, other: object) -> bool: ... def __hash__(self, /) -> int: ... def __le__(self, /, other: object) -> bool: ... def __lt__(self, /, other: object) -> bool: ... def __ne__(self, /, other: object) -> bool: ... def __new__(cls, /, value: int) -> OrderedDerived: ... def __str__(self, /) -> str: ... @final class OrderedRichCmp: def __eq__(self, /, other: object) -> bool: ... def __ge__(self, /, other: object) -> bool: ... def __gt__(self, /, other: object) -> bool: ... def __le__(self, /, other: object) -> bool: ... def __lt__(self, /, other: object) -> bool: ... def __ne__(self, /, other: object) -> bool: ... def __new__(cls, /, value: int) -> OrderedRichCmp: ... ================================================ FILE: pytests/stubs/consts.pyi ================================================ from typing import Final, final ESCAPING: Final = "S\0\x01\t\n\r\"'\\" """ We experiment with "escaping" """ PI: Final[float] """ Exports PI constant as part of the module """ @final class ClassWithConst: INSTANCE: Final[ClassWithConst] """ A constant """ ================================================ FILE: pytests/stubs/datetime.pyi ================================================ from datetime import date, datetime, time, timedelta, tzinfo from typing import final @final class TzClass(tzinfo): def __new__(cls, /) -> TzClass: ... def dst(self, _dt: datetime | None, /) -> timedelta | None: ... def tzname(self, _dt: datetime | None, /) -> str: ... def utcoffset(self, _dt: datetime | None, /) -> timedelta: ... def date_from_timestamp(timestamp: float) -> date: ... def datetime_from_timestamp(ts: float, tz: tzinfo | None = None) -> datetime: ... def get_date_tuple(d: date) -> tuple: ... def get_datetime_tuple(dt: datetime) -> tuple: ... def get_datetime_tuple_fold(dt: datetime) -> tuple: ... def get_datetime_tzinfo(dt: datetime) -> tzinfo | None: ... def get_delta_tuple(delta: timedelta) -> tuple: ... def get_time_tuple(dt: time) -> tuple: ... def get_time_tuple_fold(dt: time) -> tuple: ... def get_time_tzinfo(dt: time) -> tzinfo | None: ... def make_date(year: int, month: int, day: int) -> date: ... def make_datetime( year: int, month: int, day: int, hour: int, minute: int, second: int, microsecond: int, tzinfo: tzinfo | None = None, ) -> datetime: ... def make_delta(days: int, seconds: int, microseconds: int) -> timedelta: ... def make_time( hour: int, minute: int, second: int, microsecond: int, tzinfo: tzinfo | None = None ) -> time: ... def time_with_fold( hour: int, minute: int, second: int, microsecond: int, tzinfo: tzinfo | None, fold: bool, ) -> time: ... ================================================ FILE: pytests/stubs/dict_iter.pyi ================================================ from typing import final @final class DictSize: def __new__(cls, /, expected: int) -> DictSize: ... def iter_dict(self, /, dict: dict) -> int: ... ================================================ FILE: pytests/stubs/enums.pyi ================================================ from typing import Any, Final, final class ComplexEnum: @final class EmptyStruct(ComplexEnum): __match_args__: Final = () def __new__(cls, /) -> ComplexEnum.EmptyStruct: ... @final class Float(ComplexEnum): __match_args__: Final = ("f",) def __new__(cls, /, f: float) -> ComplexEnum.Float: ... @property def f(self, /) -> float: ... @final class Int(ComplexEnum): """ A struct variant """ __match_args__: Final = ("i",) def __new__(cls, /, i: int) -> ComplexEnum.Int: ... @property def i(self, /) -> int: """ An integer """ @final class MultiFieldStruct(ComplexEnum): __match_args__: Final = ("a", "b", "c") def __new__( cls, /, a: int, b: float, c: bool ) -> ComplexEnum.MultiFieldStruct: ... @property def a(self, /) -> int: ... @property def b(self, /) -> float: ... @property def c(self, /) -> bool: ... @final class Str(ComplexEnum): __match_args__: Final = ("s",) def __new__(cls, /, s: str) -> ComplexEnum.Str: ... @property def s(self, /) -> str: ... @final class VariantWithDefault(ComplexEnum): __match_args__: Final = ("a", "b") def __new__( cls, /, a: int = 42, b: str | None = None ) -> ComplexEnum.VariantWithDefault: ... @property def a(self, /) -> int: ... @property def b(self, /) -> str | None: ... class MixedComplexEnum: @final class Empty(MixedComplexEnum): __match_args__: Final = () def __getitem__(self, /, key: int) -> Any: ... def __len__(self, /) -> int: ... def __new__(cls, /) -> MixedComplexEnum.Empty: ... @final class Nothing(MixedComplexEnum): __match_args__: Final = () def __new__(cls, /) -> MixedComplexEnum.Nothing: ... @final class SimpleEnum: Friday: Final[SimpleEnum] Monday: Final[SimpleEnum] Saturday: Final[SimpleEnum] Sunday: Final[SimpleEnum] """ A variant """ Thursday: Final[SimpleEnum] Tuesday: Final[SimpleEnum] Wednesday: Final[SimpleEnum] def __eq__(self, /, other: object) -> bool: ... def __int__(self, /) -> int: ... def __ne__(self, /, other: object) -> bool: ... def __repr__(self, /) -> str: ... @final class SimpleEnumWithoutDerive: A: Final[SimpleEnumWithoutDerive] B: Final[SimpleEnumWithoutDerive] def __int__(self, /) -> int: ... def __repr__(self, /) -> str: ... class SimpleTupleEnum: @final class Int(SimpleTupleEnum): __match_args__: Final = ("_0",) @property def _0(self, /) -> int: ... def __getitem__(self, /, key: int) -> Any: ... def __len__(self, /) -> int: ... def __new__(cls, /, _0: int) -> SimpleTupleEnum.Int: ... @final class Str(SimpleTupleEnum): __match_args__: Final = ("_0",) @property def _0(self, /) -> str: ... def __getitem__(self, /, key: int) -> Any: ... def __len__(self, /) -> int: ... def __new__(cls, /, _0: str) -> SimpleTupleEnum.Str: ... class TupleEnum: @final class EmptyTuple(TupleEnum): __match_args__: Final = () def __getitem__(self, /, key: int) -> Any: ... def __len__(self, /) -> int: ... def __new__(cls, /) -> TupleEnum.EmptyTuple: ... @final class Full(TupleEnum): __match_args__: Final = ("_0", "_1", "_2") @property def _0(self, /) -> int: ... @property def _1(self, /) -> float: ... @property def _2(self, /) -> bool: ... def __getitem__(self, /, key: int) -> Any: ... def __len__(self, /) -> int: ... def __new__(cls, /, _0: int, _1: float, _2: bool) -> TupleEnum.Full: ... @final class FullWithDefault(TupleEnum): """ A tuple variant """ __match_args__: Final = ("_0", "_1", "_2") @property def _0(self, /) -> int: ... @property def _1(self, /) -> float: ... @property def _2(self, /) -> bool: ... def __getitem__(self, /, key: int) -> Any: ... def __len__(self, /) -> int: ... def __new__( cls, /, _0: int = 1, _1: float = 1.0, _2: bool = True ) -> TupleEnum.FullWithDefault: ... def do_complex_stuff(thing: ComplexEnum) -> ComplexEnum: ... def do_mixed_complex_stuff(thing: MixedComplexEnum) -> MixedComplexEnum: ... def do_simple_stuff(thing: SimpleEnum) -> SimpleEnum: ... def do_tuple_stuff(thing: TupleEnum) -> TupleEnum: ... ================================================ FILE: pytests/stubs/exception.pyi ================================================ from _typeshed import Incomplete from typing import Any def raise_my_value_error() -> None: ... def return_my_value_error() -> Any: ... def return_pyerr() -> BaseException: ... def return_value_error() -> ValueError: ... def __getattr__(name: str) -> Incomplete: ... ================================================ FILE: pytests/stubs/misc.pyi ================================================ from typing import Any def accepts_bool(val: bool) -> bool: ... def get_item_and_run_callback(dict: dict, callback: Any) -> None: ... def get_type_fully_qualified_name(obj: Any) -> str: ... def hammer_attaching_in_thread() -> Any: ... def issue_219() -> None: ... ================================================ FILE: pytests/stubs/objstore.pyi ================================================ from typing import Any, final @final class ObjStore: def __new__(cls, /) -> ObjStore: ... def push(self, /, obj: Any) -> None: ... ================================================ FILE: pytests/stubs/othermod.pyi ================================================ from typing import Final, final USIZE_MAX: Final[int] USIZE_MIN: Final[int] @final class ModClass: def __new__(cls, /) -> ModClass: ... def noop(self, /, x: int) -> int: ... def double(x: int) -> int: ... ================================================ FILE: pytests/stubs/path.pyi ================================================ from os import PathLike from pathlib import Path def make_path() -> Path: ... def take_pathbuf(path: str | PathLike[str]) -> Path: ... ================================================ FILE: pytests/stubs/pyclasses.pyi ================================================ from _typeshed import Incomplete from typing import Final, final class AssertingBaseClass: """ Demonstrates a base class which can operate on the relevant subclass in its constructor. """ def __new__(cls, /, expected_type: type) -> AssertingBaseClass: ... @final class ClassWithDecorators: cls_attribute: Final[int] """ A class attribute """ def __new__(cls, /) -> ClassWithDecorators: ... @property def attr(self, /) -> int: """ A getter """ @attr.deleter def attr(self, /) -> None: """ A deleter """ @attr.setter def attr(self, /, value: int) -> None: """ A setter """ @classmethod def cls_method(cls, /) -> int: """ A class method """ @staticmethod def static_method() -> int: """ A static method """ @final class ClassWithDict: def __new__(cls, /) -> ClassWithDict: ... @final class ClassWithoutConstructor: ... @final class EmptyClass: def __len__(self, /) -> int: ... def __new__(cls, /) -> EmptyClass: ... def method(self, /) -> None: ... @final class Number: def __abs__(self, /) -> Number: ... def __add__(self, /, other: object) -> Number: ... def __and__(self, /, other: object) -> Number: ... def __complex__(self, /) -> complex: ... def __divmod__(self, /, other: object) -> tuple[Number, Number]: ... def __eq__(self, /, other: object) -> bool: ... def __float__(self, /) -> float: ... def __floordiv__(self, /, other: object) -> Number: ... def __ge__(self, /, other: object) -> bool: ... def __gt__(self, /, other: object) -> bool: ... def __hash__(self, /) -> int: ... def __int__(self, /) -> int: ... def __invert__(self, /) -> Number: ... def __le__(self, /, other: object) -> bool: ... def __lshift__(self, /, other: object) -> Number: ... def __lt__(self, /, other: object) -> bool: ... def __matmul__(self, /, other: object) -> Number: ... def __mod__(self, /, other: object) -> Number: ... def __mul__(self, /, other: object) -> Number: ... def __ne__(self, /, other: object) -> bool: ... def __neg__(self, /) -> Number: ... def __new__(cls, /, value: int) -> Number: ... def __or__(self, /, other: object) -> Number: ... def __pos__(self, /) -> Number: ... def __pow__(self, /, other: object, modulo: object) -> Number: ... def __repr__(self, /) -> str: ... def __rshift__(self, /, other: object) -> Number: ... def __str__(self, /) -> str: ... def __sub__(self, /, other: object) -> Number: ... def __truediv__(self, /, other: object) -> Number: ... def __xor__(self, /, other: object) -> Number: ... @final class PlainObject: @property def bar(self, /) -> int: """ Bar """ @bar.setter def bar(self, /, value: int) -> None: """ Bar """ @property def foo(self, /) -> str: """ Foo """ @foo.setter def foo(self, /, value: str) -> None: """ Foo """ @final class PyClassIter: """ This is for demonstrating how to return a value from __next__ """ def __new__(cls, /) -> PyClassIter: """ A constructor """ def __next__(self, /) -> int: ... @final class PyClassThreadIter: def __new__(cls, /) -> PyClassThreadIter: ... def __next__(self, /) -> int: ... @final class SubClassWithInit(dict): def __init__(self, /, *args, **kwargs) -> None: ... def __new__(cls, /, *args, **kwargs) -> SubClassWithInit: ... def map_a_class( cls: EmptyClass | tuple[EmptyClass, EmptyClass] | Incomplete, ) -> EmptyClass | tuple[EmptyClass, EmptyClass] | Incomplete: ... ================================================ FILE: pytests/stubs/pyfunctions.pyi ================================================ from typing import Any def args_kwargs(*args, **kwargs) -> tuple[tuple, dict | None]: ... def many_keyword_arguments( *, ant: Any | None = None, bear: Any | None = None, cat: Any | None = None, dog: Any | None = None, elephant: Any | None = None, fox: Any | None = None, goat: Any | None = None, horse: Any | None = None, iguana: Any | None = None, jaguar: Any | None = None, koala: Any | None = None, lion: Any | None = None, monkey: Any | None = None, newt: Any | None = None, owl: Any | None = None, penguin: Any | None = None, ) -> None: ... def none() -> None: ... def positional_only(a: Any, /, b: Any) -> tuple[Any, Any]: ... def simple( a: Any, b: Any | None = None, *, c: Any | None = None ) -> tuple[Any, Any | None, Any | None]: ... def simple_args( a: Any, b: Any | None = None, *args, c: Any | None = None ) -> tuple[Any, Any | None, tuple, Any | None]: ... def simple_args_kwargs( a: Any, b: Any | None = None, *args, c: Any | None = None, **kwargs ) -> tuple[Any, Any | None, tuple, Any | None, dict | None]: ... def simple_kwargs( a: Any, b: Any | None = None, c: Any | None = None, **kwargs ) -> tuple[Any, Any | None, Any | None, dict | None]: ... async def with_async() -> None: ... def with_custom_type_annotations( a: "int", *_args: "str", _b: "int | None" = None, **_kwargs: "bool" ) -> "int": ... def with_typed_args( a: bool = False, b: int = 0, c: float = 0.0, d: str = "" ) -> tuple[bool, int, float, str]: ... ================================================ FILE: pytests/stubs/sequence.pyi ================================================ from collections.abc import Sequence def array_to_array_i32(arr: Sequence[int]) -> list[int]: ... def vec_to_vec_i32(vec: Sequence[int]) -> list[int]: ... def vec_to_vec_pystring(vec: Sequence[str]) -> list[str]: ... ================================================ FILE: pytests/stubs/subclassing.pyi ================================================ from typing import final @final class SubDict(dict): def __new__(cls, /) -> SubDict: ... def __str__(self, /) -> str: ... @final class Subclass(Subclassable): def __new__(cls, /) -> Subclass: ... def __str__(self, /) -> str: ... class Subclassable: def __new__(cls, /) -> Subclassable: ... def __str__(self, /) -> str: ... ================================================ FILE: pytests/tests/test_awaitable.py ================================================ import pytest import sys from pyo3_pytests.awaitable import IterAwaitable, FutureAwaitable @pytest.mark.skipif( sys.implementation.name == "graalpy", reason="GraalPy's asyncio module has a bug with native classes, see oracle/graalpython#365", ) @pytest.mark.asyncio async def test_iter_awaitable(): assert await IterAwaitable(5) == 5 @pytest.mark.skipif( sys.implementation.name == "graalpy", reason="GraalPy's asyncio module has a bug with native classes, see oracle/graalpython#365", ) @pytest.mark.asyncio async def test_future_awaitable(): assert await FutureAwaitable(5) == 5 ================================================ FILE: pytests/tests/test_buf_and_str.py ================================================ from pyo3_pytests.buf_and_str import BytesExtractor, return_memoryview def test_extract_bytes(): extractor = BytesExtractor() message = b'\\(-"-;) A message written in bytes' assert extractor.from_bytes(message) == len(message) def test_extract_str(): extractor = BytesExtractor() message = '\\(-"-;) A message written as a string' assert extractor.from_str(message) == len(message) def test_extract_str_lossy(): extractor = BytesExtractor() message = '\\(-"-;) A message written with a trailing surrogate \ud800' rust_surrogate_len = extractor.from_str_lossy("\ud800") assert extractor.from_str_lossy(message) == len(message) - 1 + rust_surrogate_len def test_extract_buffer(): extractor = BytesExtractor() message = b'\\(-"-;) A message written in bytes' assert extractor.from_buffer(message) == len(message) arr = bytearray(b'\\(-"-;) A message written in bytes') assert extractor.from_buffer(arr) == len(arr) def test_return_memoryview(): view = return_memoryview() assert view.readonly assert view.contiguous assert view.tobytes() == b"hello world" ================================================ FILE: pytests/tests/test_comparisons.py ================================================ from typing import Type, TypeVar import sys import pytest from pyo3_pytests.comparisons import ( Eq, EqDefaultNe, EqDerived, Ordered, OrderedDefaultNe, OrderedDerived, OrderedRichCmp, ) from typing_extensions import Self class PyEq: def __init__(self, x: int) -> None: self.x = x def __eq__(self, other: object) -> bool: if isinstance(other, self.__class__): return self.x == other.x else: return NotImplemented def __ne__(self, other: object) -> bool: if isinstance(other, self.__class__): return self.x != other.x else: return NotImplemented EqType = TypeVar("EqType", Eq, EqDerived, PyEq) @pytest.mark.skipif( sys.implementation.name == "graalpy" and __graalpython__.get_graalvm_version().startswith("24.1"), # type: ignore[name-defined] # noqa: F821 reason="Bug in GraalPy 24.1", ) @pytest.mark.parametrize( "ty", (Eq, EqDerived, PyEq), ids=("rust", "rust-derived", "python") ) def test_eq(ty: Type[EqType]): a = ty(0) b = ty(0) c = ty(1) assert a == b assert not (a != b) assert a != c assert not (a == c) assert b == a assert not (a != b) assert b != c assert not (b == c) assert not a == 0 assert a != 0 assert not b == 0 assert b != 1 assert not c == 1 assert c != 1 with pytest.raises(TypeError): assert a <= b # type: ignore[operator] with pytest.raises(TypeError): assert a >= b # type: ignore[operator] with pytest.raises(TypeError): assert a < c # type: ignore[operator] with pytest.raises(TypeError): assert c > a # type: ignore[operator] class PyEqDefaultNe: def __init__(self, x: int) -> None: self.x = x def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) and self.x == other.x EqDefaultType = TypeVar("EqDefaultType", EqDefaultNe, PyEqDefaultNe) @pytest.mark.parametrize("ty", (EqDefaultNe, PyEqDefaultNe), ids=("rust", "python")) def test_eq_default_ne(ty: Type[EqDefaultType]): a = ty(0) b = ty(0) c = ty(1) assert a == b assert not (a != b) assert a != c assert not (a == c) assert b == a assert not (a != b) assert b != c assert not (b == c) with pytest.raises(TypeError): assert a <= b # type: ignore[operator] with pytest.raises(TypeError): assert a >= b # type: ignore[operator] with pytest.raises(TypeError): assert a < c # type: ignore[operator] with pytest.raises(TypeError): assert c > a # type: ignore[operator] class PyOrdered: def __init__(self, x: int) -> None: self.x = x def __lt__(self, other: Self) -> bool: return self.x < other.x def __le__(self, other: Self) -> bool: return self.x <= other.x def __eq__(self, other: object) -> bool: if not isinstance(other, self.__class__): return NotImplemented return self.x == other.x def __ne__(self, other: object) -> bool: if not isinstance(other, self.__class__): return NotImplemented return self.x != other.x def __gt__(self, other: Self) -> bool: return self.x >= other.x def __ge__(self, other: Self) -> bool: return self.x >= other.x OrderedType = TypeVar("OrderedType", Ordered, OrderedDerived, OrderedRichCmp, PyOrdered) @pytest.mark.parametrize( "ty", (Ordered, OrderedDerived, OrderedRichCmp, PyOrdered), ids=("rust", "rust-derived", "rust-richcmp", "python"), ) def test_ordered(ty: Type[OrderedType]): a = ty(0) b = ty(0) c = ty(1) assert a == b assert a <= b assert a >= b assert a != c assert a <= c assert b == a assert b <= a assert b >= a assert b != c assert b <= c assert c != a assert c != b assert c > a assert c >= a assert c > b assert c >= b class PyOrderedDefaultNe: def __init__(self, x: int) -> None: self.x = x def __lt__(self, other: Self) -> bool: return self.x < other.x def __le__(self, other: Self) -> bool: return self.x <= other.x def __eq__(self, other: object) -> bool: if not isinstance(other, self.__class__): return NotImplemented return self.x == other.x def __gt__(self, other: Self) -> bool: return self.x >= other.x def __ge__(self, other: Self) -> bool: return self.x >= other.x OrderedDefaultType = TypeVar("OrderedDefaultType", OrderedDefaultNe, PyOrderedDefaultNe) @pytest.mark.parametrize( "ty", (OrderedDefaultNe, PyOrderedDefaultNe), ids=("rust", "python") ) def test_ordered_default_ne(ty: Type[OrderedDefaultType]): a = ty(0) b = ty(0) c = ty(1) assert a == b assert not (a != b) assert a <= b assert a >= b assert a != c assert not (a == c) assert a <= c assert b == a assert not (b != a) assert b <= a assert b >= a assert b != c assert not (b == c) assert b <= c assert c != a assert not (c == a) assert c != b assert not (c == b) assert c > a assert c >= a assert c > b assert c >= b ================================================ FILE: pytests/tests/test_datetime.py ================================================ import datetime as pdt import platform import re import struct import sys import pyo3_pytests.datetime as rdt import pytest from hypothesis import example, given from hypothesis import strategies as st # Constants def _get_utc(): timezone = getattr(pdt, "timezone", None) if timezone: return timezone.utc else: class UTC(pdt.tzinfo): def utcoffset(self, dt): return pdt.timedelta(0) def dst(self, dt): return pdt.timedelta(0) def tzname(self, dt): return "UTC" return UTC() UTC = _get_utc() MAX_SECONDS = int(pdt.timedelta.max.total_seconds()) MIN_SECONDS = int(pdt.timedelta.min.total_seconds()) MAX_DAYS = pdt.timedelta.max // pdt.timedelta(days=1) MIN_DAYS = pdt.timedelta.min // pdt.timedelta(days=1) MAX_MICROSECONDS = int(pdt.timedelta.max.total_seconds() * 1e6) MIN_MICROSECONDS = int(pdt.timedelta.min.total_seconds() * 1e6) # The reason we don't use platform.architecture() here is that it's not # reliable on macOS. See https://stackoverflow.com/a/1405971/823869. Similarly, # sys.maxsize is not reliable on Windows. See # https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os/1405971#comment6209952_1405971 # and https://stackoverflow.com/a/3411134/823869. _pointer_size = struct.calcsize("P") if _pointer_size == 8: IS_32_BIT = False elif _pointer_size == 4: IS_32_BIT = True else: raise RuntimeError("unexpected pointer size: " + repr(_pointer_size)) IS_WINDOWS = sys.platform == "win32" if IS_WINDOWS: MIN_DATETIME = pdt.datetime(1970, 1, 1, 0, 0, 0) if IS_32_BIT: MAX_DATETIME = pdt.datetime(2038, 1, 18, 23, 59, 59) else: MAX_DATETIME = pdt.datetime(3000, 12, 31, 23, 59, 59) else: if IS_32_BIT: # TS ±2147483648 (2**31) MIN_DATETIME = pdt.datetime(1901, 12, 13, 20, 45, 52) MAX_DATETIME = pdt.datetime(2038, 1, 19, 3, 14, 8) else: MIN_DATETIME = pdt.datetime(1, 1, 2, 0, 0) MAX_DATETIME = pdt.datetime(9999, 12, 31, 18, 59, 59) PYPY = platform.python_implementation() == "PyPy" # Tests def test_date(): assert rdt.make_date(2017, 9, 1) == pdt.date(2017, 9, 1) @given(d=st.dates()) def test_date_accessors(d): act = rdt.get_date_tuple(d) exp = (d.year, d.month, d.day) assert act == exp def test_invalid_date_fails(): with pytest.raises(ValueError): rdt.make_date(2017, 2, 30) @given(d=st.dates(MIN_DATETIME.date(), MAX_DATETIME.date())) def test_date_from_timestamp(d): try: ts = pdt.datetime.timestamp(d) except Exception: # out of range for timestamp return try: expected = pdt.date.fromtimestamp(ts) except Exception as pdt_fail: # date from timestamp failed; expect the same from Rust binding with pytest.raises(type(pdt_fail)) as exc_info: rdt.date_from_timestamp(ts) assert str(exc_info.value) == str(pdt_fail) else: assert rdt.date_from_timestamp(int(ts)) == expected @pytest.mark.parametrize( "args, kwargs", [ ((0, 0, 0, 0, None), {}), ((1, 12, 14, 124731), {}), ((1, 12, 14, 124731), {"tzinfo": UTC}), ], ) def test_time(args, kwargs): act = rdt.make_time(*args, **kwargs) exp = pdt.time(*args, **kwargs) assert act == exp assert act.tzinfo is exp.tzinfo assert rdt.get_time_tzinfo(act) == exp.tzinfo @given(t=st.times()) def test_time_hypothesis(t): act = rdt.get_time_tuple(t) exp = (t.hour, t.minute, t.second, t.microsecond) assert act == exp @given(t=st.times()) def test_time_tuple_fold(t): t_nofold = t.replace(fold=0) t_fold = t.replace(fold=1) for t in (t_nofold, t_fold): act = rdt.get_time_tuple_fold(t) exp = (t.hour, t.minute, t.second, t.microsecond, t.fold) assert act == exp @pytest.mark.parametrize("fold", [False, True]) def test_time_with_fold(fold): t = rdt.time_with_fold(0, 0, 0, 0, None, fold) assert t.fold == fold @pytest.mark.parametrize( "args", [(-1, 0, 0, 0), (0, -1, 0, 0), (0, 0, -1, 0), (0, 0, 0, -1)] ) def test_invalid_time_fails_overflow(args): with pytest.raises(OverflowError): rdt.make_time(*args) @pytest.mark.parametrize( "args", [ (24, 0, 0, 0), (25, 0, 0, 0), (0, 60, 0, 0), (0, 61, 0, 0), (0, 0, 60, 0), (0, 0, 61, 0), (0, 0, 0, 1000000), ], ) def test_invalid_time_fails(args): with pytest.raises(ValueError): rdt.make_time(*args) @pytest.mark.parametrize( "args", [ ("0", 0, 0, 0), (0, "0", 0, 0), (0, 0, "0", 0), (0, 0, 0, "0"), (0, 0, 0, 0, "UTC"), ], ) def test_time_typeerror(args): with pytest.raises(TypeError): rdt.make_time(*args) @pytest.mark.parametrize( "args, kwargs", [((2017, 9, 1, 12, 45, 30, 0), {}), ((2017, 9, 1, 12, 45, 30, 0), {"tzinfo": UTC})], ) def test_datetime(args, kwargs): act = rdt.make_datetime(*args, **kwargs) exp = pdt.datetime(*args, **kwargs) assert act == exp assert act.tzinfo is exp.tzinfo assert rdt.get_datetime_tzinfo(act) == exp.tzinfo @given(dt=st.datetimes()) def test_datetime_tuple(dt): act = rdt.get_datetime_tuple(dt) exp = dt.timetuple()[0:6] + (dt.microsecond,) assert act == exp @given(dt=st.datetimes()) def test_datetime_tuple_fold(dt): dt_fold = dt.replace(fold=1) dt_nofold = dt.replace(fold=0) for dt in (dt_fold, dt_nofold): act = rdt.get_datetime_tuple_fold(dt) exp = dt.timetuple()[0:6] + (dt.microsecond, dt.fold) assert act == exp def test_invalid_datetime_fails(): with pytest.raises(ValueError): rdt.make_datetime(2011, 1, 42, 0, 0, 0, 0) def test_datetime_typeerror(): with pytest.raises(TypeError): rdt.make_datetime("2011", 1, 1, 0, 0, 0, 0) @given(dt=st.datetimes(MIN_DATETIME, MAX_DATETIME)) @example(dt=pdt.datetime(1971, 1, 2, 0, 0)) def test_datetime_from_timestamp(dt): try: ts = pdt.datetime.timestamp(dt) except Exception: # out of range for timestamp return try: expected = pdt.datetime.fromtimestamp(ts) except Exception as pdt_fail: # datetime from timestamp failed; expect the same from Rust binding with pytest.raises(type(pdt_fail)) as exc_info: rdt.datetime_from_timestamp(ts) assert str(exc_info.value) == str(pdt_fail) else: assert rdt.datetime_from_timestamp(ts) == expected def test_datetime_from_timestamp_tzinfo(): d1 = rdt.datetime_from_timestamp(0, tz=UTC) d2 = rdt.datetime_from_timestamp(0, tz=UTC) assert d1 == d2 assert d1.tzinfo is d2.tzinfo @pytest.mark.parametrize( "args", [ (0, 0, 0), (1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (1, -1, 0), (-1, 1, 0), (0, 0, 123456), (0, 0, -123456), ], ) def test_delta(args): act = pdt.timedelta(*args) exp = rdt.make_delta(*args) assert act == exp @given(td=st.timedeltas()) def test_delta_accessors(td): act = rdt.get_delta_tuple(td) exp = (td.days, td.seconds, td.microseconds) assert act == exp @pytest.mark.parametrize( "args,err_type", [ ((MAX_DAYS + 1, 0, 0), OverflowError), ((MIN_DAYS - 1, 0, 0), OverflowError), ((0, MAX_SECONDS + 1, 0), OverflowError), ((0, MIN_SECONDS - 1, 0), OverflowError), ((0, 0, MAX_MICROSECONDS + 1), OverflowError), ((0, 0, MIN_MICROSECONDS - 1), OverflowError), (("0", 0, 0), TypeError), ((0, "0", 0), TypeError), ((0, 0, "0"), TypeError), ], ) def test_delta_err(args, err_type): with pytest.raises(err_type): rdt.make_delta(*args) def test_tz_class(): tzi = rdt.TzClass() dt = pdt.datetime(2018, 1, 1, tzinfo=tzi) assert dt.tzname() == "+01:00" assert dt.utcoffset() == pdt.timedelta(hours=1) assert dt.dst() is None def test_tz_class_introspection(): tzi = rdt.TzClass() assert tzi.__class__ == rdt.TzClass # PyPy generates for some reason. assert re.match(r"^<[\w\.]*TzClass object at", repr(tzi)) ================================================ FILE: pytests/tests/test_dict_iter.py ================================================ import pytest from pyo3_pytests.dict_iter import DictSize @pytest.mark.parametrize("size", [64, 128, 256]) def test_size(size): d = {} for i in range(size): d[i] = str(i) assert DictSize(len(d)).iter_dict(d) == size ================================================ FILE: pytests/tests/test_enums.py ================================================ import pytest from pyo3_pytests import enums def test_complex_enum_variant_constructors(): int_variant = enums.ComplexEnum.Int(42) assert isinstance(int_variant, enums.ComplexEnum.Int) float_variant = enums.ComplexEnum.Float(3.14) assert isinstance(float_variant, enums.ComplexEnum.Float) str_variant = enums.ComplexEnum.Str("hello") assert isinstance(str_variant, enums.ComplexEnum.Str) empty_struct_variant = enums.ComplexEnum.EmptyStruct() assert isinstance(empty_struct_variant, enums.ComplexEnum.EmptyStruct) multi_field_struct_variant = enums.ComplexEnum.MultiFieldStruct(42, 3.14, True) assert isinstance(multi_field_struct_variant, enums.ComplexEnum.MultiFieldStruct) variant_with_default_1 = enums.ComplexEnum.VariantWithDefault() assert isinstance(variant_with_default_1, enums.ComplexEnum.VariantWithDefault) variant_with_default_2 = enums.ComplexEnum.VariantWithDefault(25, "Hello") assert isinstance(variant_with_default_2, enums.ComplexEnum.VariantWithDefault) @pytest.mark.parametrize( "variant", [ enums.ComplexEnum.Int(42), enums.ComplexEnum.Float(3.14), enums.ComplexEnum.Str("hello"), enums.ComplexEnum.EmptyStruct(), enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), enums.ComplexEnum.VariantWithDefault(), ], ) def test_complex_enum_variant_subclasses(variant: enums.ComplexEnum): assert isinstance(variant, enums.ComplexEnum) def test_complex_enum_field_getters(): int_variant = enums.ComplexEnum.Int(42) assert int_variant.i == 42 float_variant = enums.ComplexEnum.Float(3.14) assert float_variant.f == 3.14 str_variant = enums.ComplexEnum.Str("hello") assert str_variant.s == "hello" multi_field_struct_variant = enums.ComplexEnum.MultiFieldStruct(42, 3.14, True) assert multi_field_struct_variant.a == 42 assert multi_field_struct_variant.b == 3.14 assert multi_field_struct_variant.c is True variant_with_default = enums.ComplexEnum.VariantWithDefault() assert variant_with_default.a == 42 assert variant_with_default.b is None @pytest.mark.parametrize( "variant", [ enums.ComplexEnum.Int(42), enums.ComplexEnum.Float(3.14), enums.ComplexEnum.Str("hello"), enums.ComplexEnum.EmptyStruct(), enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), enums.ComplexEnum.VariantWithDefault(), ], ) def test_complex_enum_desugared_match(variant: enums.ComplexEnum): if isinstance(variant, enums.ComplexEnum.Int): assert variant.i == 42 elif isinstance(variant, enums.ComplexEnum.Float): assert variant.f == 3.14 elif isinstance(variant, enums.ComplexEnum.Str): assert variant.s == "hello" elif isinstance(variant, enums.ComplexEnum.EmptyStruct): assert True elif isinstance(variant, enums.ComplexEnum.MultiFieldStruct): assert variant.a == 42 assert variant.b == 3.14 assert variant.c is True elif isinstance(variant, enums.ComplexEnum.VariantWithDefault): assert variant.a == 42 assert variant.b is None else: assert False @pytest.mark.parametrize( "variant", [ enums.ComplexEnum.Int(42), enums.ComplexEnum.Float(3.14), enums.ComplexEnum.Str("hello"), enums.ComplexEnum.EmptyStruct(), enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), enums.ComplexEnum.VariantWithDefault(b="hello"), ], ) def test_complex_enum_pyfunction_in_out_desugared_match(variant: enums.ComplexEnum): variant = enums.do_complex_stuff(variant) if isinstance(variant, enums.ComplexEnum.Int): assert variant.i == 5 elif isinstance(variant, enums.ComplexEnum.Float): assert variant.f == 9.8596 elif isinstance(variant, enums.ComplexEnum.Str): assert variant.s == "42" elif isinstance(variant, enums.ComplexEnum.EmptyStruct): assert True elif isinstance(variant, enums.ComplexEnum.MultiFieldStruct): assert variant.a == 42 assert variant.b == 3.14 assert variant.c is True elif isinstance(variant, enums.ComplexEnum.VariantWithDefault): assert variant.a == 84 assert variant.b == "HELLO" else: assert False def test_tuple_enum_variant_constructors(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert isinstance(tuple_variant, enums.TupleEnum.Full) empty_tuple_variant = enums.TupleEnum.EmptyTuple() assert isinstance(empty_tuple_variant, enums.TupleEnum.EmptyTuple) @pytest.mark.parametrize( "variant", [ enums.TupleEnum.FullWithDefault(), enums.TupleEnum.Full(42, 3.14, False), enums.TupleEnum.EmptyTuple(), ], ) def test_tuple_enum_variant_subclasses(variant: enums.TupleEnum): assert isinstance(variant, enums.TupleEnum) def test_tuple_enum_defaults(): variant = enums.TupleEnum.FullWithDefault() assert variant._0 == 1 assert variant._1 == 1.0 assert variant._2 is True def test_tuple_enum_field_getters(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert tuple_variant._0 == 42 assert tuple_variant._1 == 3.14 assert tuple_variant._2 is False def test_tuple_enum_index_getter(): tuple_variant = enums.TupleEnum.Full(42, 3.14, False) assert len(tuple_variant) == 3 assert tuple_variant[0] == 42 @pytest.mark.parametrize( "variant", [enums.MixedComplexEnum.Nothing()], ) def test_mixed_complex_enum_pyfunction_instance_nothing( variant: enums.MixedComplexEnum, ): assert isinstance(variant, enums.MixedComplexEnum.Nothing) assert isinstance( enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Empty ) @pytest.mark.parametrize( "variant", [enums.MixedComplexEnum.Empty()], ) def test_mixed_complex_enum_pyfunction_instance_empty(variant: enums.MixedComplexEnum): assert isinstance(variant, enums.MixedComplexEnum.Empty) assert isinstance( enums.do_mixed_complex_stuff(variant), enums.MixedComplexEnum.Nothing ) ================================================ FILE: pytests/tests/test_enums_match.py ================================================ # This file is only collected when Python >= 3.10, because it tests match syntax. import pytest from pyo3_pytests import enums @pytest.mark.parametrize( "variant", [ enums.ComplexEnum.Int(42), enums.ComplexEnum.Float(3.14), enums.ComplexEnum.Str("hello"), enums.ComplexEnum.EmptyStruct(), enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), ], ) def test_complex_enum_match_statement(variant: enums.ComplexEnum): match variant: case enums.ComplexEnum.Int(i=x): assert x == 42 case enums.ComplexEnum.Float(f=x): assert x == 3.14 case enums.ComplexEnum.Str(s=x): assert x == "hello" case enums.ComplexEnum.EmptyStruct(): assert True case enums.ComplexEnum.MultiFieldStruct(a=x, b=y, c=z): assert x == 42 assert y == 3.14 assert z is True case _: assert False @pytest.mark.parametrize( "variant", [ enums.ComplexEnum.Int(42), enums.ComplexEnum.Float(3.14), enums.ComplexEnum.Str("hello"), enums.ComplexEnum.EmptyStruct(), enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), ], ) def test_complex_enum_pyfunction_in_out(variant: enums.ComplexEnum): match enums.do_complex_stuff(variant): case enums.ComplexEnum.Int(i=x): assert x == 5 case enums.ComplexEnum.Float(f=x): assert x == 9.8596 case enums.ComplexEnum.Str(s=x): assert x == "42" case enums.ComplexEnum.EmptyStruct(): assert True case enums.ComplexEnum.MultiFieldStruct(a=x, b=y, c=z): assert x == 42 assert y == 3.14 assert z is True case _: assert False @pytest.mark.parametrize( "variant", [ enums.ComplexEnum.MultiFieldStruct(42, 3.14, True), ], ) def test_complex_enum_partial_match(variant: enums.ComplexEnum): match variant: case enums.ComplexEnum.MultiFieldStruct(a): assert a == 42 case _: assert False @pytest.mark.parametrize( "variant", [ enums.TupleEnum.Full(42, 3.14, True), enums.TupleEnum.EmptyTuple(), ], ) def test_tuple_enum_match_statement(variant: enums.TupleEnum): match variant: case enums.TupleEnum.Full(_0=x, _1=y, _2=z): assert x == 42 assert y == 3.14 assert z is True case enums.TupleEnum.EmptyTuple(): assert True case _: print(variant) assert False @pytest.mark.parametrize( "variant", [ enums.SimpleTupleEnum.Int(42), enums.SimpleTupleEnum.Str("hello"), ], ) def test_simple_tuple_enum_match_statement(variant: enums.SimpleTupleEnum): match variant: case enums.SimpleTupleEnum.Int(x): assert x == 42 case enums.SimpleTupleEnum.Str(x): assert x == "hello" case _: assert False @pytest.mark.parametrize( "variant", [ enums.TupleEnum.Full(42, 3.14, True), ], ) def test_tuple_enum_match_match_args(variant: enums.TupleEnum): match variant: case enums.TupleEnum.Full(x, y, z): assert x == 42 assert y == 3.14 assert z is True assert True case _: assert False @pytest.mark.parametrize( "variant", [ enums.TupleEnum.Full(42, 3.14, True), ], ) def test_tuple_enum_partial_match(variant: enums.TupleEnum): match variant: case enums.TupleEnum.Full(a): assert a == 42 case _: assert False @pytest.mark.parametrize( "variant", [ enums.MixedComplexEnum.Nothing(), enums.MixedComplexEnum.Empty(), ], ) def test_mixed_complex_enum_match_statement(variant: enums.MixedComplexEnum): match variant: case enums.MixedComplexEnum.Nothing(): assert True case enums.MixedComplexEnum.Empty(): assert True case _: assert False ================================================ FILE: pytests/tests/test_hammer_attaching_in_thread.py ================================================ import sysconfig import pytest from pyo3_pytests import misc def make_loop(): # create a reference loop that will only be destroyed when the GC is called at the end # of execution start = [] cur = [start] for _ in range(1000 * 1000 * 10): cur = [cur] start.append(cur) return start # set a bomb that will explode when modules are cleaned up loopy = [make_loop()] @pytest.mark.skipif( sysconfig.get_config_var("Py_DEBUG"), reason="causes a crash on debug builds, see discussion in https://github.com/PyO3/pyo3/pull/4874", ) def test_hammer_attaching_in_thread(): loopy.append(misc.hammer_attaching_in_thread()) ================================================ FILE: pytests/tests/test_misc.py ================================================ import importlib import platform import sys import pyo3_pytests.misc import pytest if sys.version_info >= (3, 13): subinterpreters = pytest.importorskip("_interpreters") else: subinterpreters = pytest.importorskip("_xxsubinterpreters") def test_issue_219(): # Should not deadlock pyo3_pytests.misc.issue_219() @pytest.mark.xfail( platform.python_implementation() == "CPython" and sys.version_info < (3, 9), reason="Cannot identify subinterpreters on Python older than 3.9", ) def test_multiple_imports_same_interpreter_ok(): spec = importlib.util.find_spec("pyo3_pytests.pyo3_pytests") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) assert dir(module) == dir(pyo3_pytests.pyo3_pytests) @pytest.mark.xfail( platform.python_implementation() == "CPython" and sys.version_info < (3, 9), reason="Cannot identify subinterpreters on Python older than 3.9", ) @pytest.mark.skipif( platform.python_implementation() in ("PyPy", "GraalVM"), reason="PyPy and GraalPy do not support subinterpreters", ) def test_import_in_subinterpreter_forbidden(): sub_interpreter = subinterpreters.create() if sys.version_info < (3, 12): expected_error = "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576" else: expected_error = "module pyo3_pytests.pyo3_pytests does not support loading in subinterpreters" if sys.version_info < (3, 13): # Python 3.12 subinterpreters had a special error for this with pytest.raises( subinterpreters.RunFailedError, match=expected_error, ): subinterpreters.run_string( sub_interpreter, "import pyo3_pytests.pyo3_pytests" ) else: res = subinterpreters.run_string( sub_interpreter, "import pyo3_pytests.pyo3_pytests" ) assert res.type.__name__ == "ImportError" assert res.msg == expected_error subinterpreters.destroy(sub_interpreter) def test_type_fully_qualified_name_includes_module(): numpy = pytest.importorskip("numpy") # For numpy 1.x and 2.x assert pyo3_pytests.misc.get_type_fully_qualified_name(numpy.bool_(True)) in [ "numpy.bool", "numpy.bool_", ] def test_accepts_numpy_bool(): # binary numpy wheel not available on all platforms numpy = pytest.importorskip("numpy") assert pyo3_pytests.misc.accepts_bool(True) is True assert pyo3_pytests.misc.accepts_bool(False) is False assert pyo3_pytests.misc.accepts_bool(numpy.bool_(True)) is True assert pyo3_pytests.misc.accepts_bool(numpy.bool_(False)) is False class ArbitraryClass: worker_id: int iteration: int def __init__(self, worker_id: int, iteration: int): self.worker_id = worker_id self.iteration = iteration def __repr__(self): return f"ArbitraryClass({self.worker_id}, {self.iteration})" def __del__(self): print("del", self.worker_id, self.iteration) def test_gevent(): gevent = pytest.importorskip("gevent") def worker(worker_id: int) -> None: for iteration in range(2): d = {"key": ArbitraryClass(worker_id, iteration)} def arbitrary_python_code(): # remove the dictionary entry so that the class value can be # garbage collected del d["key"] print("gevent sleep", worker_id, iteration) gevent.sleep(0) print("after gevent sleep", worker_id, iteration) print("start", worker_id, iteration) pyo3_pytests.misc.get_item_and_run_callback(d, arbitrary_python_code) print("end", worker_id, iteration) workers = [gevent.spawn(worker, i) for i in range(2)] gevent.joinall(workers) ================================================ FILE: pytests/tests/test_objstore.py ================================================ import gc import sys from pyo3_pytests.objstore import ObjStore def test_objstore_doesnot_leak_memory(): N = 10000 message = b'\\(-"-;) Praying that memory leak would not happen..' # PyPy does not have sys.getrefcount, provide a no-op lambda and don't # check refcount on PyPy getrefcount = getattr(sys, "getrefcount", lambda obj: 0) if sys.implementation.name == "graalpy": # GraalPy has an incomplete sys.getrefcount implementation def getrefcount(obj): return 0 before = getrefcount(message) store = ObjStore() for _ in range(N): store.push(message) del store gc.collect() after = getrefcount(message) assert after - before == 0 ================================================ FILE: pytests/tests/test_othermod.py ================================================ from hypothesis import given, assume from hypothesis import strategies as st from pyo3_pytests import othermod INTEGER31_ST = st.integers(min_value=(-(2**30)), max_value=(2**30 - 1)) USIZE_ST = st.integers(min_value=othermod.USIZE_MIN, max_value=othermod.USIZE_MAX) # If the full 32 bits are used here, then you can get failures that look like this: # hypothesis.errors.FailedHealthCheck: It looks like your strategy is filtering out a lot of data. # Health check found 50 filtered examples but only 7 good ones. # # Limit the range to 31 bits to avoid this problem. @given(x=INTEGER31_ST) def test_double(x): expected = x * 2 assume(-(2**31) <= expected <= (2**31 - 1)) assert othermod.double(x) == expected def test_modclass(): # Test that the repr of the class itself doesn't crash anything repr(othermod.ModClass) assert isinstance(othermod.ModClass, type) def test_modclass_instance(): mi = othermod.ModClass() repr(mi) repr(mi.__class__) assert isinstance(mi, othermod.ModClass) assert isinstance(mi, object) @given(x=USIZE_ST) def test_modclas_noop(x): mi = othermod.ModClass() assert mi.noop(x) == x ================================================ FILE: pytests/tests/test_path.py ================================================ import pathlib import pytest import pyo3_pytests.path as rpath def test_make_path(): p = rpath.make_path() assert p == pathlib.Path("/root") def test_take_pathbuf(): p = "/root" assert rpath.take_pathbuf(p) == pathlib.Path(p) def test_take_pathlib(): p = pathlib.Path("/root") assert rpath.take_pathbuf(p) == p def test_take_pathlike(): assert rpath.take_pathbuf(PathLike("/root")) == pathlib.Path("/root") def test_take_invalid_pathlike(): with pytest.raises(TypeError): assert rpath.take_pathbuf(PathLike(1)) def test_take_invalid(): with pytest.raises(TypeError): assert rpath.take_pathbuf(3) class PathLike: def __init__(self, path): self._path = path def __fspath__(self): return self._path ================================================ FILE: pytests/tests/test_pyclasses.py ================================================ import platform import sys from typing import Type import pytest from pyo3_pytests import pyclasses def test_empty_class_init(benchmark): benchmark(pyclasses.EmptyClass) def test_method_call(benchmark): obj = pyclasses.EmptyClass() assert benchmark(obj.method) is None def test_proto_call(benchmark): obj = pyclasses.EmptyClass() assert benchmark(len, obj) == 0 class EmptyClassPy: def method(self): pass def __len__(self) -> int: return 0 def test_empty_class_init_py(benchmark): benchmark(EmptyClassPy) def test_method_call_py(benchmark): obj = EmptyClassPy() assert benchmark(obj.method) == pyclasses.EmptyClass().method() def test_proto_call_py(benchmark): obj = EmptyClassPy() assert benchmark(len, obj) == len(pyclasses.EmptyClass()) def test_iter(): i = pyclasses.PyClassIter() assert next(i) == 1 assert next(i) == 2 assert next(i) == 3 assert next(i) == 4 assert next(i) == 5 with pytest.raises(StopIteration) as excinfo: next(i) assert excinfo.value.value == "Ended" @pytest.mark.skipif( platform.machine() in ["wasm32", "wasm64"], reason="not supporting threads in CI for WASM yet", ) def test_parallel_iter(): import concurrent.futures import threading thread_iter = pyclasses.PyClassThreadIter() max_workers = 2 b = threading.Barrier(max_workers) error_happened = threading.Event() # the second thread attempts to borrow a reference to the instance's # state while the first thread is still sleeping, so we trigger a # runtime borrow-check error def closure(i): b.wait() # should never reach 100 iterations, the borrow error should # happen relatively quickly because the loops are synchronized for j in range(100): if not error_happened.is_set(): try: next(thread_iter) except RuntimeError as e: assert "Already borrowed" in str(e), str(e) error_happened.set() else: break else: assert False, "Should not be able to complete loop" with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as tpe: for _ in tpe.map(closure, range(max_workers)): pass class AssertingSubClass(pyclasses.AssertingBaseClass): pass def test_new_classmethod(): # The `AssertingBaseClass` constructor errors if it is not passed the # relevant subclass. _ = AssertingSubClass(expected_type=AssertingSubClass) with pytest.raises(ValueError): _ = AssertingSubClass(expected_type=str) class ClassWithoutConstructor: def __new__(cls): raise TypeError( f"cannot create '{cls.__module__}.{cls.__qualname__}' instances" ) @pytest.mark.xfail( platform.python_implementation() == "PyPy" and sys.version_info[:2] == (3, 11), reason="broken on PyPy 3.11 due to https://github.com/pypy/pypy/issues/5319, waiting for next release", ) @pytest.mark.parametrize( "cls, exc_message", [ ( pyclasses.ClassWithoutConstructor, "cannot create 'builtins.ClassWithoutConstructor' instances", ), ( ClassWithoutConstructor, "cannot create 'test_pyclasses.ClassWithoutConstructor' instances", ), ], ) def test_no_constructor_defined_propagates_cause(cls: Type, exc_message: str): original_error = ValueError("Original message") with pytest.raises(Exception) as exc_info: try: raise original_error except Exception: cls() # should raise TypeError("No constructor defined for ...") assert exc_info.type is TypeError assert exc_info.value.args == (exc_message,) assert exc_info.value.__context__ is original_error def test_dict(): try: ClassWithDict = pyclasses.ClassWithDict except AttributeError: pytest.skip("not defined using abi3 < 3.9") d = ClassWithDict() assert d.__dict__ == {} d.foo = 42 assert d.__dict__ == {"foo": 42} def test_getter(benchmark): obj = pyclasses.ClassWithDecorators() benchmark(lambda: obj.attr) def test_setter(benchmark): obj = pyclasses.ClassWithDecorators() def set_attr(): obj.attr = 42 benchmark(set_attr) def test_deleter(): obj = pyclasses.ClassWithDecorators() del obj.attr with pytest.raises(AttributeError): _ = obj.attr obj.attr = 42 assert obj.attr == 42 def test_class_attribute(benchmark): cls = pyclasses.ClassWithDecorators benchmark(lambda: cls.cls_attribute) def test_class_method(benchmark): cls = pyclasses.ClassWithDecorators benchmark(lambda: cls.cls_method()) def test_static_method(benchmark): cls = pyclasses.ClassWithDecorators benchmark(lambda: cls.static_method()) def test_class_init_method(): try: SubClassWithInit = pyclasses.SubClassWithInit except AttributeError: pytest.skip("not defined using abi3") d = SubClassWithInit() assert d == {"__init__": True} d = SubClassWithInit({"a": 1}, b=2) assert d == {"__init__": True, "a": 1, "b": 2} ================================================ FILE: pytests/tests/test_pyfunctions.py ================================================ from typing import Any, Tuple from pyo3_pytests import pyfunctions def none_py(): return None def test_none_py(benchmark): benchmark(none_py) def test_none_rs(benchmark): rust = benchmark(pyfunctions.none) py = none_py() assert rust == py def simple_py(a, b=None, *, c=None): return a, b, c def test_simple_py(benchmark): benchmark(simple_py, 1, "foo", c={1: 2}) def test_simple_rs(benchmark): rust = benchmark(pyfunctions.simple, 1, "foo", c={1: 2}) py = simple_py(1, "foo", c={1: 2}) assert rust == py def simple_args_py(a, b=None, *args, c=None): return a, b, args, c def test_simple_args_py(benchmark): benchmark(simple_args_py, 1, "foo", 4, 5, 6, c={1: 2}) def test_simple_args_rs(benchmark): rust = benchmark(pyfunctions.simple_args, 1, "foo", 4, 5, 6, c={1: 2}) py = simple_args_py(1, "foo", 4, 5, 6, c={1: 2}) assert rust == py def simple_kwargs_py(a, b=None, c=None, **kwargs): return a, b, c, kwargs def test_simple_kwargs_py(benchmark): benchmark(simple_kwargs_py, 1, "foo", c={1: 2}, bar=4, foo=10) def test_simple_kwargs_rs(benchmark): rust = benchmark(pyfunctions.simple_kwargs, 1, "foo", c={1: 2}, bar=4, foo=10) py = simple_kwargs_py(1, "foo", c={1: 2}, bar=4, foo=10) assert rust == py def simple_args_kwargs_py(a, b=None, *args, c=None, **kwargs): return a, b, args, c, kwargs def test_simple_args_kwargs_py(benchmark): benchmark(simple_args_kwargs_py, 1, "foo", "baz", bar=4, foo=10) def test_simple_args_kwargs_rs(benchmark): rust = benchmark(pyfunctions.simple_args_kwargs, 1, "foo", "baz", bar=4, foo=10) py = simple_args_kwargs_py(1, "foo", "baz", bar=4, foo=10) assert rust == py def args_kwargs_py(*args, **kwargs): return args, kwargs def test_args_kwargs_py(benchmark): benchmark(args_kwargs_py, 1, "foo", {1: 2}, bar=4, foo=10) def test_args_kwargs_rs(benchmark): rust = benchmark(pyfunctions.args_kwargs, 1, "foo", {1: 2}, bar=4, foo=10) py = args_kwargs_py(1, "foo", {1: 2}, bar=4, foo=10) assert rust == py # TODO: the second argument should be positional-only # but can't be without breaking tests on Python 3.7. # See gh-5095. def positional_only_py(a, b): return a, b def test_positional_only_py(benchmark): benchmark(positional_only_py, 1, "foo") def test_positional_only_rs(benchmark): rust = benchmark(pyfunctions.positional_only, 1, "foo") py = positional_only_py(1, "foo") assert rust == py def with_typed_args_py( a: bool, b: int, c: float, d: str ) -> Tuple[bool, int, float, str]: return a, b, c, d def test_with_typed_args_py(benchmark): benchmark(with_typed_args_py, True, 1, 1.2, "foo") def test_with_typed_args_rs(benchmark): rust = benchmark(pyfunctions.with_typed_args, True, 1, 1.2, "foo") py = with_typed_args_py(True, 1, 1.2, "foo") assert rust == py def many_keyword_arguments_py( *, ant: Any = None, bear: Any = None, cat: Any = None, dog: Any = None, elephant: Any = None, fox: Any = None, goat: Any = None, horse: Any = None, iguana: Any = None, jaguar: Any = None, koala: Any = None, lion: Any = None, monkey: Any = None, newt: Any = None, owl: Any = None, penguin: Any = None, ): ... def call_with_many_keyword_arguments(f) -> Any: return f( ant=True, bear=1, cat=1.2, dog="foo", elephant=None, fox=8, goat=9, horse=10, iguana=None, jaguar=None, koala=None, lion=11, owl=None, penguin=None, ) def test_many_keyword_arguments_py(benchmark): benchmark(call_with_many_keyword_arguments, many_keyword_arguments_py) def test_many_keyword_arguments_rs(benchmark): rust = benchmark( call_with_many_keyword_arguments, pyfunctions.many_keyword_arguments ) py = call_with_many_keyword_arguments(many_keyword_arguments_py) assert rust == py ================================================ FILE: pytests/tests/test_sequence.py ================================================ import pytest from pyo3_pytests import sequence def test_vec_from_list_i32(): assert sequence.vec_to_vec_i32([1, 2, 3]) == [1, 2, 3] def test_vec_from_list_pystring(): assert sequence.vec_to_vec_pystring(["1", "2", "3"]) == ["1", "2", "3"] def test_vec_from_bytes(): assert sequence.vec_to_vec_i32(b"123") == [49, 50, 51] def test_vec_from_str(): with pytest.raises(TypeError): sequence.vec_to_vec_pystring("123") def test_vec_from_array(): # binary numpy wheel not available on all platforms numpy = pytest.importorskip("numpy") assert sequence.vec_to_vec_i32(numpy.array([1, 2, 3])) == [1, 2, 3] def test_rust_array_from_array(): # binary numpy wheel not available on all platforms numpy = pytest.importorskip("numpy") assert sequence.array_to_array_i32(numpy.array([1, 2, 3])) == [1, 2, 3] ================================================ FILE: pytests/tests/test_subclassing.py ================================================ from pyo3_pytests.subclassing import Subclassable, Subclass class SomeSubClass(Subclassable): def __str__(self): return "SomeSubclass" def test_python_subclassing(): a = SomeSubClass() assert str(a) == "SomeSubclass" assert type(a) is SomeSubClass def test_rust_subclassing(): a = Subclass() assert str(a) == "Subclass" assert type(a) is Subclass ================================================ FILE: src/buffer.rs ================================================ #![cfg(any(not(Py_LIMITED_API), Py_3_11))] // Copyright (c) 2017 Daniel Grunwald // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. //! `PyBuffer` implementation #[cfg(feature = "experimental-inspect")] use crate::inspect::{type_hint_identifier, PyStaticExpr}; use crate::{err, exceptions::PyBufferError, ffi, FromPyObject, PyAny, PyResult, Python}; use crate::{Borrowed, Bound, PyErr}; use std::ffi::{ c_char, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort, c_void, }; use std::marker::{PhantomData, PhantomPinned}; use std::pin::Pin; use std::ptr::NonNull; use std::{cell, mem, ptr, slice}; use std::{ffi::CStr, fmt::Debug}; /// A typed form of [`PyUntypedBuffer`]. #[repr(transparent)] pub struct PyBuffer(PyUntypedBuffer, PhantomData<[T]>); /// Allows access to the underlying buffer used by a python object such as `bytes`, `bytearray` or `array.array`. #[repr(transparent)] pub struct PyUntypedBuffer( // It is common for exporters filling `Py_buffer` struct to make it self-referential, e.g. see // implementation of // [`PyBuffer_FillInfo`](https://github.com/python/cpython/blob/2fd43a1ffe4ff1f6c46f6045bc327d6085c40fbf/Objects/abstract.c#L798-L802). // // Therefore we use `Pin>` to document for ourselves that the memory address of the `Py_buffer` is expected to be stable Pin>, ); /// Wrapper around `ffi::Py_buffer` to be `!Unpin`. #[repr(transparent)] struct RawBuffer(ffi::Py_buffer, PhantomPinned); // PyBuffer send & sync guarantees are upheld by Python. unsafe impl Send for PyUntypedBuffer {} unsafe impl Sync for PyUntypedBuffer {} impl Debug for PyBuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { debug_buffer("PyBuffer", &self.0, f) } } impl Debug for PyUntypedBuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { debug_buffer("PyUntypedBuffer", self, f) } } fn debug_buffer( name: &str, b: &PyUntypedBuffer, f: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { let raw = b.raw(); f.debug_struct(name) .field("buf", &raw.buf) .field("obj", &raw.obj) .field("len", &raw.len) .field("itemsize", &raw.itemsize) .field("readonly", &raw.readonly) .field("ndim", &raw.ndim) .field("format", &b.format()) .field("shape", &b.shape()) .field("strides", &b.strides()) .field("suboffsets", &b.suboffsets()) .field("internal", &raw.internal) .finish() } /// Represents the type of a Python buffer element. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ElementType { /// A signed integer type. SignedInteger { /// The width of the signed integer in bytes. bytes: usize, }, /// An unsigned integer type. UnsignedInteger { /// The width of the unsigned integer in bytes. bytes: usize, }, /// A boolean type. Bool, /// A float type. Float { /// The width of the float in bytes. bytes: usize, }, /// An unknown type. This may occur when parsing has failed. Unknown, } impl ElementType { /// Determines the `ElementType` from a Python `struct` module format string. /// /// See for more information /// about struct format strings. pub fn from_format(format: &CStr) -> ElementType { match format.to_bytes() { [size] | [b'@', size] => native_element_type_from_type_char(*size), [b'=' | b'<' | b'>' | b'!', size] => standard_element_type_from_type_char(*size), _ => ElementType::Unknown, } } } fn native_element_type_from_type_char(type_char: u8) -> ElementType { use self::ElementType::*; match type_char { b'c' => UnsignedInteger { bytes: mem::size_of::(), }, b'b' => SignedInteger { bytes: mem::size_of::(), }, b'B' => UnsignedInteger { bytes: mem::size_of::(), }, b'?' => Bool, b'h' => SignedInteger { bytes: mem::size_of::(), }, b'H' => UnsignedInteger { bytes: mem::size_of::(), }, b'i' => SignedInteger { bytes: mem::size_of::(), }, b'I' => UnsignedInteger { bytes: mem::size_of::(), }, b'l' => SignedInteger { bytes: mem::size_of::(), }, b'L' => UnsignedInteger { bytes: mem::size_of::(), }, b'q' => SignedInteger { bytes: mem::size_of::(), }, b'Q' => UnsignedInteger { bytes: mem::size_of::(), }, b'n' => SignedInteger { bytes: mem::size_of::(), }, b'N' => UnsignedInteger { bytes: mem::size_of::(), }, b'e' => Float { bytes: 2 }, b'f' => Float { bytes: 4 }, b'd' => Float { bytes: 8 }, _ => Unknown, } } fn standard_element_type_from_type_char(type_char: u8) -> ElementType { use self::ElementType::*; match type_char { b'c' | b'B' => UnsignedInteger { bytes: 1 }, b'b' => SignedInteger { bytes: 1 }, b'?' => Bool, b'h' => SignedInteger { bytes: 2 }, b'H' => UnsignedInteger { bytes: 2 }, b'i' | b'l' => SignedInteger { bytes: 4 }, b'I' | b'L' => UnsignedInteger { bytes: 4 }, b'q' => SignedInteger { bytes: 8 }, b'Q' => UnsignedInteger { bytes: 8 }, b'e' => Float { bytes: 2 }, b'f' => Float { bytes: 4 }, b'd' => Float { bytes: 8 }, _ => Unknown, } } #[cfg(target_endian = "little")] fn is_matching_endian(c: u8) -> bool { c == b'@' || c == b'=' || c == b'>' } #[cfg(target_endian = "big")] fn is_matching_endian(c: u8) -> bool { c == b'@' || c == b'=' || c == b'>' || c == b'!' } /// Trait implemented for possible element types of `PyBuffer`. /// /// # Safety /// /// This trait must only be implemented for types which represent valid elements of Python buffers. pub unsafe trait Element: Copy { /// Gets whether the element specified in the format string is potentially compatible. /// Alignment and size are checked separately from this function. fn is_compatible_format(format: &CStr) -> bool; } impl FromPyObject<'_, '_> for PyBuffer { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("collections.abc", "Buffer"); fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result, Self::Error> { Self::get(&obj) } } impl PyBuffer { /// Gets the underlying buffer from the specified python object. pub fn get(obj: &Bound<'_, PyAny>) -> PyResult { PyUntypedBuffer::get(obj)?.into_typed() } /// Gets the buffer memory as a slice. /// /// This function succeeds if: /// * the buffer format is compatible with `T` /// * alignment and size of buffer elements is matching the expectations for type `T` /// * the buffer is C-style contiguous /// /// The returned slice uses type `Cell` because it's theoretically possible for any call into the Python runtime /// to modify the values in the slice. pub fn as_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell]> { if self.is_c_contiguous() { unsafe { Some(slice::from_raw_parts( self.raw().buf.cast(), self.item_count(), )) } } else { None } } /// Gets the buffer memory as a slice. /// /// This function succeeds if: /// * the buffer is not read-only /// * the buffer format is compatible with `T` /// * alignment and size of buffer elements is matching the expectations for type `T` /// * the buffer is C-style contiguous /// /// The returned slice uses type `Cell` because it's theoretically possible for any call into the Python runtime /// to modify the values in the slice. pub fn as_mut_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell]> { if !self.readonly() && self.is_c_contiguous() { unsafe { Some(slice::from_raw_parts( self.raw().buf.cast(), self.item_count(), )) } } else { None } } /// Gets the buffer memory as a slice. /// /// This function succeeds if: /// * the buffer format is compatible with `T` /// * alignment and size of buffer elements is matching the expectations for type `T` /// * the buffer is Fortran-style contiguous /// /// The returned slice uses type `Cell` because it's theoretically possible for any call into the Python runtime /// to modify the values in the slice. pub fn as_fortran_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell]> { if mem::size_of::() == self.item_size() && self.is_fortran_contiguous() { unsafe { Some(slice::from_raw_parts( self.raw().buf.cast(), self.item_count(), )) } } else { None } } /// Gets the buffer memory as a slice. /// /// This function succeeds if: /// * the buffer is not read-only /// * the buffer format is compatible with `T` /// * alignment and size of buffer elements is matching the expectations for type `T` /// * the buffer is Fortran-style contiguous /// /// The returned slice uses type `Cell` because it's theoretically possible for any call into the Python runtime /// to modify the values in the slice. pub fn as_fortran_mut_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell]> { if !self.readonly() && self.is_fortran_contiguous() { unsafe { Some(slice::from_raw_parts( self.raw().buf.cast(), self.item_count(), )) } } else { None } } /// Copies the buffer elements to the specified slice. /// If the buffer is multi-dimensional, the elements are written in C-style order. /// /// * Fails if the slice does not have the correct length (`buf.item_count()`). /// * Fails if the buffer format is not compatible with type `T`. /// /// To check whether the buffer format is compatible before calling this method, /// you can use `::is_compatible_format(buf.format())`. /// Alternatively, `match buffer::ElementType::from_format(buf.format())`. pub fn copy_to_slice(&self, py: Python<'_>, target: &mut [T]) -> PyResult<()> { self._copy_to_slice(py, target, b'C') } /// Copies the buffer elements to the specified slice. /// If the buffer is multi-dimensional, the elements are written in Fortran-style order. /// /// * Fails if the slice does not have the correct length (`buf.item_count()`). /// * Fails if the buffer format is not compatible with type `T`. /// /// To check whether the buffer format is compatible before calling this method, /// you can use `::is_compatible_format(buf.format())`. /// Alternatively, `match buffer::ElementType::from_format(buf.format())`. pub fn copy_to_fortran_slice(&self, py: Python<'_>, target: &mut [T]) -> PyResult<()> { self._copy_to_slice(py, target, b'F') } fn _copy_to_slice(&self, py: Python<'_>, target: &mut [T], fort: u8) -> PyResult<()> { if mem::size_of_val(target) != self.len_bytes() { return Err(PyBufferError::new_err(format!( "slice to copy to (of length {}) does not match buffer length of {}", target.len(), self.item_count() ))); } err::error_on_minusone(py, unsafe { ffi::PyBuffer_ToContiguous( target.as_mut_ptr().cast(), #[cfg(Py_3_11)] self.raw(), #[cfg(not(Py_3_11))] ptr::from_ref(self.raw()).cast_mut(), self.raw().len, fort as std::ffi::c_char, ) }) } /// Copies the buffer elements to a newly allocated vector. /// If the buffer is multi-dimensional, the elements are written in C-style order. /// /// Fails if the buffer format is not compatible with type `T`. pub fn to_vec(&self, py: Python<'_>) -> PyResult> { self._to_vec(py, b'C') } /// Copies the buffer elements to a newly allocated vector. /// If the buffer is multi-dimensional, the elements are written in Fortran-style order. /// /// Fails if the buffer format is not compatible with type `T`. pub fn to_fortran_vec(&self, py: Python<'_>) -> PyResult> { self._to_vec(py, b'F') } fn _to_vec(&self, py: Python<'_>, fort: u8) -> PyResult> { let item_count = self.item_count(); let mut vec: Vec = Vec::with_capacity(item_count); // Copy the buffer into the uninitialized space in the vector. // Due to T:Copy, we don't need to be concerned with Drop impls. err::error_on_minusone(py, unsafe { ffi::PyBuffer_ToContiguous( vec.as_mut_ptr().cast(), #[cfg(Py_3_11)] self.raw(), #[cfg(not(Py_3_11))] ptr::from_ref(self.raw()).cast_mut(), self.raw().len, fort as std::ffi::c_char, ) })?; // set vector length to mark the now-initialized space as usable unsafe { vec.set_len(item_count) }; Ok(vec) } /// Copies the specified slice into the buffer. /// If the buffer is multi-dimensional, the elements in the slice are expected to be in C-style order. /// /// * Fails if the buffer is read-only. /// * Fails if the slice does not have the correct length (`buf.item_count()`). /// * Fails if the buffer format is not compatible with type `T`. /// /// To check whether the buffer format is compatible before calling this method, /// use `::is_compatible_format(buf.format())`. /// Alternatively, `match buffer::ElementType::from_format(buf.format())`. pub fn copy_from_slice(&self, py: Python<'_>, source: &[T]) -> PyResult<()> { self._copy_from_slice(py, source, b'C') } /// Copies the specified slice into the buffer. /// If the buffer is multi-dimensional, the elements in the slice are expected to be in Fortran-style order. /// /// * Fails if the buffer is read-only. /// * Fails if the slice does not have the correct length (`buf.item_count()`). /// * Fails if the buffer format is not compatible with type `T`. /// /// To check whether the buffer format is compatible before calling this method, /// use `::is_compatible_format(buf.format())`. /// Alternatively, `match buffer::ElementType::from_format(buf.format())`. pub fn copy_from_fortran_slice(&self, py: Python<'_>, source: &[T]) -> PyResult<()> { self._copy_from_slice(py, source, b'F') } fn _copy_from_slice(&self, py: Python<'_>, source: &[T], fort: u8) -> PyResult<()> { if self.readonly() { return Err(PyBufferError::new_err("cannot write to read-only buffer")); } else if mem::size_of_val(source) != self.len_bytes() { return Err(PyBufferError::new_err(format!( "slice to copy from (of length {}) does not match buffer length of {}", source.len(), self.item_count() ))); } err::error_on_minusone(py, unsafe { ffi::PyBuffer_FromContiguous( #[cfg(Py_3_11)] self.raw(), #[cfg(not(Py_3_11))] ptr::from_ref(self.raw()).cast_mut(), #[cfg(Py_3_11)] { source.as_ptr().cast() }, #[cfg(not(Py_3_11))] { source.as_ptr().cast::().cast_mut() }, self.raw().len, fort as std::ffi::c_char, ) }) } } impl std::ops::Deref for PyBuffer { type Target = PyUntypedBuffer; fn deref(&self) -> &Self::Target { &self.0 } } impl PyUntypedBuffer { /// Gets the underlying buffer from the specified python object. pub fn get(obj: &Bound<'_, PyAny>) -> PyResult { let buf = { let mut buf = Box::::new_uninit(); // SAFETY: RawBuffer is `#[repr(transparent)]` around FFI struct err::error_on_minusone(obj.py(), unsafe { ffi::PyObject_GetBuffer( obj.as_ptr(), buf.as_mut_ptr().cast::(), ffi::PyBUF_FULL_RO, ) })?; // Safety: buf is initialized by PyObject_GetBuffer. unsafe { buf.assume_init() } }; // Create PyBuffer immediately so that if validation checks fail, the PyBuffer::drop code // will call PyBuffer_Release (thus avoiding any leaks). let buf = Self(Pin::from(buf)); let raw = buf.raw(); if raw.shape.is_null() { Err(PyBufferError::new_err("shape is null")) } else if raw.strides.is_null() { Err(PyBufferError::new_err("strides is null")) } else { Ok(buf) } } /// Returns a `[PyBuffer]` instance if the buffer can be interpreted as containing elements of type `T`. pub fn into_typed(self) -> PyResult> { self.ensure_compatible_with::()?; Ok(PyBuffer(self, PhantomData)) } /// Non-owning equivalent of [`into_typed()`][Self::into_typed]. pub fn as_typed(&self) -> PyResult<&PyBuffer> { self.ensure_compatible_with::()?; // SAFETY: PyBuffer is repr(transparent) around PyUntypedBuffer Ok(unsafe { NonNull::from(self).cast::>().as_ref() }) } fn ensure_compatible_with(&self) -> PyResult<()> { if mem::size_of::() != self.item_size() || !T::is_compatible_format(self.format()) { Err(PyBufferError::new_err(format!( "buffer contents are not compatible with {}", std::any::type_name::() ))) } else if self.raw().buf.align_offset(mem::align_of::()) != 0 { Err(PyBufferError::new_err(format!( "buffer contents are insufficiently aligned for {}", std::any::type_name::() ))) } else { Ok(()) } } /// Releases the buffer object, freeing the reference to the Python object /// which owns the buffer. /// /// This will automatically be called on drop. pub fn release(self, _py: Python<'_>) { // First move self into a ManuallyDrop, so that PyBuffer::drop will // never be called. (It would attach to the interpreter and call PyBuffer_Release // again.) let mut mdself = mem::ManuallyDrop::new(self); unsafe { // Next, make the actual PyBuffer_Release call. // Fine to get a mutable reference to the inner ffi::Py_buffer here, as we're destroying it. mdself.0.release(); // Finally, drop the contained Pin> in place, to free the // Box memory. ptr::drop_in_place::>>(&mut mdself.0); } } /// Gets the pointer to the start of the buffer memory. /// /// Warning: the buffer memory can be mutated by other code (including /// other Python functions, if the GIL is released, or other extension /// modules even if the GIL is held). You must either access memory /// atomically, or ensure there are no data races yourself. See /// [this blog post] for more details. /// /// [this blog post]: https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/ #[inline] pub fn buf_ptr(&self) -> *mut c_void { self.raw().buf } /// Gets a pointer to the specified item. /// /// If `indices.len() < self.dimensions()`, returns the start address of the sub-array at the specified dimension. pub fn get_ptr(&self, indices: &[usize]) -> *mut c_void { let shape = &self.shape()[..indices.len()]; for i in 0..indices.len() { assert!(indices[i] < shape[i]); } unsafe { ffi::PyBuffer_GetPointer( #[cfg(Py_3_11)] self.raw(), #[cfg(not(Py_3_11))] ptr::from_ref(self.raw()).cast_mut(), #[cfg(Py_3_11)] indices.as_ptr().cast(), #[cfg(not(Py_3_11))] indices.as_ptr().cast_mut().cast(), ) } } /// Gets whether the underlying buffer is read-only. #[inline] pub fn readonly(&self) -> bool { self.raw().readonly != 0 } /// Gets the size of a single element, in bytes. /// Important exception: when requesting an unformatted buffer, item_size still has the value #[inline] pub fn item_size(&self) -> usize { self.raw().itemsize as usize } /// Gets the total number of items. #[inline] pub fn item_count(&self) -> usize { (self.raw().len as usize) / (self.raw().itemsize as usize) } /// `item_size() * item_count()`. /// For contiguous arrays, this is the length of the underlying memory block. /// For non-contiguous arrays, it is the length that the logical structure would have if it were copied to a contiguous representation. #[inline] pub fn len_bytes(&self) -> usize { self.raw().len as usize } /// Gets the number of dimensions. /// /// May be 0 to indicate a single scalar value. #[inline] pub fn dimensions(&self) -> usize { self.raw().ndim as usize } /// Returns an array of length `dimensions`. `shape()[i]` is the length of the array in dimension number `i`. /// /// May return None for single-dimensional arrays or scalar values (`dimensions() <= 1`); /// You can call `item_count()` to get the length of the single dimension. /// /// Despite Python using an array of signed integers, the values are guaranteed to be non-negative. /// However, dimensions of length 0 are possible and might need special attention. #[inline] pub fn shape(&self) -> &[usize] { unsafe { slice::from_raw_parts(self.raw().shape.cast(), self.raw().ndim as usize) } } /// Returns an array that holds, for each dimension, the number of bytes to skip to get to the next element in the dimension. /// /// Stride values can be any integer. For regular arrays, strides are usually positive, /// but a consumer MUST be able to handle the case `strides[n] <= 0`. #[inline] pub fn strides(&self) -> &[isize] { unsafe { slice::from_raw_parts(self.raw().strides, self.raw().ndim as usize) } } /// An array of length ndim. /// If `suboffsets[n] >= 0`, the values stored along the nth dimension are pointers and the suboffset value dictates how many bytes to add to each pointer after de-referencing. /// A suboffset value that is negative indicates that no de-referencing should occur (striding in a contiguous memory block). /// /// If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be NULL (the default value). #[inline] pub fn suboffsets(&self) -> Option<&[isize]> { unsafe { if self.raw().suboffsets.is_null() { None } else { Some(slice::from_raw_parts( self.raw().suboffsets, self.raw().ndim as usize, )) } } } /// A string in struct module style syntax describing the contents of a single item. #[inline] pub fn format(&self) -> &CStr { if self.raw().format.is_null() { ffi::c_str!("B") } else { unsafe { CStr::from_ptr(self.raw().format) } } } /// Gets whether the buffer is contiguous in C-style order (last index varies fastest when visiting items in order of memory address). #[inline] pub fn is_c_contiguous(&self) -> bool { unsafe { ffi::PyBuffer_IsContiguous(self.raw(), b'C' as std::ffi::c_char) != 0 } } /// Gets whether the buffer is contiguous in Fortran-style order (first index varies fastest when visiting items in order of memory address). #[inline] pub fn is_fortran_contiguous(&self) -> bool { unsafe { ffi::PyBuffer_IsContiguous(self.raw(), b'F' as std::ffi::c_char) != 0 } } fn raw(&self) -> &ffi::Py_buffer { &self.0 .0 } } impl RawBuffer { /// Release the contents of this pinned buffer. /// /// # Safety /// /// - The buffer must not be used after calling this function. /// - This function can only be called once. /// - Must be attached to the interpreter. /// unsafe fn release(self: &mut Pin>) { unsafe { ffi::PyBuffer_Release(&mut Pin::get_unchecked_mut(self.as_mut()).0); } } } impl Drop for PyUntypedBuffer { fn drop(&mut self) { if Python::try_attach(|_| unsafe { self.0.release() }).is_none() && crate::internal::state::is_in_gc_traversal() { eprintln!("Warning: PyBuffer dropped while in GC traversal, this is a bug and will leak memory."); } // If `try_attach` failed and `is_in_gc_traversal()` is false, then probably the interpreter has // already finalized and we can just assume that the underlying memory has already been freed. // // So we don't handle that case here. } } /// Like [std::cell::Cell], but only provides read-only access to the data. /// /// `&ReadOnlyCell` is basically a safe version of `*const T`: /// The data cannot be modified through the reference, but other references may /// be modifying the data. #[repr(transparent)] pub struct ReadOnlyCell(cell::UnsafeCell); impl ReadOnlyCell { /// Returns a copy of the current value. #[inline] pub fn get(&self) -> T { unsafe { *self.0.get() } } /// Returns a pointer to the current value. #[inline] pub fn as_ptr(&self) -> *const T { self.0.get() } } macro_rules! impl_element( ($t:ty, $f:ident) => { unsafe impl Element for $t { fn is_compatible_format(format: &CStr) -> bool { let slice = format.to_bytes(); if slice.len() > 1 && !is_matching_endian(slice[0]) { return false; } ElementType::from_format(format) == ElementType::$f { bytes: mem::size_of::<$t>() } } } } ); impl_element!(u8, UnsignedInteger); impl_element!(u16, UnsignedInteger); impl_element!(u32, UnsignedInteger); impl_element!(u64, UnsignedInteger); impl_element!(usize, UnsignedInteger); impl_element!(i8, SignedInteger); impl_element!(i16, SignedInteger); impl_element!(i32, SignedInteger); impl_element!(i64, SignedInteger); impl_element!(isize, SignedInteger); impl_element!(f32, Float); impl_element!(f64, Float); #[cfg(test)] mod tests { use super::*; use crate::ffi; use crate::types::any::PyAnyMethods; use crate::types::PyBytes; use crate::Python; #[test] fn test_debug() { Python::attach(|py| { let bytes = PyBytes::new(py, b"abcde"); let buffer: PyBuffer = PyBuffer::get(&bytes).unwrap(); let expected = format!( concat!( "PyBuffer {{ buf: {:?}, obj: {:?}, ", "len: 5, itemsize: 1, readonly: 1, ", "ndim: 1, format: \"B\", shape: [5], ", "strides: [1], suboffsets: None, internal: {:?} }}", ), buffer.raw().buf, buffer.raw().obj, buffer.raw().internal ); let debug_repr = format!("{:?}", buffer); assert_eq!(debug_repr, expected); }); } #[test] fn test_element_type_from_format() { use super::ElementType::*; use std::mem::size_of; for (cstr, expected) in [ // @ prefix goes to native_element_type_from_type_char ( c"@b", SignedInteger { bytes: size_of::(), }, ), ( c"@c", UnsignedInteger { bytes: size_of::(), }, ), ( c"@b", SignedInteger { bytes: size_of::(), }, ), ( c"@B", UnsignedInteger { bytes: size_of::(), }, ), (c"@?", Bool), ( c"@h", SignedInteger { bytes: size_of::(), }, ), ( c"@H", UnsignedInteger { bytes: size_of::(), }, ), ( c"@i", SignedInteger { bytes: size_of::(), }, ), ( c"@I", UnsignedInteger { bytes: size_of::(), }, ), ( c"@l", SignedInteger { bytes: size_of::(), }, ), ( c"@L", UnsignedInteger { bytes: size_of::(), }, ), ( c"@q", SignedInteger { bytes: size_of::(), }, ), ( c"@Q", UnsignedInteger { bytes: size_of::(), }, ), ( c"@n", SignedInteger { bytes: size_of::(), }, ), ( c"@N", UnsignedInteger { bytes: size_of::(), }, ), (c"@e", Float { bytes: 2 }), (c"@f", Float { bytes: 4 }), (c"@d", Float { bytes: 8 }), (c"@z", Unknown), // = prefix goes to standard_element_type_from_type_char (c"=b", SignedInteger { bytes: 1 }), (c"=c", UnsignedInteger { bytes: 1 }), (c"=B", UnsignedInteger { bytes: 1 }), (c"=?", Bool), (c"=h", SignedInteger { bytes: 2 }), (c"=H", UnsignedInteger { bytes: 2 }), (c"=l", SignedInteger { bytes: 4 }), (c"=l", SignedInteger { bytes: 4 }), (c"=I", UnsignedInteger { bytes: 4 }), (c"=L", UnsignedInteger { bytes: 4 }), (c"=q", SignedInteger { bytes: 8 }), (c"=Q", UnsignedInteger { bytes: 8 }), (c"=e", Float { bytes: 2 }), (c"=f", Float { bytes: 4 }), (c"=d", Float { bytes: 8 }), (c"=z", Unknown), (c"=0", Unknown), // unknown prefix -> Unknown (c":b", Unknown), ] { assert_eq!( ElementType::from_format(cstr), expected, "element from format &Cstr: {cstr:?}", ); } } #[test] fn test_compatible_size() { // for the cast in PyBuffer::shape() assert_eq!( std::mem::size_of::(), std::mem::size_of::() ); } #[test] fn test_bytes_buffer() { Python::attach(|py| { let bytes = PyBytes::new(py, b"abcde"); let buffer = PyBuffer::get(&bytes).unwrap(); assert_eq!(buffer.dimensions(), 1); assert_eq!(buffer.item_count(), 5); assert_eq!(buffer.format().to_str().unwrap(), "B"); assert_eq!(buffer.shape(), [5]); // single-dimensional buffer is always contiguous assert!(buffer.is_c_contiguous()); assert!(buffer.is_fortran_contiguous()); let slice = buffer.as_slice(py).unwrap(); assert_eq!(slice.len(), 5); assert_eq!(slice[0].get(), b'a'); assert_eq!(slice[2].get(), b'c'); assert_eq!(unsafe { *(buffer.get_ptr(&[1]).cast::()) }, b'b'); assert!(buffer.as_mut_slice(py).is_none()); assert!(buffer.copy_to_slice(py, &mut [0u8]).is_err()); let mut arr = [0; 5]; buffer.copy_to_slice(py, &mut arr).unwrap(); assert_eq!(arr, b"abcde" as &[u8]); assert!(buffer.copy_from_slice(py, &[0u8; 5]).is_err()); assert_eq!(buffer.to_vec(py).unwrap(), b"abcde"); }); } #[test] fn test_array_buffer() { Python::attach(|py| { let array = py .import("array") .unwrap() .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None) .unwrap(); let buffer = PyBuffer::get(&array).unwrap(); assert_eq!(buffer.dimensions(), 1); assert_eq!(buffer.item_count(), 4); assert_eq!(buffer.format().to_str().unwrap(), "f"); assert_eq!(buffer.shape(), [4]); // array creates a 1D contiguous buffer, so it's both C and F contiguous. This would // be more interesting if we can come up with a 2D buffer but I think it would need a // third-party lib or a custom class. // C-contiguous fns let slice = buffer.as_slice(py).unwrap(); assert_eq!(slice.len(), 4); assert_eq!(slice[0].get(), 1.0); assert_eq!(slice[3].get(), 2.5); let mut_slice = buffer.as_mut_slice(py).unwrap(); assert_eq!(mut_slice.len(), 4); assert_eq!(mut_slice[0].get(), 1.0); mut_slice[3].set(2.75); assert_eq!(slice[3].get(), 2.75); buffer .copy_from_slice(py, &[10.0f32, 11.0, 12.0, 13.0]) .unwrap(); assert_eq!(slice[2].get(), 12.0); assert_eq!(buffer.to_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]); // F-contiguous fns let buffer = PyBuffer::get(&array).unwrap(); let slice = buffer.as_fortran_slice(py).unwrap(); assert_eq!(slice.len(), 4); assert_eq!(slice[1].get(), 11.0); let mut_slice = buffer.as_fortran_mut_slice(py).unwrap(); assert_eq!(mut_slice.len(), 4); assert_eq!(mut_slice[2].get(), 12.0); mut_slice[3].set(2.75); assert_eq!(slice[3].get(), 2.75); buffer .copy_from_fortran_slice(py, &[10.0f32, 11.0, 12.0, 13.0]) .unwrap(); assert_eq!(slice[2].get(), 12.0); assert_eq!(buffer.to_fortran_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]); }); } #[test] fn test_untyped_buffer() { Python::attach(|py| { let bytes = PyBytes::new(py, b"abcde"); let untyped = PyUntypedBuffer::get(&bytes).unwrap(); assert_eq!(untyped.dimensions(), 1); assert_eq!(untyped.item_count(), 5); assert_eq!(untyped.format().to_str().unwrap(), "B"); assert_eq!(untyped.shape(), [5]); let typed: &PyBuffer = untyped.as_typed().unwrap(); assert_eq!(typed.dimensions(), 1); assert_eq!(typed.item_count(), 5); assert_eq!(typed.format().to_str().unwrap(), "B"); assert_eq!(typed.shape(), [5]); }); } } ================================================ FILE: src/byteswriter.rs ================================================ #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::PyTypeInfo; #[cfg(not(Py_LIMITED_API))] use crate::{ err::error_on_minusone, ffi::{ self, compat::{ PyBytesWriter_Create, PyBytesWriter_Discard, PyBytesWriter_Finish, PyBytesWriter_GetData, PyBytesWriter_GetSize, PyBytesWriter_Resize, }, }, ffi_ptr_ext::FfiPtrExt, py_result_ext::PyResultExt, }; use crate::{types::PyBytes, Bound, IntoPyObject, PyErr, PyResult, Python}; use std::io::IoSlice; #[cfg(not(Py_LIMITED_API))] use std::{ mem::ManuallyDrop, ptr::{self, NonNull}, }; pub struct PyBytesWriter<'py> { python: Python<'py>, #[cfg(not(Py_LIMITED_API))] writer: NonNull, #[cfg(Py_LIMITED_API)] buffer: Vec, } impl<'py> PyBytesWriter<'py> { /// Create a new `PyBytesWriter` with a default initial capacity. #[inline] pub fn new(py: Python<'py>) -> PyResult { Self::with_capacity(py, 0) } /// Create a new `PyBytesWriter` with the specified initial capacity. #[inline] #[cfg_attr(Py_LIMITED_API, allow(clippy::unnecessary_wraps))] pub fn with_capacity(py: Python<'py>, capacity: usize) -> PyResult { #[cfg(not(Py_LIMITED_API))] { NonNull::new(unsafe { PyBytesWriter_Create(capacity as _) }).map_or_else( || Err(PyErr::fetch(py)), |writer| { let mut writer = PyBytesWriter { python: py, writer }; if capacity > 0 { // SAFETY: By setting the length to 0, we ensure no bytes are considered uninitialized. unsafe { writer.set_len(0)? }; } Ok(writer) }, ) } #[cfg(Py_LIMITED_API)] { Ok(PyBytesWriter { python: py, buffer: Vec::with_capacity(capacity), }) } } /// Get the current length of the internal buffer. #[inline] pub fn len(&self) -> usize { #[cfg(not(Py_LIMITED_API))] unsafe { PyBytesWriter_GetSize(self.writer.as_ptr()) as _ } #[cfg(Py_LIMITED_API)] { self.buffer.len() } } #[inline] #[cfg(not(Py_LIMITED_API))] fn as_mut_ptr(&mut self) -> *mut u8 { unsafe { PyBytesWriter_GetData(self.writer.as_ptr()) as _ } } /// Set the length of the internal buffer to `new_len`. The new bytes are uninitialized. /// /// # Safety /// The caller must ensure the new bytes are initialized. #[inline] #[cfg(not(Py_LIMITED_API))] unsafe fn set_len(&mut self, new_len: usize) -> PyResult<()> { unsafe { error_on_minusone( self.python, PyBytesWriter_Resize(self.writer.as_ptr(), new_len as _), ) } } } impl<'py> TryFrom> for Bound<'py, PyBytes> { type Error = PyErr; #[inline] fn try_from(value: PyBytesWriter<'py>) -> Result { let py = value.python; #[cfg(not(Py_LIMITED_API))] unsafe { PyBytesWriter_Finish(ManuallyDrop::new(value).writer.as_ptr()) .assume_owned_or_err(py) .cast_into_unchecked() } #[cfg(Py_LIMITED_API)] { Ok(PyBytes::new(py, &value.buffer)) } } } impl<'py> IntoPyObject<'py> for PyBytesWriter<'py> { type Target = PyBytes; type Output = Bound<'py, PyBytes>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; #[inline] fn into_pyobject(self, _py: Python<'py>) -> Result { self.try_into() } } #[cfg(not(Py_LIMITED_API))] impl<'py> Drop for PyBytesWriter<'py> { #[inline] fn drop(&mut self) { unsafe { PyBytesWriter_Discard(self.writer.as_ptr()) } } } #[cfg(not(Py_LIMITED_API))] impl std::io::Write for PyBytesWriter<'_> { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.write_all(buf)?; Ok(buf.len()) } fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> std::io::Result { let len = bufs.iter().map(|b| b.len()).sum(); // SAFETY: We ensure enough capacity below. let mut pos = unsafe { self.as_mut_ptr().add(self.len()) }; // SAFETY: We write the new uninitialized bytes below. unsafe { self.set_len(self.len() + len)? } for buf in bufs { // SAFETY: We have ensured enough capacity above. unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), pos, buf.len()) }; // SAFETY: We just wrote buf.len() bytes pos = unsafe { pos.add(buf.len()) }; } Ok(len) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { let len = buf.len(); let pos = self.len(); // SAFETY: We write the new uninitialized bytes below. unsafe { self.set_len(pos + len)? } // SAFETY: We have ensured enough capacity above. unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), self.as_mut_ptr().add(pos), len) }; Ok(()) } } #[cfg(Py_LIMITED_API)] impl std::io::Write for PyBytesWriter<'_> { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.buffer.write(buf) } fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> std::io::Result { self.buffer.write_vectored(bufs) } fn flush(&mut self) -> std::io::Result<()> { self.buffer.flush() } fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { self.buffer.write_all(buf) } fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> { self.buffer.write_fmt(args) } } #[cfg(test)] mod tests { use super::*; use crate::types::PyBytesMethods; use std::io::Write; #[test] fn test_io_write() { Python::attach(|py| { let buf = b"hallo world"; let mut writer = PyBytesWriter::new(py).unwrap(); assert_eq!(writer.write(buf).unwrap(), 11); let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap(); assert_eq!(bytes.as_bytes(), buf); }) } #[test] fn test_pre_allocated() { Python::attach(|py| { let buf = b"hallo world"; let mut writer = PyBytesWriter::with_capacity(py, buf.len()).unwrap(); assert_eq!(writer.len(), 0, "Writer position should be zero"); assert_eq!(writer.write(buf).unwrap(), 11); let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap(); assert_eq!(bytes.as_bytes(), buf); }) } #[test] fn test_io_write_vectored() { Python::attach(|py| { let bufs = [IoSlice::new(b"hallo "), IoSlice::new(b"world")]; let mut writer = PyBytesWriter::new(py).unwrap(); assert_eq!(writer.write_vectored(&bufs).unwrap(), 11); let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap(); assert_eq!(bytes.as_bytes(), b"hallo world"); }) } #[test] fn test_large_data() { Python::attach(|py| { let mut writer = PyBytesWriter::new(py).unwrap(); let large_data = vec![0; 1024]; // 1 KB writer.write_all(&large_data).unwrap(); let bytes: Bound<'_, PyBytes> = writer.try_into().unwrap(); assert_eq!(bytes.as_bytes(), large_data.as_slice()); }) } } ================================================ FILE: src/call.rs ================================================ //! Defines how Python calls are dispatched, see [`PyCallArgs`].for more information. use crate::ffi_ptr_ext::FfiPtrExt as _; use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple}; use crate::{ffi, Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult}; pub(crate) mod private { use super::*; pub trait Sealed {} impl Sealed for () {} impl Sealed for Bound<'_, PyTuple> {} impl Sealed for &'_ Bound<'_, PyTuple> {} impl Sealed for Py {} impl Sealed for &'_ Py {} impl Sealed for Borrowed<'_, '_, PyTuple> {} pub struct Token; } /// This trait marks types that can be used as arguments to Python function /// calls. /// /// This trait is currently implemented for Rust tuple (up to a size of 12), /// [`Bound<'py, PyTuple>`] and [`Py`]. Custom types that are /// convertible to `PyTuple` via `IntoPyObject` need to do so before passing it /// to `call`. /// /// This trait is not intended to used by downstream crates directly. As such it /// has no publicly available methods and cannot be implemented outside of /// `pyo3`. The corresponding public API is available through [`call`] /// ([`call0`], [`call1`] and friends) on [`PyAnyMethods`]. /// /// # What is `PyCallArgs` used for? /// `PyCallArgs` is used internally in `pyo3` to dispatch the Python calls in /// the most optimal way for the current build configuration. Certain types, /// such as Rust tuples, do allow the usage of a faster calling convention of /// the Python interpreter (if available). More types that may take advantage /// from this may be added in the future. /// /// [`call0`]: crate::types::PyAnyMethods::call0 /// [`call1`]: crate::types::PyAnyMethods::call1 /// [`call`]: crate::types::PyAnyMethods::call /// [`PyAnyMethods`]: crate::types::PyAnyMethods #[diagnostic::on_unimplemented( message = "`{Self}` cannot used as a Python `call` argument", note = "`PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py`", note = "if your type is convertible to `PyTuple` via `IntoPyObject`, call `.into_pyobject(py)` manually", note = "if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(,)`" )] pub trait PyCallArgs<'py>: Sized + private::Sealed { #[doc(hidden)] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult>; #[doc(hidden)] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult>; #[doc(hidden)] fn call_method_positional( self, object: Borrowed<'_, 'py, PyAny>, method_name: Borrowed<'_, 'py, PyString>, _: private::Token, ) -> PyResult> { object .getattr(method_name) .and_then(|method| method.call1(self)) } } impl<'py> PyCallArgs<'py> for () { fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { let args = self.into_pyobject_or_pyerr(function.py())?; args.call(function, kwargs, token) } fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { let args = self.into_pyobject_or_pyerr(function.py())?; args.call_positional(function, token) } } impl<'py> PyCallArgs<'py> for Bound<'py, PyTuple> { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.as_borrowed().call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.as_borrowed().call_positional(function, token) } } impl<'py> PyCallArgs<'py> for &'_ Bound<'py, PyTuple> { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.as_borrowed().call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.as_borrowed().call_positional(function, token) } } impl<'py> PyCallArgs<'py> for Py { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call_positional(function, token) } } impl<'py> PyCallArgs<'py> for &'_ Py { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call(function, kwargs, token) } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, token: private::Token, ) -> PyResult> { self.bind_borrowed(function.py()) .call_positional(function, token) } } impl<'py> PyCallArgs<'py> for Borrowed<'_, 'py, PyTuple> { #[inline] fn call( self, function: Borrowed<'_, 'py, PyAny>, kwargs: Borrowed<'_, 'py, PyDict>, _: private::Token, ) -> PyResult> { unsafe { ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr()) .assume_owned_or_err(function.py()) } } #[inline] fn call_positional( self, function: Borrowed<'_, 'py, PyAny>, _: private::Token, ) -> PyResult> { unsafe { ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), std::ptr::null_mut()) .assume_owned_or_err(function.py()) } } } #[cfg(test)] #[cfg(feature = "macros")] mod tests { use crate::{ pyfunction, types::{PyDict, PyTuple}, Py, }; #[pyfunction(signature = (*args, **kwargs), crate = "crate")] fn args_kwargs( args: Py, kwargs: Option>, ) -> (Py, Option>) { (args, kwargs) } #[test] fn test_call() { use crate::{ types::{IntoPyDict, PyAnyMethods, PyDict, PyTuple}, wrap_pyfunction, Py, Python, }; Python::attach(|py| { let f = wrap_pyfunction!(args_kwargs, py).unwrap(); let args = PyTuple::new(py, [1, 2, 3]).unwrap(); let kwargs = &[("foo", 1), ("bar", 2)].into_py_dict(py).unwrap(); macro_rules! check_call { ($args:expr, $kwargs:expr) => { let (a, k): (Py, Py) = f .call(args.clone(), Some(kwargs)) .unwrap() .extract() .unwrap(); assert!(a.is(&args)); assert!(k.is(kwargs)); }; } // Bound<'py, PyTuple> check_call!(args.clone(), kwargs); // &Bound<'py, PyTuple> check_call!(&args, kwargs); // Py check_call!(args.clone().unbind(), kwargs); // &Py check_call!(&args.as_unbound(), kwargs); // Borrowed<'_, '_, PyTuple> check_call!(args.as_borrowed(), kwargs); }) } #[test] fn test_call_positional() { use crate::{ types::{PyAnyMethods, PyNone, PyTuple}, wrap_pyfunction, Py, Python, }; Python::attach(|py| { let f = wrap_pyfunction!(args_kwargs, py).unwrap(); let args = PyTuple::new(py, [1, 2, 3]).unwrap(); macro_rules! check_call { ($args:expr, $kwargs:expr) => { let (a, k): (Py, Py) = f.call1(args.clone()).unwrap().extract().unwrap(); assert!(a.is(&args)); assert!(k.is_none(py)); }; } // Bound<'py, PyTuple> check_call!(args.clone(), kwargs); // &Bound<'py, PyTuple> check_call!(&args, kwargs); // Py check_call!(args.clone().unbind(), kwargs); // &Py check_call!(args.as_unbound(), kwargs); // Borrowed<'_, '_, PyTuple> check_call!(args.as_borrowed(), kwargs); }) } } ================================================ FILE: src/conversion.rs ================================================ //! Defines conversions between Rust and Python types. use crate::err::PyResult; use crate::impl_::pyclass::ExtractPyClassWithClone; #[cfg(feature = "experimental-inspect")] use crate::inspect::{type_hint_identifier, type_hint_subscript, PyStaticExpr}; use crate::pyclass::boolean_struct::False; use crate::pyclass::{PyClassGuardError, PyClassGuardMutError}; use crate::types::PyList; use crate::types::PyTuple; use crate::{ Borrowed, Bound, BoundObject, Py, PyAny, PyClass, PyClassGuard, PyErr, PyRef, PyRefMut, PyTypeCheck, Python, }; use std::convert::Infallible; use std::marker::PhantomData; /// Defines a conversion from a Rust type to a Python object, which may fail. /// /// This trait has `#[derive(IntoPyObject)]` to automatically implement it for simple types and /// `#[derive(IntoPyObjectRef)]` to implement the same for references. /// /// It functions similarly to std's [`TryInto`] trait, but requires a [`Python<'py>`] token /// as an argument. /// /// The [`into_pyobject`][IntoPyObject::into_pyobject] method is designed for maximum flexibility and efficiency; it /// - allows for a concrete Python type to be returned (the [`Target`][IntoPyObject::Target] associated type) /// - allows for the smart pointer containing the Python object to be either `Bound<'py, Self::Target>` or `Borrowed<'a, 'py, Self::Target>` /// to avoid unnecessary reference counting overhead /// - allows for a custom error type to be returned in the event of a conversion error to avoid /// unnecessarily creating a Python exception /// /// # See also /// /// - The [`IntoPyObjectExt`] trait, which provides convenience methods for common usages of /// `IntoPyObject` which erase type information and convert errors to `PyErr`. #[diagnostic::on_unimplemented( message = "`{Self}` cannot be converted to a Python object", note = "`IntoPyObject` is automatically implemented by the `#[pyclass]` macro", note = "if you do not wish to have a corresponding Python type, implement it manually", note = "if you do not own `{Self}` you can perform a manual conversion to one of the types in `pyo3::types::*`" )] pub trait IntoPyObject<'py>: Sized { /// The Python output type type Target; /// The smart pointer type to use. /// /// This will usually be [`Bound<'py, Target>`], but in special cases [`Borrowed<'a, 'py, Target>`] can be /// used to minimize reference counting overhead. type Output: BoundObject<'py, Self::Target>; /// The type returned in the event of a conversion error. type Error: Into; /// Extracts the type hint information for this type when it appears as a return value. /// /// For example, `Vec` would return `List[int]`. /// The default implementation returns `Any`, which is correct for any type. /// /// For most types, the return value for this method will be identical to that of [`FromPyObject::INPUT_TYPE`]. /// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument. #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete"); /// Performs the conversion. fn into_pyobject(self, py: Python<'py>) -> Result; /// Converts sequence of Self into a Python object. Used to specialize `Vec`, `[u8; N]` /// and `SmallVec<[u8; N]>` as a sequence of bytes into a `bytes` object. #[doc(hidden)] fn owned_sequence_into_pyobject( iter: I, py: Python<'py>, _: private::Token, ) -> Result, PyErr> where I: IntoIterator + AsRef<[Self]>, I::IntoIter: ExactSizeIterator, { Ok(PyList::new(py, iter)?.into_any()) } /// Converts sequence of Self into a Python object. Used to specialize `&[u8]` and `Cow<[u8]>` /// as a sequence of bytes into a `bytes` object. #[doc(hidden)] fn borrowed_sequence_into_pyobject( iter: I, py: Python<'py>, _: private::Token, ) -> Result, PyErr> where Self: private::Reference, I: IntoIterator + AsRef<[::BaseType]>, I::IntoIter: ExactSizeIterator, { Ok(PyList::new(py, iter)?.into_any()) } /// The output type of [`IntoPyObject::owned_sequence_into_pyobject`] and [`IntoPyObject::borrowed_sequence_into_pyobject`] #[cfg(feature = "experimental-inspect")] #[doc(hidden)] const SEQUENCE_OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyList::TYPE_HINT, Self::OUTPUT_TYPE); } pub(crate) mod private { pub struct Token; pub trait Reference { type BaseType; } impl Reference for &'_ T { type BaseType = T; } } impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Bound<'py, T> { type Target = T; type Output = Bound<'py, Self::Target>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self) } } impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Bound<'py, T> { type Target = T; type Output = Borrowed<'a, 'py, Self::Target>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self.as_borrowed()) } } impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for Borrowed<'a, 'py, T> { type Target = T; type Output = Borrowed<'a, 'py, Self::Target>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(self) } } impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &Borrowed<'a, 'py, T> { type Target = T; type Output = Borrowed<'a, 'py, Self::Target>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, _py: Python<'py>) -> Result { Ok(*self) } } impl<'py, T: PyTypeCheck> IntoPyObject<'py> for Py { type Target = T; type Output = Bound<'py, Self::Target>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.into_bound(py)) } } impl<'a, 'py, T: PyTypeCheck> IntoPyObject<'py> for &'a Py { type Target = T; type Output = Borrowed<'a, 'py, Self::Target>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = T::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(self.bind_borrowed(py)) } } impl<'a, 'py, T> IntoPyObject<'py> for &&'a T where &'a T: IntoPyObject<'py>, { type Target = <&'a T as IntoPyObject<'py>>::Target; type Output = <&'a T as IntoPyObject<'py>>::Output; type Error = <&'a T as IntoPyObject<'py>>::Error; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = <&'a T as IntoPyObject<'py>>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } mod into_pyobject_ext { pub trait Sealed {} impl<'py, T> Sealed for T where T: super::IntoPyObject<'py> {} } /// Convenience methods for common usages of [`IntoPyObject`]. Every type that implements /// [`IntoPyObject`] also implements this trait. /// /// These methods: /// - Drop type information from the output, returning a `PyAny` object. /// - Always convert the `Error` type to `PyErr`, which may incur a performance penalty but it /// more convenient in contexts where the `?` operator would produce a `PyErr` anyway. pub trait IntoPyObjectExt<'py>: IntoPyObject<'py> + into_pyobject_ext::Sealed { /// Converts `self` into an owned Python object, dropping type information. #[inline] fn into_bound_py_any(self, py: Python<'py>) -> PyResult> { match self.into_pyobject(py) { Ok(obj) => Ok(obj.into_any().into_bound()), Err(err) => Err(err.into()), } } /// Converts `self` into an owned Python object, dropping type information and unbinding it /// from the `'py` lifetime. #[inline] fn into_py_any(self, py: Python<'py>) -> PyResult> { match self.into_pyobject(py) { Ok(obj) => Ok(obj.into_any().unbind()), Err(err) => Err(err.into()), } } /// Converts `self` into a Python object. /// /// This is equivalent to calling [`into_pyobject`][IntoPyObject::into_pyobject] followed /// with `.map_err(Into::into)` to convert the error type to [`PyErr`]. This is helpful /// for generic code which wants to make use of the `?` operator. #[inline] fn into_pyobject_or_pyerr(self, py: Python<'py>) -> PyResult { match self.into_pyobject(py) { Ok(obj) => Ok(obj), Err(err) => Err(err.into()), } } } impl<'py, T> IntoPyObjectExt<'py> for T where T: IntoPyObject<'py> {} /// Extract a type from a Python object. /// /// /// Normal usage is through the `extract` methods on [`Bound`], [`Borrowed`] and [`Py`], which /// forward to this trait. /// /// # Examples /// /// ```rust /// use pyo3::prelude::*; /// use pyo3::types::PyString; /// /// # fn main() -> PyResult<()> { /// Python::attach(|py| { /// // Calling `.extract()` on a `Bound` smart pointer /// let obj: Bound<'_, PyString> = PyString::new(py, "blah"); /// let s: String = obj.extract()?; /// # assert_eq!(s, "blah"); /// /// // Calling `.extract(py)` on a `Py` smart pointer /// let obj: Py = obj.unbind(); /// let s: String = obj.extract(py)?; /// # assert_eq!(s, "blah"); /// # Ok(()) /// }) /// # } /// ``` /// /// Note: Depending on the Python version and implementation, some [`FromPyObject`] implementations /// may produce a result that borrows into the Python type. This is described by the input lifetime /// `'a` of `obj`. /// /// Types that must not borrow from the input can use [`FromPyObjectOwned`] as a restriction. This /// is most often the case for collection types. See its documentation for more details. /// /// # How to implement [`FromPyObject`]? /// ## `#[derive(FromPyObject)]` /// The simplest way to implement [`FromPyObject`] for a custom type is to make use of our derive /// macro. /// ```rust,no_run /// # #![allow(dead_code)] /// use pyo3::prelude::*; /// /// #[derive(FromPyObject)] /// struct MyObject { /// msg: String, /// list: Vec /// } /// # fn main() {} /// ``` /// By default this will try to extract each field from the Python object by attribute access, but /// this can be customized. For more information about the derive macro, its configuration as well /// as its working principle for other types, take a look at the [guide]. /// /// In case the derive macro is not sufficient or can not be used for some other reason, /// [`FromPyObject`] can be implemented manually. In the following types without lifetime parameters /// are handled first, because they are a little bit simpler. Types with lifetime parameters are /// explained below. /// /// ## Manual implementation for types without lifetime /// Types that do not contain lifetime parameters are unable to borrow from the Python object, so /// the lifetimes of [`FromPyObject`] can be elided: /// ```rust,no_run /// # #![allow(dead_code)] /// use pyo3::prelude::*; /// /// struct MyObject { /// msg: String, /// list: Vec /// } /// /// impl FromPyObject<'_, '_> for MyObject { /// type Error = PyErr; /// /// fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { /// Ok(MyObject { /// msg: obj.getattr("msg")?.extract()?, /// list: obj.getattr("list")?.extract()?, /// }) /// } /// } /// /// # fn main() {} /// ``` /// This is basically what the derive macro above expands to. /// /// ## Manual implementation for types with lifetime parameters /// For types that contain lifetimes, these lifetimes need to be bound to the corresponding /// [`FromPyObject`] lifetime. This is roughly how the extraction of a typed [`Bound`] is /// implemented within PyO3. /// /// ```rust,no_run /// # #![allow(dead_code)] /// use pyo3::prelude::*; /// use pyo3::types::PyString; /// /// struct MyObject<'py>(Bound<'py, PyString>); /// /// impl<'py> FromPyObject<'_, 'py> for MyObject<'py> { /// type Error = PyErr; /// /// fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { /// Ok(MyObject(obj.cast()?.to_owned())) /// } /// } /// /// # fn main() {} /// ``` /// /// # Details /// [`Cow<'a, str>`] is an example of an output type that may or may not borrow from the input /// lifetime `'a`. Which variant will be produced depends on the runtime type of the Python object. /// For a Python byte string, the existing string data can be borrowed for `'a` into a /// [`Cow::Borrowed`]. For a Python Unicode string, the data may have to be reencoded to UTF-8, and /// copied into a [`Cow::Owned`]. It does _not_ depend on the Python lifetime `'py`. /// /// The output type may also depend on the Python lifetime `'py`. This allows the output type to /// keep interacting with the Python interpreter. See also [`Bound<'py, T>`]. /// /// [`Cow<'a, str>`]: std::borrow::Cow /// [`Cow::Borrowed`]: std::borrow::Cow::Borrowed /// [`Cow::Owned`]: std::borrow::Cow::Owned /// [guide]: https://pyo3.rs/latest/conversions/traits.html#deriving-frompyobject pub trait FromPyObject<'a, 'py>: Sized { /// The type returned in the event of a conversion error. /// /// For most use cases defaulting to [PyErr] here is perfectly acceptable. Using a custom error /// type can be used to avoid having to create a Python exception object in the case where that /// exception never reaches Python. This may lead to slightly better performance under certain /// conditions. /// /// # Note /// Unfortunately `Try` and thus `?` is based on [`From`], not [`Into`], so implementations may /// need to use `.map_err(Into::into)` sometimes to convert a generic `Error` into a [`PyErr`]. type Error: Into; /// Provides the type hint information for this type when it appears as an argument. /// /// For example, `Vec` would be `collections.abc.Sequence[int]`. /// The default value is `typing.Any`, which is correct for any type. #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete"); /// Extracts `Self` from the bound smart pointer `obj`. /// /// Users are advised against calling this method directly: instead, use this via /// [`Bound<'_, PyAny>::extract`](crate::types::any::PyAnyMethods::extract) or [`Py::extract`]. fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result; /// Specialization hook for extracting sequences for types like `Vec` and `[u8; N]`, /// where the bytes can be directly copied from some python objects without going through /// iteration. #[doc(hidden)] #[inline(always)] fn sequence_extractor( _obj: Borrowed<'_, 'py, PyAny>, _: private::Token, ) -> Option> { struct NeverASequence(PhantomData); impl FromPyObjectSequence for NeverASequence { type Target = T; fn to_vec(&self) -> Vec { unreachable!() } fn to_array(&self) -> PyResult<[Self::Target; N]> { unreachable!() } } Option::>::None } /// Helper used to make a specialized path in extracting `DateTime` where `Tz` is /// `chrono::Local`, which will accept "naive" datetime objects as being in the local timezone. #[cfg(feature = "chrono-local")] #[inline] fn as_local_tz(_: private::Token) -> Option { None } } mod from_py_object_sequence { use crate::PyResult; /// Private trait for implementing specialized sequence extraction for `Vec` and `[u8; N]` #[doc(hidden)] pub trait FromPyObjectSequence { type Target; fn to_vec(&self) -> Vec; fn to_array(&self) -> PyResult<[Self::Target; N]>; } } // Only reachable / implementable inside PyO3 itself. pub(crate) use from_py_object_sequence::FromPyObjectSequence; /// A data structure that can be extracted without borrowing any data from the input. /// /// This is primarily useful for trait bounds. For example a [`FromPyObject`] implementation of a /// wrapper type may be able to borrow data from the input, but a [`FromPyObject`] implementation of /// a collection type may only extract owned data. /// /// For example [`PyList`] will not hand out references tied to its own lifetime, but "owned" /// references independent of it. (Similar to [`Vec>`] where you clone the [`Arc`] out). /// This makes it impossible to collect borrowed types in a collection, since they would not borrow /// from the original [`PyList`], but the much shorter lived element reference. See the example /// below. /// /// ```,no_run /// # use pyo3::prelude::*; /// # #[allow(dead_code)] /// pub struct MyWrapper(T); /// /// impl<'a, 'py, T> FromPyObject<'a, 'py> for MyWrapper /// where /// T: FromPyObject<'a, 'py> /// { /// type Error = T::Error; /// /// fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { /// obj.extract().map(MyWrapper) /// } /// } /// /// # #[allow(dead_code)] /// pub struct MyVec(Vec); /// /// impl<'py, T> FromPyObject<'_, 'py> for MyVec /// where /// T: FromPyObjectOwned<'py> // 👈 can only extract owned values, because each `item` below /// // is a temporary short lived owned reference /// { /// type Error = PyErr; /// /// fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { /// let mut v = MyVec(Vec::new()); /// for item in obj.try_iter()? { /// v.0.push(item?.extract::().map_err(Into::into)?); /// } /// Ok(v) /// } /// } /// ``` /// /// [`PyList`]: crate::types::PyList /// [`Arc`]: std::sync::Arc pub trait FromPyObjectOwned<'py>: for<'a> FromPyObject<'a, 'py> {} impl<'py, T> FromPyObjectOwned<'py> for T where T: for<'a> FromPyObject<'a, 'py> {} impl<'a, 'py, T> FromPyObject<'a, 'py> for T where T: PyClass + Clone + ExtractPyClassWithClone, { type Error = PyClassGuardError<'a, 'py>; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = ::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { Ok(obj.extract::>()?.clone()) } } impl<'a, 'py, T> FromPyObject<'a, 'py> for PyRef<'py, T> where T: PyClass, { type Error = PyClassGuardError<'a, 'py>; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = ::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { obj.cast::() .map_err(|e| PyClassGuardError(Some(e)))? .try_borrow() .map_err(|_| PyClassGuardError(None)) } } impl<'a, 'py, T> FromPyObject<'a, 'py> for PyRefMut<'py, T> where T: PyClass, { type Error = PyClassGuardMutError<'a, 'py>; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = ::TYPE_HINT; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { obj.cast::() .map_err(|e| PyClassGuardMutError(Some(e)))? .try_borrow_mut() .map_err(|_| PyClassGuardMutError(None)) } } impl<'py> IntoPyObject<'py> for () { type Target = PyTuple; type Output = Bound<'py, Self::Target>; type Error = Infallible; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyTuple::TYPE_HINT, PyStaticExpr::Tuple { elts: &[] }); fn into_pyobject(self, py: Python<'py>) -> Result { Ok(PyTuple::empty(py)) } } /// ```rust,compile_fail /// use pyo3::prelude::*; /// /// #[pyclass] /// struct TestClass { /// num: u32, /// } /// /// let t = TestClass { num: 10 }; /// /// Python::attach(|py| { /// let pyvalue = Py::new(py, t).unwrap().to_object(py); /// let t: TestClass = pyvalue.extract(py).unwrap(); /// }) /// ``` mod test_no_clone {} #[cfg(test)] mod tests { #[test] #[cfg(feature = "macros")] fn test_pyclass_skip_from_py_object() { use crate::{types::PyAnyMethods, FromPyObject, IntoPyObject, PyErr, Python}; #[crate::pyclass(crate = "crate", skip_from_py_object)] #[derive(Clone)] struct Foo(i32); impl<'py> FromPyObject<'_, 'py> for Foo { type Error = PyErr; fn extract(obj: crate::Borrowed<'_, 'py, crate::PyAny>) -> Result { if let Ok(obj) = obj.cast::() { Ok(obj.borrow().clone()) } else { obj.extract::().map(Self) } } } Python::attach(|py| { let foo1 = 42i32.into_pyobject(py)?; assert_eq!(foo1.extract::()?.0, 42); let foo2 = Foo(0).into_pyobject(py)?; assert_eq!(foo2.extract::()?.0, 0); Ok::<_, PyErr>(()) }) .unwrap(); } #[test] #[cfg(feature = "macros")] fn test_pyclass_from_py_object() { use crate::{types::PyAnyMethods, IntoPyObject, PyErr, Python}; #[crate::pyclass(crate = "crate", from_py_object)] #[derive(Clone)] struct Foo(i32); Python::attach(|py| { let foo1 = 42i32.into_pyobject(py)?; assert!(foo1.extract::().is_err()); let foo2 = Foo(0).into_pyobject(py)?; assert_eq!(foo2.extract::()?.0, 0); Ok::<_, PyErr>(()) }) .unwrap(); } } ================================================ FILE: src/conversions/anyhow.rs ================================================ #![cfg(feature = "anyhow")] //! A conversion from [anyhow]’s [`Error`][anyhow-error] type to [`PyErr`]. //! //! Use of an error handling library like [anyhow] is common in application code and when you just //! want error handling to be easy. If you are writing a library or you need more control over your //! errors you might want to design your own error type instead. //! //! When the inner error is a [`PyErr`] without source, it will be extracted out. //! Otherwise a Python [`RuntimeError`] will be created. //! You might find that you need to map the error from your Rust code into another Python exception. //! See [`PyErr::new`] for more information about that. //! //! For information about error handling in general, see the [Error handling] chapter of the Rust //! book. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! ## change * to the version you want to use, ideally the latest. //! anyhow = "*" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"anyhow\"] }")] //! ``` //! //! Note that you must use compatible versions of anyhow and PyO3. //! The required anyhow version may vary based on the version of PyO3. //! //! # Example: Propagating a `PyErr` into [`anyhow::Error`] //! //! ```rust //! use pyo3::prelude::*; //! use std::path::PathBuf; //! //! // A wrapper around a Rust function. //! // The pyfunction macro performs the conversion to a PyErr //! #[pyfunction] //! fn py_open(filename: PathBuf) -> anyhow::Result> { //! let data = std::fs::read(filename)?; //! Ok(data) //! } //! //! fn main() { //! let error = Python::attach(|py| -> PyResult> { //! let fun = wrap_pyfunction!(py_open, py)?; //! let text = fun.call1(("foo.txt",))?.extract::>()?; //! Ok(text) //! }).unwrap_err(); //! //! println!("{}", error); //! } //! ``` //! //! # Example: Using `anyhow` in general //! //! Note that you don't need this feature to convert a [`PyErr`] into an [`anyhow::Error`], because //! it can already convert anything that implements [`Error`](std::error::Error): //! //! ```rust //! use pyo3::prelude::*; //! use pyo3::types::PyBytes; //! //! // An example function that must handle multiple error types. //! // //! // To do this you usually need to design your own error type or use //! // `Box`. `anyhow` is a convenient alternative for this. //! pub fn decompress(bytes: &[u8]) -> anyhow::Result { //! // An arbitrary example of a Python api you //! // could call inside an application... //! // This might return a `PyErr`. //! let res = Python::attach(|py| { //! let zlib = PyModule::import(py, "zlib")?; //! let decompress = zlib.getattr("decompress")?; //! let bytes = PyBytes::new(py, bytes); //! let value = decompress.call1((bytes,))?; //! value.extract::>() //! })?; //! //! // This might be a `FromUtf8Error`. //! let text = String::from_utf8(res)?; //! //! Ok(text) //! } //! //! fn main() -> anyhow::Result<()> { //! let bytes: &[u8] = b"x\x9c\x8b\xcc/U(\xce\xc8/\xcdIQ((\xcaOJL\xca\xa9T\ //! (-NU(\xc9HU\xc8\xc9LJ\xcbI,IUH.\x02\x91\x99y\xc5%\ //! \xa9\x89)z\x00\xf2\x15\x12\xfe"; //! let text = decompress(bytes)?; //! //! println!("The text is \"{}\"", text); //! # assert_eq!(text, "You should probably use the libflate crate instead."); //! Ok(()) //! } //! ``` //! //! [anyhow]: https://docs.rs/anyhow/ "A trait object based error system for easy idiomatic error handling in Rust applications." //! [anyhow-error]: https://docs.rs/anyhow/latest/anyhow/struct.Error.html "Anyhows `Error` type, a wrapper around a dynamic error type" //! [`RuntimeError`]: https://docs.python.org/3/library/exceptions.html#RuntimeError "Built-in Exceptions — Python documentation" //! [Error handling]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html "Recoverable Errors with Result - The Rust Programming Language" use crate::exceptions::PyRuntimeError; use crate::PyErr; impl From for PyErr { fn from(mut error: anyhow::Error) -> Self { // Errors containing a PyErr without chain or context are returned as the underlying error if error.source().is_none() { error = match error.downcast::() { Ok(py_err) => return py_err, Err(error) => error, }; } PyRuntimeError::new_err(format!("{error:?}")) } } #[cfg(test)] mod test_anyhow { use crate::exceptions::{PyRuntimeError, PyValueError}; use crate::prelude::*; use crate::types::IntoPyDict; use anyhow::{anyhow, bail, Context, Result}; fn f() -> Result<()> { use std::io; bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!")); } fn g() -> Result<()> { f().context("f failed") } fn h() -> Result<()> { g().context("g failed") } #[test] fn test_pyo3_exception_contents() { let err = h().unwrap_err(); let expected_contents = format!("{err:?}"); let pyerr = PyErr::from(err); Python::attach(|py| { let locals = [("err", pyerr)].into_py_dict(py).unwrap(); let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err(); assert_eq!(pyerr.value(py).to_string(), expected_contents); }) } fn k() -> Result<()> { Err(anyhow!("Some sort of error")) } #[test] fn test_pyo3_exception_contents2() { let err = k().unwrap_err(); let expected_contents = format!("{err:?}"); let pyerr = PyErr::from(err); Python::attach(|py| { let locals = [("err", pyerr)].into_py_dict(py).unwrap(); let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err(); assert_eq!(pyerr.value(py).to_string(), expected_contents); }) } #[test] fn test_pyo3_unwrap_simple_err() { let origin_exc = PyValueError::new_err("Value Error"); let err: anyhow::Error = origin_exc.into(); let converted: PyErr = err.into(); assert!(Python::attach( |py| converted.is_instance_of::(py) )) } #[test] fn test_pyo3_unwrap_complex_err() { let origin_exc = PyValueError::new_err("Value Error"); let mut err: anyhow::Error = origin_exc.into(); err = err.context("Context"); let converted: PyErr = err.into(); assert!(Python::attach( |py| converted.is_instance_of::(py) )) } } ================================================ FILE: src/conversions/bigdecimal.rs ================================================ #![cfg(feature = "bigdecimal")] //! Conversions to and from [bigdecimal](https://docs.rs/bigdecimal)'s [`BigDecimal`] type. //! //! This is useful for converting Python's decimal.Decimal into and from a native Rust type. //! //! # Setup //! //! To use this feature, add to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"bigdecimal\"] }")] //! bigdecimal = "0.4" //! ``` //! //! Note that you must use a compatible version of bigdecimal and PyO3. //! The required bigdecimal version may vary based on the version of PyO3. //! //! # Example //! //! Rust code to create a function that adds one to a BigDecimal //! //! ```rust //! use bigdecimal::BigDecimal; //! use pyo3::prelude::*; //! //! #[pyfunction] //! fn add_one(d: BigDecimal) -> BigDecimal { //! d + 1 //! } //! //! #[pymodule] //! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> { //! m.add_function(wrap_pyfunction!(add_one, m)?)?; //! Ok(()) //! } //! ``` //! //! Python code that validates the functionality //! //! //! ```python //! from my_module import add_one //! from decimal import Decimal //! //! d = Decimal("2") //! value = add_one(d) //! //! assert d + 1 == value //! ``` use std::str::FromStr; #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_hint_identifier; use crate::types::PyTuple; use crate::{ exceptions::PyValueError, sync::PyOnceLock, types::{PyAnyMethods, PyStringMethods, PyType}, Borrowed, Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyResult, Python, }; use bigdecimal::BigDecimal; use num_bigint::Sign; fn get_decimal_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { static DECIMAL_CLS: PyOnceLock> = PyOnceLock::new(); DECIMAL_CLS.import(py, "decimal", "Decimal") } fn get_invalid_operation_error_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { static INVALID_OPERATION_CLS: PyOnceLock> = PyOnceLock::new(); INVALID_OPERATION_CLS.import(py, "decimal", "InvalidOperation") } impl FromPyObject<'_, '_> for BigDecimal { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("decimal", "Decimal"); fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult { let py_str = &obj.str()?; let rs_str = &py_str.to_cow()?; BigDecimal::from_str(rs_str).map_err(|e| PyValueError::new_err(e.to_string())) } } impl<'py> IntoPyObject<'py> for BigDecimal { type Target = PyAny; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("decimal", "Decimal"); fn into_pyobject(self, py: Python<'py>) -> Result { let cls = get_decimal_cls(py)?; let (bigint, scale) = self.into_bigint_and_scale(); if scale == 0 { return cls.call1((bigint,)); } let exponent = scale.checked_neg().ok_or_else(|| { get_invalid_operation_error_cls(py) .map_or_else(|err| err, |cls| PyErr::from_type(cls.clone(), ())) })?; let (sign, digits) = bigint.to_radix_be(10); let signed = matches!(sign, Sign::Minus).into_pyobject(py)?; let digits = PyTuple::new(py, digits)?; cls.call1(((signed, digits, exponent),)) } } #[cfg(test)] mod test_bigdecimal { use super::*; use crate::types::dict::PyDictMethods; use crate::types::PyDict; use std::ffi::CString; use bigdecimal::{One, Zero}; #[cfg(not(target_arch = "wasm32"))] use proptest::prelude::*; macro_rules! convert_constants { ($name:ident, $rs:expr, $py:literal) => { #[test] fn $name() { Python::attach(|py| { let rs_orig = $rs; let rs_dec = rs_orig.clone().into_pyobject(py).unwrap(); let locals = PyDict::new(py); locals.set_item("rs_dec", &rs_dec).unwrap(); // Checks if BigDecimal -> Python Decimal conversion is correct py.run( &CString::new(format!( "import decimal\npy_dec = decimal.Decimal(\"{}\")\nassert py_dec == rs_dec", $py )) .unwrap(), None, Some(&locals), ) .unwrap(); // Checks if Python Decimal -> BigDecimal conversion is correct let py_dec = locals.get_item("py_dec").unwrap().unwrap(); let py_result: BigDecimal = py_dec.extract().unwrap(); assert_eq!(rs_orig, py_result); }) } }; } convert_constants!(convert_zero, BigDecimal::zero(), "0"); convert_constants!(convert_one, BigDecimal::one(), "1"); convert_constants!(convert_neg_one, -BigDecimal::one(), "-1"); convert_constants!(convert_two, BigDecimal::from(2), "2"); convert_constants!(convert_ten, BigDecimal::from_str("10").unwrap(), "10"); convert_constants!( convert_one_hundred_point_one, BigDecimal::from_str("100.1").unwrap(), "100.1" ); convert_constants!( convert_one_thousand, BigDecimal::from_str("1000").unwrap(), "1000" ); convert_constants!( convert_scientific, BigDecimal::from_str("1e10").unwrap(), "1e10" ); #[cfg(not(target_arch = "wasm32"))] proptest! { #[test] fn test_roundtrip( number in 0..28u32 ) { let num = BigDecimal::from(number); Python::attach(|py| { let rs_dec = num.clone().into_pyobject(py).unwrap(); let locals = PyDict::new(py); locals.set_item("rs_dec", &rs_dec).unwrap(); py.run( &CString::new(format!( "import decimal\npy_dec = decimal.Decimal(\"{num}\")\nassert py_dec == rs_dec")).unwrap(), None, Some(&locals)).unwrap(); let roundtripped: BigDecimal = rs_dec.extract().unwrap(); assert_eq!(num, roundtripped); }) } #[test] fn test_integers(num in any::()) { Python::attach(|py| { let py_num = num.into_pyobject(py).unwrap(); let roundtripped: BigDecimal = py_num.extract().unwrap(); let rs_dec = BigDecimal::from(num); assert_eq!(rs_dec, roundtripped); }) } } #[test] fn test_nan() { Python::attach(|py| { let locals = PyDict::new(py); py.run( c"import decimal\npy_dec = decimal.Decimal(\"NaN\")", None, Some(&locals), ) .unwrap(); let py_dec = locals.get_item("py_dec").unwrap().unwrap(); let roundtripped: Result = py_dec.extract(); assert!(roundtripped.is_err()); }) } #[test] fn test_infinity() { Python::attach(|py| { let locals = PyDict::new(py); py.run( c"import decimal\npy_dec = decimal.Decimal(\"Infinity\")", None, Some(&locals), ) .unwrap(); let py_dec = locals.get_item("py_dec").unwrap().unwrap(); let roundtripped: Result = py_dec.extract(); assert!(roundtripped.is_err()); }) } #[test] fn test_no_precision_loss() { Python::attach(|py| { let src = "1e4"; let expected = get_decimal_cls(py) .unwrap() .call1((src,)) .unwrap() .call_method0("as_tuple") .unwrap(); let actual = src .parse::() .unwrap() .into_pyobject(py) .unwrap() .call_method0("as_tuple") .unwrap(); assert!(actual.eq(expected).unwrap()); }); } } ================================================ FILE: src/conversions/bytes.rs ================================================ #![cfg(feature = "bytes")] //! Conversions to and from [bytes](https://docs.rs/bytes/latest/bytes/)'s [`Bytes`]. //! //! This is useful for efficiently converting Python's `bytes` types efficiently. //! While `bytes` will be directly borrowed, converting from `bytearray` will result in a copy. //! //! When converting `Bytes` back into Python, this will do a copy, just like `&[u8]` and `Vec`. //! //! # When to use `Bytes` //! //! Unless you specifically need [`Bytes`] for ref-counted ownership and sharing, //! you may find that using `&[u8]`, `Vec`, [`Bound`], or [`PyBackedBytes`] //! is simpler for most use cases. //! //! # Setup //! //! To use this feature, add in your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! bytes = "1.10" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"bytes\"] }")] //! ``` //! //! Note that you must use compatible versions of bytes and PyO3. //! //! # Example //! //! Rust code to create functions which return `Bytes` or take `Bytes` as arguments: //! //! ```rust,no_run //! use pyo3::prelude::*; //! use bytes::Bytes; //! //! #[pyfunction] //! fn get_message_bytes() -> Bytes { //! Bytes::from_static(b"Hello Python!") //! } //! //! #[pyfunction] //! fn num_bytes(bytes: Bytes) -> usize { //! bytes.len() //! } //! //! #[pymodule] //! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> { //! m.add_function(wrap_pyfunction!(get_message_bytes, m)?)?; //! m.add_function(wrap_pyfunction!(num_bytes, m)?)?; //! Ok(()) //! } //! ``` //! //! Python code that calls these functions: //! //! ```python //! from my_module import get_message_bytes, num_bytes //! //! message = get_message_bytes() //! assert message == b"Hello Python!" //! //! size = num_bytes(message) //! assert size == 13 //! ``` use bytes::Bytes; use crate::conversion::IntoPyObject; #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; use crate::instance::Bound; use crate::pybacked::PyBackedBytes; use crate::types::PyBytes; #[cfg(feature = "experimental-inspect")] use crate::PyTypeInfo; use crate::{Borrowed, CastError, FromPyObject, PyAny, PyErr, Python}; impl<'a, 'py> FromPyObject<'a, 'py> for Bytes { type Error = CastError<'a, 'py>; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyBackedBytes::INPUT_TYPE; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { Ok(Bytes::from_owner(obj.extract::()?)) } } impl<'py> IntoPyObject<'py> for Bytes { type Target = PyBytes; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(PyBytes::new(py, &self)) } } impl<'py> IntoPyObject<'py> for &Bytes { type Target = PyBytes; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyBytes::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { Ok(PyBytes::new(py, self)) } } #[cfg(test)] mod tests { use super::*; use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods, PyBytes}; use crate::Python; #[test] fn test_bytes() { Python::attach(|py| { let py_bytes = PyBytes::new(py, b"foobar"); let bytes: Bytes = py_bytes.extract().unwrap(); assert_eq!(&*bytes, b"foobar"); let bytes = Bytes::from_static(b"foobar").into_pyobject(py).unwrap(); assert!(bytes.is_instance_of::()); }); } #[test] fn test_bytearray() { Python::attach(|py| { let py_bytearray = PyByteArray::new(py, b"foobar"); let bytes: Bytes = py_bytearray.extract().unwrap(); assert_eq!(&*bytes, b"foobar"); // Editing the bytearray should not change extracted Bytes unsafe { py_bytearray.as_bytes_mut()[0] = b'x' }; assert_eq!(&bytes, "foobar"); assert_eq!(&py_bytearray.extract::>().unwrap(), b"xoobar"); }); } } ================================================ FILE: src/conversions/chrono.rs ================================================ #![cfg(feature = "chrono")] //! Conversions to and from [chrono](https://docs.rs/chrono/)’s `Duration`, //! `NaiveDate`, `NaiveTime`, `DateTime`, `FixedOffset`, and `Utc`. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! chrono = "0.4" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"chrono\"] }")] //! ``` //! //! Note that you must use compatible versions of chrono and PyO3. //! The required chrono version may vary based on the version of PyO3. //! //! # Example: Convert a `datetime.datetime` to chrono's `DateTime` //! //! ```rust //! use chrono::{DateTime, Duration, TimeZone, Utc}; //! use pyo3::{Python, PyResult, IntoPyObject, types::PyAnyMethods}; //! //! fn main() -> PyResult<()> { //! Python::initialize(); //! Python::attach(|py| { //! // Build some chrono values //! let chrono_datetime = Utc.with_ymd_and_hms(2022, 1, 1, 12, 0, 0).unwrap(); //! let chrono_duration = Duration::seconds(1); //! // Convert them to Python //! let py_datetime = chrono_datetime.into_pyobject(py)?; //! let py_timedelta = chrono_duration.into_pyobject(py)?; //! // Do an operation in Python //! let py_sum = py_datetime.call_method1("__add__", (py_timedelta,))?; //! // Convert back to Rust //! let chrono_sum: DateTime = py_sum.extract()?; //! println!("DateTime: {}", chrono_datetime); //! Ok(()) //! }) //! } //! ``` use crate::conversion::{FromPyObjectOwned, IntoPyObject}; use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError}; #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; use crate::intern; use crate::types::any::PyAnyMethods; use crate::types::PyNone; use crate::types::{PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo, PyTzInfoAccess}; #[cfg(not(Py_LIMITED_API))] use crate::types::{PyDateAccess, PyDeltaAccess, PyTimeAccess}; #[cfg(feature = "chrono-local")] use crate::{ exceptions::PyRuntimeError, sync::PyOnceLock, types::{PyString, PyStringMethods}, Py, }; #[cfg(feature = "experimental-inspect")] use crate::{type_hint_identifier, PyTypeInfo}; use crate::{Borrowed, Bound, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyResult, Python}; use chrono::offset::{FixedOffset, Utc}; #[cfg(feature = "chrono-local")] use chrono::Local; use chrono::{ DateTime, Datelike, Duration, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, }; impl<'py> IntoPyObject<'py> for Duration { type Target = PyDelta; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { // Total number of days let days = self.num_days(); // Remainder of seconds let secs_dur = self - Duration::days(days); let secs = secs_dur.num_seconds(); // Fractional part of the microseconds let micros = (secs_dur - Duration::seconds(secs_dur.num_seconds())) .num_microseconds() // This should never panic since we are just getting the fractional // part of the total microseconds, which should never overflow. .unwrap(); // We do not need to check the days i64 to i32 cast from rust because // python will panic with OverflowError. // We pass true as the `normalize` parameter since we'd need to do several checks here to // avoid that, and it shouldn't have a big performance impact. // The seconds and microseconds cast should never overflow since it's at most the number of seconds per day PyDelta::new( py, days.try_into().unwrap_or(i32::MAX), secs.try_into()?, micros.try_into()?, true, ) } } impl<'py> IntoPyObject<'py> for &Duration { type Target = PyDelta; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = Duration::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } impl FromPyObject<'_, '_> for Duration { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyDelta::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let delta = ob.cast::()?; // Python size are much lower than rust size so we do not need bound checks. // 0 <= microseconds < 1000000 // 0 <= seconds < 3600*24 // -999999999 <= days <= 999999999 #[cfg(not(Py_LIMITED_API))] let (days, seconds, microseconds) = { ( delta.get_days().into(), delta.get_seconds().into(), delta.get_microseconds().into(), ) }; #[cfg(Py_LIMITED_API)] let (days, seconds, microseconds) = { let py = delta.py(); ( delta.getattr(intern!(py, "days"))?.extract()?, delta.getattr(intern!(py, "seconds"))?.extract()?, delta.getattr(intern!(py, "microseconds"))?.extract()?, ) }; Ok( Duration::days(days) + Duration::seconds(seconds) + Duration::microseconds(microseconds), ) } } impl<'py> IntoPyObject<'py> for NaiveDate { type Target = PyDate; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyDate::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let DateArgs { year, month, day } = (&self).into(); PyDate::new(py, year, month, day) } } impl<'py> IntoPyObject<'py> for &NaiveDate { type Target = PyDate; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = NaiveDate::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } impl FromPyObject<'_, '_> for NaiveDate { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyDate::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let date = &*ob.cast::()?; py_date_to_naive_date(date) } } impl<'py> IntoPyObject<'py> for NaiveTime { type Target = PyTime; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyTime::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let TimeArgs { hour, min, sec, micro, truncated_leap_second, } = (&self).into(); let time = PyTime::new(py, hour, min, sec, micro, None)?; if truncated_leap_second { warn_truncated_leap_second(&time); } Ok(time) } } impl<'py> IntoPyObject<'py> for &NaiveTime { type Target = PyTime; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = NaiveTime::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } impl FromPyObject<'_, '_> for NaiveTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyTime::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let time = &*ob.cast::()?; py_time_to_naive_time(time) } } impl<'py> IntoPyObject<'py> for NaiveDateTime { type Target = PyDateTime; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let DateArgs { year, month, day } = (&self.date()).into(); let TimeArgs { hour, min, sec, micro, truncated_leap_second, } = (&self.time()).into(); let datetime = PyDateTime::new(py, year, month, day, hour, min, sec, micro, None)?; if truncated_leap_second { warn_truncated_leap_second(&datetime); } Ok(datetime) } } impl<'py> IntoPyObject<'py> for &NaiveDateTime { type Target = PyDateTime; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = NaiveDateTime::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } impl FromPyObject<'_, '_> for NaiveDateTime { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn extract(dt: Borrowed<'_, '_, PyAny>) -> Result { let dt = &*dt.cast::()?; // If the user tries to convert a timezone aware datetime into a naive one, // we return a hard error. We could silently remove tzinfo, or assume local timezone // and do a conversion, but better leave this decision to the user of the library. let has_tzinfo = dt.get_tzinfo().is_some(); if has_tzinfo { return Err(PyTypeError::new_err("expected a datetime without tzinfo")); } let dt = NaiveDateTime::new(py_date_to_naive_date(dt)?, py_time_to_naive_time(dt)?); Ok(dt) } } impl<'py, Tz: TimeZone> IntoPyObject<'py> for DateTime where Tz: IntoPyObject<'py>, { type Target = PyDateTime; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = <&DateTime>::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (&self).into_pyobject(py) } } impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime where Tz: IntoPyObject<'py>, { type Target = PyDateTime; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { let tz = self.timezone().into_bound_py_any(py)?.cast_into()?; let DateArgs { year, month, day } = (&self.naive_local().date()).into(); let TimeArgs { hour, min, sec, micro, truncated_leap_second, } = (&self.naive_local().time()).into(); let fold = matches!( self.timezone().offset_from_local_datetime(&self.naive_local()), LocalResult::Ambiguous(_, latest) if self.offset().fix() == latest.fix() ); let datetime = PyDateTime::new_with_fold( py, year, month, day, hour, min, sec, micro, Some(&tz), fold, )?; if truncated_leap_second { warn_truncated_leap_second(&datetime); } Ok(datetime) } } impl<'py, Tz> FromPyObject<'_, 'py> for DateTime where Tz: TimeZone + FromPyObjectOwned<'py>, { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyDateTime::TYPE_HINT; fn extract(dt: Borrowed<'_, 'py, PyAny>) -> Result { let dt = &*dt.cast::()?; let tzinfo = dt.get_tzinfo(); let tz = if let Some(tzinfo) = tzinfo { tzinfo.extract().map_err(Into::into)? } else { // Special case: allow naive `datetime` objects for `DateTime`, interpreting them as local time. #[cfg(feature = "chrono-local")] if let Some(tz) = Tz::as_local_tz(crate::conversion::private::Token) { return py_datetime_to_datetime_with_timezone(dt, tz); } return Err(PyTypeError::new_err( "expected a datetime with non-None tzinfo", )); }; py_datetime_to_datetime_with_timezone(dt, tz) } } impl<'py> IntoPyObject<'py> for FixedOffset { type Target = PyTzInfo; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("datetime", "timezone"); fn into_pyobject(self, py: Python<'py>) -> Result { let seconds_offset = self.local_minus_utc(); let td = PyDelta::new(py, 0, seconds_offset, 0, true)?; PyTzInfo::fixed_offset(py, td) } } impl<'py> IntoPyObject<'py> for &FixedOffset { type Target = PyTzInfo; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = FixedOffset::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } impl FromPyObject<'_, '_> for FixedOffset { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; /// Convert python tzinfo to rust [`FixedOffset`]. /// /// Note that the conversion will result in precision lost in microseconds as chrono offset /// does not supports microseconds. fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let ob = ob.cast::()?; // Passing Python's None to the `utcoffset` function will only // work for timezones defined as fixed offsets in Python. // Any other timezone would require a datetime as the parameter, and return // None if the datetime is not provided. // Trying to convert None to a PyDelta in the next line will then fail. let py_timedelta = ob.call_method1(intern!(ob.py(), "utcoffset"), (PyNone::get(ob.py()),))?; if py_timedelta.is_none() { return Err(PyTypeError::new_err(format!( "{ob:?} is not a fixed offset timezone" ))); } let total_seconds: Duration = py_timedelta.extract()?; // This cast is safe since the timedelta is limited to -24 hours and 24 hours. let total_seconds = total_seconds.num_seconds() as i32; FixedOffset::east_opt(total_seconds) .ok_or_else(|| PyValueError::new_err("fixed offset out of bounds")) } } impl<'py> IntoPyObject<'py> for Utc { type Target = PyTzInfo; type Output = Borrowed<'static, 'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("datetime", "timezone"); fn into_pyobject(self, py: Python<'py>) -> Result { PyTzInfo::utc(py) } } impl<'py> IntoPyObject<'py> for &Utc { type Target = PyTzInfo; type Output = Borrowed<'static, 'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = Utc::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } impl FromPyObject<'_, '_> for Utc { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = Utc::OUTPUT_TYPE; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let py_utc = Utc.into_pyobject(ob.py())?; if ob.eq(py_utc)? { Ok(Utc) } else { Err(PyValueError::new_err("expected datetime.timezone.utc")) } } } #[cfg(feature = "chrono-local")] impl<'py> IntoPyObject<'py> for Local { type Target = PyTzInfo; type Output = Borrowed<'static, 'py, Self::Target>; type Error = PyErr; #[cfg(all(feature = "experimental-inspect", Py_3_9))] const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("zoneinfo", "ZoneInfo"); #[cfg(all(feature = "experimental-inspect", not(Py_3_9)))] const OUTPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { static LOCAL_TZ: PyOnceLock> = PyOnceLock::new(); let tz = LOCAL_TZ .get_or_try_init(py, || { let iana_name = iana_time_zone::get_timezone().map_err(|e| { PyRuntimeError::new_err(format!("Could not get local timezone: {e}")) })?; PyTzInfo::timezone(py, iana_name).map(Bound::unbind) })? .bind_borrowed(py); Ok(tz) } } #[cfg(feature = "chrono-local")] impl<'py> IntoPyObject<'py> for &Local { type Target = PyTzInfo; type Output = Borrowed<'static, 'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = Local::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } #[cfg(feature = "chrono-local")] impl FromPyObject<'_, '_> for Local { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = Local::OUTPUT_TYPE; fn extract(ob: Borrowed<'_, '_, PyAny>) -> PyResult { let local_tz = Local.into_pyobject(ob.py())?; if ob.eq(local_tz)? { Ok(Local) } else { let name = local_tz.getattr("key")?.cast_into::()?; Err(PyValueError::new_err(format!( "expected local timezone {}", name.to_cow()? ))) } } #[inline] fn as_local_tz(_: crate::conversion::private::Token) -> Option { Some(Local) } } struct DateArgs { year: i32, month: u8, day: u8, } impl From<&NaiveDate> for DateArgs { fn from(value: &NaiveDate) -> Self { Self { year: value.year(), month: value.month() as u8, day: value.day() as u8, } } } struct TimeArgs { hour: u8, min: u8, sec: u8, micro: u32, truncated_leap_second: bool, } impl From<&NaiveTime> for TimeArgs { fn from(value: &NaiveTime) -> Self { let ns = value.nanosecond(); let checked_sub = ns.checked_sub(1_000_000_000); let truncated_leap_second = checked_sub.is_some(); let micro = checked_sub.unwrap_or(ns) / 1000; Self { hour: value.hour() as u8, min: value.minute() as u8, sec: value.second() as u8, micro, truncated_leap_second, } } } fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) { let py = obj.py(); if let Err(e) = PyErr::warn( py, &py.get_type::(), c"ignored leap-second, `datetime` does not support leap-seconds", 0, ) { e.write_unraisable(py, Some(obj)) }; } #[cfg(not(Py_LIMITED_API))] fn py_date_to_naive_date( py_date: impl std::ops::Deref, ) -> PyResult { NaiveDate::from_ymd_opt( py_date.get_year(), py_date.get_month().into(), py_date.get_day().into(), ) .ok_or_else(|| PyValueError::new_err("invalid or out-of-range date")) } #[cfg(Py_LIMITED_API)] fn py_date_to_naive_date(py_date: &Bound<'_, PyAny>) -> PyResult { NaiveDate::from_ymd_opt( py_date.getattr(intern!(py_date.py(), "year"))?.extract()?, py_date.getattr(intern!(py_date.py(), "month"))?.extract()?, py_date.getattr(intern!(py_date.py(), "day"))?.extract()?, ) .ok_or_else(|| PyValueError::new_err("invalid or out-of-range date")) } #[cfg(not(Py_LIMITED_API))] fn py_time_to_naive_time( py_time: impl std::ops::Deref, ) -> PyResult { NaiveTime::from_hms_micro_opt( py_time.get_hour().into(), py_time.get_minute().into(), py_time.get_second().into(), py_time.get_microsecond(), ) .ok_or_else(|| PyValueError::new_err("invalid or out-of-range time")) } #[cfg(Py_LIMITED_API)] fn py_time_to_naive_time(py_time: &Bound<'_, PyAny>) -> PyResult { NaiveTime::from_hms_micro_opt( py_time.getattr(intern!(py_time.py(), "hour"))?.extract()?, py_time .getattr(intern!(py_time.py(), "minute"))? .extract()?, py_time .getattr(intern!(py_time.py(), "second"))? .extract()?, py_time .getattr(intern!(py_time.py(), "microsecond"))? .extract()?, ) .ok_or_else(|| PyValueError::new_err("invalid or out-of-range time")) } fn py_datetime_to_datetime_with_timezone( dt: &Bound<'_, PyDateTime>, tz: Tz, ) -> PyResult> { let naive_dt = NaiveDateTime::new(py_date_to_naive_date(dt)?, py_time_to_naive_time(dt)?); match naive_dt.and_local_timezone(tz) { LocalResult::Single(value) => Ok(value), LocalResult::Ambiguous(earliest, latest) => { #[cfg(not(Py_LIMITED_API))] let fold = dt.get_fold(); #[cfg(Py_LIMITED_API)] let fold = dt.getattr(intern!(dt.py(), "fold"))?.extract::()? > 0; if fold { Ok(latest) } else { Ok(earliest) } } LocalResult::None => Err(PyValueError::new_err(format!( "The datetime {dt:?} contains an incompatible timezone" ))), } } #[cfg(test)] mod tests { use super::*; use crate::{test_utils::assert_warnings, types::PyTuple, BoundObject}; use std::{cmp::Ordering, panic}; #[test] // Only Python>=3.9 has the zoneinfo package // We skip the test on windows too since we'd need to install // tzdata there to make this work. #[cfg(all(Py_3_9, not(target_os = "windows")))] fn test_zoneinfo_is_not_fixed_offset() { use crate::types::any::PyAnyMethods; use crate::types::dict::PyDictMethods; Python::attach(|py| { let locals = crate::types::PyDict::new(py); py.run( c"import zoneinfo; zi = zoneinfo.ZoneInfo('Europe/London')", None, Some(&locals), ) .unwrap(); let result: PyResult = locals.get_item("zi").unwrap().unwrap().extract(); assert!(result.is_err()); let res = result.err().unwrap(); // Also check the error message is what we expect let msg = res.value(py).repr().unwrap().to_string(); assert_eq!(msg, "TypeError(\"zoneinfo.ZoneInfo(key='Europe/London') is not a fixed offset timezone\")"); }); } #[test] fn test_timezone_aware_to_naive_fails() { // Test that if a user tries to convert a python's timezone aware datetime into a naive // one, the conversion fails. Python::attach(|py| { let py_datetime = new_py_datetime_ob(py, "datetime", (2022, 1, 1, 1, 0, 0, 0, python_utc(py))); // Now test that converting a PyDateTime with tzinfo to a NaiveDateTime fails let res: PyResult = py_datetime.extract(); assert_eq!( res.unwrap_err().value(py).repr().unwrap().to_string(), "TypeError('expected a datetime without tzinfo')" ); }); } #[test] fn test_naive_to_timezone_aware_fails() { // Test that if a user tries to convert a python's timezone aware datetime into a naive // one, the conversion fails. Python::attach(|py| { let py_datetime = new_py_datetime_ob(py, "datetime", (2022, 1, 1, 1, 0, 0, 0)); // Now test that converting a PyDateTime with tzinfo to a NaiveDateTime fails let res: PyResult> = py_datetime.extract(); assert_eq!( res.unwrap_err().value(py).repr().unwrap().to_string(), "TypeError('expected a datetime with non-None tzinfo')" ); // Now test that converting a PyDateTime with tzinfo to a NaiveDateTime fails let res: PyResult> = py_datetime.extract(); assert_eq!( res.unwrap_err().value(py).repr().unwrap().to_string(), "TypeError('expected a datetime with non-None tzinfo')" ); }); } #[test] fn test_invalid_types_fail() { // Test that if a user tries to convert a python's timezone aware datetime into a naive // one, the conversion fails. Python::attach(|py| { let none = py.None().into_bound(py); assert_eq!( none.extract::().unwrap_err().to_string(), "TypeError: 'None' is not an instance of 'timedelta'" ); assert_eq!( none.extract::().unwrap_err().to_string(), "TypeError: 'None' is not an instance of 'tzinfo'" ); assert_eq!( none.extract::().unwrap_err().to_string(), "ValueError: expected datetime.timezone.utc" ); assert_eq!( none.extract::().unwrap_err().to_string(), "TypeError: 'None' is not an instance of 'time'" ); assert_eq!( none.extract::().unwrap_err().to_string(), "TypeError: 'None' is not an instance of 'date'" ); assert_eq!( none.extract::().unwrap_err().to_string(), "TypeError: 'None' is not an instance of 'datetime'" ); assert_eq!( none.extract::>().unwrap_err().to_string(), "TypeError: 'None' is not an instance of 'datetime'" ); assert_eq!( none.extract::>() .unwrap_err() .to_string(), "TypeError: 'None' is not an instance of 'datetime'" ); }); } #[test] fn test_pyo3_timedelta_into_pyobject() { // Utility function used to check different durations. // The `name` parameter is used to identify the check in case of a failure. let check = |name: &'static str, delta: Duration, py_days, py_seconds, py_ms| { Python::attach(|py| { let delta = delta.into_pyobject(py).unwrap(); let py_delta = new_py_datetime_ob(py, "timedelta", (py_days, py_seconds, py_ms)); assert!( delta.eq(&py_delta).unwrap(), "{name}: {delta} != {py_delta}" ); }); }; let delta = Duration::days(-1) + Duration::seconds(1) + Duration::microseconds(-10); check("delta normalization", delta, -1, 1, -10); // Check the minimum value allowed by PyDelta, which is different // from the minimum value allowed in Duration. This should pass. let delta = Duration::seconds(-86399999913600); // min check("delta min value", delta, -999999999, 0, 0); // Same, for max value let delta = Duration::seconds(86399999999999) + Duration::nanoseconds(999999000); // max check("delta max value", delta, 999999999, 86399, 999999); // Also check that trying to convert an out of bound value errors. Python::attach(|py| { // min_value and max_value were deprecated in chrono 0.4.39 #[allow(deprecated)] { assert!(Duration::min_value().into_pyobject(py).is_err()); assert!(Duration::max_value().into_pyobject(py).is_err()); } }); } #[test] fn test_pyo3_timedelta_frompyobject() { // Utility function used to check different durations. // The `name` parameter is used to identify the check in case of a failure. let check = |name: &'static str, delta: Duration, py_days, py_seconds, py_ms| { Python::attach(|py| { let py_delta = new_py_datetime_ob(py, "timedelta", (py_days, py_seconds, py_ms)); let py_delta: Duration = py_delta.extract().unwrap(); assert_eq!(py_delta, delta, "{name}: {py_delta} != {delta}"); }) }; // Check the minimum value allowed by PyDelta, which is different // from the minimum value allowed in Duration. This should pass. check( "min py_delta value", Duration::seconds(-86399999913600), -999999999, 0, 0, ); // Same, for max value check( "max py_delta value", Duration::seconds(86399999999999) + Duration::microseconds(999999), 999999999, 86399, 999999, ); // This check is to assert that we can't construct every possible Duration from a PyDelta // since they have different bounds. Python::attach(|py| { let low_days: i32 = -1000000000; // This is possible assert!(panic::catch_unwind(|| Duration::days(low_days as i64)).is_ok()); // This panics on PyDelta::new assert!(panic::catch_unwind(|| { let py_delta = new_py_datetime_ob(py, "timedelta", (low_days, 0, 0)); if let Ok(_duration) = py_delta.extract::() { // So we should never get here } }) .is_err()); let high_days: i32 = 1000000000; // This is possible assert!(panic::catch_unwind(|| Duration::days(high_days as i64)).is_ok()); // This panics on PyDelta::new assert!(panic::catch_unwind(|| { let py_delta = new_py_datetime_ob(py, "timedelta", (high_days, 0, 0)); if let Ok(_duration) = py_delta.extract::() { // So we should never get here } }) .is_err()); }); } #[test] fn test_pyo3_date_into_pyobject() { let eq_ymd = |name: &'static str, year, month, day| { Python::attach(|py| { let date = NaiveDate::from_ymd_opt(year, month, day) .unwrap() .into_pyobject(py) .unwrap(); let py_date = new_py_datetime_ob(py, "date", (year, month, day)); assert_eq!( date.compare(&py_date).unwrap(), Ordering::Equal, "{name}: {date} != {py_date}" ); }) }; eq_ymd("past date", 2012, 2, 29); eq_ymd("min date", 1, 1, 1); eq_ymd("future date", 3000, 6, 5); eq_ymd("max date", 9999, 12, 31); } #[test] fn test_pyo3_date_frompyobject() { let eq_ymd = |name: &'static str, year, month, day| { Python::attach(|py| { let py_date = new_py_datetime_ob(py, "date", (year, month, day)); let py_date: NaiveDate = py_date.extract().unwrap(); let date = NaiveDate::from_ymd_opt(year, month, day).unwrap(); assert_eq!(py_date, date, "{name}: {date} != {py_date}"); }) }; eq_ymd("past date", 2012, 2, 29); eq_ymd("min date", 1, 1, 1); eq_ymd("future date", 3000, 6, 5); eq_ymd("max date", 9999, 12, 31); } #[test] fn test_pyo3_datetime_into_pyobject_utc() { Python::attach(|py| { let check_utc = |name: &'static str, year, month, day, hour, minute, second, ms, py_ms| { let datetime = NaiveDate::from_ymd_opt(year, month, day) .unwrap() .and_hms_micro_opt(hour, minute, second, ms) .unwrap() .and_utc(); let datetime = datetime.into_pyobject(py).unwrap(); let py_datetime = new_py_datetime_ob( py, "datetime", ( year, month, day, hour, minute, second, py_ms, python_utc(py), ), ); assert_eq!( datetime.compare(&py_datetime).unwrap(), Ordering::Equal, "{name}: {datetime} != {py_datetime}" ); }; check_utc("regular", 2014, 5, 6, 7, 8, 9, 999_999, 999_999); assert_warnings!( py, check_utc("leap second", 2014, 5, 6, 7, 8, 59, 1_999_999, 999_999), [( PyUserWarning, "ignored leap-second, `datetime` does not support leap-seconds" )] ); }) } #[test] fn test_pyo3_datetime_into_pyobject_fixed_offset() { Python::attach(|py| { let check_fixed_offset = |name: &'static str, year, month, day, hour, minute, second, ms, py_ms| { let offset = FixedOffset::east_opt(3600).unwrap(); let datetime = NaiveDate::from_ymd_opt(year, month, day) .unwrap() .and_hms_micro_opt(hour, minute, second, ms) .unwrap() .and_local_timezone(offset) .unwrap(); let datetime = datetime.into_pyobject(py).unwrap(); let py_tz = offset.into_pyobject(py).unwrap(); let py_datetime = new_py_datetime_ob( py, "datetime", (year, month, day, hour, minute, second, py_ms, py_tz), ); assert_eq!( datetime.compare(&py_datetime).unwrap(), Ordering::Equal, "{name}: {datetime} != {py_datetime}" ); }; check_fixed_offset("regular", 2014, 5, 6, 7, 8, 9, 999_999, 999_999); assert_warnings!( py, check_fixed_offset("leap second", 2014, 5, 6, 7, 8, 59, 1_999_999, 999_999), [( PyUserWarning, "ignored leap-second, `datetime` does not support leap-seconds" )] ); }) } #[test] #[cfg(all(Py_3_9, feature = "chrono-tz", not(windows)))] fn test_pyo3_datetime_into_pyobject_tz() { Python::attach(|py| { let datetime = NaiveDate::from_ymd_opt(2024, 12, 11) .unwrap() .and_hms_opt(23, 3, 13) .unwrap() .and_local_timezone(chrono_tz::Tz::Europe__London) .unwrap(); let datetime = datetime.into_pyobject(py).unwrap(); let py_datetime = new_py_datetime_ob( py, "datetime", ( 2024, 12, 11, 23, 3, 13, 0, python_zoneinfo(py, "Europe/London"), ), ); assert_eq!(datetime.compare(&py_datetime).unwrap(), Ordering::Equal); }) } #[test] fn test_pyo3_datetime_frompyobject_utc() { Python::attach(|py| { let year = 2014; let month = 5; let day = 6; let hour = 7; let minute = 8; let second = 9; let micro = 999_999; let tz_utc = PyTzInfo::utc(py).unwrap(); let py_datetime = new_py_datetime_ob( py, "datetime", (year, month, day, hour, minute, second, micro, tz_utc), ); let py_datetime: DateTime = py_datetime.extract().unwrap(); let datetime = NaiveDate::from_ymd_opt(year, month, day) .unwrap() .and_hms_micro_opt(hour, minute, second, micro) .unwrap() .and_utc(); assert_eq!(py_datetime, datetime,); }) } #[test] #[cfg(feature = "chrono-local")] fn test_pyo3_naive_datetime_frompyobject_local() { Python::attach(|py| { let year = 2014; let month = 5; let day = 6; let hour = 7; let minute = 8; let second = 9; let micro = 999_999; let py_datetime = new_py_datetime_ob( py, "datetime", (year, month, day, hour, minute, second, micro), ); let py_datetime: DateTime = py_datetime.extract().unwrap(); let expected_datetime = NaiveDate::from_ymd_opt(year, month, day) .unwrap() .and_hms_micro_opt(hour, minute, second, micro) .unwrap() .and_local_timezone(Local) .unwrap(); assert_eq!(py_datetime, expected_datetime); }) } #[test] fn test_pyo3_datetime_frompyobject_fixed_offset() { Python::attach(|py| { let year = 2014; let month = 5; let day = 6; let hour = 7; let minute = 8; let second = 9; let micro = 999_999; let offset = FixedOffset::east_opt(3600).unwrap(); let py_tz = offset.into_pyobject(py).unwrap(); let py_datetime = new_py_datetime_ob( py, "datetime", (year, month, day, hour, minute, second, micro, py_tz), ); let datetime_from_py: DateTime = py_datetime.extract().unwrap(); let datetime = NaiveDate::from_ymd_opt(year, month, day) .unwrap() .and_hms_micro_opt(hour, minute, second, micro) .unwrap(); let datetime = datetime.and_local_timezone(offset).unwrap(); assert_eq!(datetime_from_py, datetime); assert!( py_datetime.extract::>().is_err(), "Extracting Utc from nonzero FixedOffset timezone will fail" ); let utc = python_utc(py); let py_datetime_utc = new_py_datetime_ob( py, "datetime", (year, month, day, hour, minute, second, micro, utc), ); assert!( py_datetime_utc.extract::>().is_ok(), "Extracting FixedOffset from Utc timezone will succeed" ); }) } #[test] fn test_pyo3_offset_fixed_into_pyobject() { Python::attach(|py| { // Chrono offset let offset = FixedOffset::east_opt(3600) .unwrap() .into_pyobject(py) .unwrap(); // Python timezone from timedelta let td = new_py_datetime_ob(py, "timedelta", (0, 3600, 0)); let py_timedelta = new_py_datetime_ob(py, "timezone", (td,)); // Should be equal assert!(offset.eq(py_timedelta).unwrap()); // Same but with negative values let offset = FixedOffset::east_opt(-3600) .unwrap() .into_pyobject(py) .unwrap(); let td = new_py_datetime_ob(py, "timedelta", (0, -3600, 0)); let py_timedelta = new_py_datetime_ob(py, "timezone", (td,)); assert!(offset.eq(py_timedelta).unwrap()); }) } #[test] fn test_pyo3_offset_fixed_frompyobject() { Python::attach(|py| { let py_timedelta = new_py_datetime_ob(py, "timedelta", (0, 3600, 0)); let py_tzinfo = new_py_datetime_ob(py, "timezone", (py_timedelta,)); let offset: FixedOffset = py_tzinfo.extract().unwrap(); assert_eq!(FixedOffset::east_opt(3600).unwrap(), offset); }) } #[test] fn test_pyo3_offset_utc_into_pyobject() { Python::attach(|py| { let utc = Utc.into_pyobject(py).unwrap(); let py_utc = python_utc(py); assert!(utc.is(&py_utc)); }) } #[test] fn test_pyo3_offset_utc_frompyobject() { Python::attach(|py| { let py_utc = python_utc(py); let py_utc: Utc = py_utc.extract().unwrap(); assert_eq!(Utc, py_utc); let py_timedelta = new_py_datetime_ob(py, "timedelta", (0, 0, 0)); let py_timezone_utc = new_py_datetime_ob(py, "timezone", (py_timedelta,)); let py_timezone_utc: Utc = py_timezone_utc.extract().unwrap(); assert_eq!(Utc, py_timezone_utc); let py_timedelta = new_py_datetime_ob(py, "timedelta", (0, 3600, 0)); let py_timezone = new_py_datetime_ob(py, "timezone", (py_timedelta,)); assert!(py_timezone.extract::().is_err()); }) } #[test] fn test_pyo3_time_into_pyobject() { Python::attach(|py| { let check_time = |name: &'static str, hour, minute, second, ms, py_ms| { let time = NaiveTime::from_hms_micro_opt(hour, minute, second, ms) .unwrap() .into_pyobject(py) .unwrap(); let py_time = new_py_datetime_ob(py, "time", (hour, minute, second, py_ms)); assert!(time.eq(&py_time).unwrap(), "{name}: {time} != {py_time}"); }; check_time("regular", 3, 5, 7, 999_999, 999_999); assert_warnings!( py, check_time("leap second", 3, 5, 59, 1_999_999, 999_999), [( PyUserWarning, "ignored leap-second, `datetime` does not support leap-seconds" )] ); }) } #[test] fn test_pyo3_time_frompyobject() { let hour = 3; let minute = 5; let second = 7; let micro = 999_999; Python::attach(|py| { let py_time = new_py_datetime_ob(py, "time", (hour, minute, second, micro)); let py_time: NaiveTime = py_time.extract().unwrap(); let time = NaiveTime::from_hms_micro_opt(hour, minute, second, micro).unwrap(); assert_eq!(py_time, time); }) } fn new_py_datetime_ob<'py, A>(py: Python<'py>, name: &str, args: A) -> Bound<'py, PyAny> where A: IntoPyObject<'py, Target = PyTuple>, { py.import("datetime") .unwrap() .getattr(name) .unwrap() .call1( args.into_pyobject(py) .map_err(Into::into) .unwrap() .into_bound(), ) .unwrap() } fn python_utc(py: Python<'_>) -> Bound<'_, PyAny> { py.import("datetime") .unwrap() .getattr("timezone") .unwrap() .getattr("utc") .unwrap() } #[cfg(all(Py_3_9, feature = "chrono-tz", not(windows)))] fn python_zoneinfo<'py>(py: Python<'py>, timezone: &str) -> Bound<'py, PyAny> { py.import("zoneinfo") .unwrap() .getattr("ZoneInfo") .unwrap() .call1((timezone,)) .unwrap() } #[cfg(not(any(target_arch = "wasm32")))] mod proptests { use super::*; use crate::test_utils::CatchWarnings; use crate::types::IntoPyDict; use proptest::prelude::*; use std::ffi::CString; proptest! { // Range is limited to 1970 to 2038 due to windows limitations #[test] fn test_pyo3_offset_fixed_frompyobject_created_in_python(timestamp in 0..(i32::MAX as i64), timedelta in -86399i32..=86399i32) { Python::attach(|py| { let globals = [("datetime", py.import("datetime").unwrap())].into_py_dict(py).unwrap(); let code = format!("datetime.datetime.fromtimestamp({timestamp}).replace(tzinfo=datetime.timezone(datetime.timedelta(seconds={timedelta})))"); let t = py.eval(&CString::new(code).unwrap(), Some(&globals), None).unwrap(); // Get ISO 8601 string from python let py_iso_str = t.call_method0("isoformat").unwrap(); // Get ISO 8601 string from rust let t = t.extract::>().unwrap(); // Python doesn't print the seconds of the offset if they are 0 let rust_iso_str = if timedelta % 60 == 0 { t.format("%Y-%m-%dT%H:%M:%S%:z").to_string() } else { t.format("%Y-%m-%dT%H:%M:%S%::z").to_string() }; // They should be equal assert_eq!(py_iso_str.to_string(), rust_iso_str); }) } #[test] fn test_duration_roundtrip(days in -999999999i64..=999999999i64) { // Test roundtrip conversion rust->python->rust for all allowed // python values of durations (from -999999999 to 999999999 days), Python::attach(|py| { let dur = Duration::days(days); let py_delta = dur.into_pyobject(py).unwrap(); let roundtripped: Duration = py_delta.extract().expect("Round trip"); assert_eq!(dur, roundtripped); }) } #[test] fn test_fixed_offset_roundtrip(secs in -86399i32..=86399i32) { Python::attach(|py| { let offset = FixedOffset::east_opt(secs).unwrap(); let py_offset = offset.into_pyobject(py).unwrap(); let roundtripped: FixedOffset = py_offset.extract().expect("Round trip"); assert_eq!(offset, roundtripped); }) } #[test] fn test_naive_date_roundtrip( year in 1i32..=9999i32, month in 1u32..=12u32, day in 1u32..=31u32 ) { // Test roundtrip conversion rust->python->rust for all allowed // python dates (from year 1 to year 9999) Python::attach(|py| { // We use to `from_ymd_opt` constructor so that we only test valid `NaiveDate`s. // This is to skip the test if we are creating an invalid date, like February 31. if let Some(date) = NaiveDate::from_ymd_opt(year, month, day) { let py_date = date.into_pyobject(py).unwrap(); let roundtripped: NaiveDate = py_date.extract().expect("Round trip"); assert_eq!(date, roundtripped); } }) } #[test] fn test_naive_time_roundtrip( hour in 0u32..=23u32, min in 0u32..=59u32, sec in 0u32..=59u32, micro in 0u32..=1_999_999u32 ) { // Test roundtrip conversion rust->python->rust for naive times. // Python time has a resolution of microseconds, so we only test // NaiveTimes with microseconds resolution, even if NaiveTime has nanosecond // resolution. Python::attach(|py| { if let Some(time) = NaiveTime::from_hms_micro_opt(hour, min, sec, micro) { // Wrap in CatchWarnings to avoid to_object firing warning for truncated leap second let py_time = CatchWarnings::enter(py, |_| time.into_pyobject(py)).unwrap(); let roundtripped: NaiveTime = py_time.extract().expect("Round trip"); // Leap seconds are not roundtripped let expected_roundtrip_time = micro.checked_sub(1_000_000).map(|micro| NaiveTime::from_hms_micro_opt(hour, min, sec, micro).unwrap()).unwrap_or(time); assert_eq!(expected_roundtrip_time, roundtripped); } }) } #[test] fn test_naive_datetime_roundtrip( year in 1i32..=9999i32, month in 1u32..=12u32, day in 1u32..=31u32, hour in 0u32..=24u32, min in 0u32..=60u32, sec in 0u32..=60u32, micro in 0u32..=999_999u32 ) { Python::attach(|py| { let date_opt = NaiveDate::from_ymd_opt(year, month, day); let time_opt = NaiveTime::from_hms_micro_opt(hour, min, sec, micro); if let (Some(date), Some(time)) = (date_opt, time_opt) { let dt = NaiveDateTime::new(date, time); let pydt = dt.into_pyobject(py).unwrap(); let roundtripped: NaiveDateTime = pydt.extract().expect("Round trip"); assert_eq!(dt, roundtripped); } }) } #[test] fn test_utc_datetime_roundtrip( year in 1i32..=9999i32, month in 1u32..=12u32, day in 1u32..=31u32, hour in 0u32..=23u32, min in 0u32..=59u32, sec in 0u32..=59u32, micro in 0u32..=1_999_999u32 ) { Python::attach(|py| { let date_opt = NaiveDate::from_ymd_opt(year, month, day); let time_opt = NaiveTime::from_hms_micro_opt(hour, min, sec, micro); if let (Some(date), Some(time)) = (date_opt, time_opt) { let dt: DateTime = NaiveDateTime::new(date, time).and_utc(); // Wrap in CatchWarnings to avoid into_py firing warning for truncated leap second let py_dt = CatchWarnings::enter(py, |_| dt.into_pyobject(py)).unwrap(); let roundtripped: DateTime = py_dt.extract().expect("Round trip"); // Leap seconds are not roundtripped let expected_roundtrip_time = micro.checked_sub(1_000_000).map(|micro| NaiveTime::from_hms_micro_opt(hour, min, sec, micro).unwrap()).unwrap_or(time); let expected_roundtrip_dt: DateTime = NaiveDateTime::new(date, expected_roundtrip_time).and_utc(); assert_eq!(expected_roundtrip_dt, roundtripped); } }) } #[test] fn test_fixed_offset_datetime_roundtrip( year in 1i32..=9999i32, month in 1u32..=12u32, day in 1u32..=31u32, hour in 0u32..=23u32, min in 0u32..=59u32, sec in 0u32..=59u32, micro in 0u32..=1_999_999u32, offset_secs in -86399i32..=86399i32 ) { Python::attach(|py| { let date_opt = NaiveDate::from_ymd_opt(year, month, day); let time_opt = NaiveTime::from_hms_micro_opt(hour, min, sec, micro); let offset = FixedOffset::east_opt(offset_secs).unwrap(); if let (Some(date), Some(time)) = (date_opt, time_opt) { let dt: DateTime = NaiveDateTime::new(date, time).and_local_timezone(offset).unwrap(); // Wrap in CatchWarnings to avoid into_py firing warning for truncated leap second let py_dt = CatchWarnings::enter(py, |_| dt.into_pyobject(py)).unwrap(); let roundtripped: DateTime = py_dt.extract().expect("Round trip"); // Leap seconds are not roundtripped let expected_roundtrip_time = micro.checked_sub(1_000_000).map(|micro| NaiveTime::from_hms_micro_opt(hour, min, sec, micro).unwrap()).unwrap_or(time); let expected_roundtrip_dt: DateTime = NaiveDateTime::new(date, expected_roundtrip_time).and_local_timezone(offset).unwrap(); assert_eq!(expected_roundtrip_dt, roundtripped); } }) } #[test] #[cfg(all(feature = "chrono-local", not(target_os = "windows")))] fn test_local_datetime_roundtrip( year in 1i32..=9999i32, month in 1u32..=12u32, day in 1u32..=31u32, hour in 0u32..=23u32, min in 0u32..=59u32, sec in 0u32..=59u32, micro in 0u32..=1_999_999u32, ) { Python::attach(|py| { let date_opt = NaiveDate::from_ymd_opt(year, month, day); let time_opt = NaiveTime::from_hms_micro_opt(hour, min, sec, micro); if let (Some(date), Some(time)) = (date_opt, time_opt) { let dts = match NaiveDateTime::new(date, time).and_local_timezone(Local) { LocalResult::None => return, LocalResult::Single(dt) => [Some((dt, false)), None], LocalResult::Ambiguous(dt1, dt2) => [Some((dt1, false)), Some((dt2, true))], }; for (dt, fold) in dts.iter().filter_map(|input| *input) { // Wrap in CatchWarnings to avoid into_py firing warning for truncated leap second let py_dt = CatchWarnings::enter(py, |_| dt.into_pyobject(py)).unwrap(); let roundtripped: DateTime = py_dt.extract().expect("Round trip"); // Leap seconds are not roundtripped let expected_roundtrip_time = micro.checked_sub(1_000_000).map(|micro| NaiveTime::from_hms_micro_opt(hour, min, sec, micro).unwrap()).unwrap_or(time); let expected_roundtrip_dt: DateTime = if fold { NaiveDateTime::new(date, expected_roundtrip_time).and_local_timezone(Local).latest() } else { NaiveDateTime::new(date, expected_roundtrip_time).and_local_timezone(Local).earliest() }.unwrap(); assert_eq!(expected_roundtrip_dt, roundtripped); } } }) } } } } ================================================ FILE: src/conversions/chrono_tz.rs ================================================ #![cfg(all(Py_3_9, feature = "chrono-tz"))] //! Conversions to and from [chrono-tz](https://docs.rs/chrono-tz/)’s `Tz`. //! //! This feature requires at least Python 3.9. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! chrono-tz = "0.8" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"chrono-tz\"] }")] //! ``` //! //! Note that you must use compatible versions of chrono, chrono-tz and PyO3. //! The required chrono version may vary based on the version of PyO3. //! //! # Example: Convert a `zoneinfo.ZoneInfo` to chrono-tz's `Tz` //! //! ```rust,no_run //! use chrono_tz::Tz; //! use pyo3::{Python, PyResult, IntoPyObject, types::PyAnyMethods}; //! //! fn main() -> PyResult<()> { //! Python::initialize(); //! Python::attach(|py| { //! // Convert to Python //! let py_tzinfo = Tz::Europe__Paris.into_pyobject(py)?; //! // Convert back to Rust //! assert_eq!(py_tzinfo.extract::()?, Tz::Europe__Paris); //! Ok(()) //! }) //! } //! ``` use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_hint_identifier; use crate::types::{any::PyAnyMethods, PyTzInfo}; #[cfg(all(feature = "experimental-inspect", not(Py_3_9)))] use crate::PyTypeInfo; use crate::{intern, Borrowed, Bound, FromPyObject, PyAny, PyErr, Python}; use chrono_tz::Tz; use std::borrow::Cow; use std::str::FromStr; impl<'py> IntoPyObject<'py> for Tz { type Target = PyTzInfo; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(all(feature = "experimental-inspect", Py_3_9))] const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("zoneinfo", "ZoneInfo"); #[cfg(all(feature = "experimental-inspect", not(Py_3_9)))] const OUTPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; fn into_pyobject(self, py: Python<'py>) -> Result { PyTzInfo::timezone(py, self.name()) } } impl<'py> IntoPyObject<'py> for &Tz { type Target = PyTzInfo; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = Tz::OUTPUT_TYPE; #[inline] fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } } impl FromPyObject<'_, '_> for Tz { type Error = PyErr; #[cfg(all(feature = "experimental-inspect", Py_3_9))] const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("zoneinfo", "ZoneInfo"); #[cfg(all(feature = "experimental-inspect", not(Py_3_9)))] const INPUT_TYPE: PyStaticExpr = PyTzInfo::TYPE_HINT; fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { Tz::from_str( &ob.getattr(intern!(ob.py(), "key"))? .extract::>()?, ) .map_err(|e| PyValueError::new_err(e.to_string())) } } #[cfg(all(test, not(windows)))] // Troubles loading timezones on Windows mod tests { use super::*; use crate::prelude::PyAnyMethods; use crate::types::IntoPyDict; use crate::types::PyTzInfo; use crate::Bound; use crate::Python; use chrono::offset::LocalResult; use chrono::NaiveDate; use chrono::{DateTime, Utc}; use chrono_tz::Tz; #[test] fn test_frompyobject() { Python::attach(|py| { assert_eq!( new_zoneinfo(py, "Europe/Paris").extract::().unwrap(), Tz::Europe__Paris ); assert_eq!(new_zoneinfo(py, "UTC").extract::().unwrap(), Tz::UTC); assert_eq!( new_zoneinfo(py, "Etc/GMT-5").extract::().unwrap(), Tz::Etc__GMTMinus5 ); }); } #[test] fn test_ambiguous_datetime_to_pyobject() { let dates = [ DateTime::::from_str("2020-10-24 23:00:00 UTC").unwrap(), DateTime::::from_str("2020-10-25 00:00:00 UTC").unwrap(), DateTime::::from_str("2020-10-25 01:00:00 UTC").unwrap(), ]; let dates = dates.map(|dt| dt.with_timezone(&Tz::Europe__London)); assert_eq!( dates.map(|dt| dt.to_string()), [ "2020-10-25 00:00:00 BST", "2020-10-25 01:00:00 BST", "2020-10-25 01:00:00 GMT" ] ); let dates = Python::attach(|py| { let pydates = dates.map(|dt| dt.into_pyobject(py).unwrap()); assert_eq!( pydates .clone() .map(|dt| dt.getattr("hour").unwrap().extract::().unwrap()), [0, 1, 1] ); assert_eq!( pydates .clone() .map(|dt| dt.getattr("fold").unwrap().extract::().unwrap() > 0), [false, false, true] ); pydates.map(|dt| dt.extract::>().unwrap()) }); assert_eq!( dates.map(|dt| dt.to_string()), [ "2020-10-25 00:00:00 BST", "2020-10-25 01:00:00 BST", "2020-10-25 01:00:00 GMT" ] ); } #[test] fn test_nonexistent_datetime_from_pyobject() { // Pacific_Apia skipped the 30th of December 2011 entirely let naive_dt = NaiveDate::from_ymd_opt(2011, 12, 30) .unwrap() .and_hms_opt(2, 0, 0) .unwrap(); let tz = Tz::Pacific__Apia; // sanity check assert_eq!(naive_dt.and_local_timezone(tz), LocalResult::None); Python::attach(|py| { // create as a Python object manually let py_tz = tz.into_pyobject(py).unwrap(); let py_dt_naive = naive_dt.into_pyobject(py).unwrap(); let py_dt = py_dt_naive .call_method( "replace", (), Some(&[("tzinfo", py_tz)].into_py_dict(py).unwrap()), ) .unwrap(); // now try to extract let err = py_dt.extract::>().unwrap_err(); assert_eq!(err.to_string(), "ValueError: The datetime datetime.datetime(2011, 12, 30, 2, 0, tzinfo=zoneinfo.ZoneInfo(key='Pacific/Apia')) contains an incompatible timezone"); }); } #[test] #[cfg(not(Py_GIL_DISABLED))] // https://github.com/python/cpython/issues/116738#issuecomment-2404360445 fn test_into_pyobject() { Python::attach(|py| { let assert_eq = |l: Bound<'_, PyTzInfo>, r: Bound<'_, PyTzInfo>| { assert!(l.eq(&r).unwrap(), "{l:?} != {r:?}"); }; assert_eq( Tz::Europe__Paris.into_pyobject(py).unwrap(), new_zoneinfo(py, "Europe/Paris"), ); assert_eq(Tz::UTC.into_pyobject(py).unwrap(), new_zoneinfo(py, "UTC")); assert_eq( Tz::Etc__GMTMinus5.into_pyobject(py).unwrap(), new_zoneinfo(py, "Etc/GMT-5"), ); }); } fn new_zoneinfo<'py>(py: Python<'py>, name: &str) -> Bound<'py, PyTzInfo> { PyTzInfo::timezone(py, name).unwrap() } } ================================================ FILE: src/conversions/either.rs ================================================ #![cfg(feature = "either")] //! Conversion to/from //! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s //! [`Either`] type to a union of two Python types. //! //! Use of a generic sum type like [either] is common when you want to either accept one of two possible //! types as an argument or return one of two possible types from a function, without having to define //! a helper type manually yourself. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! ## change * to the version you want to use, ideally the latest. //! either = "*" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"either\"] }")] //! ``` //! //! Note that you must use compatible versions of either and PyO3. //! The required either version may vary based on the version of PyO3. //! //! # Example: Convert a `int | str` to `Either`. //! //! ```rust //! use either::Either; //! use pyo3::{Python, PyResult, IntoPyObject, types::PyAnyMethods}; //! //! fn main() -> PyResult<()> { //! Python::initialize(); //! Python::attach(|py| { //! // Create a string and an int in Python. //! let py_str = "crab".into_pyobject(py)?; //! let py_int = 42i32.into_pyobject(py)?; //! // Now convert it to an Either. //! let either_str: Either = py_str.extract()?; //! let either_int: Either = py_int.extract()?; //! Ok(()) //! }) //! } //! ``` //! //! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; #[cfg(feature = "experimental-inspect")] use crate::type_hint_union; use crate::{ exceptions::PyTypeError, Borrowed, Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, PyAny, PyErr, Python, }; use either::Either; #[cfg_attr(docsrs, doc(cfg(feature = "either")))] impl<'py, L, R> IntoPyObject<'py> for Either where L: IntoPyObject<'py>, R: IntoPyObject<'py>, { type Target = PyAny; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(L::OUTPUT_TYPE, R::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { match self { Either::Left(l) => l.into_bound_py_any(py), Either::Right(r) => r.into_bound_py_any(py), } } } #[cfg_attr(docsrs, doc(cfg(feature = "either")))] impl<'a, 'py, L, R> IntoPyObject<'py> for &'a Either where &'a L: IntoPyObject<'py>, &'a R: IntoPyObject<'py>, { type Target = PyAny; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_union!(<&L>::OUTPUT_TYPE, <&R>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { match self { Either::Left(l) => l.into_bound_py_any(py), Either::Right(r) => r.into_bound_py_any(py), } } } #[cfg_attr(docsrs, doc(cfg(feature = "either")))] impl<'a, 'py, L, R> FromPyObject<'a, 'py> for Either where L: FromPyObject<'a, 'py>, R: FromPyObject<'a, 'py>, { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = type_hint_union!(L::INPUT_TYPE, R::INPUT_TYPE); #[inline] fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { if let Ok(l) = obj.extract::() { Ok(Either::Left(l)) } else if let Ok(r) = obj.extract::() { Ok(Either::Right(r)) } else { // TODO: it might be nice to use the `type_input()` name here once `type_input` // is not experimental, rather than the Rust type names. let err_msg = format!( "failed to convert the value to 'Union[{}, {}]'", std::any::type_name::(), std::any::type_name::() ); Err(PyTypeError::new_err(err_msg)) } } } #[cfg(test)] mod tests { use std::borrow::Cow; use crate::exceptions::PyTypeError; use crate::{IntoPyObject, Python}; use crate::types::PyAnyMethods; use either::Either; #[test] fn test_either_conversion() { type E = Either; type E1 = Either; type E2 = Either; Python::attach(|py| { let l = E::Left(42); let obj_l = (&l).into_pyobject(py).unwrap(); assert_eq!(obj_l.extract::().unwrap(), 42); assert_eq!(obj_l.extract::().unwrap(), l); let r = E::Right("foo".to_owned()); let obj_r = (&r).into_pyobject(py).unwrap(); assert_eq!(obj_r.extract::>().unwrap(), "foo"); assert_eq!(obj_r.extract::().unwrap(), r); let obj_s = "foo".into_pyobject(py).unwrap(); let err = obj_s.extract::().unwrap_err(); assert!(err.is_instance_of::(py)); assert_eq!( err.to_string(), "TypeError: failed to convert the value to 'Union[i32, f32]'" ); let obj_i = 42i32.into_pyobject(py).unwrap(); assert_eq!(obj_i.extract::().unwrap(), E1::Left(42)); assert_eq!(obj_i.extract::().unwrap(), E2::Left(42.0)); let obj_f = 42.0f64.into_pyobject(py).unwrap(); assert_eq!(obj_f.extract::().unwrap(), E1::Right(42.0)); assert_eq!(obj_f.extract::().unwrap(), E2::Left(42.0)); }); } } ================================================ FILE: src/conversions/eyre.rs ================================================ #![cfg(feature = "eyre")] //! A conversion from //! [eyre](https://docs.rs/eyre/ "A library for easy idiomatic error handling and reporting in Rust applications.")’s //! [`Report`] type to [`PyErr`]. //! //! Use of an error handling library like [eyre] is common in application code and when you just //! want error handling to be easy. If you are writing a library or you need more control over your //! errors you might want to design your own error type instead. //! //! When the inner error is a [`PyErr`] without source, it will be extracted out. //! Otherwise a Python [`RuntimeError`] will be created. //! You might find that you need to map the error from your Rust code into another Python exception. //! See [`PyErr::new`] for more information about that. //! //! For information about error handling in general, see the [Error handling] chapter of the Rust //! book. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! ## change * to the version you want to use, ideally the latest. //! eyre = "*" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"eyre\"] }")] //! ``` //! //! Note that you must use compatible versions of eyre and PyO3. //! The required eyre version may vary based on the version of PyO3. //! //! # Example: Propagating a `PyErr` into [`eyre::Report`] //! //! ```rust //! use pyo3::prelude::*; //! use std::path::PathBuf; //! //! // A wrapper around a Rust function. //! // The pyfunction macro performs the conversion to a PyErr //! #[pyfunction] //! fn py_open(filename: PathBuf) -> eyre::Result> { //! let data = std::fs::read(filename)?; //! Ok(data) //! } //! //! fn main() { //! let error = Python::attach(|py| -> PyResult> { //! let fun = wrap_pyfunction!(py_open, py)?; //! let text = fun.call1(("foo.txt",))?.extract::>()?; //! Ok(text) //! }).unwrap_err(); //! //! println!("{}", error); //! } //! ``` //! //! # Example: Using `eyre` in general //! //! Note that you don't need this feature to convert a [`PyErr`] into an [`eyre::Report`], because //! it can already convert anything that implements [`Error`](std::error::Error): //! //! ```rust //! use pyo3::prelude::*; //! use pyo3::types::PyBytes; //! //! // An example function that must handle multiple error types. //! // //! // To do this you usually need to design your own error type or use //! // `Box`. `eyre` is a convenient alternative for this. //! pub fn decompress(bytes: &[u8]) -> eyre::Result { //! // An arbitrary example of a Python api you //! // could call inside an application... //! // This might return a `PyErr`. //! let res = Python::attach(|py| { //! let zlib = PyModule::import(py, "zlib")?; //! let decompress = zlib.getattr("decompress")?; //! let bytes = PyBytes::new(py, bytes); //! let value = decompress.call1((bytes,))?; //! value.extract::>() //! })?; //! //! // This might be a `FromUtf8Error`. //! let text = String::from_utf8(res)?; //! //! Ok(text) //! } //! //! fn main() -> eyre::Result<()> { //! let bytes: &[u8] = b"x\x9c\x8b\xcc/U(\xce\xc8/\xcdIQ((\xcaOJL\xca\xa9T\ //! (-NU(\xc9HU\xc8\xc9LJ\xcbI,IUH.\x02\x91\x99y\xc5%\ //! \xa9\x89)z\x00\xf2\x15\x12\xfe"; //! let text = decompress(bytes)?; //! //! println!("The text is \"{}\"", text); //! # assert_eq!(text, "You should probably use the libflate crate instead."); //! Ok(()) //! } //! ``` //! //! [eyre]: https://docs.rs/eyre/ "A library for easy idiomatic error handling and reporting in Rust applications." //! [`RuntimeError`]: https://docs.python.org/3/library/exceptions.html#RuntimeError "Built-in Exceptions — Python documentation" //! [Error handling]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html "Recoverable Errors with Result - The Rust Programming Language" use crate::exceptions::PyRuntimeError; use crate::PyErr; use eyre::Report; /// Converts [`eyre::Report`] to a [`PyErr`] containing a [`PyRuntimeError`]. /// /// If you want to raise a different Python exception you will have to do so manually. See /// [`PyErr::new`] for more information about that. impl From for PyErr { fn from(mut error: Report) -> Self { // Errors containing a PyErr without chain or context are returned as the underlying error if error.source().is_none() { error = match error.downcast::() { Ok(py_err) => return py_err, Err(error) => error, }; } PyRuntimeError::new_err(format!("{error:?}")) } } #[cfg(test)] mod tests { use crate::exceptions::{PyRuntimeError, PyValueError}; use crate::prelude::*; use crate::types::IntoPyDict; use eyre::{bail, eyre, Report, Result, WrapErr}; fn f() -> Result<()> { use std::io; bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!")); } fn g() -> Result<()> { f().wrap_err("f failed") } fn h() -> Result<()> { g().wrap_err("g failed") } #[test] fn test_pyo3_exception_contents() { let err = h().unwrap_err(); let expected_contents = format!("{err:?}"); let pyerr = PyErr::from(err); Python::attach(|py| { let locals = [("err", pyerr)].into_py_dict(py).unwrap(); let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err(); assert_eq!(pyerr.value(py).to_string(), expected_contents); }) } fn k() -> Result<()> { Err(eyre!("Some sort of error")) } #[test] fn test_pyo3_exception_contents2() { let err = k().unwrap_err(); let expected_contents = format!("{err:?}"); let pyerr = PyErr::from(err); Python::attach(|py| { let locals = [("err", pyerr)].into_py_dict(py).unwrap(); let pyerr = py.run(c"raise err", None, Some(&locals)).unwrap_err(); assert_eq!(pyerr.value(py).to_string(), expected_contents); }) } #[test] fn test_pyo3_unwrap_simple_err() { let origin_exc = PyValueError::new_err("Value Error"); let report: Report = origin_exc.into(); let converted: PyErr = report.into(); assert!(Python::attach( |py| converted.is_instance_of::(py) )) } #[test] fn test_pyo3_unwrap_complex_err() { let origin_exc = PyValueError::new_err("Value Error"); let mut report: Report = origin_exc.into(); report = report.wrap_err("Wrapped"); let converted: PyErr = report.into(); assert!(Python::attach( |py| converted.is_instance_of::(py) )) } } ================================================ FILE: src/conversions/hashbrown.rs ================================================ #![cfg(feature = "hashbrown")] //! Conversions to and from [hashbrown](https://docs.rs/hashbrown/)’s //! `HashMap` and `HashSet`. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! # change * to the latest versions //! hashbrown = "*" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"hashbrown\"] }")] //! ``` //! //! Note that you must use compatible versions of hashbrown and PyO3. //! The required hashbrown version may vary based on the version of PyO3. #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; use crate::{ conversion::{FromPyObjectOwned, IntoPyObject}, types::{ any::PyAnyMethods, dict::PyDictMethods, frozenset::PyFrozenSetMethods, set::PySetMethods, PyDict, PyFrozenSet, PySet, }, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, Python, }; #[cfg(feature = "experimental-inspect")] use crate::{type_hint_subscript, type_hint_union, PyTypeInfo}; use std::hash; impl<'py, K, V, H> IntoPyObject<'py> for hashbrown::HashMap where K: IntoPyObject<'py> + Eq + hash::Hash, V: IntoPyObject<'py>, H: hash::BuildHasher, { type Target = PyDict; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); for (k, v) in self { dict.set_item(k, v)?; } Ok(dict) } } impl<'a, 'py, K, V, H> IntoPyObject<'py> for &'a hashbrown::HashMap where &'a K: IntoPyObject<'py> + Eq + hash::Hash, &'a V: IntoPyObject<'py>, H: hash::BuildHasher, { type Target = PyDict; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); for (k, v) in self { dict.set_item(k, v)?; } Ok(dict) } } impl<'py, K, V, S> FromPyObject<'_, 'py> for hashbrown::HashMap where K: FromPyObjectOwned<'py> + Eq + hash::Hash, V: FromPyObjectOwned<'py>, S: hash::BuildHasher + Default, { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default()); for (k, v) in dict.iter() { ret.insert( k.extract().map_err(Into::into)?, v.extract().map_err(Into::into)?, ); } Ok(ret) } } impl<'py, K, H> IntoPyObject<'py> for hashbrown::HashSet where K: IntoPyObject<'py> + Eq + hash::Hash, H: hash::BuildHasher, { type Target = PySet; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, K::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { PySet::new(py, self) } } impl<'a, 'py, K, H> IntoPyObject<'py> for &'a hashbrown::HashSet where &'a K: IntoPyObject<'py> + Eq + hash::Hash, H: hash::BuildHasher, { type Target = PySet; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PySet::TYPE_HINT, <&K>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { PySet::new(py, self) } } impl<'py, K, S> FromPyObject<'_, 'py> for hashbrown::HashSet where K: FromPyObjectOwned<'py> + Eq + hash::Hash, S: hash::BuildHasher + Default, { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = type_hint_union!( type_hint_subscript!(PySet::TYPE_HINT, K::INPUT_TYPE), type_hint_subscript!(PyFrozenSet::TYPE_HINT, K::INPUT_TYPE) ); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { match ob.cast::() { Ok(set) => set .iter() .map(|any| any.extract().map_err(Into::into)) .collect(), Err(err) => { if let Ok(frozen_set) = ob.cast::() { frozen_set .iter() .map(|any| any.extract().map_err(Into::into)) .collect() } else { Err(PyErr::from(err)) } } } } } #[cfg(test)] mod tests { use super::*; use crate::types::IntoPyDict; use std::collections::hash_map::RandomState; #[test] fn test_hashbrown_hashmap_into_pyobject() { Python::attach(|py| { let mut map = hashbrown::HashMap::::with_hasher(RandomState::new()); map.insert(1, 1); let py_map = (&map).into_pyobject(py).unwrap(); assert_eq!(py_map.len(), 1); assert!( py_map .get_item(1) .unwrap() .unwrap() .extract::() .unwrap() == 1 ); assert_eq!(map, py_map.extract().unwrap()); }); } #[test] fn test_hashbrown_hashmap_into_dict() { Python::attach(|py| { let mut map = hashbrown::HashMap::::with_hasher(RandomState::new()); map.insert(1, 1); let py_map = map.into_py_dict(py).unwrap(); assert_eq!(py_map.len(), 1); assert_eq!( py_map .get_item(1) .unwrap() .unwrap() .extract::() .unwrap(), 1 ); }); } #[test] fn test_extract_hashbrown_hashset() { Python::attach(|py| { let set = PySet::new(py, [1, 2, 3, 4, 5]).unwrap(); let hash_set: hashbrown::HashSet = set.extract().unwrap(); assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect()); let set = PyFrozenSet::new(py, [1, 2, 3, 4, 5]).unwrap(); let hash_set: hashbrown::HashSet = set.extract().unwrap(); assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect()); }); } #[test] fn test_hashbrown_hashset_into_pyobject() { Python::attach(|py| { let hs: hashbrown::HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); let hso = hs.clone().into_pyobject(py).unwrap(); assert_eq!(hs, hso.extract().unwrap()); }); } } ================================================ FILE: src/conversions/indexmap.rs ================================================ #![cfg(feature = "indexmap")] //! Conversions to and from [indexmap](https://docs.rs/indexmap/)’s //! `IndexMap`. //! //! [`indexmap::IndexMap`] is a hash table that is closely compatible with the standard [`std::collections::HashMap`], //! with the difference that it preserves the insertion order when iterating over keys. It was inspired //! by Python's 3.6+ dict implementation. //! //! Dictionary order is guaranteed to be insertion order in Python, hence IndexMap is a good candidate //! for maintaining an equivalent behaviour in Rust. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! # change * to the latest versions //! indexmap = "*" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"indexmap\"] }")] //! ``` //! //! Note that you must use compatible versions of indexmap and PyO3. //! The required indexmap version may vary based on the version of PyO3. //! //! # Examples //! //! Using [indexmap](https://docs.rs/indexmap) to return a dictionary with some statistics //! about a list of numbers. Because of the insertion order guarantees, the Python code will //! always print the same result, matching users' expectations about Python's dict. //! ```rust //! use indexmap::{indexmap, IndexMap}; //! use pyo3::prelude::*; //! //! fn median(data: &Vec) -> f32 { //! let sorted_data = data.clone().sort(); //! let mid = data.len() / 2; //! if data.len() % 2 == 0 { //! data[mid] as f32 //! } //! else { //! (data[mid] + data[mid - 1]) as f32 / 2.0 //! } //! } //! //! fn mean(data: &Vec) -> f32 { //! data.iter().sum::() as f32 / data.len() as f32 //! } //! fn mode(data: &Vec) -> f32 { //! let mut frequency = IndexMap::new(); // we can use IndexMap as any hash table //! //! for &element in data { //! *frequency.entry(element).or_insert(0) += 1; //! } //! //! frequency //! .iter() //! .max_by(|a, b| a.1.cmp(&b.1)) //! .map(|(k, _v)| *k) //! .unwrap() as f32 //! } //! //! #[pyfunction] //! fn calculate_statistics(data: Vec) -> IndexMap<&'static str, f32> { //! indexmap! { //! "median" => median(&data), //! "mean" => mean(&data), //! "mode" => mode(&data), //! } //! } //! //! #[pymodule] //! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> { //! m.add_function(wrap_pyfunction!(calculate_statistics, m)?)?; //! Ok(()) //! } //! ``` //! //! Python code: //! ```python //! from my_module import calculate_statistics //! //! data = [1, 1, 1, 3, 4, 5] //! print(calculate_statistics(data)) //! # always prints {"median": 2.0, "mean": 2.5, "mode": 1.0} in the same order //! # if another hash table was used, the order could be random //! ``` use crate::conversion::{FromPyObjectOwned, IntoPyObject}; #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; use crate::types::*; #[cfg(feature = "experimental-inspect")] use crate::{type_hint_subscript, PyTypeInfo}; use crate::{Borrowed, Bound, FromPyObject, PyErr, Python}; use std::hash; impl<'py, K, V, H> IntoPyObject<'py> for indexmap::IndexMap where K: IntoPyObject<'py> + Eq + hash::Hash, V: IntoPyObject<'py>, H: hash::BuildHasher, { type Target = PyDict; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyDict::TYPE_HINT, K::OUTPUT_TYPE, V::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); for (k, v) in self { dict.set_item(k, v)?; } Ok(dict) } } impl<'a, 'py, K, V, H> IntoPyObject<'py> for &'a indexmap::IndexMap where &'a K: IntoPyObject<'py> + Eq + hash::Hash, &'a V: IntoPyObject<'py>, H: hash::BuildHasher, { type Target = PyDict; type Output = Bound<'py, Self::Target>; type Error = PyErr; #[cfg(feature = "experimental-inspect")] const OUTPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyDict::TYPE_HINT, <&K>::OUTPUT_TYPE, <&V>::OUTPUT_TYPE); fn into_pyobject(self, py: Python<'py>) -> Result { let dict = PyDict::new(py); for (k, v) in self { dict.set_item(k, v)?; } Ok(dict) } } impl<'py, K, V, S> FromPyObject<'_, 'py> for indexmap::IndexMap where K: FromPyObjectOwned<'py> + Eq + hash::Hash, V: FromPyObjectOwned<'py>, S: hash::BuildHasher + Default, { type Error = PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(PyDict::TYPE_HINT, K::INPUT_TYPE, V::INPUT_TYPE); fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; let mut ret = indexmap::IndexMap::with_capacity_and_hasher(dict.len(), S::default()); for (k, v) in dict.iter() { ret.insert( k.extract().map_err(Into::into)?, v.extract().map_err(Into::into)?, ); } Ok(ret) } } #[cfg(test)] mod test_indexmap { use crate::types::*; use crate::{IntoPyObject, Python}; #[test] fn test_indexmap_indexmap_into_pyobject() { Python::attach(|py| { let mut map = indexmap::IndexMap::::new(); map.insert(1, 1); let py_map = (&map).into_pyobject(py).unwrap(); assert_eq!(py_map.len(), 1); assert!( py_map .get_item(1) .unwrap() .unwrap() .extract::() .unwrap() == 1 ); assert_eq!( map, py_map.extract::>().unwrap() ); }); } #[test] fn test_indexmap_indexmap_into_dict() { Python::attach(|py| { let mut map = indexmap::IndexMap::::new(); map.insert(1, 1); let py_map = map.into_py_dict(py).unwrap(); assert_eq!(py_map.len(), 1); assert_eq!( py_map .get_item(1) .unwrap() .unwrap() .extract::() .unwrap(), 1 ); }); } #[test] fn test_indexmap_indexmap_insertion_order_round_trip() { Python::attach(|py| { let n = 20; let mut map = indexmap::IndexMap::::new(); for i in 1..=n { if i % 2 == 1 { map.insert(i, i); } else { map.insert(n - i, i); } } let py_map = (&map).into_py_dict(py).unwrap(); let trip_map = py_map.extract::>().unwrap(); for (((k1, v1), (k2, v2)), (k3, v3)) in map.iter().zip(py_map.iter()).zip(trip_map.iter()) { let k2 = k2.extract::().unwrap(); let v2 = v2.extract::().unwrap(); assert_eq!((k1, v1), (&k2, &v2)); assert_eq!((k1, v1), (k3, v3)); assert_eq!((&k2, &v2), (k3, v3)); } }); } } ================================================ FILE: src/conversions/jiff.rs ================================================ #![cfg(feature = "jiff-02")] //! Conversions to and from [jiff](https://docs.rs/jiff/)’s `Span`, `SignedDuration`, `TimeZone`, //! `Offset`, `Date`, `Time`, `DateTime`, `Zoned`, and `Timestamp`. //! //! # Setup //! //! To use this feature, add this to your **`Cargo.toml`**: //! //! ```toml //! [dependencies] //! jiff = "0.2" #![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"jiff-02\"] }")] //! ``` //! //! Note that you must use compatible versions of jiff and PyO3. //! The required jiff version may vary based on the version of PyO3. //! //! # Example: Convert a `datetime.datetime` to jiff `Zoned` //! //! ```rust //! # #![cfg_attr(windows, allow(unused_imports))] //! # use jiff_02 as jiff; //! use jiff::{Zoned, SignedDuration, ToSpan}; //! use pyo3::{Python, PyResult, IntoPyObject, types::PyAnyMethods}; //! //! # #[cfg(windows)] //! # fn main() -> () {} //! # #[cfg(not(windows))] //! fn main() -> PyResult<()> { //! Python::initialize(); //! Python::attach(|py| { //! // Build some jiff values //! let jiff_zoned = Zoned::now(); //! let jiff_span = 1.second(); //! // Convert them to Python //! let py_datetime = jiff_zoned.into_pyobject(py)?; //! let py_timedelta = SignedDuration::try_from(jiff_span)?.into_pyobject(py)?; //! // Do an operation in Python //! let py_sum = py_datetime.call_method1("__add__", (py_timedelta,))?; //! // Convert back to Rust //! let jiff_sum: Zoned = py_sum.extract()?; //! println!("Zoned: {}", jiff_sum); //! Ok(()) //! }) //! } //! ``` use crate::exceptions::{PyTypeError, PyValueError}; #[cfg(feature = "experimental-inspect")] use crate::inspect::PyStaticExpr; use crate::types::{PyAnyMethods, PyNone}; use crate::types::{PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo, PyTzInfoAccess}; #[cfg(not(Py_LIMITED_API))] use crate::types::{PyDateAccess, PyDeltaAccess, PyTimeAccess}; use crate::{intern, Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python}; #[cfg(feature = "experimental-inspect")] use crate::{type_hint_identifier, PyTypeInfo}; use jiff::civil::{Date, DateTime, ISOWeekDate, Time}; use jiff::tz::{Offset, TimeZone}; use jiff::{SignedDuration, Span, Timestamp, Zoned}; #[cfg(feature = "jiff-02")] use jiff_02 as jiff; use std::borrow::Cow; fn datetime_to_pydatetime<'py>( py: Python<'py>, datetime: DateTime, fold: bool, timezone: Option<&TimeZone>, ) -> PyResult> { PyDateTime::new_with_fold( py, datetime.year().into(), datetime.month().try_into()?, datetime.day().try_into()?, datetime.hour().try_into()?, datetime.minute().try_into()?, datetime.second().try_into()?, (datetime.subsec_nanosecond() / 1000).try_into()?, timezone .map(|tz| tz.into_pyobject(py)) .transpose()? .as_ref(), fold, ) } #[cfg(not(Py_LIMITED_API))] fn pytime_to_time(time: &impl PyTimeAccess) -> PyResult