cent


Namecent JSON
Version 5.0.0 PyPI version JSON
download
home_page
SummaryPython library to communicate with Centrifugo v5 server HTTP API
upload_time2024-03-16 09:29:18
maintainer
docs_urlNone
authorAlexander Emelin
requires_python>=3.9,<4.0
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Python SDK to communicate with Centrifugo v5 HTTP API. Python >= 3.9 supported.

To install run:

```bash
pip install cent
```

## Centrifugo compatibility

* **Cent v5 and higher works only with Centrifugo v5**.
* If you need to work with Centrifugo v3, v4 => use Cent v4
* If you need to work with Centrifugo v2 => use Cent v3

## Usage

First of all, see the description of Centrifugo [server API](https://centrifugal.dev/docs/server/server_api) in the documentation. This library also supports API extensions provided by Centrifugo PRO. In general, refer to [api.proto](https://github.com/centrifugal/centrifugo/blob/master/internal/apiproto/api.proto) Protobuf schema file as a source of truth about all available Centrifugo server APIs. Don't forget that Centrifugo supports both HTTP and GRPC API – so you can switch to GRPC by using `api.proto` file to generate stubs for communication.

This library contains `Client` and `AsyncClient` to work with Centrifugo HTTP server API. Both clients have the same methods to work with Centrifugo API and raise the same top-level exceptions.

## Sync HTTP client

```python
from cent import Client
```

Required init arguments:

* `api_url` (`str`) - Centrifugo HTTP API URL address, for example, `http://localhost:8000/api`
* `api_key` (`str`) - Centrifugo HTTP API key for auth

Optional arguments:

* `timeout` (`float`) - base timeout for all requests in seconds, default is 10 seconds.
* `session` (`requests.Session`) - custom `requests` session to use.

Example:

```python
from cent import Client, PublishRequest

api_url = "http://localhost:8000/api"
api_key = "<CENTRIFUGO_API_KEY>"

client = Client(api_url, api_key)
request = PublishRequest(channel="channel", data={"input": "Hello world!"})
result = client.publish(request)
print(result)
```

## Async HTTP client

```python
from cent import AsyncClient
```

Required init arguments:

* `api_url` (`str`) - Centrifugo HTTP API URL address, for example, `http://localhost:8000`
* `api_key` (`str`) - Centrifugo HTTP API key for auth

Optional arguments:

* `timeout` (`float`) - base timeout for all requests in seconds, default is 10 seconds.
* `session` (`aiohttp.ClientSession`) - custom `aiohttp` session to use.

Example:

```python
import asyncio
from cent import AsyncClient, PublishRequest

api_url = "http://localhost:8000/api"
api_key = "<CENTRIFUGO_API_KEY>"

async def main():
    client = AsyncClient(api_url, api_key)
    request = PublishRequest(channel="channel", data={"input": "Hello world!"})
    result = await client.publish(request)
    print(result)

if __name__ == "__main__":
    asyncio.run(main())
```

## Handling errors

This library raises exceptions if sth goes wrong. All exceptions are subclasses of `cent.CentError`.

* `CentError` - base class for all exceptions
* `CentNetworkError` - raised in case of network related errors (connection refused)
* `CentTransportError` - raised in case of transport related errors (HTTP status code is not 2xx)
* `CentTimeoutError` - raised in case of timeout
* `CentUnauthorizedError` - raised in case of unauthorized access (signal of invalid API key)
* `CentDecodeError` - raised in case of server response decoding error
* `CentApiResponseError` - raised in case of API response error (i.e. error returned by Centrifugo itself, you can inspect code and message returned by Centrifugo in this case)

Note, that `BroadcastRequest` and `BatchRequest` are quite special – since they contain multiple commands in one request, handling `CentApiResponseError` is still required, but not enough – you also need to manually iterate over the results to check for individual errors. For example, one publish command can fail while another one can succeed. For example:

```python
from cent import *

c = Client("http://localhost:8000/api", "api_key")
req = BroadcastRequest(channels=["1", "2"], data={})
c.broadcast(req)
# BroadcastResult(
#   responses=[
#       Response[PublishResult](error=None, result=PublishResult(offset=7, epoch='rqKx')),
#       Response[PublishResult](error=None, result=PublishResult(offset=7, epoch='nUrf'))
#   ]
# )
req = BroadcastRequest(channels=["invalid:1", "2"], data={})
c.broadcast(req)
# BroadcastResult(
#   responses=[
#       Response[PublishResult](error=Error(code=102, message='unknown channel'), result=None),
#       Response[PublishResult](error=None, result=PublishResult(offset=8, epoch='nUrf'))
#   ]
# )
```

I.e. `cent` library does not raise exceptions for individual errors in `BroadcastRequest` or `BatchRequest`, only for top-level response error, for example, sending empty list of channels in broadcast:

```
req = BroadcastRequest(channels=[], data={})
c.broadcast(req)
Traceback (most recent call last):
    ...
    raise CentApiResponseError(
cent.exceptions.CentApiResponseError: Server API response error #107: bad request
```

So this all adds some complexity, but that's the trade-off for the performance and efficiency of these two methods. You can always write some convenient wrappers around `cent` library to handle errors in a way that suits your application.

## Using for async consumers

You can use this library to constructs events for Centrifugo [async consumers](https://centrifugal.dev/docs/server/consumers). For example, to get proper method and payload for async publish:

```python
from cent import PublishRequest

request = PublishRequest(channel="channel", data={"input": "Hello world!"})
method = request.api_method
payload = request.api_payload
# use method and payload to construct async consumer event.
```

## Using Broadcast and Batch

To demonstrate the benefits of using `BroadcastRequest` and `BatchRequest` let's compare approaches. Let's say at some point in your app you need to publish the same message into 10k different channels. Let's compare sequential publish, batch publish and broadcast publish. Here is the code to do the comparison:

```python
from cent import *
from time import time


def main():
    publish_requests = []
    channels = []
    for i in range(10000):
        channel = f"test_{i}"
        publish_requests.append(PublishRequest(channel=channel, data={"msg": "hello"}))
        channels.append(channel)
    batch_request = BatchRequest(requests=publish_requests)
    broadcast_request = BroadcastRequest(channels=channels, data={"msg": "hello"})

    client = Client("http://localhost:8000/api", "api_key")

    start = time()
    for request in publish_requests:
        client.publish(request)
    print("sequential", time() - start)

    start = time()
    client.batch(batch_request)
    print("batch", time() - start)

    start = time()
    client.broadcast(broadcast_request)
    print("broadcast", time() - start)


if __name__ == "__main__":
    main()
```

On local machine, the output may look like this:

```
sequential 5.731332778930664
batch 0.12313580513000488
broadcast 0.06050515174865723
```

So `BatchRequest` is much faster than sequential requests in this case, and `BroadcastRequest` is the fastest - publication to 10k Centrifugo channels took only 60ms. Because all the work is done in one network round-trip. In reality the difference will be even more significant because of network latency.

## For contributors

### Tests and benchmarks

Prerequisites – start Centrifugo server locally:

```bash
CENTRIFUGO_API_KEY=api_key CENTRIFUGO_HISTORY_TTL=300s CENTRIFUGO_HISTORY_SIZE=100 \
CENTRIFUGO_PRESENCE=true CENTRIFUGO_GRPC_API=true ./centrifugo
```

And install dependencies:

```bash
make dev
```

Then to run tests, run:

```bash
make test
```

To run benchmarks, run:

```bash
make bench
```

## Migrate to Cent v5

Cent v5 contains the following notable changes compared to Cent v4:

* Client constructor slightly changed, refer to the examples above.
* To call desired API import and construct a request object (inherited from Pydantic `BaseModel`) and then call corresponding method of client. This should feel very similar to how GRPC is usually structured.
* Base exception class is now `CentError` instead of `CentException`, exceptions SDK raises were refactored.
* To send multiple commands in one HTTP request SDK provides `batch` method.

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "cent",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.9,<4.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Alexander Emelin",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/04/07/3376f7f4697a171b2e19248d5112c63ffc55178f9abea30e77f7855b39e6/cent-5.0.0.tar.gz",
    "platform": null,
    "description": "Python SDK to communicate with Centrifugo v5 HTTP API. Python >= 3.9 supported.\n\nTo install run:\n\n```bash\npip install cent\n```\n\n## Centrifugo compatibility\n\n* **Cent v5 and higher works only with Centrifugo v5**.\n* If you need to work with Centrifugo v3, v4 => use Cent v4\n* If you need to work with Centrifugo v2 => use Cent v3\n\n## Usage\n\nFirst of all, see the description of Centrifugo [server API](https://centrifugal.dev/docs/server/server_api) in the documentation. This library also supports API extensions provided by Centrifugo PRO. In general, refer to [api.proto](https://github.com/centrifugal/centrifugo/blob/master/internal/apiproto/api.proto) Protobuf schema file as a source of truth about all available Centrifugo server APIs. Don't forget that Centrifugo supports both HTTP and GRPC API \u2013 so you can switch to GRPC by using `api.proto` file to generate stubs for communication.\n\nThis library contains `Client` and `AsyncClient` to work with Centrifugo HTTP server API. Both clients have the same methods to work with Centrifugo API and raise the same top-level exceptions.\n\n## Sync HTTP client\n\n```python\nfrom cent import Client\n```\n\nRequired init arguments:\n\n* `api_url` (`str`) - Centrifugo HTTP API URL address, for example, `http://localhost:8000/api`\n* `api_key` (`str`) - Centrifugo HTTP API key for auth\n\nOptional arguments:\n\n* `timeout` (`float`) - base timeout for all requests in seconds, default is 10 seconds.\n* `session` (`requests.Session`) - custom `requests` session to use.\n\nExample:\n\n```python\nfrom cent import Client, PublishRequest\n\napi_url = \"http://localhost:8000/api\"\napi_key = \"<CENTRIFUGO_API_KEY>\"\n\nclient = Client(api_url, api_key)\nrequest = PublishRequest(channel=\"channel\", data={\"input\": \"Hello world!\"})\nresult = client.publish(request)\nprint(result)\n```\n\n## Async HTTP client\n\n```python\nfrom cent import AsyncClient\n```\n\nRequired init arguments:\n\n* `api_url` (`str`) - Centrifugo HTTP API URL address, for example, `http://localhost:8000`\n* `api_key` (`str`) - Centrifugo HTTP API key for auth\n\nOptional arguments:\n\n* `timeout` (`float`) - base timeout for all requests in seconds, default is 10 seconds.\n* `session` (`aiohttp.ClientSession`) - custom `aiohttp` session to use.\n\nExample:\n\n```python\nimport asyncio\nfrom cent import AsyncClient, PublishRequest\n\napi_url = \"http://localhost:8000/api\"\napi_key = \"<CENTRIFUGO_API_KEY>\"\n\nasync def main():\n    client = AsyncClient(api_url, api_key)\n    request = PublishRequest(channel=\"channel\", data={\"input\": \"Hello world!\"})\n    result = await client.publish(request)\n    print(result)\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n## Handling errors\n\nThis library raises exceptions if sth goes wrong. All exceptions are subclasses of `cent.CentError`.\n\n* `CentError` - base class for all exceptions\n* `CentNetworkError` - raised in case of network related errors (connection refused)\n* `CentTransportError` - raised in case of transport related errors (HTTP status code is not 2xx)\n* `CentTimeoutError` - raised in case of timeout\n* `CentUnauthorizedError` - raised in case of unauthorized access (signal of invalid API key)\n* `CentDecodeError` - raised in case of server response decoding error\n* `CentApiResponseError` - raised in case of API response error (i.e. error returned by Centrifugo itself, you can inspect code and message returned by Centrifugo in this case)\n\nNote, that `BroadcastRequest` and `BatchRequest` are quite special \u2013 since they contain multiple commands in one request, handling `CentApiResponseError` is still required, but not enough \u2013 you also need to manually iterate over the results to check for individual errors. For example, one publish command can fail while another one can succeed. For example:\n\n```python\nfrom cent import *\n\nc = Client(\"http://localhost:8000/api\", \"api_key\")\nreq = BroadcastRequest(channels=[\"1\", \"2\"], data={})\nc.broadcast(req)\n# BroadcastResult(\n#   responses=[\n#       Response[PublishResult](error=None, result=PublishResult(offset=7, epoch='rqKx')),\n#       Response[PublishResult](error=None, result=PublishResult(offset=7, epoch='nUrf'))\n#   ]\n# )\nreq = BroadcastRequest(channels=[\"invalid:1\", \"2\"], data={})\nc.broadcast(req)\n# BroadcastResult(\n#   responses=[\n#       Response[PublishResult](error=Error(code=102, message='unknown channel'), result=None),\n#       Response[PublishResult](error=None, result=PublishResult(offset=8, epoch='nUrf'))\n#   ]\n# )\n```\n\nI.e. `cent` library does not raise exceptions for individual errors in `BroadcastRequest` or `BatchRequest`, only for top-level response error, for example, sending empty list of channels in broadcast:\n\n```\nreq = BroadcastRequest(channels=[], data={})\nc.broadcast(req)\nTraceback (most recent call last):\n    ...\n    raise CentApiResponseError(\ncent.exceptions.CentApiResponseError: Server API response error #107: bad request\n```\n\nSo this all adds some complexity, but that's the trade-off for the performance and efficiency of these two methods. You can always write some convenient wrappers around `cent` library to handle errors in a way that suits your application.\n\n## Using for async consumers\n\nYou can use this library to constructs events for Centrifugo [async consumers](https://centrifugal.dev/docs/server/consumers). For example, to get proper method and payload for async publish:\n\n```python\nfrom cent import PublishRequest\n\nrequest = PublishRequest(channel=\"channel\", data={\"input\": \"Hello world!\"})\nmethod = request.api_method\npayload = request.api_payload\n# use method and payload to construct async consumer event.\n```\n\n## Using Broadcast and Batch\n\nTo demonstrate the benefits of using `BroadcastRequest` and `BatchRequest` let's compare approaches. Let's say at some point in your app you need to publish the same message into 10k different channels. Let's compare sequential publish, batch publish and broadcast publish. Here is the code to do the comparison:\n\n```python\nfrom cent import *\nfrom time import time\n\n\ndef main():\n    publish_requests = []\n    channels = []\n    for i in range(10000):\n        channel = f\"test_{i}\"\n        publish_requests.append(PublishRequest(channel=channel, data={\"msg\": \"hello\"}))\n        channels.append(channel)\n    batch_request = BatchRequest(requests=publish_requests)\n    broadcast_request = BroadcastRequest(channels=channels, data={\"msg\": \"hello\"})\n\n    client = Client(\"http://localhost:8000/api\", \"api_key\")\n\n    start = time()\n    for request in publish_requests:\n        client.publish(request)\n    print(\"sequential\", time() - start)\n\n    start = time()\n    client.batch(batch_request)\n    print(\"batch\", time() - start)\n\n    start = time()\n    client.broadcast(broadcast_request)\n    print(\"broadcast\", time() - start)\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\nOn local machine, the output may look like this:\n\n```\nsequential 5.731332778930664\nbatch 0.12313580513000488\nbroadcast 0.06050515174865723\n```\n\nSo `BatchRequest` is much faster than sequential requests in this case, and `BroadcastRequest` is the fastest - publication to 10k Centrifugo channels took only 60ms. Because all the work is done in one network round-trip. In reality the difference will be even more significant because of network latency.\n\n## For contributors\n\n### Tests and benchmarks\n\nPrerequisites \u2013 start Centrifugo server locally:\n\n```bash\nCENTRIFUGO_API_KEY=api_key CENTRIFUGO_HISTORY_TTL=300s CENTRIFUGO_HISTORY_SIZE=100 \\\nCENTRIFUGO_PRESENCE=true CENTRIFUGO_GRPC_API=true ./centrifugo\n```\n\nAnd install dependencies:\n\n```bash\nmake dev\n```\n\nThen to run tests, run:\n\n```bash\nmake test\n```\n\nTo run benchmarks, run:\n\n```bash\nmake bench\n```\n\n## Migrate to Cent v5\n\nCent v5 contains the following notable changes compared to Cent v4:\n\n* Client constructor slightly changed, refer to the examples above.\n* To call desired API import and construct a request object (inherited from Pydantic `BaseModel`) and then call corresponding method of client. This should feel very similar to how GRPC is usually structured.\n* Base exception class is now `CentError` instead of `CentException`, exceptions SDK raises were refactored.\n* To send multiple commands in one HTTP request SDK provides `batch` method.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Python library to communicate with Centrifugo v5 server HTTP API",
    "version": "5.0.0",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3e04d61a2eed5cd3afac1e39901a5687d1a6788554f6c48b25de0a580996ee96",
                "md5": "f9c649e759d86f726c0ddb582673e57f",
                "sha256": "0b0d457e69e6eb074d6d75bdbda0d12f430b9bbebdac0adcd0d28b6aa1bb3e63"
            },
            "downloads": -1,
            "filename": "cent-5.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f9c649e759d86f726c0ddb582673e57f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9,<4.0",
            "size": 19165,
            "upload_time": "2024-03-16T09:29:17",
            "upload_time_iso_8601": "2024-03-16T09:29:17.027357Z",
            "url": "https://files.pythonhosted.org/packages/3e/04/d61a2eed5cd3afac1e39901a5687d1a6788554f6c48b25de0a580996ee96/cent-5.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "04073376f7f4697a171b2e19248d5112c63ffc55178f9abea30e77f7855b39e6",
                "md5": "69166028997b128de77e0c4fa1b66f79",
                "sha256": "4400691777f5c179ff67394d3e224327c58c7812f8ffad69dded400e7583c475"
            },
            "downloads": -1,
            "filename": "cent-5.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "69166028997b128de77e0c4fa1b66f79",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9,<4.0",
            "size": 17799,
            "upload_time": "2024-03-16T09:29:18",
            "upload_time_iso_8601": "2024-03-16T09:29:18.911252Z",
            "url": "https://files.pythonhosted.org/packages/04/07/3376f7f4697a171b2e19248d5112c63ffc55178f9abea30e77f7855b39e6/cent-5.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-16 09:29:18",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "cent"
}
        
Elapsed time: 0.21998s