Builtouts
Building out Python's batteries.
Builtouts provides dependency-free interfaces and explicit conventions over Python's builtins and standard library. Python 3.14+ is required. Modules are independently usable and do not modify Python globals.
| Module | Standard-library foundation | Added interface |
|---|---|---|
builtouts.html |
html.parser |
Bounded trees, queries, visible text, streaming links |
builtouts.concurrent |
concurrent.futures |
Bounded thread iteration and collection |
builtouts.conventions |
str, unicodedata |
Explicit Unicode naming transformations |
Install builtouts:
python -m pip install builtouts
For a local development checkout, use python -m pip install ..
HTML extraction
from builtouts.html import Document
doc = Document.from_html('''
<title>Example</title>
<article class="card"><h2>First</h2><a href="/one">Read more</a></article>
''')
assert doc.title == 'Example'
article = doc.select_one('article.card')
assert article is not None
link = article.find('a', href=True)
assert link is not None
assert link.attrs['href'] == '/one'
assert article.get_text(' ', strip=True) == 'First Read more'
DocumentParser subclasses HTMLParser. Feed string chunks, call close(),
then use its document. The document is live during feeds. Parsers are single
use: closing twice is harmless, but feeding after close/failure or calling
reset() raises RuntimeError. Bytes must be decoded by the caller.
Nodes expose kind, tag, read-only attrs and children, parent, and
data. descendants() iterates in source order and excludes the receiver.
find() and find_all() search descendant elements by optional tag and ANDed
attribute filters, supplied through attrs={...} or keyword arguments:
- Strings match exact attribute values.
class_='card'matches a class token. Truemeans present;Falsemeans absent.Nonematches a present boolean attribute, distinct from absence or''.
text concatenates descendant text, omitting comments and script/style
subtrees. get_text(separator='', strip=False, include_hidden=False) controls
joining, per-piece trimming and script/style inclusion. No layout whitespace
is inferred around block or void elements. Hidden here refers only to script
and style; CSS visibility, the hidden attribute and browser layout are not
evaluated. title returns the first title's stripped text, or None.
Supported CSS selectors use ASCII identifiers (-?[_a-zA-Z][_a-zA-Z0-9-]*) for tags, IDs and classes,
*, [attr], [attr=value], compound selectors, descendants, > children,
and comma groups. Attribute strings can be quoted, including non-ASCII text;
unquoted values must be identifiers. Results are deduplicated in source order.
An element receiver can match an ancestor part but is excluded from results;
matching never climbs above it. Tags/attribute names are case-normalized;
IDs, classes and values are case-sensitive. Unsupported or malformed syntax
raises SelectorSyntaxError, including sibling combinators, pseudo-classes,
escapes and substring attribute operators. Selectors are limited to 4,096
characters and 64 compound parts across all groups.
Parsing preserves text, comments, declarations and processing instructions.
Names follow HTMLParser normalization; duplicate attributes use the last value.
Void elements never become parents; self-closing tags close immediately.
A matching end tag closes its element and intervening open elements; unmatched
end tags are ignored. EOF closes remaining elements. Optional end tags are not
inferred: <li>a<li>b produces nested elements. This is a documented extraction
tree, without HTML5 browser tree construction, XML namespaces or JavaScript.
Raw script/style text follows HTMLParser's handling. Parsed text and URLs are
untrusted data; extraction does not sanitize content or validate URLs for fetching.
All parsers accept positive integer max_chars=10_000_000,
max_nodes=100_000, and max_depth=256. Excess raises ParseLimitError and
invalidates the parser. Node counts exclude the document root and coalesce
adjacent text across feeds. Streaming collectors count structural/text events
without retaining nodes. Limits are in-process guardrails, not a hard CPU or
memory isolation boundary; downloaded input needs its own byte/deadline bounds.
Streaming extraction
from builtouts.html import LinkCollector, TextCollector
links = LinkCollector(base_url='https://example.test/notes/')
links.feed('<a href="../one">One & ')
links.feed('<b>two</b></a>')
links.close()
assert links.links[0].url == 'https://example.test/one'
assert links.links[0].text == 'One & two'
pieces = []
text = TextCollector(on_text=pieces.append, collect=False)
text.feed('<p>Hello</p><script>ignored</script>')
text.close()
assert ''.join(pieces) == 'Hello'
assert text.text == ''
Collectors do not build trees. Link is an immutable href, text, url
record. Without base_url, url equals the original href. An input <base>
tag is ignored for resolution. Missing/boolean href attributes emit no link;
empty hrefs are retained. Links emit when anchors close, including EOF;
nested malformed anchors emit inner links first and include inner text in the
outer link. No URL is fetched. Resolution can still produce arbitrary schemes
or different hosts; apply an operation-specific URL policy before using them.
Set collect=False and on_link=callback or on_text=callback to avoid retaining
output. Callbacks run synchronously inside feed()/close() and must not reenter
the parser. Callback failures propagate and invalidate it. Text callback chunk
boundaries depend on input feeds/tokenization. Accumulated results, and text in
currently open anchors, consume memory proportional to retained output.
Bounded thread concurrency
from builtouts.concurrent import thread_iter, thread_map
assert thread_map(len, ['a', 'abcd'], max_workers=4) == [1, 4]
with thread_iter(len, ['a', 'abcd'], max_workers=4,
max_pending=8, ordered=False) as results:
for length in results:
print(length)
thread_map() returns a list; its input must terminate and the list grows with
all results. Always use thread_iter() in a with block, especially when
stopping early. Its result iterator is single-pass and intended for one consumer.
The caller owns input iterable resources. No input is consumed until iteration
starts; only max_pending submitted but unconsumed results are retained,
including already-completed results. A smaller bound than the worker count is
allowed and can reduce utilization.
ordered=True preserves input order and can wait behind a slow early item.
ordered=False yields ready results first, with no ordering guarantee between
simultaneous completions. The default worker count is
min(32, (os.process_cpu_count() or 1) + 4); the default pending bound is twice
that count. Worker/pending values must be positive integers, excluding bool.
Input and worker exceptions propagate unchanged. Failure, caller exceptions,
or early context exit cancel queued calls and join running workers. Running
threads cannot be forcibly interrupted: each I/O operation needs its own
timeout. Never make worker functions wait on dependent work in an exhausted
pool. Use threads for overlapping I/O; CPU-bound Python parsing has no promised
speedup. There are no implicit retries, rate limits, process pools or asyncio
adapters. concurrent.futures is unavailable on WASI.
Naming conventions
from builtouts.conventions import words, snake_case, kebab_case, camel_case, pascal_case
assert words('HTTPServer version2ID') == ('http', 'server', 'version2', 'id')
assert snake_case('HTTPServer') == 'http_server'
assert kebab_case('a name') == 'a-name'
assert camel_case('HTTP_server') == 'httpServer'
assert pascal_case('HTTP_server') == 'HttpServer'
Words are lowercase and NFC-normalized. Punctuation/whitespace separates words;
acronyms split before a following title word; digit runs remain with adjacent
text. Unicode letters, numbers and attached combining marks are retained;
uncased scripts remain intact. Empty/separator-only input returns () from
words() and '' from converters. Casing may expand characters.
These conventions do not validate identifiers, reserve keywords, transliterate,
ensure uniqueness or make filenames/URLs safe. Different spellings can collide.
Development and release
uv sync --locked
uv run --frozen python -W error::ResourceWarning -m unittest discover -s tests -v
uv run --frozen python scripts/check_distribution.py --output-dir dist
CI checks Python 3.14 on Linux, macOS and Windows, then builds and verifies exact
wheel/sdist contents, source bytes, metadata, typing/license, RECORD hashes and
source rebuild parity. Both distributions are independently installed and
tested outside the checkout with offline examples. Build tooling is declared
in pyproject.toml; installed builtouts requires no third-party packages.
Rebuilds require identical package, license and METADATA bytes and compatibility
headers; only the build-tool Generator header may vary, with both RECORDs
independently verified.
The implementation plan describes future HTTP, serialization/mutation, extraction models, additional executors and other modules. GitHub issues and the Project hold the queue.
release.yaml uses PyPI Trusted Publishing through environment
pypi; only an authorized v0.1.0 tag on main can publish. Before tagging,
verify final-main CI and the release checklist in the implementation plan.
Published versions and release tags must never be replaced. See
CHANGELOG.md. MIT licensed.
Metadata
Release files for builtouts 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| builtouts-0.1.0.tar.gz | 32.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| builtouts-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 50.3 kB
Release files / builtouts-0.1.0.tar.gz
| Download URL | builtouts-0.1.0.tar.gz |
|---|---|
| Size | 32.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c7ab67ba88a736b937dfb70046176883435eb7bb0b9bdb6975071e3d8a56b1af
|
|
BLAKE2b-256 checksum How to use checksums |
00cf6137c6011b1fa2c214dc2c09e4de71e14fc9e752dba4cc5d6c15b8d5ac80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logRelease files / builtouts-0.1.0-py3-none-any.whl
| Download URL | builtouts-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d06a9d263c5fa6eedc7d7e0e723cebf4c1a3477d569d9d7985e5daf99345b29
|
|
BLAKE2b-256 checksum How to use checksums |
44facb1c78639e506a22adbe91599cee590fcf28ba049732a26bf2378b3762d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logSecurity for builtouts 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for builtouts-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.