Skip to content

[Performance] packaging and release improvements and cleanup #925

Description

@farhan

Context

Follow-up work after #917 (folding web_fragments into the XBlock project).

Tasks

1. Drop __version__ from xblock/__init__.py and unused Sphinx import

As noted in this comment:

Ideally we should drop __version__ from xblock/__init__.py since we've shifted to semantic versioning. The only reason it still exists is that docs/conf.py imports it for Sphinx — but that import is unused too (no .rst file references version), so both can be removed together.

  • Remove __version__ from xblock/__init__.py
  • Remove the corresponding unused import in docs/conf.py

2. Migrate to src/ layout

Move both xblock and web_fragments packages under a src/ directory to follow modern Python packaging conventions and prevent accidental imports from the repo root during development.

  • Create src/ directory
  • Move xblock/src/xblock/
  • Move web_fragments/src/web_fragments/
  • Update pyproject.toml (package-dir = {"" = "src"}, where = ["src"])

3. Optimize bundling via MANIFEST.in

The current MANIFEST.in is a single line:

recursive-include xblock *.mo *.po

With include-package-data = true and no exclusions, the built wheel ends up bundling a large number of files that have no business being in a distribution — including the entire docs/ tree (79 files), CI configs, linting configs, lock files, and dev tooling. This inflates the master wheel to ~1.1MB when it should be well under 200KB.

Taking inspiration from openedx/xblocks-extra's MANIFEST.in, MANIFEST.in should be updated to explicitly prune everything that isn't needed by an end user:

recursive-include xblock *.mo *.po
recursive-include web_fragments *.html

# Exclude development, CI, and documentation folders
prune .github
prune docs

# Exclude root-level config and build files
exclude .coveragerc
exclude .gitignore
exclude .readthedocs.yaml
exclude Makefile
exclude catalog-info.yaml
exclude codecov.yml
exclude conftest.py
exclude openedx.yaml
exclude pylintrc
exclude pylintrc_tweaks
exclude tox.ini
exclude uv.lock

# Exclude nested test files
global-exclude tests/*
global-exclude test_*.py

Files currently git-tracked at the repo root that should be excluded:

File Reason to exclude
Makefile Developer tooling
conftest.py pytest root config
.coveragerc Coverage tooling
.gitignore VCS config
.readthedocs.yaml Docs CI config
catalog-info.yaml Backstage catalog metadata
codecov.yml CI coverage reporting
openedx.yaml Open edX repo metadata
pylintrc, pylintrc_tweaks Linting config
tox.ini Test runner config
uv.lock Dependency lock file
docs/ Full documentation tree (79 files, major bloat source)
.github/ GitHub Actions and templates

Note: CHANGELOG.rst exclusion is covered by task 5.

4. Review whether tests should ship in the wheel

Currently both xblock/test/ and web_fragments/tests/ are included in the built wheel. This needs a deliberate decision:

  • Some of xblock/test/ contains base test classes and utilities (e.g. toy_runtime.py, tools.py) that downstream packages depend on — those should ship
  • Pure test files (e.g. test_fields.py, test_core.py) have no value for end users — those should not ship
  • web_fragments/tests/ — evaluate the same way

Suggested outcome: ship xblock/test/ selectively (keep test utilities) and exclude web_fragments/tests/ entirely.

5. Remove CHANGELOG.rst if no longer needed

Since XBlock has shifted to semantic release, release notes are managed via GitHub Releases automatically. CHANGELOG.rst may be redundant — study how openedx/xblocks-extra handles this.

In xblocks-extra, CHANGELOG.rst was dropped (along with its MANIFEST.in reference) because changelog: false is set in release.yml, meaning semantic release manages release notes entirely via GitHub Releases.

  • Verify XBlock's release.yml configuration
  • If changelog generation is handled by semantic release, remove CHANGELOG.rst and its reference in MANIFEST.in

Reference: openedx/xblocks-extra#50

6. Fix semantic release build failures and improve the release process

Two issues surfaced after #917 was merged, both worth addressing:

a) refactor commits silently skip a release

After #917 merged (using refactor: commit prefixes), the semantic release workflow ran but produced no release — because neither chore nor refactor is configured as a version-bumping commit type. The full log from that run:

INFO     No commits found since the last release!
INFO     The type of the next release release is: no_release
INFO     No release will be made

Reference: actions/runs/27294163918, PR #917 comment

A refactor that adds new functionality (like absorbing web_fragments) is arguably a feat and warrants a release. The fix is twofold:

  • Short-term: contributors should use feat: when a refactor introduces new capabilities available to users.
  • Long-term: consider adding refactor as a patch-level trigger in the semantic release config so accidental use of the wrong prefix doesn't silently suppress a release.

b) uv: command not found during the build step

A follow-up force-release PR (#926) did trigger a release, but the build step failed with exit code 127:

bash: line 1: uv: command not found
ERROR    Command '['bash', '-c', 'SETUPTOOLS_SCM_PRETEND_VERSION=$NEW_VERSION uv build']'
         returned non-zero exit status 127.
ERROR    Build command failed with exit code 127

Reference: actions/runs/27301129203/job/80647052902

The root cause: astral-sh/setup-uv (step 6 in release.yml) installs uv on the GitHub Actions runner, but python-semantic-release executes the build command inside its own Docker container — where uv is not present.

Possible fixes:

  • Change build_command in pyproject.toml from uv build to pip install uv && uv build so uv is installed inside the container before use.
  • Or switch to python -m build (requires build as a dev dependency) to avoid the uv-in-container problem entirely.

Testing

To verify bundling changes, build the package locally and inspect the wheel contents:

python -m build

Then inspect what got bundled:

python3 -c "
import zipfile, sys
whl = sys.argv[1]
for f in sorted(zipfile.ZipFile(whl).namelist()):
    print(f)
" dist/XBlock-*.whl

This makes it easy to catch regressions like docs bloat, missing templates, or unwanted test files shipping in the wheel.

Reference

openedx/xblocks-extra follows a modernized packaging approach and is a good reference for how the above tasks can be implemented — particularly the src/ layout, MANIFEST.in structure, and pyproject.toml conventions.

Related

🤖 Generated with Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    performanceRelates to improving latency/throughput or reducing resource usage

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions