diff --git a/AGENTS.md b/AGENTS.md index fe40510b295..33d278a6e23 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 +`..md`, or `+..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. @@ -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 diff --git a/news/6966.bugfix.md b/news/6966.bugfix.md new file mode 100644 index 00000000000..e00ab26a2dd --- /dev/null +++ b/news/6966.bugfix.md @@ -0,0 +1 @@ +Reduce published wheel and sdist size by removing misplaced generated artifacts. diff --git a/packages/reflex-components-code/pyproject.toml b/packages/reflex-components-code/pyproject.toml index b9f9ac4c2b0..d94a730635a 100644 --- a/packages/reflex-components-code/pyproject.toml +++ b/packages/reflex-components-code/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index aa98c9f391b..b5de1a12e80 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-dataeditor/pyproject.toml b/packages/reflex-components-dataeditor/pyproject.toml index 945713f08b8..c14fe2d473b 100644 --- a/packages/reflex-components-dataeditor/pyproject.toml +++ b/packages/reflex-components-dataeditor/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-gridjs/pyproject.toml b/packages/reflex-components-gridjs/pyproject.toml index b4faf7bdace..d5cade74326 100644 --- a/packages/reflex-components-gridjs/pyproject.toml +++ b/packages/reflex-components-gridjs/pyproject.toml @@ -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"] diff --git a/packages/reflex-components-internal/pyproject.toml b/packages/reflex-components-internal/pyproject.toml index 0c9df6658fc..60a60491415 100644 --- a/packages/reflex-components-internal/pyproject.toml +++ b/packages/reflex-components-internal/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-lucide/pyproject.toml b/packages/reflex-components-lucide/pyproject.toml index 11e0ba5cfe8..27551e0635d 100644 --- a/packages/reflex-components-lucide/pyproject.toml +++ b/packages/reflex-components-lucide/pyproject.toml @@ -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"] diff --git a/packages/reflex-components-markdown/pyproject.toml b/packages/reflex-components-markdown/pyproject.toml index 3269e93e648..6a97c0bb7fb 100644 --- a/packages/reflex-components-markdown/pyproject.toml +++ b/packages/reflex-components-markdown/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-moment/pyproject.toml b/packages/reflex-components-moment/pyproject.toml index 80debf17400..4c39653b356 100644 --- a/packages/reflex-components-moment/pyproject.toml +++ b/packages/reflex-components-moment/pyproject.toml @@ -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"] diff --git a/packages/reflex-components-plotly/pyproject.toml b/packages/reflex-components-plotly/pyproject.toml index a265512c125..b8f5dcb3171 100644 --- a/packages/reflex-components-plotly/pyproject.toml +++ b/packages/reflex-components-plotly/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 9b786ce4c72..55652aac732 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-react-player/pyproject.toml b/packages/reflex-components-react-player/pyproject.toml index ce09493fbe0..a578b0fe876 100644 --- a/packages/reflex-components-react-player/pyproject.toml +++ b/packages/reflex-components-react-player/pyproject.toml @@ -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 = [ diff --git a/packages/reflex-components-recharts/pyproject.toml b/packages/reflex-components-recharts/pyproject.toml index 7f82ea8baf7..a9b27fd3c80 100644 --- a/packages/reflex-components-recharts/pyproject.toml +++ b/packages/reflex-components-recharts/pyproject.toml @@ -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"] diff --git a/packages/reflex-components-sonner/pyproject.toml b/packages/reflex-components-sonner/pyproject.toml index e6db6d409c2..607c9053979 100644 --- a/packages/reflex-components-sonner/pyproject.toml +++ b/packages/reflex-components-sonner/pyproject.toml @@ -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"] diff --git a/packages/reflex-site-shared/pyproject.toml b/packages/reflex-site-shared/pyproject.toml index 9b9917ecae1..f745b86d273 100644 --- a/packages/reflex-site-shared/pyproject.toml +++ b/packages/reflex-site-shared/pyproject.toml @@ -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 = [ diff --git a/pyproject.toml b/pyproject.toml index 107456caa98..a382e04e931 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/tests/units/test_build_config.py b/tests/units/test_build_config.py new file mode 100644 index 00000000000..646ee98edf6 --- /dev/null +++ b/tests/units/test_build_config.py @@ -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): + 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")