blobatar
Python port of blobatar — deterministic geometric avatars generated from any string.
Built for server-side rendering, where the avatar is produced once per seed and cached forever. No browser, no Node, no network call.
What parity means here
Two tiers, and the distinction is not pedantry.
Observable output is exact. Hex colours, SVG path strings and CSS custom property values must match the original byte for byte. This is what a user sees.
Intermediate floats are compared within 4 ULP, because the JavaScript
engines disagree with each other. V8 and JavaScriptCore return different doubles
from Math.hypot for inputs with heavy cancellation — so the upstream library
does not produce identical bits in Chrome and Safari either. Demanding exact
equality on those intermediates would be demanding an agreement that the
original does not have with itself.
Everything else — hashing, traits, geometry, expressions — is exact.
Parity is checked, not asserted
Every release is validated against reference vectors produced by running the original JavaScript implementation and comparing seed by seed, with exact equality and no tolerances.
$ for t in tests/test_*.py; do python "$t"; done
PARITY: 144 checks across 18 colours (observable exact, intermediates <=4 ULP)
PARITY EXACT: 210 checks across 14 expressions
PARITY EXACT: 136 checks across 17 seeds
PARITY EXACT: 102 checks across 17 seeds
PARITY EXACT: 432 checks across 12 seeds
Reference vectors are generated with bun, running the original TypeScript directly — never from this port's own output. A reference derived from the port proves only that the port equals itself.
The vector set deliberately covers the cases where a naive port silently diverges:
| Case | Why it breaks a naive port |
|---|---|
🎮🎲 |
JS .length counts UTF-16 units, Python len() counts code points |
alain |
JS trim() strips U+FEFF; Python .strip() does not |
José precomposed vs decomposed |
NFC must run before trimming |
İstanbul, ΣΟΦΟΣ |
Case folding differs between engines |
| 200-char seeds | 32-bit overflow behaviour in the mixing loop |
Regenerate the vectors with node scripts/gen_vectors.mjs.
The three portability traps
Documented in src/blobatar/hash.py, because they are not obvious:
Math.imulis a 32-bit signed multiply. Python integers are arbitrary precision, so every product must be masked and reinterpreted.String.prototype.lengthcounts UTF-16 code units. That length feeds the initial hash state, so any seed containing astral characters would otherwise produce a different avatar.trim()andstrip()do not remove the same set. JS strips U+FEFF and Python does not; Python strips U+0085 and JS does not.
Install
pip install blobatar
from blobatar import blobatar
svg = blobatar("alain") # -> '<svg xmlns="..." viewBox="0 0 100 100">...'
Requires Python 3.11 or newer (math.cbrt, needed for parity).
Using it from Django
No settings, no app registry, no database. It works from a view, a template tag, a management command or a background task:
from django.utils.safestring import mark_safe
from blobatar import blobatar
def lab_avatar(lab):
return mark_safe(blobatar(lab.name))
Output is deterministic per seed, so cache it once and serve it forever.
tests/test_django_integration.py verifies all of the above, including that
nothing leaks a SafeString that would bypass autoescaping.
What parity means here
Two tiers, and the distinction is not pedantry.
Observable output is exact. Hex colours, SVG path strings and CSS custom property values must match the original byte for byte. This is what a user sees.
Intermediate floats are compared within 4 ULP, because the JavaScript
engines disagree with each other. V8 and JavaScriptCore return different doubles
from Math.hypot for inputs with heavy cancellation — so the upstream library
does not produce identical bits in Chrome and Safari either. Demanding exact
equality on those intermediates would be demanding an agreement that the
original does not have with itself.
Everything else — hashing, traits, geometry, expressions — is exact.
Parity is checked, not asserted
Every release is validated against reference vectors produced by running the original JavaScript implementation and comparing seed by seed, with exact equality and no tolerances.
$ for t in tests/test_*.py; do python "$t"; done
PARITY: 144 checks across 18 colours (observable exact, intermediates <=4 ULP)
PARITY EXACT: 210 checks across 14 expressions
PARITY EXACT: 136 checks across 17 seeds
PARITY EXACT: 102 checks across 17 seeds
PARITY EXACT: 432 checks across 12 seeds
Reference vectors are generated with bun, running the original TypeScript directly — never from this port's own output. A reference derived from the port proves only that the port equals itself.
The vector set deliberately covers the cases where a naive port silently diverges:
| Case | Why it breaks a naive port |
|---|---|
🎮🎲 |
JS .length counts UTF-16 units, Python len() counts code points |
alain |
JS trim() strips U+FEFF; Python .strip() does not |
José precomposed vs decomposed |
NFC must run before trimming |
İstanbul, ΣΟΦΟΣ |
Case folding differs between engines |
| 200-char seeds | 32-bit overflow behaviour in the mixing loop |
Regenerate the vectors with node scripts/gen_vectors.mjs.
The three portability traps
Documented in src/blobatar/hash.py, because they are not obvious:
Math.imulis a 32-bit signed multiply. Python integers are arbitrary precision, so every product must be masked and reinterpreted.String.prototype.lengthcounts UTF-16 code units. That length feeds the initial hash state, so any seed containing astral characters would otherwise produce a different avatar.trim()andstrip()do not remove the same set. JS strips U+FEFF and Python does not; Python strips U+0085 and JS does not.
Install
pip install blobatar
Not published yet. See Status below.
Status
Complete. Every module is ported and at parity — 1,062 checks across six suites, plus an end-to-end integration test.
Verified independently: the SVG produced by this port is byte-identical to the original for the same seed, including astral characters and non-Latin scripts.
Animation (idle, morph, animate, ease) is deliberately out of scope:
it is CSS- and time-driven, and this port targets server-side static rendering.
posed() raises NotImplementedError if asked to animate.
Animation (idle, morph, animate, ease) is out of scope: it is
CSS- and time-driven, and this port targets server-side static rendering.
Credit
All design decisions, the generation algorithm and the visual language are Alain's work. This repository only carries them to another ecosystem.
Licensed MIT, preserving the original copyright notice.
Metadata
Release files for blobatar 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 | |
|---|---|---|---|
| blobatar-0.1.0.tar.gz | 54.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| blobatar-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.7 kB
Release files / blobatar-0.1.0.tar.gz
| Download URL | blobatar-0.1.0.tar.gz |
|---|---|
| Size | 54.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5380d9f9f3c8a9b33e8307ad1a78d5823c758539b5a87e5c8129ec73fbf3dd21
|
|
BLAKE2b-256 checksum How to use checksums |
2d70a9ef8319bfe6aaffeced2a3231b7dd2e80c8ff9f17cd3a59ccfa27449fb7
|
| 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 Aug 27, 2026.
Transparency logRelease files / blobatar-0.1.0-py3-none-any.whl
| Download URL | blobatar-0.1.0-py3-none-any.whl |
|---|---|
| Size | 30.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6d57b5e0c84115e126faaf462294f5e5d32fec517dad68bcd2fc9925cf113f36
|
|
BLAKE2b-256 checksum How to use checksums |
19e7745078295ca42bc8ee95922b261a2a73b88e46dfa897d601090b715de4ab
|
| 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 Aug 27, 2026.
Transparency log