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
27 changes: 26 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,30 @@ Playwright tests use the `page` fixture and navigate to `harness.frontend_url`.

When adding/modifying components: `uv run python scripts/make_pyi.py`. Commit `pyi_hashes.json` (not `.pyi` files). If the diff removes many modules, run `uv sync`, delete `.pyi_generator_last_run`, and regenerate.

## Changelog fragments

User-facing changes need a news fragment in the `news/` directory of each
package they touch (the repo root's `news/` for `reflex`), named
`<PR number>.<type>.md`, or `+<slug>.<type>.md` before the PR number is known.
Types: `breaking`, `deprecation`, `feature`, `bugfix`, `performance`, `docs`,
`misc`.

Write for external downstream users, not for reviewers. Every entry links to
its PR, so motivation, narrative, and implementation details belong in the PR
and the commit message — a reader who wants them will follow the link. Keep the
fragment to a sentence or two saying what changed and what it means for a user:

> Reduce published wheel and sdist size by removing misplaced generated artifacts.

Brevity is about the narrative, not the substance: whatever is genuinely useful
downstream belongs in the fragment. A brief usage example for a new feature, or
the before/after of converting deprecated usage to the supported style, earns
its place. Once it runs past a few sentences and a small code block, it is
documentation — write it under `docs/` and let the fragment link there.

CI requires a fragment for every package whose source the PR touches; the
`skip-changelog` label waives it for changes that are genuinely not user-facing.

## Breaking changes and deprecation

Reflex has downstream users — don't break them. Provide a fallback path during deprecation.
Expand Down Expand Up @@ -128,4 +152,5 @@ Before submitting:
3. `uv run pyright reflex tests` passes
4. `pyi_hashes.json` updated if components changed
5. Documentation updated if user-facing behavior changed
6. Deprecation warnings added if breaking changes introduced
6. News fragment added for user-facing changes
7. Deprecation warnings added if breaking changes introduced
1 change: 1 addition & 0 deletions news/6966.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reduce published wheel and sdist size by removing misplaced generated artifacts.
4 changes: 2 additions & 2 deletions packages/reflex-components-code/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pattern-prefix = "reflex-components-code-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ pattern-prefix = "reflex-components-core-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-dataeditor/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pattern-prefix = "reflex-components-dataeditor-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-gridjs/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pattern-prefix = "reflex-components-gridjs-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = ["ruff", "reflex-base"]
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-internal/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pattern-prefix = "reflex-components-internal-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-lucide/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pattern-prefix = "reflex-components-lucide-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = ["ruff", "reflex-base"]
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-markdown/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pattern-prefix = "reflex-components-markdown-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-moment/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pattern-prefix = "reflex-components-moment-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = ["ruff", "reflex-base"]
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-plotly/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pattern-prefix = "reflex-components-plotly-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-radix/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pattern-prefix = "reflex-components-radix-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-react-player/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pattern-prefix = "reflex-components-react-player-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-recharts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pattern-prefix = "reflex-components-recharts-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = ["ruff", "reflex-base"]
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-components-sonner/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pattern-prefix = "reflex-components-sonner-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = ["ruff", "reflex-base", "reflex-components-lucide"]
Expand Down
4 changes: 2 additions & 2 deletions packages/reflex-site-shared/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pattern-prefix = "reflex-site-shared-"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/src/**/*.pyi"]

[tool.hatch.build.hooks.reflex-pyi]
dependencies = [
Expand Down
10 changes: 7 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,13 @@ source = "uv-dynamic-versioning"
fallback-version = "0.0.0dev0"

[tool.hatch.build]
include = ["reflex", "scripts/hatch_build.py"]
targets.sdist.artifacts = ["*.pyi"]
targets.wheel.artifacts = ["*.pyi"]
# Leading slashes anchor to the repo root; unanchored patterns match nested
# directories under packages/ too.
include = ["/reflex"]
# Include uncommitted pyi stubs generated for this package.
artifacts = ["/reflex/**/*.pyi"]
# The build hook is only needed to build the wheel from the sdist.
targets.sdist.include = ["/reflex", "/scripts/hatch_build.py"]

[tool.hatch.build.hooks.custom]
path = "scripts/hatch_build.py"
Expand Down
112 changes: 112 additions & 0 deletions tests/units/test_build_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""Tests for the file selection of the workspace's hatch build configs."""

from pathlib import Path

import pytest
from hatchling.builders.plugin.interface import BuilderInterface
from hatchling.builders.sdist import SdistBuilder
from hatchling.builders.wheel import WheelBuilder

REPO_ROOT = Path(__file__).parents[2]

# The build hook that regenerates the stubs. The sdist ships it so that the
# wheel can be built from the sdist; the wheel itself has no use for it.
BUILD_HOOK = "scripts/hatch_build.py"

# Everything the reflex distributions are meant to ship: the package and its
# generated stubs.
SELECTED = [
"reflex/app.py",
"reflex/__init__.pyi",
"reflex/components/__init__.pyi",
]

# Sibling workspace packages bundle their own stubs and templates, so none of
# their files belong in the reflex distributions. Neither do the pyi_generator
# golden files, which are only test fixtures.
NOT_SELECTED = [
"packages/reflex-components-core/src/reflex_components_core/el/element.py",
"packages/reflex-components-core/src/reflex_components_core/el/element.pyi",
"packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi",
"packages/reflex-base/src/reflex_base/.templates/web/components/reflex/color_mode.js",
"tests/units/reflex_base/utils/pyi_generator/golden/var_types.pyi",
"docs/app/docs.py",
]

BUILDERS = pytest.mark.parametrize(
"builder_class", [SdistBuilder, WheelBuilder], ids=["sdist", "wheel"]
)


@pytest.fixture
def builder(builder_class: type[BuilderInterface]) -> BuilderInterface:
return builder_class(str(REPO_ROOT))


def stub_packages() -> list[Path]:
"""Find the workspace packages that generate stubs at build time.

Discovery keys off the build hook rather than off the artifact patterns, so
that dropping a package's patterns fails its assertions below instead of
quietly dropping it from the parametrization.

Returns:
The directory of every package that runs the stub generator.
"""
packages = []
for path in sorted((REPO_ROOT / "packages").glob("*/pyproject.toml")):
build_config = SdistBuilder(str(path.parent)).config.build_config
if "reflex-pyi" in build_config.get("hooks", {}):
packages.append(path.parent)
return packages


@BUILDERS
@pytest.mark.parametrize("relative_path", SELECTED)
def test_build_selects_reflex_files(builder: BuilderInterface, relative_path: str):
Comment thread
masenf marked this conversation as resolved.
assert builder.config.include_path(relative_path)


@BUILDERS
@pytest.mark.parametrize("relative_path", NOT_SELECTED)
def test_build_skips_files_outside_reflex(
builder: BuilderInterface, relative_path: str
):
assert not builder.config.include_path(relative_path)


@BUILDERS
def test_build_walks_only_the_reflex_tree(
builder: BuilderInterface, builder_class: type[BuilderInterface]
):
allowed = {BUILD_HOOK} if builder_class is SdistBuilder else set()
selected = {
Path(included.relative_path).as_posix()
for included in builder.recurse_included_files()
}
outside = sorted(
path
for path in selected
if not path.startswith("reflex/") and path not in allowed
)
assert outside == []


def test_sdist_ships_the_build_hook():
assert SdistBuilder(str(REPO_ROOT)).config.include_path(BUILD_HOOK)


def test_wheel_omits_the_build_hook():
assert not WheelBuilder(str(REPO_ROOT)).config.include_path(BUILD_HOOK)


@BUILDERS
@pytest.mark.parametrize("package", stub_packages(), ids=lambda path: path.name)
def test_package_ships_only_its_own_stubs(
builder_class: type[BuilderInterface], package: Path
):
config = builder_class(str(package)).config
assert config.include_path("src/module/component.pyi")
# Anything a package keeps beside `src` — fixtures, docs, a vendored
# checkout — is not part of what it distributes.
assert not config.include_path("tests/golden.pyi")
Loading