Skip to content

Add python bindings for bonsai#60

Open
kmolan wants to merge 25 commits into
mainfrom
feature/python-bindings
Open

Add python bindings for bonsai#60
kmolan wants to merge 25 commits into
mainfrom
feature/python-bindings

Conversation

@kmolan
Copy link
Copy Markdown
Collaborator

@kmolan kmolan commented May 17, 2026

Added python bindings for bonsai-bt - also called bonsai-bt. The new folder comes with its tests/ and examples/, where the examples/ folder should mimic the existing ones in rust. Since the original rust code is still small in size, writing the python bindings was not the hard part, in fact the bindings themselves are a small part of this PR. The hard part was all the devops work surrounding it. Namely:

  1. Adding thorough tests to ensure the bindings preserve original behavior. This forms the major bulk of this PR.
  2. Adding automation in our pipelines to ensure bindings do not get out of sync due to API changes. Builds will fail if API changes but bindings did not. You are allowed to add new API without creating their bindings, but you are not allowed to change existing API without updating the bindings.
  3. Adding automation in our pipelines to automatically build python wheels+sdist on every PR push.

Opening up the PR early for reviews. What is left now:

  1. Creating a PyPI account and creating a bonsai-bt project.
  2. Creating github scaffolding to support publishing to PyPI (this will remain a manual process, similar to tagging a release).
  3. Doing a first ever bonsai-bt release, verify by being able to do pip install bonsai-bt and running a test python script.
  4. Set up future automation.

@Sollimann I am stopping at this point for reviews. I will go ahead with setting up the above python publishing workflow if you are happy with what we currently have. For referece, once the above is done, all future releases workflow will look like this:

  1. Bump bonsai-py/Cargo.toml version. Merge to main. For this example lets assume the version is 0.13.0
  2. git tag py-test-0.13.0 && git push origin py-test-0.13.0 → TestPyPI dry-run.
  3. Verify on TestPyPI: pip install -i https://test.pypi.org/simple/ bonsai-bt==0.13.0 in a clean venv.
  4. git tag py-v0.13.0 && git push origin py-v0.13.0 → real PyPI release.
  5. Approve the publish in GitHub Environments. NO PYTHON PUBLISHING without a manual approval.
  6. Users can now pip install --upgrade bonsai-bt and get latest version.

Disclaimer: Used claude code to generate documentation, verify 100% test coverage, and generate plans for devops automation.

@Sollimann
Copy link
Copy Markdown
Owner

Again, great work on picking up items for the issues tab. This looks like the right direction 🚀 Its gonna take me some time to review all of this, and I'm gonna have to do it in portions

Copy link
Copy Markdown
Owner

@Sollimann Sollimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of reviews!

Comment thread .github/workflows/python-wheels.yml
Comment thread .github/workflows/python-wheels.yml
Comment thread .github/workflows/rust-pr.yml Outdated
Comment on lines +189 to +191
while True:
await asyncio.sleep(0.5)
now = time.perf_counter()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the async sleep here? threading and queue does not rely on async

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a simple example of showing an async job happening on a different thread, What would you recommend?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await asyncio.sleep(0.5) is identical to time.sleep(0.5) here. The threads progress regardless because they're OS threads driven by the kernel scheduler, not by the asyncio loop. So this example is threaded and not async

I think the simplest here is probably to rename the file to threaded_drone.py and drop asyncio and use time.sleep(0.5) instead. And then you can introduce another example called async_drone.py that demonstrate another concurrency model replacing the threading jobs with coroutines and asyncio.Queue. Then we'll demonstrate both cases for our users, e.g my actions block on hardware → look at threaded; my actions hit a network API → look at async

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment thread bonsai-py/examples/README.md Outdated
Comment thread bonsai-py/python/bonsai_bt/__init__.pyi
Comment thread bonsai-py/pyproject.toml
Comment on lines +5 to +8
[project]
name = "bonsai-py"
description = "Behavior trees in Python, powered by the bonsai-bt Rust crate."
readme = "README.md"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the directory bonsai-py is good to separate the Rust implementation from the python implementation, however, I think the exported library in PyPi should be bonsai-bt and not bonsai-py as its already implicit that its a python library. So import in python would be import bonsai_bt instead

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept all names the same to prevent confusion during dev (having names in code/file paths be different than the name of the published version can get tricky). I will think more about this if you have a strong preference. I can see your point, just need to think the tradeoffs for future dev confusions.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed after much thought. The python package will also be bonsai-bt now, however in our source code it will live under bonsai_py folder. I saw that other packages follow same strategy. The argument really came down to whether the burden should be created for the developers, or for the users. Keeping different names is better for dev but not for users, while keeping same name is much more convenient for the user.

Comment thread bonsai-py/pyproject.toml Outdated
license = { text = "MIT" }
requires-python = ">=3.10"
authors = [
{ name = "Kristoffer Solberg Rakstad", email = "kristoffer.solberg@cognite.com" },
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've included my work email by mistake in the past, but use my personal email here instead: solkristoffer@gmail.com

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, when creating the PyPi account, are you able to create dual ownership of that account with the two of us?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. should I go ahead and do a find-replace everywhere for this email? Its in three other places.

We will both need to create PyPI accounts. The steps look like this:

After the first successful publish:

  1. You will create your own PyPI account at https://pypi.org/account/register/ . Then give me your PyPI username.
  2. On my side, go to https://pypi.org/manage/project/bonsai-py/collaboration/.
  3. I will enter your PyPI username and pick role Owner .
  4. PyPI emails you an invite. From that point on, both are Owners on PyPI.

A key info is that neither of us will actually have the authority to publish a new version. Only this repository will have the rights. The owner tags are simply "These are the emails to reach out for questions". The only single way to publish a new version will be to run the github action in this repository.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PyPi username: Kembosol

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. should I go ahead and do a find-replace everywhere for this email? Its in three other places.

ye, you can do that, thanks

Copy link
Copy Markdown
Owner

@Sollimann Sollimann May 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only single way to publish a new version will be to run the github action in this repository.

Will the python package build from source, meaning that we can keep the Rust and Python releases separate? Its not like we have to publish the Rust version first, then the python version after. That reminds me I should set up a workflow for releasing Rust packages automatically after version bump, I've just done that manually until now

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the python package build from source, meaning that we can keep the Rust and Python releases separate? Its not like we have to publish the Rust version first, then the python version after.

Correct, they are separate. We can publish either/both based on preference.

I should set up a workflow for releasing Rust packages automatically after version bump

Created issue #67

Comment thread bonsai-py/src/behavior.rs
Comment thread bonsai-py/src/bt.rs
Comment thread bonsai-py/scripts/verify.py Outdated
Comment on lines +1 to +7
r"""
Verification script for bonsai-py.

WHAT THIS IS
Python-side assertions for the installed `bonsai_py` extension module.
Every test runs on every invocation; the script exits 0 on full pass,
1 on the first failure, 2 on a usage error.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great addition, is this best-practices for py03 to include a verification script?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this actually had a lot o repeated tests that pytest already covered. I refactored the tests so there's less duplicates now.

But yes, its generally good practice to test every single API surface, to catch drifts when the source code changes without updating the python bindings. And its only possible to do this if both the source and the bindings live in the same repo, otherwise catching drifts become impossible and a headache honestly.

Comment thread bonsai-py/examples/async_drone.py
Copy link
Copy Markdown
Owner

@Sollimann Sollimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the py.typed file for?

Copy link
Copy Markdown
Owner

@Sollimann Sollimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great test coverage!

Copy link
Copy Markdown
Owner

@Sollimann Sollimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add a note under https://github.com/Sollimann/bonsai#using-bonsai that we support python too and e.g link to the bonsai-py folder for more info. Also, In the bonsai-py folder mabye include a super simple example showing a side-by-side view of a very simple BT in Rust and then the same one in python. WDYT ?

Comment on lines +1 to +3
# bonsai-py examples

Pure-Python examples mirroring `examples/` in the Rust workspace. Each example is a single self-contained `.py` file.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we done in a separate PR, but I think it would be nice to have a python example where we serialize and deserialise the BT with json. ref

fn test_deserialize_behavior() {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #66

@kmolan
Copy link
Copy Markdown
Collaborator Author

kmolan commented May 24, 2026

what is the py.typed file for?

I run the static type checker, mypy, on the entire bonsai-py folder as one of the mandatory tests in our pipeline. This checker will only run if it sees the existence of the py.typed file.

I think we should add a note under https://github.com/Sollimann/bonsai#using-bonsai that we support python too and e.g link to the bonsai-py folder for more info. Also, In the bonsai-py folder mabye include a super simple example showing a side-by-side view of a very simple BT in Rust and then the same one in python.

Done.

@kmolan
Copy link
Copy Markdown
Collaborator Author

kmolan commented May 24, 2026

@Sollimann both python package and rust package will be called bonsai-bt, however the python files will live in bonsai_py in this repo. That's purely dev-facing, so users have no impact. If you're happy with the changes, we can merge to main and then I will kickoff the python publishing process.

Copy link
Copy Markdown
Owner

@Sollimann Sollimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants