Phase 7: import grammar
Phase 7 introduces package3/python/importspec, the parser that consumes the body of the MEP-71 surface form
import python "<spec>" as <alias>
and produces a typed Spec value. The parser does not consume the alias (the MEP-51 grammar already handles that): it is fed only the double-quoted string body. The returned Spec is what Phase 8 (build orchestration) and Phase 9 (lockfile integration) consume.
Gate
go test ./package3/python/importspec/... is green. The gate covers:
- The
Parseentry point: rejects empty input, leading or trailing whitespace, an empty qualifier after@, and any invalid distribution name. - The five MEP-71 §3 spec shapes, each round-tripping through
Spec.String():- Bare (
requests) setsSource=SourceRegistryand leavesSpecifierempty. - Semver-pinned (
requests@>=2.0,<3.0) parses the PEP 440 specifier intoSpecifier.Clausesviasemver.ParseSpecifier. - Non-default index (
torch@torch+https://download.pytorch.org/whl/cu121) setsSource=SourceIndexandIndexURLto the URL after the+. - VCS (
mypkg@git+https://github.com/user/repo#commit) setsSource=SourceGit,GitURL, and the optionalGitRevfrom the fragment. - Local path (
mypkg@path+../sibling) setsSource=SourcePathandLocalPath.
- Bare (
- PEP 503 name normalisation:
Foo.Bar_baz→foo-bar-baz;RawNamepreserves the original spelling for emit and error display,Namecarries the normalised form for resolution. - PEP 508 / PEP 426 name validation: rejects empty, leading or trailing separators, internal whitespace, and characters outside
[A-Za-z0-9._-]. - Index / local-version disambiguation: a
+followed by a recognised URL scheme (http,https,ssh,git,file,ftp) is an index URL; a+followed by alphanumerics is a PEP 440 local-version segment and stays inside the specifier. - Git form edge cases: empty URL, empty URL before fragment, empty fragment after
#, and non-URL targets are all rejected with a Spec-quoted error message. - Path form edge cases: empty path,
.,/, and paths that collapse to either viapath.Cleanare rejected. - Index form edge cases: empty index identifier (
@+<url>) is rejected. Source.String()rendersregistry/index/git/path/unknownfor the four valid kinds plus the out-of-range fallback.isURLLikeadmits exactly the six schemes the bridge supports and rejects unknown schemes, local-version segments, and the empty string.- Sentinel
TestPhase7ImportGrammarwalks all five spec shapes end-to-end and asserts the decomposed Spec matches.
Files
package3/python/importspec/doc.go— package overview enumerating the five spec shapes and pointing at MEP-71 §3 / §5.package3/python/importspec/spec.go—Sourceenum,Specstruct,Parse,Spec.String, and thevalidateName/normaliseName/isURLLikehelpers.package3/python/importspec/spec_test.go,phase07_test.go— ~40 tests covering the gate above.
Fixtures
The unit tests cover the spec shapes directly with synthetic strings drawn from the MEP-71 §3 examples. Corpus-wide validation against the 25-package fixture set lands with Phase 8 (build orchestration), where Spec values flow into the wheel-install pipeline and the resolved Specifier drives the uv invocation from Phase 2.
Skip count
Phase 7 does not introduce its own refusal cases. Any rejection it surfaces (invalid name, malformed version, bad URL) is a hard parse error visible at the call site rather than a Phase-4 / Phase-5 SkipReport. Per-fixture SkipReport counts remain identical to Phase 5's.
Notes
- The package preserves
RawNamefor emit and error display, so the alias and error messages in the importing file show the user's spelling rather than the PEP 503-normalised form. TheNamefield is what the lockfile and resolver key against. Spec.Stringis the inverse ofParseand is exact for git, path, index, and bare forms. The semver-pinned form round-trips throughsemver.Specifier.String(), which inserts a space after every comma; this is the only intentional whitespace drift.- Phase 8 will turn
SourceIndexinto a non-default[[tool.uv.index]]entry,SourceGitinto agit+VCS URL passed to uv, andSourcePathinto an editable install rooted at the manifest dir.
Timestamps
- 2026-05-30 00:00 (GMT+7): Phase 7 started.
- 2026-05-30 00:03 (GMT+7): Phase 7 LANDED.