Metadata-Version: 2.4
Name: klarient-listmonk
Version: 0.1.0
Summary: A typed Python client for the Listmonk API built with Klarient.
Author-email: Ludvik Jerabek <83429267+ludvikjerabek@users.noreply.github.com>
License-Expression: MIT
Project-URL: Documentation, https://github.com/ludvikjerabek/klarient-listmonk#readme
Project-URL: Issues, https://github.com/ludvikjerabek/klarient-listmonk/issues
Project-URL: Repository, https://github.com/ludvikjerabek/klarient-listmonk
Keywords: api,klarient,listmonk,rest,typed
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: klarient[requests]>=0.2.0
Provides-Extra: httpx
Requires-Dist: klarient[httpx]>=0.2.0; extra == "httpx"
Provides-Extra: dev
Requires-Dist: klarient[dev]>=0.2.0; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"

# Listmonk Client

This package is a typed Python wrapper for the [Listmonk](https://listmonk.app/)
REST API built with Klarient.

The wrapper models Listmonk's API as a resource tree. The Python object you use
matches the URI shape you are calling, so it is easy to move between Listmonk's
API docs and client code.

```python
from listmonk import ListMonkClient

client = ListMonkClient(
    base_url="https://listmonk.example.com",
    username="api-user",
    access_token="api-token",
)

print(client.lists.path)              # /api/lists
print(client.lists[1].path)           # /api/lists/1
print(client.templates[1].preview.path)  # /api/templates/1/preview
```

## Installation

If this wrapper is published separately, install that package directly:

```bash
python3 -m pip install klarient-listmonk
```

If the final package name changes, install that package instead. For local
development from this folder, use an editable install:

```bash
python3 -m pip install -e .
```

The package would depend on Klarient and one supported sync HTTP backend. The
current wrapper is synchronous and defaults to the requests transport.

## Quick Start

```python
from listmonk import ListMonkClient
from listmonk.common import PerPage
from listmonk.lists.requests import ListQuery

client = ListMonkClient(
    base_url="https://listmonk.example.com",
    username="api-user",
    access_token="api-token",
)

lists = client.lists.retrieve(ListQuery().with_per_page(PerPage.ALL))

for item in lists.data.results:
    print(item.id, item.name)
```

## Using A Proxy

`ListMonkClient` defaults to Klarient's requests transport. Proxy settings can
be passed through `native_options`, which are forwarded to the underlying
requests call.

```python
from listmonk import ListMonkClient

client = ListMonkClient(
    base_url="https://listmonk.example.com",
    username="api-user",
    access_token="api-token",
    native_options={
        "proxies": {
            "http": "http://proxy.example.com:8080",
            "https": "http://proxy.example.com:8080",
        },
    },
)

lists = client.lists.retrieve()
```

If your environment already uses standard proxy variables, requests can also
pick them up automatically:

```bash
export HTTP_PROXY="http://proxy.example.com:8080"
export HTTPS_PROXY="http://proxy.example.com:8080"
```

Use `native_options` when you want the proxy configuration to live with the
client instead of the process environment.

## Resource Tree

The root client exposes the primary Listmonk API areas:

```python
client.subscribers
client.lists
client.imports
client.campaigns
client.media
client.templates
client.tx
client.transactional
client.bounces
client.public
```

Collection resources can expose item resources by indexing:

```python
subscriber = client.subscribers[123].retrieve()
template_html = client.templates[1].preview.retrieve()
```

Nested endpoints are modeled as nested resources:

```python
client.subscribers[123].bounces.retrieve()
client.campaigns.running.stats.retrieve(...)
client.campaigns.analytics["views"].retrieve(...)
```

## Request Models

Request options are typed objects. You can pass values directly to constructors
for quick use or use chainable builders when a request has several optional
fields.

```python
from listmonk.common import PerPage, SortOrder
from listmonk.subscribers.requests import SubscriberOrderBy, SubscriberQuery

query = (
    SubscriberQuery()
    .with_order_by(SubscriberOrderBy.NAME)
    .with_order(SortOrder.ASC)
    .with_per_page(PerPage.ALL)
)

response = client.subscribers.retrieve(query)
```

Repeated query parameters, JSON bodies, form bodies, and multipart bodies are
handled by the request model for each endpoint.

## Common Examples

Retrieve subscribers:

```python
from listmonk.common import PerPage
from listmonk.subscribers.requests import SubscriberQuery

subscribers = client.subscribers.retrieve(
    SubscriberQuery().with_per_page(PerPage.ALL)
)
```

Preview an existing template:

```python
preview = client.templates[1].preview.retrieve()
print(preview.data)
```

Render a draft template body:

```python
from listmonk.templates.requests import TemplatePreviewRender, TemplateType

html = """
<!doctype html>
<html>
  <body>
    {{ template "content" . }}
    {{ TrackView }}
  </body>
</html>
""".strip()

preview = client.templates.preview.render(
    TemplatePreviewRender(
        type=TemplateType.CAMPAIGN,
        body=html,
    )
)
```

Listmonk expects `POST /api/templates/preview` to be form encoded, even though
template create and update use JSON. The wrapper hides that detail behind
`TemplatePreviewRender`.

Run a subscriber SQL filter:

```python
from listmonk.common import PerPage
from listmonk.subscribers.requests import SubscriberSQLQuery

response = client.subscribers.sql_query.retrieve(
    SubscriberSQLQuery()
    .with_sql("subscribers.id > 0")
    .with_per_page(PerPage.ALL)
)
```

This calls `GET /api/subscribers` with Listmonk's SQL filter parameter. It
requires the `subscribers:sql_query` permission. Listmonk documents that
permission as powerful because it can bypass individual list and subscriber
permission boundaries, even though the query is read-only.

Upload media:

```python
from listmonk.media.requests import MediaUpload

uploaded = client.media.upload(MediaUpload.from_file("logo.png"))
```

Send a transactional message:

```python
from listmonk.transactional.requests import (
    TransactionalContentType,
    TransactionalMessage,
    TransactionalSubscriberMode,
)

message = (
    TransactionalMessage()
    .with_template(1)
    .with_subscriber_mode(TransactionalSubscriberMode.EXTERNAL)
    .add_subscriber_email("user@example.com")
    .with_content_type(TransactionalContentType.HTML)
    .with_data("name", "Jane")
)

client.tx.send(message)
```

## Endpoint Coverage

The wrapper models the documented Listmonk API areas:

- Bounces
- Campaigns
- Imports
- Lists
- Media
- Public list and subscription endpoints
- Subscribers
- Templates
- Transactional messages

It also exposes the subscriber SQL query capability as
`client.subscribers.sql_query`.

## Usage Examples

The `usage/` directory contains endpoint-focused scripts that can be used as a
practical tour of the wrapper.

Copy the example settings file:

```bash
cp usage/settings.example.json usage/settings.json
```

Fill in your Listmonk URL and API credentials:

```json
{
  "base_url": "https://listmonk.example.com",
  "username": "api-user",
  "access_token": "api-token"
}
```

Run one endpoint example at a time:

```bash
python3 usage/lists.py
python3 usage/subscribers.py
python3 usage/campaigns.py
python3 usage/templates.py
```

Most scripts are read-only by default. Actions that create, update, delete,
upload, blocklist, or send messages are guarded by settings flags such as:

```json
{
  "create_test_data": false,
  "delete_import": false,
  "delete_bounces": false,
  "delete_uploaded_media": false
}
```

Keep those disabled unless you are intentionally testing against disposable
data.

## Project Layout

```text
src/listmonk/
  bounces/
  campaigns/
  imports/
  lists/
  media/
  public/
  subscribers/
  templates/
  transactional/
  client.py
  common.py
  paging.py

usage/
  bounces.py
  campaigns.py
  imports.py
  lists.py
  media.py
  public.py
  subscribers.py
  templates.py
  transactional.py
```

Each API area groups its request models, response models, and resources together.
That keeps the wrapper close to the API documentation while still allowing
shared helpers for common types and pagination.
