Metadata-Version: 2.4
Name: stapel-listings
Version: 0.6.1
Summary: Listings and catalog for the Stapel framework
License: MIT
Project-URL: Homepage, https://github.com/usestapel/stapel-listings
Project-URL: Repository, https://github.com/usestapel/stapel-listings
Project-URL: Documentation, https://github.com/usestapel/stapel-listings#readme
Project-URL: Changelog, https://github.com/usestapel/stapel-listings/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/usestapel/stapel-listings/issues
Keywords: django,stapel,listings
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: stapel-core<1.0,>=0.26.0
Requires-Dist: stapel-attributes<0.5,>=0.4.7
Requires-Dist: djangorestframework>=3.14
Requires-Dist: djangorestframework-dataclasses>=1.2
Requires-Dist: drf-spectacular>=0.27
Provides-Extra: all
Dynamic: license-file

<!-- Generated by stapel-readme from docs/readme.md + docs/*.json. Do not edit this file; edit docs/readme.md and re-run `make readme`. -->

# stapel-listings

[![CI](https://img.shields.io/github/actions/workflow/status/usestapel/stapel-listings/ci.yml?branch=main&logo=github&label=CI)](https://github.com/usestapel/stapel-listings/actions/workflows/ci.yml?query=branch%3Amain)
[![coverage](https://img.shields.io/codecov/c/github/usestapel/stapel-listings?branch=main&logo=codecov&label=coverage)](https://app.codecov.io/gh/usestapel/stapel-listings)
[![pypi](https://img.shields.io/pypi/v/stapel-listings?logo=pypi&logoColor=white&label=pypi)](https://pypi.org/project/stapel-listings/)
[![downloads](https://static.pepy.tech/badge/stapel-listings/month)](https://pepy.tech/project/stapel-listings)
[![python](https://img.shields.io/pypi/pyversions/stapel-listings?logo=python&logoColor=white)](https://pypi.org/project/stapel-listings/)
[![license](https://img.shields.io/github/license/usestapel/stapel-listings)](https://github.com/usestapel/stapel-listings/blob/main/LICENSE)
[![llms.txt](https://img.shields.io/badge/llms.txt-blue)](https://github.com/usestapel/stapel-listings/blob/main/docs/llms.txt)

> Listings and catalog vertical: a Listing core (owner, opaque category, typed attribute values, price + price_base, inventory) with a draft/publish lifecycle including the moderation takedown state 'blocked', an independent moderation status, a value-validation pipeline delegated to stapel-attributes against a category schema fetched over comm, a publish service, first-class favorites, and the two pull seams its consumers read it through — listings.search_documents / listings.search_export for an indexer and listings.moderation_content for a moderation queue.

Part of the [Stapel framework](https://github.com/usestapel) — composable Django apps that deploy as a monolith or as microservices without changing module code.

## Install

```bash
pip install stapel-listings
```

## At a glance

| Fact | Value |
|---|---|
| Version | `0.6.1` |
| Python | `>=3.11` (3.11, 3.12, 3.13, 3.14) |
| Django | `djangorestframework>=3.14` |
| HTTP operations | 16 |
| Config axes | 2 |
| Usage surface | 7 |
| Extension points | 6 |
| Error codes | 63 |
| Fleet dependencies | [`stapel-attributes`](https://github.com/usestapel/stapel-attributes) · [`stapel-categories`](https://github.com/usestapel/stapel-categories) (optional) · [`stapel-core`](https://github.com/usestapel/stapel-core) |

## Documentation

[OpenAPI](https://github.com/usestapel/stapel-listings/blob/main/docs/schema.json) · [capabilities.json](https://github.com/usestapel/stapel-listings/blob/main/docs/capabilities.json) · [llms.txt (for agents)](https://github.com/usestapel/stapel-listings/blob/main/docs/llms.txt)

## What this is

`Listing` is the marketplace core: an owner, an opaque category, typed
attribute values, a two-machine lifecycle + moderation status, a publish
pipeline, and first-class favorites. It **consumes** stapel-categories (feature
schema, over comm) and **stapel-attributes** (value validation), and stays
decoupled from search, moderation and currencies (see the boundaries below).

## Quick start

```python
INSTALLED_APPS = [
    # ...
    "stapel_listings",
]

# urls.py — this module's own urls.py bakes in only `v1/`; the host
# contributes `api/`, giving the canonical `/listings/api/v1/...` prefix.
path("listings/api/", include("stapel_listings.urls"))
```

Requires a `categories.features` comm Function provider (stapel-categories) for
value validation against a category's schema.

## Settings

All configuration lives in the `STAPEL_LISTINGS` namespace (dict setting, flat
setting, or env var — resolved lazily). Full table with seam semantics in
[MODULE.md](https://github.com/usestapel/stapel-listings/blob/main/MODULE.md).

| Key | Default | Meaning |
|---|---|---|
| `CATEGORY_FEATURES_FUNCTION` | `"categories.features"` | comm Function resolving a category's feature schema. |
| `PRICE_BASE_CONVERTER` | identity | Dotted-path `(amount, currency, base) -> Decimal`. |
| `AUTO_APPROVE_ON_PUBLISH` | `False` | Publish immediately when no moderation module is installed. |
| `REQUIRE_IMAGE_ON_PUBLISH` | `True` | Require ≥1 image to publish. |
| `MODERATION_TARGET_TYPE` | `"listing"` | `target_type` this module answers to in `moderation.completed`. |
| `LISTING_URL_TEMPLATE` | `""` | Public URL template (`{listing_id}`) for the moderator's card. |
| `DEFAULT_LISTING_TTL_DAYS` | `30` | Days until a published listing expires. |

## comm surface

Emits (Actions): `listing.submitted` (moderation boundary),
`listing.published` / `listing.updated` / `listing.removed` (search boundary).
Consumes: `category.changed`, `moderation.completed`, `user.deleted`.
Provides Functions: `listings.status`, `listings.search_documents`,
`listings.search_export`, `listings.moderation_content`.
Calls: `categories.features`.

**Boundaries:** search/filtering is a separate **stapel-search** module; this
module builds `features_search`, signals with the `listing.*` events and hands
over the document through `listings.search_documents` (keyed batch) and
`listings.search_export` (cursor snapshot), but exposes no search endpoints —
the events carry identity, so no listing content rides the durable bus and no
indexer reads this database. Moderation is a separate **stapel-moderation**
module: this module emits `listing.submitted`, serves the content over
`listings.moderation_content` and applies the target-generic
`moderation.completed` verdict (including the `published → blocked` takedown),
but runs no moderation pipeline. Re-moderating an edit of a **live** listing is
post-moderation: the lifecycle stays `published`, `moderation_status` goes to
`pending`, the edit is visible immediately, and a rejecting verdict removes it
through the takedown edge.

## Contract

`docs/{schema,flows,errors}.json` are emitted from a single-module
`{listings + core}` Django instance mounted at the canonical
`/listings/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.

**Delta note — one field stays untyped on purpose.** `features_search`
(`ListingDetailSerializer`) is a flattened per-category search index: one
dynamic key per feature slug, shaped by whatever category schema a given
listing happens to carry. There is no fixed property set to declare, so the
schema types it as a bare `object` rather than fake a closed shape that would
go stale the moment any category adds a feature. Every other field that used
to fall back to an untyped blob this way — `images` / `images_draft`, both
lists of opaque `<type>/<hash>` CDN refs (models.py "Opaque list of CDN image
references") — is now typed as `array[string]`, and the ten polymorphic
attribute-value shapes (`FeatureDto`/`FeatureDao`) are a proper
discriminated `oneOf` keyed by `type`, contributed by stapel-attributes.

## Extension points

See [MODULE.md](https://github.com/usestapel/stapel-listings/blob/main/MODULE.md) — the agent-facing map of every fork-free seam
(settings, serializer seams, comm surface, GDPR provider).

## Development

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

## License

MIT — see [LICENSE](https://github.com/usestapel/stapel-listings/blob/main/LICENSE).

---

<sub>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`.</sub>
