Skip to main content

Jazzy Fish - Sufficiently-large, unique, human-friendly identifiers

Build Status PyPI version Python Versions License

⚠️ Warning: This library is in development and not yet ready for production. Its API is not yet stable and might (and probably will) still change. Follow repository issues for more information.

Jazzy Fish is a library that helps you generate a sufficient number of identifiers, with a human-friendly kick.

This is not a new idea and similar implementation can be found in various places (i.e., GitHub new repository name suggestions).

Jazzy Fish is able to map a large integer solution space to unique keyphrases that can be used as human-friendly identifiers. Each keyphrase also has a fixed-length (character-based) abbreviated form that can be used as a short-form identifier.

The implementation roughly works as follows:

  • configure a Generator (details below)
  • call generator.next_id(), which returns a unique, ever-increasing integer value
  • call Encoder.encode(id), which returns a keyphrase
  • decode a Keyphrase into its integer from by calling Encoder.decode(keyphrase)

Terminology

  • word part: parts of speech used in the English language that can be used to form sentences/phrases: adverbs, verbs, adjectives, and nouns
  • dictionary: a list of English words categorized by word part, which is used to generate wordlists
  • wordlist: a list of word parts that can be combined to generate a large number of key phrases
  • keyphrase: a uniquely ordered sequence of words, each of a certain word part, which can be mapped to a unique integer identifier (e.g., niftier engine)
  • (keyphrase) abbreviations: a fixed-length (ASCII character) representation of the keyphrase, which can be used as a non-numerical identifier (e.g., nif-eng)
  • identifier: a numerical (integer) value that can be used to represent unique entities (e.g., 12040320103821)

Quickstart

The project is published to https://pypi.org/project/jazzy-fish/. Install it via:

pip install jazzy-fish

# or alternatively, directly from git
pip install "git+https://github.com/MihaiBojin/jazzy-fish@main#subdirectory=python"

The implementation roughly works as follows:

  • configure a Generator (details below)
  • call generator.next_id(), which returns a unique, ever-increasing integer value
  • call Encoder.encode(id), which returns a keyphrase
  • decode a Keyphrase into its integer from by calling Encoder.decode(keyphrase)

Configuring a Generator

Integer IDs are constructed by combining 3 parts:

  • a timestamp: can be relative to the UNIX epoch, or a custom epoch - to maximize the possible solution size; the timestamp can be chosen between seconds and milliseconds, in increments of 1/10ms (1s, 1/10s, 1/100s, 1ms)
  • a machine id: since it may be necessary to run multiple generators (i.e., in distributed systems), the solution domain can be partitioned by multiple 'machines'
  • a sequence id: representing a number of identifiers that can be generated, all things being equal (e.g., same time, same machine)

Thus, the algorithm is configurable enough to split a solution domain (e.g., N potential word combinations, where N is a large integer) into smaller partitions, that can be reasoned about in terms of: For how many years can IDs/word sequences be generated before the implementation needs to be changed?

The idea behind this implementation is also inspired from Bitcoin's Improvement Proposal 39.

Note: The BIP39 implementation uses a single word list to convert 12 or 24 unique words out of a total of 2048 words into a very large integer that can be used to derive secret keys.

Jazzy Fish different from BIP39 in that it uses multiple word lists (specifically, adverbs, verbs, adjectives, and nouns) to generate word sequences that are similar to natural (English) language, with the assumption that sequences such as yellow cat, hectic fish, dreadful elephant (while somewhat nonsensical) are easy to memorize by humans used to combining word parts. So, the aim of this library is to choose sufficiently-large word lists that can generate sufficiently-large unique word sequences, for a reasonable duration (i.e., several years or more).

Another relevant detail of this algorithm, is its ability to map chosen word sequences to smaller prefixes that can be used to form constant-length identifiers. While each sequence maps to an integer, remembering integers is hard for most humans. Thus, based on this implementation's assumption that humans can remember structured sentences, it selects the input wordlists in such a way that, for a given and pre-configured prefix length, there exists a single word that corresponds to that prefix.

For example, given a prefix length of size=1, yellow cat can be encoded to yc and then decoded back to the same two words. In this example, yellow is an adjective, and cat is a noun. There do not exist any other adjectives that start with y, nor nouns that start with c in our input word lists.

The reference implementation of the algorithm comes with a default wordlist of prefix 3, containing adverbs, verbs, adjectives, and nouns.

It can map the following solution domains:

  • 2,178,560 unique combinations of adjective noun
  • 2,740,628,480 unique combinations of verb adjective noun
  • 1,205,876,531,200 unique combinations of adverb verb adjective noun

Two-word sequences may be impractical for sustained identifier generation, however, three word and four word sequences can sustain 87 and 38,238 years respectively at a rate of 1 identifier generated per second, using a single machine.

If the default wordlists are unsuitable, they can be changed. Consult the Generate wordlists section for details.

Contents

This directory contains the following resources:

  • src/jazzy_fish: Python code that generates unique identifiers and can encode/decode them to keyphrases; this is the main package a client implementation needs to generate jazzy-fish keyphrases.
  • src/jazzy_fish_tools: Utility that works with wordlists, cleaning up input words (removing invalid or inappropriate words) and generating various combinations that allow a user to create new input wordlists with different capabilities.

Jazzy Fish Tools

The library also provides tooling that can generate all combinations of wordlists, abbreviations of a given length, and character positions chosen for the abbreviation.

generate-wordlists $PATH_TO_REPO/dictionary/5

These can help users infer the best choice depending on their use-case.

You may replace the wordlist, with a directory of your choosing, as long as it contains the same file structure - four files named after the relevant four parts in the English language:

  • adverb.txt
  • adjective.txt
  • verb.txt
  • noun.txt

After running this command, you can examine all outputs in the out/processed directory and you can see a high-level comparison by running scripts/show-stats.bash.

Developers

Unless you are interested in contributing to this code (or are curious about this library's development processes), you can stop reading here.

Publishing

GitHub-based version publishing

Releasing is merging a version bump. Nothing is tagged or published by hand.

# In a release branch: set the new version and refresh the lock file
#   (uv.lock records the project version, so CI's `uv sync --locked` fails without this)
uv lock

Open a pull request with that change. Merging it runs cicd.yml, a single pipeline of three jobs:

  1. lint-test -- linters and tests across every supported Python version.
  2. tag -- reads version from pyproject.toml and tags the commit v<version>, unless that tag already exists. It needs lint-test, so nothing is tagged until the tests pass: a bad tag is far more awkward to retract than a failed build is to fix.
  3. publish -- builds the tag and uploads it to test.PyPI and PyPI.

Merges that do not change the version find their tag already present, and publish is skipped. Pull requests run lint-test only.

publish re-runs scripts/check-tag-version.bash before building and refuses to publish if the tag and pyproject.toml disagree.

The three steps live in one workflow deliberately. Splitting them meant a workflow pushing a tag and another watching for it, which does not work directly: GitHub suppresses events made with GITHUB_TOKEN, so a pushed tag raises no push event, and bridging that gap needs either a long-lived personal access token or a workflow_dispatch hop. Job dependencies express the same ordering with none of that.

Note the filename is load-bearing: PyPI and test.PyPI each register a trusted publisher against cicd.yml, and OIDC uploads stop working the moment it disagrees.

The workflow authenticates with Trusted Publishing, so it holds no API tokens. PyPI and test.PyPI each exchange the workflow's OIDC identity for a short-lived upload token, which requires id-token: write on the publishing job and a trusted publisher registered on both indexes, pointing at cicd.yml in this repository.

Manual

These steps can also be performed locally. Trusted Publishing only works from CI, so publishing by hand still needs tokens:

export TESTPYPI_PASSWORD=... # token for https://test.pypi.org/legacy/
export PYPI_PASSWORD=... # token for https://upload.pypi.org/legacy/

The Makefile passes these to uv publish as UV_PUBLISH_TOKEN; the test index is defined as testpypi under [[tool.uv.index]] in pyproject.toml.

Every target below publishes the version currently in pyproject.toml, so set it there first. Note that uploads are irreversible: a version number can never be reused, even after deleting a release.

First, publish to the test repo, then inspect and verify it:

make publish-test
make build-inspect        # lists the wheel and archive contents
make publish-test-verify  # installs from test.PyPI into a throwaway venv

If correct, distribute the wheel to the PyPI index and verify it:

make publish
make publish-verify

Unlike the tagged release, this path runs no linters or tests, and nothing checks the version against a tag. Prefer tagging; reach for this only when the workflow cannot run.

Generate wordlists

The jazzy_fish_tools package contains code that can process dictionaries and generate all combinations of wordlists, abbreviations of a given length, and character positions chosen for the abbreviation. These can help users infer the best choice depending on their use-case.

First, install CLI dependencies:

pip install jazzy-fish[cli]

Then, call generate-wordlists PATH_TO_DICTIONARY_DIR to generate all possible combinations. The resulting wordlists will be stored in out/processed.

If you want to generate wordlists using a dictionary of your choosing, use the clean-dictionary PATH_TO_DICTIONARY_DIR script to sanitize the inputs (in-place). Consult one of the included dictionaries (dictionary/) to determine the required file structure.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jazzy_fish-0.4.1.tar.gz (121.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jazzy_fish-0.4.1-py3-none-any.whl (126.6 kB view details)

Uploaded Python 3

File details

Details for the file jazzy_fish-0.4.1.tar.gz.

File metadata

  • Download URL: jazzy_fish-0.4.1.tar.gz
  • Upload date:
  • Size: 121.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for jazzy_fish-0.4.1.tar.gz
Algorithm Hash digest
SHA256 ace8e0ce732ef5aca19d4eefb719f600cb5d328cd88cc79debeb23736d8a4904
MD5 3bac450526f879a026ecfe6f532b2653
BLAKE2b-256 482754f880cc2d844c49fc4330d9d31145e1f51242c6c4d8da067dbb7ed7ff13

See more details on using hashes here.

File details

Details for the file jazzy_fish-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: jazzy_fish-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 126.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for jazzy_fish-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6ed2cea0be762e42329bdd960de8b34f592ff893afb2fbfa1aabf3cb2e447e71
MD5 2811a78576a7c4516a7c4cced0d6814a
BLAKE2b-256 28c350ffc342f4694a22571b552617e97d87fd198846b1abf1f371d54dd217c8

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 files

0.4.0

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.7

2 files

0.2.6

2 files

0.2.5

2 files

0.2.4

2 files

0.2.3

2 files

0.2.2

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.2

2 files

0.1.1

2 files

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