diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5c8923e0..fb010a24 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -66,6 +66,16 @@ jobs: command: test args: --no-default-features -p pyo3-stub-gen + deny: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Check licenses + uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check licenses + semver-check: runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index a86a8142..61ce0ea1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,27 +166,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - [[package]] name = "either" version = "1.15.0" @@ -230,7 +209,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "feature_gated" -version = "0.17.1" +version = "0.17.2" dependencies = [ "env_logger", "pyo3", @@ -282,15 +261,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.16.0" @@ -334,7 +304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown", ] [[package]] @@ -454,76 +424,12 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -[[package]] -name = "malachite" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbdf9cb251732db30a7200ebb6ae5d22fe8e11397364416617d2c2cf0c51cb5" -dependencies = [ - "malachite-base", - "malachite-nz", - "malachite-q", -] - -[[package]] -name = "malachite-base" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" -dependencies = [ - "hashbrown 0.14.5", - "itertools 0.11.0", - "libm", - "ryu", -] - -[[package]] -name = "malachite-bigint" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d149aaa2965d70381709d9df4c7ee1fc0de1c614a4efc2ee356f5e43d68749f8" -dependencies = [ - "derive_more", - "malachite", - "num-integer", - "num-traits", - "paste", -] - -[[package]] -name = "malachite-nz" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" -dependencies = [ - "itertools 0.11.0", - "libm", - "malachite-base", -] - -[[package]] -name = "malachite-q" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f235d5747b1256b47620f5640c2a17a88c7569eebdf27cd9cb130e1a619191" -dependencies = [ - "itertools 0.11.0", - "malachite-base", - "malachite-nz", -] - [[package]] name = "maplit" version = "1.0.2" @@ -557,7 +463,7 @@ dependencies = [ [[package]] name = "mixed" -version = "0.17.1" +version = "0.17.2" dependencies = [ "env_logger", "pyo3", @@ -566,7 +472,7 @@ dependencies = [ [[package]] name = "mixed_sub" -version = "0.17.1" +version = "0.17.2" dependencies = [ "env_logger", "pyo3", @@ -588,6 +494,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -652,12 +568,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "phf" version = "0.11.3" @@ -741,7 +651,7 @@ dependencies = [ [[package]] name = "pure" -version = "0.17.1" +version = "0.17.2" dependencies = [ "ahash", "env_logger", @@ -814,7 +724,7 @@ dependencies = [ [[package]] name = "pyo3-stub-gen" -version = "0.17.1" +version = "0.17.2" dependencies = [ "anyhow", "chrono", @@ -837,7 +747,7 @@ dependencies = [ [[package]] name = "pyo3-stub-gen-derive" -version = "0.17.1" +version = "0.17.2" dependencies = [ "heck", "indexmap", @@ -962,7 +872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cdaf8ee5c1473b993b398c174641d3aa9da847af36e8d5eb8291930b72f31a5" dependencies = [ "is-macro", - "malachite-bigint", + "num-bigint", "rustpython-parser-core", "static_assertions", ] @@ -978,7 +888,7 @@ dependencies = [ "itertools 0.11.0", "lalrpop-util", "log", - "malachite-bigint", + "num-bigint", "num-traits", "phf", "phf_codegen", @@ -1167,7 +1077,7 @@ dependencies = [ [[package]] name = "test-dash-package" -version = "0.17.1" +version = "0.17.2" dependencies = [ "pyo3", "pyo3-stub-gen", @@ -1300,12 +1210,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "unicode_names2" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index d4908e12..c9c9a77a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.17.1" +version = "0.17.2" edition = "2021" description = "Stub file (*.pyi) generator for PyO3" diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..fae2b831 --- /dev/null +++ b/deny.toml @@ -0,0 +1,239 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = true +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "CC0-1.0", + "Unicode-DFS-2016", + "Unicode-3.0", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/examples/feature_gated/Cargo.toml b/examples/feature_gated/Cargo.toml index 594e7d50..b6b207b5 100644 --- a/examples/feature_gated/Cargo.toml +++ b/examples/feature_gated/Cargo.toml @@ -3,6 +3,7 @@ name = "feature_gated" description = "Example for feature_gated stub generation" edition.workspace = true version.workspace = true +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] diff --git a/examples/mixed/Cargo.toml b/examples/mixed/Cargo.toml index 9158cd1e..f4cb6afb 100644 --- a/examples/mixed/Cargo.toml +++ b/examples/mixed/Cargo.toml @@ -3,6 +3,7 @@ name = "mixed" description = "Example for Mixed Rust/Python layout" version.workspace = true edition.workspace = true +publish = false [lib] crate-type = ["cdylib", "rlib"] diff --git a/examples/mixed_sub/Cargo.toml b/examples/mixed_sub/Cargo.toml index c310de14..572b0630 100644 --- a/examples/mixed_sub/Cargo.toml +++ b/examples/mixed_sub/Cargo.toml @@ -3,6 +3,7 @@ name = "mixed_sub" description = "Example for Mixed Rust/Python layout with submodule" version.workspace = true edition.workspace = true +publish = false [lib] crate-type = ["cdylib", "rlib"] diff --git a/examples/pure/Cargo.toml b/examples/pure/Cargo.toml index 22bf2858..48d8b477 100644 --- a/examples/pure/Cargo.toml +++ b/examples/pure/Cargo.toml @@ -3,6 +3,7 @@ name = "pure" description = "Example for pure-Rust layout" edition.workspace = true version.workspace = true +publish = false [lib] crate-type = ["cdylib", "rlib"] diff --git a/examples/pure/pure.pyi b/examples/pure/pure.pyi index 5d262c08..7a101b63 100644 --- a/examples/pure/pure.pyi +++ b/examples/pure/pure.pyi @@ -100,6 +100,40 @@ class ComparableStruct: def __ge__(self, other: builtins.object) -> builtins.bool: ... def __new__(cls, value: builtins.int) -> ComparableStruct: ... +class CustomComplexEnum: + r""" + Test complex enum with skip_stub_type + """ + @typing.final + class VARIANT_A(CustomComplexEnum): + __match_args__ = ("value",) + @property + def value(self) -> builtins.int: ... + def __new__(cls, value: builtins.int) -> CustomComplexEnum.VARIANT_A: ... + + @typing.final + class VARIANT_B(CustomComplexEnum): + __match_args__ = ("_0",) + @property + def _0(self) -> builtins.str: ... + def __new__(cls, _0: builtins.str) -> CustomComplexEnum.VARIANT_B: ... + def __len__(self) -> builtins.int: ... + def __getitem__(self, key: builtins.int) -> typing.Any: ... + + ... + +@typing.final +class CustomStubType: + r""" + Test class with manually defined PyStubType + """ + @property + def value(self) -> builtins.int: ... + @value.setter + def value(self, value: builtins.int) -> None: ... + def __new__(cls, value: builtins.int) -> CustomStubType: ... + def increment(self) -> builtins.int: ... + @typing.final class DataContainer: @property @@ -165,6 +199,17 @@ class MyDate(datetime.date): class MyError(builtins.RuntimeError): ... +@typing.final +class NormalClass: + r""" + Test class without skip_stub_type (normal behavior) + """ + @property + def value(self) -> builtins.str: ... + @value.setter + def value(self, value: builtins.str) -> None: ... + def __new__(cls, value: builtins.str) -> NormalClass: ... + @typing.final class NotIntError(builtins.TypeError): r""" @@ -358,6 +403,14 @@ class TypeIgnoreTest: Test method with type: ignore (without equals for catch-all) """ +@typing.final +class CustomEnum(enum.Enum): + r""" + Test enum with skip_stub_type + """ + OPTION_A = ... + OPTION_B = ... + @typing.final class Number(enum.Enum): FLOAT = ... diff --git a/examples/pure/src/lib.rs b/examples/pure/src/lib.rs index 12ce9637..88994a98 100644 --- a/examples/pure/src/lib.rs +++ b/examples/pure/src/lib.rs @@ -6,6 +6,7 @@ mod manual_submit; mod overloading; mod overriding; mod rust_type_marker; +mod skip_stub_type_test; use custom_exceptions::*; use manual_overloading::*; @@ -13,6 +14,7 @@ use manual_submit::*; use overloading::*; use overriding::*; use rust_type_marker::*; +use skip_stub_type_test::*; #[cfg_attr(target_os = "macos", doc = include_str!("../../../README.md"))] mod readme {} @@ -435,6 +437,10 @@ fn pure(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_function(wrap_pyfunction!(sum, m)?)?; m.add_function(wrap_pyfunction!(create_dict, m)?)?; m.add_function(wrap_pyfunction!(read_dict, m)?)?; diff --git a/examples/pure/src/skip_stub_type_test.rs b/examples/pure/src/skip_stub_type_test.rs new file mode 100644 index 00000000..52decbdf --- /dev/null +++ b/examples/pure/src/skip_stub_type_test.rs @@ -0,0 +1,86 @@ +use pyo3::prelude::*; +use pyo3_stub_gen::derive::*; + +/// Test class with manually defined PyStubType +#[gen_stub_pyclass(skip_stub_type)] +#[pyclass] +pub struct CustomStubType { + #[pyo3(get, set)] + pub value: i32, +} + +#[gen_stub_pymethods] +#[pymethods] +impl CustomStubType { + #[new] + pub fn new(value: i32) -> Self { + Self { value } + } + + pub fn increment(&mut self) -> i32 { + self.value += 1; + self.value + } +} + +// Manually implement PyStubType with custom type representation +// This demonstrates that skip_stub_type allows us to provide a custom implementation +// Here we're using a type alias to show it's different from the auto-generated one +impl pyo3_stub_gen::PyStubType for CustomStubType { + fn type_output() -> pyo3_stub_gen::TypeInfo { + // You could customize this to use a different type name, module, etc. + // For now, we keep it simple but this proves skip_stub_type works + pyo3_stub_gen::TypeInfo::with_module("CustomStubType", "pure".into()) + } +} + +/// Test class without skip_stub_type (normal behavior) +#[gen_stub_pyclass] +#[pyclass] +pub struct NormalClass { + #[pyo3(get, set)] + pub value: String, +} + +#[gen_stub_pymethods] +#[pymethods] +impl NormalClass { + #[new] + pub fn new(value: String) -> Self { + Self { value } + } +} + +/// Test enum with skip_stub_type +#[gen_stub_pyclass_enum(skip_stub_type)] +#[pyclass] +pub enum CustomEnum { + #[pyo3(name = "OPTION_A")] + OptionA, + #[pyo3(name = "OPTION_B")] + OptionB, +} + +// Manually implement PyStubType for the enum +impl pyo3_stub_gen::PyStubType for CustomEnum { + fn type_output() -> pyo3_stub_gen::TypeInfo { + pyo3_stub_gen::TypeInfo::with_module("CustomEnum", "pure".into()) + } +} + +/// Test complex enum with skip_stub_type +#[gen_stub_pyclass_complex_enum(skip_stub_type)] +#[pyclass] +pub enum CustomComplexEnum { + #[pyo3(name = "VARIANT_A")] + VariantA { value: i32 }, + #[pyo3(name = "VARIANT_B")] + VariantB(String), +} + +// Manually implement PyStubType for the complex enum +impl pyo3_stub_gen::PyStubType for CustomComplexEnum { + fn type_output() -> pyo3_stub_gen::TypeInfo { + pyo3_stub_gen::TypeInfo::with_module("CustomComplexEnum", "pure".into()) + } +} diff --git a/examples/test-dash-package/Cargo.toml b/examples/test-dash-package/Cargo.toml index efccbbcf..65c545cc 100644 --- a/examples/test-dash-package/Cargo.toml +++ b/examples/test-dash-package/Cargo.toml @@ -2,6 +2,7 @@ name = "test-dash-package" version.workspace = true edition.workspace = true +publish = false [lib] name = "test_dash_package" diff --git a/pyo3-stub-gen-derive/Cargo.toml b/pyo3-stub-gen-derive/Cargo.toml index d9dfaf4e..52e2e377 100644 --- a/pyo3-stub-gen-derive/Cargo.toml +++ b/pyo3-stub-gen-derive/Cargo.toml @@ -17,7 +17,7 @@ heck.workspace = true proc-macro2.workspace = true quote.workspace = true syn = { workspace = true, features = ["full", "extra-traits"] } -rustpython-parser = "0.4" +rustpython-parser = { version = "0.4", default-features = false, features = ["location", "num-bigint"] } indexmap.workspace = true [dev-dependencies] diff --git a/pyo3-stub-gen-derive/src/gen_stub.rs b/pyo3-stub-gen-derive/src/gen_stub.rs index c857caf9..2d82fe60 100644 --- a/pyo3-stub-gen-derive/src/gen_stub.rs +++ b/pyo3-stub-gen-derive/src/gen_stub.rs @@ -109,42 +109,75 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{parse2, ItemEnum, ItemFn, ItemImpl, ItemStruct, LitStr, Result}; -pub fn pyclass(item: TokenStream2) -> Result { +pub fn pyclass(attr: TokenStream2, item: TokenStream2) -> Result { + let attr = parse2::(attr)?; let mut item_struct = parse2::(item)?; let inner = PyClassInfo::try_from(item_struct.clone())?; - let derive_stub_type = StubType::from(&inner); pyclass::prune_attrs(&mut item_struct); - Ok(quote! { - #item_struct - #derive_stub_type - pyo3_stub_gen::inventory::submit! { - #inner - } - }) + + if attr.skip_stub_type { + Ok(quote! { + #item_struct + pyo3_stub_gen::inventory::submit! { + #inner + } + }) + } else { + let derive_stub_type = StubType::from(&inner); + Ok(quote! { + #item_struct + #derive_stub_type + pyo3_stub_gen::inventory::submit! { + #inner + } + }) + } } -pub fn pyclass_enum(item: TokenStream2) -> Result { +pub fn pyclass_enum(attr: TokenStream2, item: TokenStream2) -> Result { + let attr = parse2::(attr)?; let inner = PyEnumInfo::try_from(parse2::(item.clone())?)?; - let derive_stub_type = StubType::from(&inner); - Ok(quote! { - #item - #derive_stub_type - pyo3_stub_gen::inventory::submit! { - #inner - } - }) + + if attr.skip_stub_type { + Ok(quote! { + #item + pyo3_stub_gen::inventory::submit! { + #inner + } + }) + } else { + let derive_stub_type = StubType::from(&inner); + Ok(quote! { + #item + #derive_stub_type + pyo3_stub_gen::inventory::submit! { + #inner + } + }) + } } -pub fn pyclass_complex_enum(item: TokenStream2) -> Result { +pub fn pyclass_complex_enum(attr: TokenStream2, item: TokenStream2) -> Result { + let attr = parse2::(attr)?; let inner = PyComplexEnumInfo::try_from(parse2::(item.clone())?)?; - let derive_stub_type = StubType::from(&inner); - Ok(quote! { - #item - #derive_stub_type - pyo3_stub_gen::inventory::submit! { - #inner - } - }) + + if attr.skip_stub_type { + Ok(quote! { + #item + pyo3_stub_gen::inventory::submit! { + #inner + } + }) + } else { + let derive_stub_type = StubType::from(&inner); + Ok(quote! { + #item + #derive_stub_type + pyo3_stub_gen::inventory::submit! { + #inner + } + }) + } } pub fn pymethods(item: TokenStream2) -> Result { diff --git a/pyo3-stub-gen-derive/src/gen_stub/attr.rs b/pyo3-stub-gen-derive/src/gen_stub/attr.rs index 83a0f053..a9907dd0 100644 --- a/pyo3-stub-gen-derive/src/gen_stub/attr.rs +++ b/pyo3-stub-gen-derive/src/gen_stub/attr.rs @@ -541,6 +541,45 @@ impl Parse for OverrideTypeAttribute { } } +/// Common attributes for `#[gen_stub_pyclass(...)]`, `#[gen_stub_pyclass_enum(...)]`, +/// and `#[gen_stub_pyclass_complex_enum(...)]` macros +#[derive(Default)] +pub struct PyClassAttr { + pub skip_stub_type: bool, +} + +impl Parse for PyClassAttr { + fn parse(input: ParseStream) -> Result { + let mut skip_stub_type = false; + + // Parse comma-separated flags + while !input.is_empty() { + let key: Ident = input.parse()?; + + match key.to_string().as_str() { + "skip_stub_type" => { + skip_stub_type = true; + } + _ => { + return Err(syn::Error::new( + key.span(), + format!("Unknown parameter: {}", key), + )); + } + } + + // Check for comma separator + if input.peek(Token![,]) { + let _: Token![,] = input.parse()?; + } else { + break; + } + } + + Ok(Self { skip_stub_type }) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/pyo3-stub-gen-derive/src/lib.rs b/pyo3-stub-gen-derive/src/lib.rs index 462a03a6..5e65cf0b 100644 --- a/pyo3-stub-gen-derive/src/lib.rs +++ b/pyo3-stub-gen-derive/src/lib.rs @@ -19,8 +19,8 @@ use proc_macro::TokenStream; /// } /// ``` #[proc_macro_attribute] -pub fn gen_stub_pyclass(_attr: TokenStream, item: TokenStream) -> TokenStream { - gen_stub::pyclass(item.into()) +pub fn gen_stub_pyclass(attr: TokenStream, item: TokenStream) -> TokenStream { + gen_stub::pyclass(attr.into(), item.into()) .unwrap_or_else(|err| err.to_compile_error()) .into() } @@ -39,8 +39,8 @@ pub fn gen_stub_pyclass(_attr: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` #[proc_macro_attribute] -pub fn gen_stub_pyclass_enum(_attr: TokenStream, item: TokenStream) -> TokenStream { - gen_stub::pyclass_enum(item.into()) +pub fn gen_stub_pyclass_enum(attr: TokenStream, item: TokenStream) -> TokenStream { + gen_stub::pyclass_enum(attr.into(), item.into()) .unwrap_or_else(|err| err.to_compile_error()) .into() } @@ -59,8 +59,8 @@ pub fn gen_stub_pyclass_enum(_attr: TokenStream, item: TokenStream) -> TokenStre /// } /// ``` #[proc_macro_attribute] -pub fn gen_stub_pyclass_complex_enum(_attr: TokenStream, item: TokenStream) -> TokenStream { - gen_stub::pyclass_complex_enum(item.into()) +pub fn gen_stub_pyclass_complex_enum(attr: TokenStream, item: TokenStream) -> TokenStream { + gen_stub::pyclass_complex_enum(attr.into(), item.into()) .unwrap_or_else(|err| err.to_compile_error()) .into() } diff --git a/pyo3-stub-gen/Cargo.toml b/pyo3-stub-gen/Cargo.toml index f2e6bc2c..d598f998 100644 --- a/pyo3-stub-gen/Cargo.toml +++ b/pyo3-stub-gen/Cargo.toml @@ -26,7 +26,7 @@ serde.workspace = true toml.workspace = true [dependencies.pyo3-stub-gen-derive] -version = "0.17.1" +version = "0.17.2" path = "../pyo3-stub-gen-derive" [dev-dependencies]