Skip to main content

Flexible taxonomy management for generic items — categories, tags, and multi-parent hierarchies with pluggable storage.

Project description

taxomesh

Flexible taxonomy management for generic items — categories, tags, and multi-parent hierarchies with pluggable storage.

CI PyPI version Python versions License: MIT Status: Alpha


What is taxomesh?

taxomesh is a Python library for organizing arbitrary items into flexible taxonomies. An "item" is any entity identified by a UUID, integer, or string — a product, a document, a user, a media file — anything. taxomesh doesn't care what your items are; it just manages how they are categorized and tagged.

Key concepts

Concept Description
Item A generic reference (UUID / int / str) to any external entity
Category A named node in a taxonomy graph
Tag A free-form label attached to an item
Multi-parent hierarchy A category can belong to multiple parent categories simultaneously
Sort index A category's position within each parent is independent — "Tango" can be rank 1 under "Argentina" and rank 5 under "World Music Genres"
Repository A pluggable backend that stores all of the above

Multi-parent categories with per-parent sort index

Unlike traditional single-parent trees, taxomesh models categories as a directed acyclic graph (DAG). The relationship between a category and each of its parents carries an independent sort_index, stored in a dedicated junction record:

(category_id, parent_category_id, sort_index)

This lets the same category appear at different positions depending on which parent context is being browsed. Cyclic dependencies are detected and rejected at write time.


Features

  • Generic item references (UUID, int, or str)
  • Categories organized as a DAG (directed acyclic graph)
  • Per-parent sort index for categories
  • Cycle detection in category hierarchies
  • Free-form tags on items with idempotent assign/remove
  • Pluggable repository interface (TaxomeshRepositoryBase) — no inheritance required
  • Built-in JSON repository backend with atomic writes
  • Typed exception hierarchy for precise error handling
  • YAML file backend (planned)
  • SQLite3 backend (planned)
  • Query/search capabilities (planned)

Installation

pip install taxomesh

Requires Python 3.11 or later. No extra dependencies needed for the default JSON backend.


Quick start

Default storage (JSON file in current directory)

from taxomesh import TaxomeshService

service = TaxomeshService()  # persists to taxomesh.json

Custom storage path

from pathlib import Path
from taxomesh import TaxomeshService
from taxomesh.adapters.repositories.json_repository import JsonRepository

service = TaxomeshService(repository=JsonRepository(Path("/data/my_taxonomy.json")))

Managing categories

from taxomesh import TaxomeshService, TaxomeshCategoryNotFoundError

service = TaxomeshService()

# Create
music = service.create_category(name="Music")
jazz  = service.create_category(name="Jazz", description="Improvisational genre.")
print(music.category_id)   # UUID assigned by the library

# Retrieve
same = service.get_category(music.category_id)
assert same.name == "Music"

# List
all_cats = service.list_categories()

# Delete
service.delete_category(jazz.category_id)

# Missing entity raises a typed error — never returns None
try:
    service.get_category(jazz.category_id)
except TaxomeshCategoryNotFoundError as e:
    print(e)

Category parent relationships (DAG)

animals  = service.create_category(name="Animals")
mammals  = service.create_category(name="Mammals")
dogs     = service.create_category(name="Dogs")

service.add_category_parent(mammals.category_id, animals.category_id)
service.add_category_parent(dogs.category_id,    mammals.category_id)

# Cycle detection — raises TaxomeshCyclicDependencyError
from taxomesh import TaxomeshCyclicDependencyError
try:
    service.add_category_parent(animals.category_id, dogs.category_id)
except TaxomeshCyclicDependencyError:
    print("cycle rejected")

Managing items

from uuid import uuid4
from taxomesh import TaxomeshService

service = TaxomeshService()

# External ID can be UUID, int, or string slug
song    = service.create_item(external_id=42)
article = service.create_item(external_id="how-to-brew-coffee")
product = service.create_item(external_id=uuid4())

print(song.item_id)      # library-assigned internal UUID
print(song.external_id)  # 42

# Retrieve by internal UUID
same = service.get_item(song.item_id)
all_items = service.list_items()
service.delete_item(song.item_id)

Managing tags

from taxomesh import TaxomeshService

service = TaxomeshService()

live_tag = service.create_tag(name="live")
song     = service.create_item(external_id=99)

# Assign — idempotent, safe to call multiple times
service.assign_tag(tag_id=live_tag.tag_id, item_id=song.item_id)
service.assign_tag(tag_id=live_tag.tag_id, item_id=song.item_id)  # no-op

# Remove — no-op if association already absent
service.remove_tag(tag_id=live_tag.tag_id, item_id=song.item_id)

Persistence across restarts

from pathlib import Path
from taxomesh import TaxomeshService
from taxomesh.adapters.repositories.json_repository import JsonRepository

DB = Path("my_taxonomy.json")

# First run — write data
s1 = TaxomeshService(repository=JsonRepository(DB))
cat = s1.create_category(name="Electronic")

# Later run — data survives process restart
s2 = TaxomeshService(repository=JsonRepository(DB))
same = s2.get_category(cat.category_id)
assert same.name == "Electronic"

Architecture overview

┌─────────────────────────────────────────────────────┐
│  Public import surface  (taxomesh)                  │
│  TaxomeshService · exception classes                │
└───────────────────┬─────────────────────────────────┘
                    │ delegates all I/O
┌───────────────────▼─────────────────────────────────┐
│  Ports  (taxomesh.ports.repository)                 │
│  TaxomeshRepositoryBase  ← typing.Protocol          │
└───────────────────┬─────────────────────────────────┘
                    │ satisfied by
┌───────────────────▼─────────────────────────────────┐
│  Adapters  (taxomesh.adapters.repositories)         │
│  JsonRepository  (default, atomic writes)           │
│  … future: YamlRepository, SqliteRepository …      │
└─────────────────────────────────────────────────────┘

TaxomeshService is the sole public entry point. It holds no storage logic and delegates every read and write to the repository backend. Any object that structurally satisfies TaxomeshRepositoryBase can be used — no inheritance required.

Repository interface

TaxomeshRepositoryBase is a typing.Protocol with 15 methods:

Group Methods
Category CRUD save_category, get_category, list_categories, delete_category
Item CRUD save_item, get_item, list_items, delete_item
Tag CRUD save_tag, get_tag, list_tags
Tag ↔ Item association assign_tag, remove_tag
Category parent links save_category_parent_link, list_category_parent_links

Import path for advanced use (e.g., type annotations on a custom backend):

from taxomesh.ports.repository import TaxomeshRepositoryBase

Plugging in a custom backend

No inheritance from TaxomeshRepositoryBase is required. Implement all 15 methods and pass the instance at construction time:

from taxomesh import TaxomeshService

service = TaxomeshService(repository=MyCustomBackend())

Domain models

taxomesh defines its domain model classes in taxomesh/domain/models.py:

Class Description
Item A generic reference to any external entity, identified by an auto-generated UUID (item_id) and a user-supplied external_id (UUID, str, or int)
Category A named node in the taxonomy DAG, with an optional description and metadata
Tag A short free-form label (max 25 chars) that can be attached to items
CategoryParentLink Junction record linking a category to one of its parent categories, with an independent sort_index
ItemParentLink Junction record placing an item under a category, with a sort index
ItemTagLink Junction record associating a tag with an item

All models are pydantic.BaseModel subclasses with populate_by_name=True and validate_assignment=True. Every direct str field carries an explicit max_length constraint.


Error handling

All errors raised by taxomesh inherit from TaxomeshError:

TaxomeshError                          ← catch-all for any taxomesh error
├── TaxomeshNotFoundError              ← any entity not found
│   ├── TaxomeshCategoryNotFoundError
│   ├── TaxomeshItemNotFoundError
│   └── TaxomeshTagNotFoundError
├── TaxomeshValidationError            ← domain constraint violation
│   └── TaxomeshCyclicDependencyError  ← DAG cycle in add_category_parent
└── TaxomeshRepositoryError            ← storage I/O / parse failure

All names are importable from the top-level taxomesh package:

from taxomesh import (
    TaxomeshService,
    TaxomeshError,
    TaxomeshNotFoundError,
    TaxomeshCategoryNotFoundError,
    TaxomeshItemNotFoundError,
    TaxomeshTagNotFoundError,
    TaxomeshValidationError,
    TaxomeshCyclicDependencyError,
    TaxomeshRepositoryError,
)

The service never returns None for a missing entity. Every not-found condition raises a specific, catchable typed error.


Roadmap

  • v0.1 — Core models, service facade, TaxomeshRepositoryBase, JSON backend, DAG cycle detection (in progress)
  • v0.2 — YAML and SQLite3 backends, bulk operations, filtering and querying
  • v0.3 — Async repository interface, additional backends (PostgreSQL, MongoDB)
  • v1.0 — Stable API, full test coverage, documentation site

Spec-driven development

This project is built using spec-driven development. Every feature begins as a written specification before any code is touched. See specs/ for published specifications.


Contributing

Contributions are welcome. Please open an issue to discuss any change before submitting a pull request. This project follows a spec-first workflow — implementation PRs without a corresponding spec will not be merged.


License

MIT — see LICENSE.

Project details


Download files

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

Source Distribution

taxomesh-0.1.0a2.tar.gz (161.7 kB view details)

Uploaded Source

Built Distribution

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

taxomesh-0.1.0a2-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file taxomesh-0.1.0a2.tar.gz.

File metadata

  • Download URL: taxomesh-0.1.0a2.tar.gz
  • Upload date:
  • Size: 161.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","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 taxomesh-0.1.0a2.tar.gz
Algorithm Hash digest
SHA256 49c9e3c8f8a6739f62c8abe4f15a93338dc4cc4888100fa43436e162b9c08535
MD5 8f15642aa6076a41422800429c1f5522
BLAKE2b-256 ad84b423fa0e77e3a651d749ce8ffe54e6f26fed8d89937c8baa4883f348ad84

See more details on using hashes here.

File details

Details for the file taxomesh-0.1.0a2-py3-none-any.whl.

File metadata

  • Download URL: taxomesh-0.1.0a2-py3-none-any.whl
  • Upload date:
  • Size: 16.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","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 taxomesh-0.1.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 77b31971b9835a2ef293f9cbcc70b9d9e46ee6e33f17c871d7dfb5804218b9f4
MD5 68acdb522594388cfb7d83dc76b17448
BLAKE2b-256 e01e850588c816313bef27931845fd229e45878c2ae4592e2f64474dfc1976f1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page