MEP-71 Phase 10. TargetPythonPackage emit
Status: LANDED (pending merge) as of 2026-05-30 00:27 (GMT+7). Implements the publish-direction artifact composer that turns a Mochi-side library into a PEP 517 source distribution, a pure-Python wheel, the bundled mochi_build PEP 517 backend, and a typed .pyi for downstream consumers.
Gate
The umbrella sentinel TestPhase10TargetPythonPackage in package3/python/pypackage/phase10_test.go is green. The sentinel:
- Builds a
Packagecarrying a frozen record (Pointwith two int fields), an async function (fetch(string) -> Awaitable[string]), and a scalar module-level constant (VERSION: string). - Validates the package via
Package.Validate()(PEP 503 distribution name, PEP 440 version, non-empty Summary/License, Python-identifier export names). - Renders both layouts via
RenderSdist(p)andRenderWheel(p). - Writes each layout to a
t.TempDir()subdirectory viaLayout.Write(dst)(mkdirall + 0644 file writes, sorted output). - Asserts the sdist contains
pyproject.toml,PKG-INFO,README.md,<module>/__init__.py,<module>/__init__.pyi, andmochi_build/__init__.py(the bundled PEP 517 backend). - Asserts the wheel contains
<module>/__init__.py,<module>/__init__.pyi,<dist>-<ver>.dist-info/METADATA,WHEEL, andRECORD. - Asserts the
.pyire-emits the frozen record as a Python class, the async function asdef fetch(arg0: str) -> Awaitable[str]: ..., the constant asVERSION: str, and pulls in theAwaitabletyping import. - Asserts the wheel
RECORDcarriessha256=lines for every artifact except RECORD itself (which is listed with empty hash and size per PEP 376). - Asserts the bundled
mochi_build/__init__.pyexposesbuild_wheel,build_sdist, and dispatches tomochi pkg build.
Plus 38 unit tests (go test ./package3/python/pypackage/... -count=1) covering:
- PEP 503 distribution-name normalisation (
-/digit/lowercase only, no leading or trailing-). - PEP 426 module-name fallback (
mochi-httpx->mochi_httpx) plus an explicitModuleoverride. Validate()error paths for empty Distribution / Version / Summary / License and for non-identifier export names.PyprojectTOMLcore fields and conditional omission of HomePage / authors / requires-python / dependencies.PKGInfoMETADATA 2.1 headers (Name / Version / Summary / Home-page / Author / License / Requires-Python / Requires-Dist).WheelMetadataPEP 427 marker (Wheel-Version 1.0, Tag py3-none-any, Root-Is-Purelib true).InitPyre-exportsmochi_runtimeand populates__all__in sorted export order.InitPYIrendering for func / record / interface / constant exports (sync + async methods, empty record + empty Protocol bodies).pyAnnotationfor everytypemap.Kind: scalars, list/set/map/tuple, Optional, Sum, Async, Stream, Callable, named record/interface (andAnyfallback when name is empty), Ref, TypeVar, and the unknown-kind fallback.pyImportsForminimalfrom typing import ...set computed by walking exports (Optional, Union, Awaitable, AsyncIterator, Callable, Protocol, Any).MochiBuildBackendcarriesbuild_wheel,build_sdist,prepare_metadata_for_build_wheel, and shells tomochi pkg build.RecordLinerendering (empty self-line vs hashed line) andHashBodydeterminism + base64-urlsafe-no-padding shape.Layout.Addoverwrite semantics, sortedPaths(),Writemkdirall + empty-destination rejection.RenderSdistartifact set, README content, module-override propagation.RenderWheelartifact set, RECORD line format, RECORD determinism across two renders.
Files
package3/python/pypackage/doc.go— package overview.package3/python/pypackage/package.go—Package,Export,ExportKind,Validate,ModuleName, PEP 503 / identifier validators.package3/python/pypackage/layout.go—Layout(flat path -> body map) withAdd, sortedPaths,Write.package3/python/pypackage/render.go—PyprojectTOML,PKGInfo,WheelMetadata,InitPy,InitPYI,pyAnnotation,pyImportsFor,MochiBuildBackend,RecordEntry/RecordLine/HashBody.package3/python/pypackage/sdist.go—RenderSdist(p) (*Layout, error).package3/python/pypackage/wheel.go—RenderWheel(p) (*Layout, error)+ RECORD synthesis.package3/python/pypackage/package_test.go—Packagevalidators (12 cases).package3/python/pypackage/layout_test.go—Layoutmechanics (4 cases).package3/python/pypackage/render_test.go— renderers + type lowering (18 cases).package3/python/pypackage/sdist_test.go— sdist composition (4 cases).package3/python/pypackage/wheel_test.go— wheel composition + RECORD (4 cases).package3/python/pypackage/phase10_test.go— umbrella sentinel.
Sub-phase decomposition
Phase 10 ships the in-memory artifact composer only. The actual archive packing (sdist .tar.gz and wheel .whl zip) and the surrounding CLI surface land as follow-ups so the umbrella gate stays subprocess-free and deterministic:
| Sub-phase | Title | Status | Notes |
|---|---|---|---|
| 10 | Layout + renderers + mochi_build backend | LANDED (pending merge) | This PR. |
| 10.1 | Sdist tar.gz packer (deterministic mtimes, uid/gid 0) | NOT STARTED | Reproducible tarball + ZIP64-aware wheel packer. |
| 10.2 | mochi pkg build --target=python-package CLI verb | NOT STARTED | Drives RenderSdist / RenderWheel, copies output to --out=<dir>. |
| 10.3 | Mochi -> Package lowering | NOT STARTED | Walks a Mochi module's public surface and produces the Package value Phase 10 consumes. |
Fixtures
Phase 10's surface is a renderer over the closed Phase 4 type table; it does not consume the 25-package corpus directly. The 10.3 sub-phase will exercise the fixture corpus when the Mochi-side surface walker lands.
Skip count
N/A. Phase 10 has no SkipReport surface; non-renderable surfaces are rejected by Validate() before any artifact is touched.
Cross-references
- MEP-71 spec §10 "Publish direction" for the normative artifact schema.
- Phase 8 for the consume-direction workspace synth that already covers
mochi_runtimepackaging. - Phase 9 for the
wrapper-sha256pin Phase 10 will eventually surface in the published.dist-info. - Phase 11 for the PyPI upload + Sigstore / PEP 740 attestation pipeline that consumes Phase 10's output.