Skip to main content

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.
  • True means present; False means absent.
  • None matches 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 &amp; ')
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)

Source distribution for builtouts 0.1.0
File Size Uploaded Provenance
builtouts-0.1.0.tar.gz 32.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for builtouts 0.1.0
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page