Skip to content
Open
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
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,26 @@ jobs:
- run: uv run --project .. --isolated --only-group nox nox -s ${{matrix.checker}}
working-directory: pytests

verify-nostd:
needs: [fmt]
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
run: |
rustup toolchain install stable --profile minimal --no-self-update --component rust-src
rustup default stable
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
save-cache: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }}
- run: uv run --isolated --only-group nox nox -s verify-nostd

conclusion:
needs:
- zizmor
Expand Down
16 changes: 16 additions & 0 deletions examples/maturin-starter-nostd/.template/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
authors = ["{{authors}}"]
name = "{{project-name}}"
version = "0.1.0"
edition = "2021"

[lib]
name = "maturin_starter"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "{{PYO3_VERSION}}", default-features = false, features = [
"macros",
"hashbrown",
"parking_lot",
] }
4 changes: 4 additions & 0 deletions examples/maturin-starter-nostd/.template/pre-script.rhai
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
variable::set("PYO3_VERSION", "0.29.2");
file::rename(".template/Cargo.toml", "Cargo.toml");
file::rename(".template/pyproject.toml", "pyproject.toml");
file::delete(".template");
10 changes: 10 additions & 0 deletions examples/maturin-starter-nostd/.template/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[build-system]
requires = ["maturin>=1,<2"]
build-backend = "maturin"

[project]
name = "{{project-name}}"
version = "0.1.0"

[project.optional-dependencies]
dev = ["pytest"]
21 changes: 21 additions & 0 deletions examples/maturin-starter-nostd/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "maturin-starter"
version = "0.1.0"
edition = "2021"
rust-version = "1.83"

[lib]
name = "maturin_starter"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { path = "../../", default-features = false, features = [
"macros",
"hashbrown",
"parking_lot",
] }

[features]
abi3 = ["pyo3/abi3-py39"]

[workspace]
2 changes: 2 additions & 0 deletions examples/maturin-starter-nostd/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include pyproject.toml Cargo.toml
recursive-include src *
35 changes: 35 additions & 0 deletions examples/maturin-starter-nostd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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-nostd
```

(`cargo generate` will take a little while to clone the PyO3 repo first; be patient when waiting for the command to run.)
5 changes: 5 additions & 0 deletions examples/maturin-starter-nostd/cargo-generate.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[template]
ignore = [".nox"]

[hooks]
pre = [".template/pre-script.rhai"]
15 changes: 15 additions & 0 deletions examples/maturin-starter-nostd/maturin_starter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# import the contents of the Rust library into the Python extension
from .maturin_starter import *

# optional: include the documentation from the Rust module
from .maturin_starter import (
__all__,
__doc__, # noqa: F401
)

__all__ = __all__ + ["PythonClass"]


class PythonClass:
def __init__(self, value: int) -> None:
self.value = value
17 changes: 17 additions & 0 deletions examples/maturin-starter-nostd/noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import argparse

import nox


@nox.session
def python(session):
parser = argparse.ArgumentParser()
parser.add_argument("--features")
args = parser.parse_args(session.posargs)

session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
if args.features:
session.env["MATURIN_PEP517_ARGS"] += f" --features={args.features}"

session.install(".[dev]")
session.run("pytest")
19 changes: 19 additions & 0 deletions examples/maturin-starter-nostd/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[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"]
37 changes: 37 additions & 0 deletions examples/maturin-starter-nostd/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#![no_std]

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::<ExampleClass>()?;
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(())
}
22 changes: 22 additions & 0 deletions examples/maturin-starter-nostd/src/submodule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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::<SubmoduleClass>()?;
Ok(())
}
19 changes: 19 additions & 0 deletions examples/maturin-starter-nostd/tests/test_maturin_starter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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."
)
6 changes: 6 additions & 0 deletions examples/maturin-starter-nostd/tests/test_submodule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from maturin_starter.submodule import SubmoduleClass


def test_submodule_class() -> None:
submodule_class = SubmoduleClass()
assert submodule_class.greeting() == "Hello, world!"
79 changes: 79 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import functools
import io
import json
import os
import re
Expand All @@ -15,6 +16,7 @@
from difflib import unified_diff
from functools import lru_cache
from glob import glob
from operator import contains
from pathlib import Path
from shlex import quote
from typing import Any, Literal, Protocol
Expand Down Expand Up @@ -298,6 +300,83 @@ def clippy(session: nox.Session) -> bool:
session.error("one or more jobs failed")


@nox.session(name="verify-nostd", venv_backend="none")
def verify_nostd(session: nox.Sesstion):
manifest_path = "examples/maturin-starter-nostd/Cargo.toml"
args = [
"cargo",
"build",
f"--manifest-path={manifest_path}",
"--message-format=json-diagnostic-rendered-ansi",
]

dylib_path: str | None = None
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:
env = os.environ.copy()
env["PYO3_WIP_NO_STD"] = "1"
stdout = subprocess.Popen(
args,
stdout=subprocess.PIPE,
env=env,
).stdout
assert stdout is not None
for line in io.TextIOWrapper(stdout).readlines():
line = line.strip()
if not line.startswith("{"):
print(line)
continue

try:
message = json.loads(line)
reason: str = message["reason"]

if reason == "compiler-message":
print(message["message"]["rendered"])

elif reason == "compiler-artifact":
this_manifest_path: str = message["manifest_path"]
if not this_manifest_path.endswith(manifest_path):
continue
target_kinds: list[str] = message["target"]["kind"]
if not contains(target_kinds, "cdylib"):
continue
filenames: list[str] = message["filenames"]
if len(filenames) != 1:
session.error(
f"found multiple files for cdylib artifact: {filenames}",
)
return
dylib_path = filenames[0]

except json.JSONDecodeError:
print(line)

except Exception:
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)

if dylib_path is None:
session.error("could not find path to compiled dylib artifact")
return

_run_cargo(
session, "run", "--manifest-path=pyo3-verify-nostd/Cargo.toml", "--", dylib_path
)


def _clippy(
session: nox.Session,
*,
Expand Down
11 changes: 11 additions & 0 deletions pyo3-verify-nostd/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "pyo3-verify-nostd"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
anyhow = "1.0.104"
object = "0.40"

[workspace]
Loading
Loading