Latest Results
Require a scheme to start with a letter
<!-- Thank you for your contribution! -->
## What do these changes do?
Require the first character of a scheme to be an ASCII letter, as
[RFC 3986 section 3.1](https://www.rfc-editor.org/rfc/rfc3986#section-3.1)
and the WHATWG URL Standard both do. `split_url()` took that first
character from urllib's `scheme_chars`, which also holds the digits, `+`,
`-` and `.`, so `URL("10.0.0.7:8080/foo.html")` was a URL with the scheme
`10.0.0.7` and `URL("1abc://example.com/")` had the host `example.com`. A
string like that is a relative reference now, so
`URL("http://example.com/a/b").join(URL("10.0.0.7:8080/foo.html"))` keeps
the base and is `http://example.com/a/10.0.0.7:8080/foo.html`, which is
what the WHATWG URL Standard expects for the `10.0.0.7:8080/foo.html`
case of the vendored `urltestdata.json`. RFC 3986 mode already rejected
both strings and is unchanged.
The same first-letter rule now applies in the two helpers that mirror the
parser: the colon of a relative path is re-encoded only when the prefix
really is a scheme shape, so `URL.build(path="1a:b")` writes `1a:b` where
it wrote `1a%3Ab`, and a backslash is read as a path separator only in a
relative reference.
The conformance report moves the `10.0.0.7:8080/foo.html` row from
*WHATWG mode differs from WHATWG* to *yarl follows each standard in its
mode*; no rows are added.
## Are there changes in behavior for the user?
Yes. A string whose text before the first `:` is not a scheme, such as
`127.0.0.1:8080/x`, is a relative reference instead of an absolute URL
with that scheme, so `.scheme` is empty, `.host` is `None` rather than a
host taken from a string that is not a URL, and `join()` resolves it
against the base instead of returning it unchanged. RFC 3986 mode is
unchanged.
## Is it a substantial burden for the maintainers to support this?
No. It is one rule that RFC 3986 and the WHATWG URL Standard state
identically, applied in the one place where the parser reads a scheme and
in the two helpers that mirror it.
## Related issue number
N/A
## Checklist
- [x] I think the code is well written
- [x] Unit tests for the changes exist
- [x] Documentation reflects the changes
- [ ] If you provide code modification, please add yourself to `CONTRIBUTORS.txt`
- [x] Add a new news fragment into the `CHANGES/` folder
Drafted with DeepSeek Harness; reviewed by simpleqt.
<details>
<summary>Agent run details (optional, for reviewers)</summary>
Reproduction, run against master and against the branch
(`scratch_repro2.py`, kept out of the commit):
```
master: RED: 7 of the checks failed: ["URL('10.0.0.7:8080/foo.html').scheme",
"URL('10.0.0.7:8080/foo.html').raw_path",
"URL('1abc://example.com/').scheme", "URL('1abc://example.com/').host",
'http join', 'file join', "URL.build(path='1a:b').raw_path"]
branch: GREEN: every check passed
```
Tests: `python -m pytest tests -q -p no:cacheprovider
--ignore=tests/test_codspeed.py`: 2566 passed, 5 skipped, 51 xfailed, 2
failed, 3 errors. The failures
(`tests/test_rfc5952.py::test_canonical_text_form[::ffff:192.0.2.1]` and
`[::ffff:c000:0201]`) and the three collection errors
(`tests/test_query_to_pairs.py`, `tests/test_quoting.py`,
`tests/test_quoting_hypothesis.py`) are the same on master with the same
command: the Cython quoter cannot be built in this environment, and the
two RFC 5952 cases are an `ipaddress` difference of the interpreter.
`tests/test_url_scheme_start.py` adds 22 cases; the strict xfail
`3.1-scheme-starts-with-digit` in `tests/test_rfc3986.py` keeps failing
for the reason that remains (a relative path with `:` in the first
segment is accepted), and a plain passing case now covers the rule
itself, so `test_scheme_without_a_leading_letter` asserts that the prefix
is no scheme.
Conformance: `python tools/conformance/compare.py` regenerated
`REPORT.md` and `--check` passes. Row movements: 79 to 78 in *WHATWG mode
differs from WHATWG*, 318 to 319 in *yarl follows each standard in its
mode*, `yarl WHATWG mode vs WHATWG` 88 to 87; *yarl differs* stays at 0.
Both RFC 3986 mode results for the case are unchanged.
Lint: `ruff check .` and `ruff format --check yarl tests tools` pass with
ruff 0.15.20, the version pinned by the pre-commit configuration (the
pre-commit hooks themselves are not installed in this environment, so
`pre-commit run --all-files` did not run).
Docs spelling: the project marks `sphinxcontrib-spelling` as Linux only
(*We only use it in Azure CI*), so the checker was installed explicitly
here and the spelling builder was run with the same options as
`make doc-spelling` (`-b spelling -W -E --keep-going -n`). It reads
`docs/spelling_wordlist.txt` and reports one misspelled word, in the
pre-existing `CHANGES/1581.contrib.rst`; the same single warning appears
on master, so the new fragment adds neither a spelling nor a docutils
warning. Sphinx 9.0.4 was used because the pinned 9.1.0 requires Python
3.12 and this environment has 3.11.
Not run here: the Cython quoter build and the tests that need it, the
coverage gate (`--cov` with the project's `fail-under` cannot be met by a
partial selection), the CodSpeed benchmarks, and `pre-commit` itself.
Timing of `split_url()` and `_encode_url()` (5 repeats of 20000 calls,
alternating branch and master runs) stayed within the run to run spread,
about ten percent.
</details>simpleqt:scheme-starts-with-letter Latest Branches
0%
simpleqt:scheme-starts-with-letter 0%
dependabot/github_actions/CodSpeedHQ/action-5.5.0 0%
dependabot/github_actions/github/codeql-action-4.39.0 © 2026 CodSpeed Technology