Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ semver = "1.0.22"
target-lexicon = "0.13.3"
indexmap = "2.2.3"
pyproject-toml = { version = "0.13.5", features = ["pep639-glob"] }
pyo3-introspection = "0.28"
python-pkginfo = "0.6.8"
textwrap = "0.16.1"
ignore = "0.4.20"
Expand Down
12 changes: 9 additions & 3 deletions src/binding_generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,15 @@ where
.rust_module
.join(format!("{ext_name}.pyi"));
if type_stub.exists() {
eprintln!("📖 Found type stub file at {ext_name}.pyi");
writer.add_file(module.join("__init__.pyi"), type_stub, false)?;
writer.add_empty_file(module.join("py.typed"))?;
if context.artifact.generate_stubs {
eprintln!(
"⚠️ Warning: Ignoring the type stub file at {ext_name}.pyi, stubs are automatically generated instead"
);
} else {
eprintln!("📖 Found type stub file at {ext_name}.pyi");
writer.add_file(module.join("__init__.pyi"), type_stub, false)?;
writer.add_empty_file(module.join("py.typed"))?;
}
}
}

Expand Down
23 changes: 23 additions & 0 deletions src/binding_generator/pyo3_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use anyhow::bail;
use pyo3_introspection::{introspect_cdylib, module_stub_files};
use tempfile::TempDir;
use tracing::debug;

Expand Down Expand Up @@ -123,6 +124,28 @@ if hasattr({ext_name}, "__all__"):
executable: false,
};
files.insert(module.join("__init__.py"), ArchiveSource::Generated(source));
if context.artifact.generate_stubs {
let module_introspection = introspect_cdylib(&artifact.path, ext_name).context("Failed to introspect the built libraries to generate type stubs, have you enabled the \"experimental-inspect\" PyO3 Cargo feature?")?;
eprintln!("📖 Type stub extracted from the built binary");
for (path, stub_content) in module_stub_files(&module_introspection) {
files.insert(
module.join(path),
ArchiveSource::Generated(GeneratedSourceData {
data: stub_content.into_bytes(),
path: None,
executable: false,
}),
);
}
files.insert(
module.join("py.typed"),
ArchiveSource::Generated(GeneratedSourceData {
data: Vec::new(),
path: None,
executable: false,
}),
);
}
Some(files)
}
};
Expand Down
12 changes: 8 additions & 4 deletions src/build_context/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use crate::python_interpreter::{InterpreterResolver, PythonInterpreter};
use crate::target::{
detect_arch_from_python, detect_target_from_cross_python, is_arch_supported_by_pypi,
};
use crate::{BridgeModel, BuildContext, PyProjectToml, Target};
use crate::{
ArtifactContext, BridgeModel, BuildContext, ProjectContext, PyProjectToml, PythonContext,
Target,
};
use anyhow::{Result, bail};
use std::collections::HashSet;
use std::env;
Expand Down Expand Up @@ -238,7 +241,7 @@ impl BuildContextBuilder {
};

Ok(BuildContext {
project: crate::build_context::ProjectContext {
project: ProjectContext {
target,
project_layout,
pyproject_toml_path,
Expand All @@ -255,7 +258,7 @@ impl BuildContextBuilder {
conditional_features,
compile_targets,
},
artifact: crate::build_context::ArtifactContext {
artifact: ArtifactContext {
out: wheel_dir,
strip,
compression: build_options.compression,
Expand All @@ -264,8 +267,9 @@ impl BuildContextBuilder {
include_debuginfo,
pgo_phase: None,
pgo_command,
generate_stubs: build_options.generate_stubs,
},
python: crate::build_context::PythonContext {
python: PythonContext {
auditwheel,
#[cfg(feature = "zig")]
zig: build_options.platform.zig,
Expand Down
4 changes: 3 additions & 1 deletion src/build_context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ pub struct ArtifactContext {
pub pgo_phase: Option<PgoPhase>,
/// PGO training command from pyproject.toml (only set when --pgo is passed)
pub pgo_command: Option<String>,
/// Auto generate Python type stubs by introspecting the binary. Requires PyO3 and its "experimental-inspect" feature
pub generate_stubs: bool,
}

/// The constraint part of the build context.
Expand All @@ -125,7 +127,7 @@ pub struct PythonContext {
///
/// This structure reflects the build lifecycle:
/// **Input (Project) → Constraints (Python) → Output (Artifact).**
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct BuildContext {
/// Project context
pub project: ProjectContext,
Expand Down
4 changes: 4 additions & 0 deletions src/build_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ pub struct BuildOptions {
/// Wheel compression options
#[command(flatten)]
pub compression: CompressionOptions,

/// Auto generate Python type stubs by introspecting the binary. Requires PyO3 and its "experimental-inspect" feature
#[arg(long)]
pub generate_stubs: bool,
}

impl Deref for BuildOptions {
Expand Down
6 changes: 6 additions & 0 deletions src/develop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ pub struct DevelopOptions {
/// Wheel compression options
#[command(flatten)]
pub compression: CompressionOptions,

/// Auto generate Python type stubs by introspecting the binary. Requires PyO3 and its "experimental-inspect" feature
#[arg(long)]
pub generate_stubs: bool,
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -283,6 +287,7 @@ pub fn develop(develop_options: DevelopOptions, venv_dir: &Path) -> Result<()> {
mut cargo_options,
uv,
compression,
generate_stubs,
} = develop_options;
compression.validate();

Expand Down Expand Up @@ -328,6 +333,7 @@ pub fn develop(develop_options: DevelopOptions, venv_dir: &Path) -> Result<()> {
..cargo_options
},
compression,
generate_stubs,
};

let build_context = build_options
Expand Down
133 changes: 133 additions & 0 deletions test-crates/pyo3-stub-generation/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions test-crates/pyo3-stub-generation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "pyo3-stub-generation"
version = "1.0.0"
edition = "2021"

[dependencies]
pyo3 = { version = "0.28", features = ["experimental-inspect"] }

[lib]
name = "pyo3_stub_generation"
crate-type = ["cdylib"]
25 changes: 25 additions & 0 deletions test-crates/pyo3-stub-generation/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright (c) 2018-present konstin

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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env python3
import os

import pyo3_stub_generation

assert pyo3_stub_generation.func(42) == 42

# Check type stub
install_path = os.path.join(os.path.dirname(pyo3_stub_generation.__file__))
assert os.path.exists(os.path.join(install_path, "__init__.pyi"))
assert os.path.exists(os.path.join(install_path, "submodule.pyi"))
assert os.path.exists(os.path.join(install_path, "py.typed"))

print("SUCCESS")
8 changes: 8 additions & 0 deletions test-crates/pyo3-stub-generation/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[project]
name = "pyo3-stub-generation"
description = "Tests compilation of packages loading stubs generated by PyO3"
license = { file = "LICENSE" }
Loading
Loading