Skip to main content

stapel-categories

CI coverage pypi downloads python license llms.txt

Category tree with typed features: a hierarchical category tree (django-treenode) and a parallel feature tree whose typed config is validated by stapel-attributes, an ordered category<->feature M2M, feature inheritance, and a feature-editor lifecycle (keep/add/edit/inherit/remove/create/replace) with optimistic-concurrency apply over a category subtree. Each node also declares how its CHILDREN are presented (children_as: tiles for real subcategories, chips for a partition of one attribute template), authored or derived by the derive_children_as command, and the whole visible tree is readable nested in one cached call (GET /categories/api/v1/tree/?depth=N).

Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.

Install

pip install stapel-categories

At a glance

Fact Value
Version 0.19.0
Python >=3.11 (3.11, 3.12, 3.13, 3.14)
HTTP operations 34
Config axes 1
Usage surface 22
Extension points 4
Error codes 64
Fleet dependencies stapel-attributes · stapel-core

Documentation

OpenAPI · capabilities.json · llms.txt (for agents)

What this is

A hierarchical category tree (django-treenode) and a parallel feature tree whose typed config is validated by stapel-attributes. Categories own the tree structure, feature inheritance, the ordered category↔feature M2M, and the feature-editor lifecycle; the attribute engine (types, config/DTO/DAO validation, polymorphic serializers, admin widgets) lives in stapel-attributes and is imported, never re-implemented.

Quick start

INSTALLED_APPS = [
    # ...
    "treenode",            # django-treenode (tree-cache signals)
    "stapel_categories",
]

# urls.py — this module's own urls.py bakes in only `v1/`; the host
# contributes `api/`, giving the canonical `/categories/api/v1/...` prefix
# (exactly what stapel-example-monolith already does for this module).
path("categories/api/", include("stapel_categories.urls"))

stapel-attributes is an imported library (no app to install); its config editor ships static assets, so run collectstatic if you use the admin.

Presenting the tree

Every node says how its children should be drawn, as children_as on every public read:

value meaning
tiles the children are real subcategories — a tile grid of destinations
chips the children partition ONE attribute template (new/used, buy/sell/rent, boys/girls) — a chip row over the parent's own feed
null the node has no children

The stored column takes a third value, auto, which a reader never sees: auto means "nobody has decided", and it is resolved server-side. Two columns hold the two answers — children_as is the authored intent, children_as_derived is the derivation's cache — so a re-run can improve its own output without ever overwriting an operator's.

django-admin derive_children_as              # report only
django-admin derive_children_as --apply      # write the derived column

The command prints one line per parent with the decision, the signal that carried it (schema, vocabulary, empty-schema, structure) and the Jaccard overlap of the children's own feature keys, so a wrong call can be pinned by hand — set children_as to tiles or chips in the admin and no future run touches it.

The whole visible tree comes back nested in one cached call:

GET /categories/api/v1/tree/?depth=3     # 1..4, default 3

Active nodes, ordered by tn_priority descending at every level, carrying id, slug, name, path (the /-joined id path a search query takes), catalog_icon, children_as and children. One query whatever the depth.

Settings

All configuration lives in the STAPEL_CATEGORIES namespace (dict setting, flat setting, or env var — resolved lazily):

Key Default Meaning
CAROUSEL_CACHE_TIMEOUT 300 Seconds the carousel response is cached.
FEATURE_DISPLAY_CACHE_TIMEOUT 60 Seconds an admin feature display label is memoized.
DISPLAY_TRANSLATOR stapel_categories.translation.identity_translator Dotted path (key)->str for rendering translation keys (default: identity).

comm surface

Kind Name Contract
Function categories.features {"category_id": int} -> {"category_id", "revision", "features":[{id,slug,name,mandatory,config}]} — resolved schema (own + inherited), cacheable by revision
Function categories.path {"category_ids": [int, ...]} -> {"<id>": ["<root_id>", ..., "<id>"]} — root->leaf ancestry, one query for the batch; segments are ids, an unknown id is absent
Action (emit) category.changed {"category_id": int, "revision": int} on any category/feature mutation — for downstream cache invalidation

categories.features lets stapel-listings validate attribute values against a category's schema without importing this module. categories.path is the provider stapel-search declares by canonical name for category rollup — without it a search index degrades to a single path segment and a filter on a parent category finds none of its descendants.

Contract

docs/{schema,flows,errors}.json are emitted from a single-module {categories + core} Django instance mounted at the canonical /categories/api/v1 prefix (make contract / make contract-check; see _codegen.py) — the same mechanism stapel-search, stapel-chat and stapel-forms already use. docs/flows.json is []: no flow is declared via @flow yet, same state as every other contract-complete module today. docs/capabilities.json stays hand-authored (see the Makefile comment); only its surface section is derived.

The ten feature-value shapes (FeatureConfig, FeatureDto) are a proper discriminated oneOf keyed by type, contributed by stapel-attributes and now used consistently everywhere a config or a values-DTO crosses the wire — Feature.config, FeatureBulk.config, the convert-type request body and validate-dto's features field all resolve through it (previously the last three fell back to an untyped JSONField/DictField).

Delta note — one pair of fields stays untyped, and it isn't this module's to fix. FeatureValidationResult.id / .ref_value render as free-form in the schema. Both are defined by stapel-attributes (FeatureValidationResult dataclass + its serializers.JSONField projection in results.py), not by this module: id is Optional[Union[int, str]] and ref_value is Optional[Union[str, int, float, list]] — plain scalar unions with no type discriminator to key a oneOf on, unlike the ten-way config/DTO shapes. Typing them is upstream's serializer to extend, not a gap stapel-categories introduced or can close by itself.

Extension points

See MODULE.md — the agent-facing map of every fork-free seam (settings, serializer seams, comm surface, feature-editor actions, admin-UI pointer to stapel-attributes).

Development

pip install -e . && pip install pytest pytest-django ruff
./setup-hooks.sh
pytest tests/

License

MIT — see LICENSE.


This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.

Download files

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

Source Distribution

stapel_categories-0.19.0.tar.gz (228.9 kB view details)

Uploaded Source

Built Distribution

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

stapel_categories-0.19.0-py3-none-any.whl (160.2 kB view details)

Uploaded Python 3

File details

Details for the file stapel_categories-0.19.0.tar.gz.

File metadata

  • Download URL: stapel_categories-0.19.0.tar.gz
  • Upload date:
  • Size: 228.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stapel_categories-0.19.0.tar.gz
Algorithm Hash digest
SHA256 d4eab4375e2286884f3716b28328a35fff2ffc8d0d9b04d358108d6f78899930
MD5 9d6ea5688f3f1c9b9d8895c57cca3567
BLAKE2b-256 0d28315b44c49bec9f42a0b4e9af0efae192d4d168aae94733c06e28628d941c

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_categories-0.19.0.tar.gz:

Publisher: publish.yml on usestapel/stapel-categories

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stapel_categories-0.19.0-py3-none-any.whl.

File metadata

File hashes

Hashes for stapel_categories-0.19.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7aa5fdf297b93e7fbfd329028130e14e3afb218b2ea51116b4c923257be8e93f
MD5 7a4a1bfaa62d494c5a60d1246f2431ba
BLAKE2b-256 fdf82c8d4cbe19d968072f9a0d2393d945cb415778071542963409ff04f5b266

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_categories-0.19.0-py3-none-any.whl:

Publisher: publish.yml on usestapel/stapel-categories

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.20.5

2 files

0.20.4

2 files

0.20.3

2 files

0.20.2

2 files

0.20.1

2 files

0.20.0

2 files

0.19.1

2 files

This release

0.19.0 This release

2 files

0.18.0

2 files

0.17.0

2 files

0.16.1

2 files

0.16.0

2 files

0.15.2

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 files

0.12.2

2 files

0.12.1

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.1

2 files

0.9.0

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.0

2 files

0.7.0

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.6

2 files

0.5.5

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

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