stompman


Namestompman JSON
Version 3.8.0 PyPI version JSON
download
home_pageNone
SummaryPython STOMP client with pleasant API
upload_time2025-09-02 07:41:30
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseMIT
keywords activemq artemis jms messaging stomp
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # stompman

A Python client for STOMP asynchronous messaging protocol that is:

- asynchronous,
- not abandoned,
- has typed, modern, comprehensible API.

## How To Use

Before you start using stompman, make sure you have it installed:

```sh
uv add stompman
poetry add stompman
```

Initialize a client:

```python
async with stompman.Client(
    servers=[
        stompman.ConnectionParameters(host="171.0.0.1", port=61616, login="user1", passcode="passcode1"),
        stompman.ConnectionParameters(host="172.0.0.1", port=61616, login="user2", passcode="passcode2"),
    ],


    # SSL — can be either `None` (default), `True`, or `ssl.SSLContext'
    ssl=None,

    # Error frame handler:
    on_error_frame=lambda error_frame: print(error_frame.body),

    # Optional parameters with sensible defaults:
    heartbeat=stompman.Heartbeat(will_send_interval_ms=1000, want_to_receive_interval_ms=1000),
    connect_retry_attempts=3,
    connect_retry_interval=1,
    connect_timeout=2,
    connection_confirmation_timeout=2,
    disconnect_confirmation_timeout=2,
    write_retry_attempts=3,
    check_server_alive_interval_factor=3,
) as client:
    ...
```

### Sending Messages

To send a message, use the following code:

```python
await client.send(b"hi there!", destination="DLQ", headers={"persistent": "true"})
```

Or, to send messages in a transaction:

```python
async with client.begin() as transaction:
    for _ in range(10):
        await transaction.send(body=b"hi there!", destination="DLQ", headers={"persistent": "true"})
        await asyncio.sleep(0.1)
```

### Listening for Messages

Now, let's subscribe to a destination and listen for messages:

```python
async def handle_message_from_dlq(message_frame: stompman.MessageFrame) -> None:
    print(message_frame.body)


await client.subscribe("DLQ", handle_message_from_dlq, on_suppressed_exception=print)
```

Entered `stompman.Client` will block forever waiting for messages if there are any active subscriptions.

Sometimes it's useful to avoid that:

```python
dlq_subscription = await client.subscribe("DLQ", handle_message_from_dlq, on_suppressed_exception=print)
await dlq_subscription.unsubscribe()
```

By default, subscription have ACK mode "client-individual". If handler successfully processes the message, an `ACK` frame will be sent. If handler raises an exception, a `NACK` frame will be sent. You can catch (and log) exceptions using `on_suppressed_exception` parameter:

```python
await client.subscribe(
    "DLQ",
    handle_message_from_dlq,
    on_suppressed_exception=lambda exception, message_frame: print(exception, message_frame),
)
```

You can change the ack mode used by specifying the `ack` parameter:

```python
# Server will assume that all messages sent to the subscription before the ACK'ed message are received and processed:
await client.subscribe("DLQ", handle_message_from_dlq, ack="client", on_suppressed_exception=print)

# Server will assume that messages are received as soon as it send them to client:
await client.subscribe("DLQ", handle_message_from_dlq, ack="auto", on_suppressed_exception=print)
```

You can pass custom headers to `client.subscribe()`:

```python
await client.subscribe("DLQ", handle_message_from_dlq, ack="client", headers={"selector": "location = 'Europe'"}, on_suppressed_exception=print)
```

#### Handling ACK/NACKs yourself

If you want to send ACK and NACK frames yourself, you can use `client.subscribe_with_manual_ack()`:

```python
async def handle_message_from_dlq(message_frame: stompman.AckableMessageFrame) -> None:
    print(message_frame.body)
    await message_frame.ack()

await client.subscribe_with_manual_ack("DLQ", handle_message_from_dlq, ack="client")
```

Note that this way exceptions won't be suppressed automatically.

### Cleaning Up

stompman takes care of cleaning up resources automatically. When you leave the context of async context managers `stompman.Client()`, or `client.begin()`, the necessary frames will be sent to the server.

### Handling Connectivity Issues

- If multiple servers were provided, stompman will attempt to connect to each one simultaneously and will use the first that succeeds. If all servers fail to connect, an `stompman.FailedAllConnectAttemptsError` will be raised. In normal situation it doesn't need to be handled: tune retry and timeout parameters in `stompman.Client()` to your needs.

- When connection is lost, stompman will attempt to handle it automatically. `stompman.FailedAllConnectAttemptsError` will be raised if all connection attempts fail. `stompman.FailedAllWriteAttemptsError` will be raised if connection succeeds but sending a frame or heartbeat lead to losing connection.
- To implement health checks, use `stompman.Client.is_alive()` — it will return `True` if everything is OK and `False` if server is not responding.
- `stompman` will write log warnings when connection is lost, after successful reconnection or invalid state during ack/nack.

### ...and caveats

- stompman supports Python 3.11 and newer.
- It implements [STOMP 1.2](https://stomp.github.io/stomp-specification-1.2.html) — the latest version of the protocol.
- Heartbeats are required, and sent automatically in background (defaults to 1 second).

Also, I want to pointed out that:

- Protocol parsing is inspired by [aiostomp](https://github.com/pedrokiefer/aiostomp/blob/3449dcb53f43e5956ccc7662bb5b7d76bc6ef36b/aiostomp/protocol.py) (meaning: consumed by me and refactored from).
- stompman is tested and used with [ActiveMQ Artemis](https://activemq.apache.org/components/artemis/) and [ActiveMQ Classic](https://activemq.apache.org/components/classic/).
    - Caveat: a message sent by a Stomp client is converted into a JMS `TextMessage`/`BytesMessage` based on the `content-length` header (see the docs [here](https://activemq.apache.org/components/classic/documentation/stomp)). In order to send a `TextMessage`, `Client.send` needs to be invoked with `add_content_length` header set to `False`
- Specification says that headers in CONNECT and CONNECTED frames shouldn't be escaped for backwards compatibility. stompman escapes headers in CONNECT frame (outcoming), but does not unescape headers in CONNECTED (outcoming).

### FastStream STOMP broker

[An implementation of STOMP broker for FastStream.](packages/faststream-stomp/README.md)

### Examples

See examples in [examples/](examples).

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "stompman",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": null,
    "keywords": "activemq, artemis, jms, messaging, stomp",
    "author": null,
    "author_email": "Lev Vereshchagin <mail@vrslev.com>",
    "download_url": "https://files.pythonhosted.org/packages/53/b5/1d1f9eb83901b3e6db511261a5b67718bc7d9d198a12d260750b5bdf57b6/stompman-3.8.0.tar.gz",
    "platform": null,
    "description": "# stompman\n\nA Python client for STOMP asynchronous messaging protocol that is:\n\n- asynchronous,\n- not abandoned,\n- has typed, modern, comprehensible API.\n\n## How To Use\n\nBefore you start using stompman, make sure you have it installed:\n\n```sh\nuv add stompman\npoetry add stompman\n```\n\nInitialize a client:\n\n```python\nasync with stompman.Client(\n    servers=[\n        stompman.ConnectionParameters(host=\"171.0.0.1\", port=61616, login=\"user1\", passcode=\"passcode1\"),\n        stompman.ConnectionParameters(host=\"172.0.0.1\", port=61616, login=\"user2\", passcode=\"passcode2\"),\n    ],\n\n\n    # SSL \u2014 can be either `None` (default), `True`, or `ssl.SSLContext'\n    ssl=None,\n\n    # Error frame handler:\n    on_error_frame=lambda error_frame: print(error_frame.body),\n\n    # Optional parameters with sensible defaults:\n    heartbeat=stompman.Heartbeat(will_send_interval_ms=1000, want_to_receive_interval_ms=1000),\n    connect_retry_attempts=3,\n    connect_retry_interval=1,\n    connect_timeout=2,\n    connection_confirmation_timeout=2,\n    disconnect_confirmation_timeout=2,\n    write_retry_attempts=3,\n    check_server_alive_interval_factor=3,\n) as client:\n    ...\n```\n\n### Sending Messages\n\nTo send a message, use the following code:\n\n```python\nawait client.send(b\"hi there!\", destination=\"DLQ\", headers={\"persistent\": \"true\"})\n```\n\nOr, to send messages in a transaction:\n\n```python\nasync with client.begin() as transaction:\n    for _ in range(10):\n        await transaction.send(body=b\"hi there!\", destination=\"DLQ\", headers={\"persistent\": \"true\"})\n        await asyncio.sleep(0.1)\n```\n\n### Listening for Messages\n\nNow, let's subscribe to a destination and listen for messages:\n\n```python\nasync def handle_message_from_dlq(message_frame: stompman.MessageFrame) -> None:\n    print(message_frame.body)\n\n\nawait client.subscribe(\"DLQ\", handle_message_from_dlq, on_suppressed_exception=print)\n```\n\nEntered `stompman.Client` will block forever waiting for messages if there are any active subscriptions.\n\nSometimes it's useful to avoid that:\n\n```python\ndlq_subscription = await client.subscribe(\"DLQ\", handle_message_from_dlq, on_suppressed_exception=print)\nawait dlq_subscription.unsubscribe()\n```\n\nBy default, subscription have ACK mode \"client-individual\". If handler successfully processes the message, an `ACK` frame will be sent. If handler raises an exception, a `NACK` frame will be sent. You can catch (and log) exceptions using `on_suppressed_exception` parameter:\n\n```python\nawait client.subscribe(\n    \"DLQ\",\n    handle_message_from_dlq,\n    on_suppressed_exception=lambda exception, message_frame: print(exception, message_frame),\n)\n```\n\nYou can change the ack mode used by specifying the `ack` parameter:\n\n```python\n# Server will assume that all messages sent to the subscription before the ACK'ed message are received and processed:\nawait client.subscribe(\"DLQ\", handle_message_from_dlq, ack=\"client\", on_suppressed_exception=print)\n\n# Server will assume that messages are received as soon as it send them to client:\nawait client.subscribe(\"DLQ\", handle_message_from_dlq, ack=\"auto\", on_suppressed_exception=print)\n```\n\nYou can pass custom headers to `client.subscribe()`:\n\n```python\nawait client.subscribe(\"DLQ\", handle_message_from_dlq, ack=\"client\", headers={\"selector\": \"location = 'Europe'\"}, on_suppressed_exception=print)\n```\n\n#### Handling ACK/NACKs yourself\n\nIf you want to send ACK and NACK frames yourself, you can use `client.subscribe_with_manual_ack()`:\n\n```python\nasync def handle_message_from_dlq(message_frame: stompman.AckableMessageFrame) -> None:\n    print(message_frame.body)\n    await message_frame.ack()\n\nawait client.subscribe_with_manual_ack(\"DLQ\", handle_message_from_dlq, ack=\"client\")\n```\n\nNote that this way exceptions won't be suppressed automatically.\n\n### Cleaning Up\n\nstompman takes care of cleaning up resources automatically. When you leave the context of async context managers `stompman.Client()`, or `client.begin()`, the necessary frames will be sent to the server.\n\n### Handling Connectivity Issues\n\n- If multiple servers were provided, stompman will attempt to connect to each one simultaneously and will use the first that succeeds. If all servers fail to connect, an `stompman.FailedAllConnectAttemptsError` will be raised. In normal situation it doesn't need to be handled: tune retry and timeout parameters in `stompman.Client()` to your needs.\n\n- When connection is lost, stompman will attempt to handle it automatically. `stompman.FailedAllConnectAttemptsError` will be raised if all connection attempts fail. `stompman.FailedAllWriteAttemptsError` will be raised if connection succeeds but sending a frame or heartbeat lead to losing connection.\n- To implement health checks, use `stompman.Client.is_alive()` \u2014 it will return `True` if everything is OK and `False` if server is not responding.\n- `stompman` will write log warnings when connection is lost, after successful reconnection or invalid state during ack/nack.\n\n### ...and caveats\n\n- stompman supports Python 3.11 and newer.\n- It implements [STOMP 1.2](https://stomp.github.io/stomp-specification-1.2.html) \u2014 the latest version of the protocol.\n- Heartbeats are required, and sent automatically in background (defaults to 1 second).\n\nAlso, I want to pointed out that:\n\n- Protocol parsing is inspired by [aiostomp](https://github.com/pedrokiefer/aiostomp/blob/3449dcb53f43e5956ccc7662bb5b7d76bc6ef36b/aiostomp/protocol.py) (meaning: consumed by me and refactored from).\n- stompman is tested and used with [ActiveMQ Artemis](https://activemq.apache.org/components/artemis/) and [ActiveMQ Classic](https://activemq.apache.org/components/classic/).\n    - Caveat: a message sent by a Stomp client is converted into a JMS `TextMessage`/`BytesMessage` based on the `content-length` header (see the docs [here](https://activemq.apache.org/components/classic/documentation/stomp)). In order to send a `TextMessage`, `Client.send` needs to be invoked with `add_content_length` header set to `False`\n- Specification says that headers in CONNECT and CONNECTED frames shouldn't be escaped for backwards compatibility. stompman escapes headers in CONNECT frame (outcoming), but does not unescape headers in CONNECTED (outcoming).\n\n### FastStream STOMP broker\n\n[An implementation of STOMP broker for FastStream.](packages/faststream-stomp/README.md)\n\n### Examples\n\nSee examples in [examples/](examples).\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Python STOMP client with pleasant API",
    "version": "3.8.0",
    "project_urls": {
        "repository": "https://github.com/community-of-python/stompman"
    },
    "split_keywords": [
        "activemq",
        " artemis",
        " jms",
        " messaging",
        " stomp"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d2711d4d38e260a8e51ec9d45974c9a1e132af4277b43319743a9bd3d67fa77d",
                "md5": "4e8f1cd7bcb4157915bb1da85a1f54ab",
                "sha256": "0b1f2085fb8274a5f688aff19d2749aab61f1419589abb2e49da101cce41ce16"
            },
            "downloads": -1,
            "filename": "stompman-3.8.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4e8f1cd7bcb4157915bb1da85a1f54ab",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 19146,
            "upload_time": "2025-09-02T07:41:29",
            "upload_time_iso_8601": "2025-09-02T07:41:29.019597Z",
            "url": "https://files.pythonhosted.org/packages/d2/71/1d4d38e260a8e51ec9d45974c9a1e132af4277b43319743a9bd3d67fa77d/stompman-3.8.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "53b51d1f9eb83901b3e6db511261a5b67718bc7d9d198a12d260750b5bdf57b6",
                "md5": "aa05ccb7e944d9e431aaca30d4697f89",
                "sha256": "2f78224e1a104e98f956350e323ee0d3f6ba08a6b16850fccbd7c972132f358b"
            },
            "downloads": -1,
            "filename": "stompman-3.8.0.tar.gz",
            "has_sig": false,
            "md5_digest": "aa05ccb7e944d9e431aaca30d4697f89",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 13780,
            "upload_time": "2025-09-02T07:41:30",
            "upload_time_iso_8601": "2025-09-02T07:41:30.162407Z",
            "url": "https://files.pythonhosted.org/packages/53/b5/1d1f9eb83901b3e6db511261a5b67718bc7d9d198a12d260750b5bdf57b6/stompman-3.8.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-02 07:41:30",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "community-of-python",
    "github_project": "stompman",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "stompman"
}
        
Elapsed time: 2.74862s