Skip to content

build: add minimal python config to publish project on pypi#8818

Draft
lypwig wants to merge 1 commit intobiomejs:mainfrom
lypwig:main
Draft

build: add minimal python config to publish project on pypi#8818
lypwig wants to merge 1 commit intobiomejs:mainfrom
lypwig:main

Conversation

@lypwig
Copy link

@lypwig lypwig commented Jan 20, 2026

Summary

This PR follows #8808 and is used to improve Biome integration with Python project.

For now, it only consists in publishing Biome to Pypi on https://pypi.org/project/biome-js/.

The build is done via Maturin (which also generated the CI file).

The pyproject content is based on Biome's package.json. I also took inspiration ruff pyproject.

This is a work in progress: I prefer to publish early in order to get feedback before to continue.

Some other things to do:

  • edit: reuse the binaries built on existing CI;
  • automatically set project version;
  • allow to define biome configuration via user's pyproject file.

Any other suggestion is welcome.

Test Plan

  • have a look to the CI
  • try published version (ie. uvx --from biome-js biome --help)
  • build locally with sudo docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin build --release --manylinux 2_28 --target-dir dist

Docs

I will update docs when this PR will be in a more stable state.

@changeset-bot
Copy link

changeset-bot bot commented Jan 20, 2026

⚠️ No Changeset found

Latest commit: 21be3f4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

Walkthrough

Adds build and packaging infrastructure for a Python–Rust hybrid package. Introduces a GitHub Actions workflow (.github/workflows/publish-pypi.yml) to build wheels and an sdist across Linux, musl, Windows and macOS, generate provenance attestations, and publish artifacts on tagged releases. Adds pyproject.toml configured for maturin as the build backend with project metadata. Adds python/biome_js/__main__.py with find_biome_bin() to locate the biome executable and a main guard that execs it with forwarded arguments.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding minimal Python configuration for PyPI publishing, which is exactly what the PR does with pyproject.toml, main.py, and CI workflow files.
Description check ✅ Passed The PR description clearly relates to the changeset, outlining Python packaging configuration for publishing Biome on PyPI using Maturin with appropriate context and test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@pyproject.toml`:
- Line 10: Update the Python minimum version in pyproject.toml by changing the
requires-python metadata value from ">=3.8" to ">=3.11" and ensure CI/test
matrices (e.g., any GitHub Actions job names or matrix entries referencing
Python versions) are updated to include 3.11 and 3.12 and remove or
de-prioritize older EOL versions; look for the requires-python field and any CI
matrix entries or tox/pytest configuration referencing Python versions to keep
them consistent with the new baseline.
🧹 Nitpick comments (3)
python/biome_js/__main__.py (1)

5-8: Placeholder binary discovery may not work in all scenarios.

Returning just "biome" relies on the binary being on PATH. When installed via pip, maturin places the binary in Python's bin/Scripts directory, so this should work. However, the docstring says "Return the biome binary path" when it actually returns just the name.

Given this is WIP, this is fine for now, but you may want to enhance this later to locate the binary relative to the package installation (e.g., using importlib.resources or sysconfig.get_path('scripts')).

pyproject.toml (1)

35-36: Empty exclude array is unnecessary.

You can remove the empty exclude = [] if you have no exclusions—it's just noise.

.github/workflows/publish-pypi.yml (1)

6-6: Consider a more descriptive workflow name.

The workflow is named "CI" but it's specifically for publishing to PyPI. A name like "Publish PyPI" or "Build and Publish Python Wheels" would be clearer in the Actions UI, especially if you have other CI workflows.

Copy link
Member

@siketyan siketyan left a comment

Choose a reason for hiding this comment

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

Biome can be distributed in many ways, but I think the binary should be always same. Can we reuse the binaries built on our existing CI to make packages for pypi?

@lypwig lypwig force-pushed the main branch 2 times, most recently from f3a3a97 to 011ea38 Compare January 20, 2026 16:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@python/biome_js/__main__.py`:
- Line 20: The code checks sys._framework directly which can raise
AttributeError on some builds; update the guard in __main__.py to use
getattr(sys, "_framework", None) (or similar) instead of direct attribute access
so the branch becomes: elif sys.platform == "darwin" and getattr(sys,
"_framework", None): ensuring safe access to sys._framework.
- Line 10: The current concatenation in biome_exe = "biome" +
sysconfig.get_config_var("EXE") can raise a TypeError if
sysconfig.get_config_var("EXE") returns None; update the code around the
biome_exe assignment to fetch the EXE value into a variable (call it exe or
similar), guard against None by defaulting to an empty string (or another
appropriate suffix), and then build biome_exe using that safe value; refer to
sysconfig.get_config_var("EXE") and the biome_exe variable to locate where to
apply this fix.
🧹 Nitpick comments (2)
pyproject.toml (1)

14-29: Consider adding Python version classifiers.

The classifiers are missing Programming Language :: Python :: 3 and version-specific entries (e.g., Programming Language :: Python :: 3.11). These help with discoverability on PyPI but aren't strictly required.

📦 Suggested addition
 classifiers = [
     "Environment :: Console",
     "Intended Audience :: Developers",
     "Operating System :: OS Independent",
     "Programming Language :: JavaScript",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
     "Programming Language :: Rust",
     "Topic :: Software Development :: Quality Assurance",
 ]
python/biome_js/__main__.py (1)

37-37: Improve error message to show all searched paths.

The FileNotFoundError only mentions scripts_path, but three locations are checked. Listing all searched paths would help users debug installation issues.

💡 Suggested improvement
-    raise FileNotFoundError(scripts_path)
+    raise FileNotFoundError(
+        f"Could not find biome executable. Searched: {scripts_path}, {user_path}, {target_path}"
+    )

def find_biome_bin() -> str:
"""Return the Biome binary path."""

biome_exe = "biome" + sysconfig.get_config_var("EXE")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle potential None from get_config_var("EXE").

sysconfig.get_config_var("EXE") can return None on some platforms, which would cause a TypeError when concatenating with the string "biome".

🔧 Suggested fix
-    biome_exe = "biome" + sysconfig.get_config_var("EXE")
+    biome_exe = "biome" + (sysconfig.get_config_var("EXE") or "")
🤖 Prompt for AI Agents
In `@python/biome_js/__main__.py` at line 10, The current concatenation in
biome_exe = "biome" + sysconfig.get_config_var("EXE") can raise a TypeError if
sysconfig.get_config_var("EXE") returns None; update the code around the
biome_exe assignment to fetch the EXE value into a variable (call it exe or
similar), guard against None by defaulting to an empty string (or another
appropriate suffix), and then build biome_exe using that safe value; refer to
sysconfig.get_config_var("EXE") and the biome_exe variable to locate where to
apply this fix.

user_scheme = sysconfig.get_preferred_scheme("user")
elif os.name == "nt":
user_scheme = "nt_user"
elif sys.platform == "darwin" and sys._framework:
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against missing sys._framework attribute.

sys._framework is a private attribute that may not exist on all Python builds. Using getattr with a default avoids a potential AttributeError.

🔧 Suggested fix
-    elif sys.platform == "darwin" and sys._framework:
+    elif sys.platform == "darwin" and getattr(sys, "_framework", None):
🤖 Prompt for AI Agents
In `@python/biome_js/__main__.py` at line 20, The code checks sys._framework
directly which can raise AttributeError on some builds; update the guard in
__main__.py to use getattr(sys, "_framework", None) (or similar) instead of
direct attribute access so the branch becomes: elif sys.platform == "darwin" and
getattr(sys, "_framework", None): ensuring safe access to sys._framework.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@python/biome_js/__main__.py`:
- Around line 25-29: The code assumes sysconfig.get_path("scripts",
scheme=user_scheme) always returns a string when constructing user_path; add a
None guard: first retrieve the scripts path into a variable (e.g., scripts_path
= sysconfig.get_path("scripts", scheme=user_scheme)), check scripts_path is not
None before calling os.path.join to form user_path, and only then call
os.path.isfile(user_path) and return it if present; update the logic around
user_path, sysconfig.get_path, user_scheme, and biome_exe accordingly.
- Around line 12-14: Guard against sysconfig.get_path("scripts") returning None
before calling os.path.join: assign the result to a local variable (e.g.,
scripts_dir = sysconfig.get_path("scripts")), check that scripts_dir is truthy
(not None/empty) before using os.path.join(scripts_dir, biome_exe), and only
then call os.path.isfile on the constructed scripts_path; if scripts_dir is None
or empty, skip the join and handle accordingly (e.g., return None or continue
the fallback logic) so that scripts_path, sysconfig.get_path, biome_exe,
os.path.join, and os.path.isfile are used safely.
♻️ Duplicate comments (2)
python/biome_js/__main__.py (2)

10-10: Handle potential None from get_config_var("EXE").

As previously noted, sysconfig.get_config_var("EXE") can return None on some platforms.


20-20: Guard against missing sys._framework attribute.

As previously noted, sys._framework is a private attribute that may not exist on all Python builds.

🧹 Nitpick comments (1)
python/biome_js/__main__.py (1)

37-37: Improve the error message to list all checked locations.

Currently only scripts_path is mentioned, but three locations are checked. A more helpful message would aid debugging, especially if the guards for None are applied (as scripts_path may not be defined).

💡 Suggested improvement
-    raise FileNotFoundError(scripts_path)
+    raise FileNotFoundError(
+        f"Could not find the biome executable '{biome_exe}'. "
+        "Searched scripts directory, user scripts directory, and package bin directory."
+    )

Comment on lines +12 to +14
scripts_path = os.path.join(sysconfig.get_path("scripts"), biome_exe)
if os.path.isfile(scripts_path):
return scripts_path
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against None from sysconfig.get_path("scripts").

sysconfig.get_path("scripts") can return None in certain configurations, which would cause a TypeError in os.path.join.

🔧 Suggested fix
-    scripts_path = os.path.join(sysconfig.get_path("scripts"), biome_exe)
-    if os.path.isfile(scripts_path):
-        return scripts_path
+    scripts_dir = sysconfig.get_path("scripts")
+    if scripts_dir:
+        scripts_path = os.path.join(scripts_dir, biome_exe)
+        if os.path.isfile(scripts_path):
+            return scripts_path
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scripts_path = os.path.join(sysconfig.get_path("scripts"), biome_exe)
if os.path.isfile(scripts_path):
return scripts_path
scripts_dir = sysconfig.get_path("scripts")
if scripts_dir:
scripts_path = os.path.join(scripts_dir, biome_exe)
if os.path.isfile(scripts_path):
return scripts_path
🤖 Prompt for AI Agents
In `@python/biome_js/__main__.py` around lines 12 - 14, Guard against
sysconfig.get_path("scripts") returning None before calling os.path.join: assign
the result to a local variable (e.g., scripts_dir =
sysconfig.get_path("scripts")), check that scripts_dir is truthy (not
None/empty) before using os.path.join(scripts_dir, biome_exe), and only then
call os.path.isfile on the constructed scripts_path; if scripts_dir is None or
empty, skip the join and handle accordingly (e.g., return None or continue the
fallback logic) so that scripts_path, sysconfig.get_path, biome_exe,
os.path.join, and os.path.isfile are used safely.

Comment on lines +25 to +29
user_path = os.path.join(
sysconfig.get_path("scripts", scheme=user_scheme), biome_exe
)
if os.path.isfile(user_path):
return user_path
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same None guard needed for the user scheme path.

sysconfig.get_path("scripts", scheme=user_scheme) can also return None.

🔧 Suggested fix
-    user_path = os.path.join(
-        sysconfig.get_path("scripts", scheme=user_scheme), biome_exe
-    )
-    if os.path.isfile(user_path):
-        return user_path
+    user_scripts_dir = sysconfig.get_path("scripts", scheme=user_scheme)
+    if user_scripts_dir:
+        user_path = os.path.join(user_scripts_dir, biome_exe)
+        if os.path.isfile(user_path):
+            return user_path
🤖 Prompt for AI Agents
In `@python/biome_js/__main__.py` around lines 25 - 29, The code assumes
sysconfig.get_path("scripts", scheme=user_scheme) always returns a string when
constructing user_path; add a None guard: first retrieve the scripts path into a
variable (e.g., scripts_path = sysconfig.get_path("scripts",
scheme=user_scheme)), check scripts_path is not None before calling os.path.join
to form user_path, and only then call os.path.isfile(user_path) and return it if
present; update the logic around user_path, sysconfig.get_path, user_scheme, and
biome_exe accordingly.

@lypwig
Copy link
Author

lypwig commented Jan 21, 2026

Biome can be distributed in many ways, but I think the binary should be always same. Can we reuse the binaries built on our existing CI to make packages for pypi?

I don't know why but i can't directly answer this comment. Yes I think it's possible, I will try something.

@lypwig lypwig changed the title [wip] Add minimal python config to publish project on pypi build: add minimal python config to publish project on pypi Jan 21, 2026
@lypwig lypwig marked this pull request as draft January 21, 2026 11:16
[tool.maturin]
bindings = "bin"
manifest-path = "crates/biome_cli/Cargo.toml"
module-name = "biome_js"
Copy link
Member

@ematipico ematipico Feb 4, 2026

Choose a reason for hiding this comment

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

I don't know how the Python world works, but can we ask the owner of biome to transfer it to us? I can reach out to them

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.

3 participants