Skip to main content

spintax-core (Python)

PyPI CI license

A framework-agnostic Spintax engine for Python — parse, render, validate, extract, analyze, and neutralize spintax templates. MIT, zero runtime dependencies, Python 3.10+.

This is the third engine in the Spintax family, and an independent implementation — not a transcription of the others. It is held to the same behavior contract by a shared golden corpus of language-neutral fixtures, which already gates the TypeScript engine and the PHP one. Every one of them passes here, none skipped, none expected to fail.

Install

pip install spintax-core

Use

from spintax_core import render, validate, parse

render("{Hello|Hi} there!")                        # "Hello there!" or "Hi there!"
render("{Hello|Hi} there!", seed=42)               # same seed, same output, every time
render("Hi %name%!", context={"name": "Sam"})      # "Hi Sam!"

# Reuse a template: parse once, render many.
ast = parse('[<sep=", ">fast|cheap|good]')
[render(ast) for _ in range(3)]
# ['Cheap, fast, good', 'Good, cheap, fast', 'Cheap, good, fast']

# Check a template before you ship it.
[d.code for d in validate("{a|b")]                 # -> ['bracket.unclosed']

Rendering is lenient: malformed markup degrades rather than raising, so a template a non-programmer wrote cannot take a page down. Output is also tidied up by default — sentence capitalization, spacing around punctuation, URLs and abbreviations left intact — which is why […fast|cheap|good] above comes back capitalized. Pass post_process=False to turn that off and get the raw pick.

A variable written inside a construct is spliced into it as text before the construct is split, so a pipe-joined value is a list rather than one item:

render('[<sep=", ">%names%]', context={"names": "Ann|Bo|Cy"}, seed=1)  # 'Cy, Bo, Ann' — three elements
render("%names%", context={"names": "Ann|Bo|Cy"})                     # 'Ann|Bo|Cy' — no construct, no split

That is the behaviour of every engine in the family (it comes from the original, which substitutes variables before it reads a bracket). It also means neutralize() does not make a value atomic there — it shields brackets, braces, % and #, and deliberately not |. Strip the pipes yourself if a value must stay one option.

Syntax — enumerations {a|b}, permutations [<sep=", ">a|b], variables %name%, conditionals {?VAR?yes|no}, plural agreement {plural 3: one|few|many}, comments /# … #/, verbatim spans /! … !/ (emitted exactly as written — no spintax, no post-process inside), and the #set / #def / #include directives — is documented in full at spintax.net/docs.

  • Spec: docs/spec-python-port.md — the parity contract, the API surface, and the decisions behind the port (including the three regex dialects this engine sits between — PHP's with /u and without it, and JavaScript's — and how each divergence from Python's was measured and pinned).
  • Sibling engines: @spintax/core (TypeScript) · spintax/core (PHP) · spintax-win (Object Pascal) — all MIT, zero-dep, held to one shared corpus · plus Spintax for WordPress (PHP, GPL, the origin).
  • Tracking issue: investblog/spintax-js#43.

Why

The existing PyPI spintax package is GPLv3 and has not shipped since 2018. GPL blocks commercial adoption; this one is MIT and maintained.

License

MIT. The WordPress plugin remains GPL; MIT/Expat is GPL-compatible.


Part of the 301.st toolset. Product home: spintax.net.

Development

The test suite is the shared golden corpus — the same JSON fixtures the TypeScript and PHP engines are tested against, read from a checkout rather than vendored here. A copy would drift, and a drifting contract is not a contract.

git clone https://github.com/investblog/spintax-js ../spintax-js   # once
python -m venv .venv && .venv/bin/pip install -e . pytest
SPINTAX_FIXTURES=../spintax-js/packages/conformance/fixtures pytest

Without the fixtures the suite fails rather than passing an empty run — a green suite that tested nothing is the most expensive kind of green.

The engine is complete, so every corpus case is a real pass: every corpus fixture passes, 0 xfailed, 0 skipped, alongside the port's own local tests. The count is deliberately not written here — the corpus grows, and a number pinned in a file that ships to PyPI immutably goes stale quietly. A skip must never appear — it would mean a case is being neither asserted nor counted. During the build (P0–P3) xfailed was the milestone tracker, one number counting down what the corpus expected and the engine could not yet do; it reached zero when the renderer and analyze landed.

If you mutate a source file to check that a test catches it, delete __pycache__ first. Python invalidates bytecode on (mtime, size). A mutation that preserves both — swapping .*? for .+?, say — leaves the stale .pyc in place, so the next run still imports the mutated module after you have restored the file. That produced a failure pointing at correct code, and the obvious response to it would have been to break the code for real.

Metadata

Release files for spintax-core 0.8.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 spintax-core 0.8.0
File Size Uploaded Provenance Actions
spintax_core-0.8.0.tar.gz 195.2 kB Without provenance Details for spintax_core-0.8.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for spintax-core 0.8.0
File Interpreter ABI Platform Actions
spintax_core-0.8.0-py3-none-any.whl Python 3 none any Details for spintax_core-0.8.0-py3-none-any.whl

Total release size: 296.5 kB

Release files / spintax_core-0.8.0.tar.gz

Download URL spintax_core-0.8.0.tar.gz
Size 195.2 kB
Tags Source
SHA-256 checksum
How to use checksums
29e180d230d2c280c1a66c3c7263377d1c82aeeec451301bb591a0f9650ff3db
BLAKE2b-256 checksum
How to use checksums
9c639ea7451b81437d783916719002876f835b4d24c960668e64eec67b6fc1a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / spintax_core-0.8.0-py3-none-any.whl

Download URL spintax_core-0.8.0-py3-none-any.whl
Size 101.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2c9b8167ef5ff8a2acbb9e97bee07aaf969dee990570b50f951dfab783f5d544
BLAKE2b-256 checksum
How to use checksums
e2362b7b5259898898eb3c1c414159e74be257f09650470ec02be55b301e536e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Security for spintax-core 0.8.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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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