Skip to main content

taxomesh

taxomesh gives a multi-parent category graph a Python stdlib-style interface: categories, items and tags take subscript, get, in, len and del as a dict does. The graph is stored in a YAML file, a JSON file, the Django ORM or a backend of your own.

CI PyPI version Python versions Typed License: MIT

What it is

taxomesh stores a taxonomy for records that your own system keeps, such as products, articles or tracks. It is not a database for the records: each record stays in your system, and an item or a category can stand for it through its external_id. taxomesh stores categories with any number of parents, items placed in any number of categories, an order under each parent, tags, and typed relations between items. It also searches items and categories. The taxomesh command, the Django admin with its graph view, and the HTTP handlers, which need no web framework, call TaxomeshService.

Use it when the taxonomy needs its own component, for example for navigation that is not a strict tree, one item in several places, an order that depends on the place, or one set of rules for scripts, admin tools and APIs. It is not needed for a flat list of choices, a tree in which each category has one parent, a small fixed part of one screen, or a taxonomy with no need for reusable validation, traversal or integrations.

Installation

Requires Python 3.13+.

pip install taxomesh             # the library
pip install "taxomesh[cli]"      # with the taxomesh command (typer, rich)
pip install "taxomesh[django]"   # with the Django ORM backend and admin

Quick start

from pathlib import Path

from taxomesh import TaxomeshService
from taxomesh.repositories import YamlRepository

Path("quickstart.yaml").unlink(missing_ok=True)  # each run starts with an empty store
svc = TaxomeshService(YamlRepository("quickstart.yaml"))

# Categories form a graph: Jazz has two parents, Music and Genres.
music = svc.categories.create(name="Music")
genres = svc.categories.create(name="Genres")
jazz = svc.categories.create(name="Jazz")
svc.categories.add_parent(jazz, music)
svc.categories.add_parent(jazz, genres)

# An item stands for a record of yours, and is found again by its external id.
album = svc.items.create(name="Kind of Blue", external_id="catalog:42")
svc.items.place_in(album, jazz)
featured = svc.tags.create(name="featured")
svc.items.tag(album, featured)

# Read it with the forms a dict has: a row, or its UUID, is the key.
assert svc.items.get_by_external_id("catalog:42").item_id == album.item_id
assert svc.items[album.item_id].name == "Kind of Blue"  # subscript raises when absent
assert svc.items.get_by_slug("no-such-slug") is None  # get* never raises
assert jazz in svc.categories and len(svc.categories) == 3
assert [c.name for c in svc.categories.list(item=album)] == ["Jazz"]
assert [i.name for i in svc.items.list(tag=featured)] == ["Kind of Blue"]
assert [c.name for c in svc.categories.roots()] == ["Music", "Genres"]  # in the order they were made

node = svc.graph()[jazz]  # a read-only snapshot of the whole taxonomy
assert sorted(parent.category.name for parent in node.parents) == ["Genres", "Music"]

With no argument, TaxomeshService() reads taxomesh.toml in the working directory, and without one keeps its store in data/taxomesh.yaml, between runs.

The forms

coll is svc.categories, svc.items or svc.tags, and a key is a row or its UUID. Tags have no slug, external id or search:

Form Returns On a miss
coll[key] the row raises Taxomesh<X>NotFoundError, a KeyError
coll.get(key, default=None) the row default
key in coll bool False
len(coll) int 0
del coll[key] None raises
coll.get_by_slug(s), coll.get_by_external_id(x) the row None
coll.get_many(keys) a new dict the key is left out
coll.list(...), coll.search(...) a tuple (); a filter naming an entity that is not stored raises

The collections do not iterate (why): coll.list(...) enumerates, and len() is the one form that reads every row.

What it does

  • Multi-parent categories: a category has any number of parents, and a cycle raises TaxomeshCyclicDependencyError.
  • An order under each parent: every parent link and placement carries its own sort_index, which reorder and move change.
  • External ids: str, int or UUID, stored as text and unique within its kind, and looked up one at a time or in bulk.
  • Tags and typed relations: free-form tags, and directed item-to-item relations (covers, version_of) read in either direction, for many items at once in two storage reads.
  • Fuzzy search: ranked, typo-tolerant and accent-insensitive, on names and slugs; an external id matches as a substring only. It runs in the process, with rapidfuzz, and needs no search server.
  • Graph snapshots: svc.graph() holds one node per category, however many paths reach it.
  • Frozen rows: update returns a new row, and expected_version makes it conditional.
  • A cache per service: reads are cached for cache_ttl seconds, 5 by default, and a write clears its own service's cache only.
  • Storage: YAML (the default), JSON or the Django ORM, chosen in code or in a taxomesh.toml, or your own backend implementing TaxomeshRepositoryBase. One contract suite holds every shipped backend to that port.
  • Errors: a member raises a TaxomeshError, a not-found one also a KeyError and a validation one also a ValueError, or a TypeError for an argument of the wrong type, except that the flags recursive, fuzzy and include_items are read as if flag:. Log records go to the taxomesh logger, which has only a NullHandler of its own.

Documentation

The whole Python API is on one page (llms.txt), so an AI coding agent can use taxomesh without reading its source.

Page Covers
Python API Every member, with runnable examples
Repositories The three backends, and writing your own
Configuration taxomesh.toml
CLI reference Every command and global option
Django integration The app, the admin, and bridging your models
HTTP API integration Request schemas, handlers, serializers and error mapping
Design The architecture and the decisions behind the surface, for contributors
Changelog What each release changed

Stability and versioning

taxomesh uses Semantic Versioning: what taxomesh, taxomesh.repositories, taxomesh.contrib.api and taxomesh.contrib.django export, and the repository port, change incompatibly only in a major release. The changelog names every breaking change.

taxomesh supports Python 3.13 and 3.14, and Django 6.x. CI checks every change to main with ruff, mypy --strict over the package and its tests, the Django integration included, and the full test suite with at least 80% line coverage, a measure that leaves out the Django integration, which its own tests exercise.

Used in production

taxomesh is used in production at LetrasTango to organise the categories and relations of its tango content.

Development

uv sync --all-extras
uv run ruff check .
uv run ruff format --check .
uv run mypy --strict .
uv run pytest

Contributions are welcome: see CONTRIBUTING.md for the setup and the quality gates. Licensed under MIT.

Metadata

Release files for taxomesh 1.0.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 taxomesh 1.0.0
File Size Uploaded
taxomesh-1.0.0.tar.gz 589.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for taxomesh 1.0.0
File Interpreter ABI Platform
taxomesh-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 779.9 kB

Release files / taxomesh-1.0.0.tar.gz

Download URL taxomesh-1.0.0.tar.gz
Size 589.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f5dd20cd4e821a1f6ca75238a86ead5fedfb4ca7b337c4feb30d58b919473d07
BLAKE2b-256 checksum
How to use checksums
fdbf4d08b89554e75d1d80082342098446a5d95a290a447bf2da45a1134b22ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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}

Release files / taxomesh-1.0.0-py3-none-any.whl

Download URL taxomesh-1.0.0-py3-none-any.whl
Size 190.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cda5423058ac5d072068be9c2d40e721bbced8208797e382afe6387ba9538557
BLAKE2b-256 checksum
How to use checksums
227714fff82577b0c3e8a2691f30653c38c853c924b863e3b9b46a8831c8f9b1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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}

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release 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