Skip to content

Commit

Permalink
fix grpc api, reduce API scope
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-emelin committed Feb 12, 2024
1 parent 62444c0 commit a0f5c16
Show file tree
Hide file tree
Showing 28 changed files with 591 additions and 533 deletions.
18 changes: 5 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,10 @@ jobs:
with:
python-version: '3.12'

- name: check GITHUB_REF matches package version
uses: samuelcolvin/[email protected]
with:
version_file_path: centrifuge/__meta__.py

- name: Install build dependencies
run: pip install build
- name: Install poetry
run: pip install poetry

- name: Build distribution
run: python -m build
- run: poetry config pypi-token.pypi "${{ secrets.PYPI_PASSWORD }}"

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_PASSWORD }}
- name: Publish package
run: poetry publish --build
2 changes: 0 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,3 @@ repos:
name: Validate types with MyPy
language: system
types: [ python ]
args:
- "cent"
63 changes: 43 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ pip install cent

**Cent v5 and higher works only with Centrifugo v5**.

* If you need to work with Centrifugo v3 then use Cent v4
* If you need to work with Centrifugo v3, v4 then use Cent v4
* If you need to work with Centrifugo v2 then use Cent v3

## High-level library API
## Usage

First see [available API methods in documentation](https://centrifugal.dev/docs/server/server_api#api-methods).

This library contains `Client` and `AsyncClient` class to send messages to Centrifugo from your python-powered backend:
This library contains `Client`, `AsyncClient` and `GrpcClient` classes to work with Centrifugo HTTP API.

```python
import asyncio
Expand All @@ -30,46 +30,69 @@ url = "http://localhost:8000/api"
api_key = "XXX"

# Initialize a client (you can use sync or async version)
async_client = AsyncClient(url, api_key=api_key)
sync_client = Client(url, api_key=api_key)
async_client = AsyncClient(url, api_key=api_key)

response = sync_client.publish("example:channel", {"input": "Hello world!"})
print(response)
# Now you can use sync client to call API methods.
result = sync_client.publish("example:channel", {"input": "Hello world!"})
print(result)


async def main():
response = await async_client.publish("example:channel", {"input": "Hello world!"})
print(response)
# And async client to call API methods too.
result = await async_client.publish("example:channel", {"input": "Hello world!"})
print(result)


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

### Client init arguments
### Handling errors

This library may raise 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
* CentDecodeError - raised in case of server response decoding error
* CentAPIError - raised in case of API error (error returned by Centrifugo itself)

### HTTP client init arguments

Required:

* base_url - Centrifugo HTTP API endpoint address
* api_key - Centrifugo HTTP API key
* `api_url` (str) - Centrifugo HTTP API URL address
* `api_key` (str) - Centrifugo HTTP API key

Optional:

* session (`BaseSession`) - session to use

You can use `AiohttpSession` or create custom from `BaseSession` class.
* `request_timeout` (float) - base timeout for all requests in seconds, default is 10 seconds.

Arguments for default session:
### GRPC client init arguments

Required:

* base_url - Centrifugo HTTP API endpoint address
* `host` (str) - Centrifugo GRPC API host
* `port` (int) - Centrifugo GRPC API port

Optional:

* json_loads — function to load JSON from response body (default is json, but you can use
orjson, ujson etc.)
* timeout - timeout for requests (default is 10.0)
* `request_timeout` (float) - base timeout for all requests in seconds, default is 10 seconds.

## HTTP vs GRPC for payloads

When using HTTP-based clients (`Client` and `AsyncClient`):

* you should pass payload as a Python objects which can be serialized to JSON
* in results, you will receive Python objects already deserialized from JSON.

When using GRPC-based client (`GrpcClient`):

* you must pass payloads as `bytes`
* in results, you will receive `bytes` for payloads

## For contributors

Expand All @@ -90,5 +113,5 @@ make bench
### Generate code from proto file, if needed

```bash
poetry run python -m grpc_tools.protoc -I . --python_betterproto_out=./cent/proto cent/proto/apiproto.proto
make proto
```
2 changes: 1 addition & 1 deletion benchmarks/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ async def async_client(
) -> AsyncGenerator[AsyncClient, None]:
client = AsyncClient(BASE_URL, API_KEY)
yield client
await client.session.close()
await client.close()
21 changes: 4 additions & 17 deletions cent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,9 @@
Client,
AsyncClient,
GrpcClient,
BaseSession,
BaseAsyncSession,
BaseSyncSession,
RequestsSession,
AiohttpSession,
)
from cent.base import CentRequest
from cent.requests import (
CentRequest,
BroadcastRequest,
PublishRequest,
SubscribeRequest,
Expand Down Expand Up @@ -55,10 +50,10 @@
from cent.exceptions import (
CentError,
CentNetworkError,
CentClientDecodeError,
CentTransportError,
CentUnauthorizedError,
CentDecodeError,
CentAPIError,
CentTransportError,
)

with contextlib.suppress(ImportError):
Expand All @@ -67,18 +62,14 @@
_asyncio.set_event_loop_policy(_uvloop.EventLoopPolicy())

__all__ = (
"AiohttpSession",
"AsyncClient",
"BaseAsyncSession",
"BaseSession",
"BaseSyncSession",
"BatchRequest",
"BatchResult",
"BoolValue",
"BroadcastRequest",
"BroadcastResult",
"CentAPIError",
"CentClientDecodeError",
"CentDecodeError",
"CentError",
"CentNetworkError",
"CentRequest",
Expand Down Expand Up @@ -110,13 +101,9 @@
"PublishResult",
"RefreshRequest",
"RefreshResult",
"RequestsSession",
"StreamPosition",
"SubscribeRequest",
"SubscribeResult",
"UnsubscribeRequest",
"UnsubscribeResult",
"exceptions",
"requests",
"types",
)
16 changes: 2 additions & 14 deletions cent/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,6 @@ class BaseResult(BaseModel, ABC):
)


try:
import orjson as _orjson # type: ignore[import-not-found]

json_dumps = _orjson.dumps
json_loads = _orjson.loads
except ImportError:
import json

def json_dumps(x: Any) -> bytes:
return json.dumps(x).encode()

json_loads = json.loads


CentType = TypeVar("CentType", bound=Any)


Expand All @@ -55,6 +41,7 @@ class CentRequest(BaseModel, Generic[CentType], ABC):
__api_method__: ClassVar[str]
__grpc_method__: ClassVar[type]
else:

@property
@abstractmethod
def __returning__(self) -> type:
Expand All @@ -80,6 +67,7 @@ class NestedModel(BaseModel, ABC):
if TYPE_CHECKING:
__grpc_method__: ClassVar[type]
else:

@property
@abstractmethod
def __grpc_method__(self) -> type:
Expand Down
12 changes: 0 additions & 12 deletions cent/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
from .session import (
BaseSession,
BaseAsyncSession,
BaseSyncSession,
AiohttpSession,
RequestsSession,
)
from .sync_client import Client
from .async_client import AsyncClient
from .grpc_client import GrpcClient

__all__ = (
"AiohttpSession",
"AsyncClient",
"BaseAsyncSession",
"BaseSession",
"BaseSyncSession",
"Client",
"GrpcClient",
"RequestsSession",
)
37 changes: 22 additions & 15 deletions cent/client/async_client.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import List, Optional, Any, Dict, TypeVar

from cent.client.session import BaseAsyncSession, AiohttpSession
from cent.client.session import AiohttpSession
from cent.base import CentRequest
from cent.requests import (
CentRequest,
BroadcastRequest,
PublishRequest,
SubscribeRequest,
Expand All @@ -18,6 +18,7 @@
BatchRequest,
)
from cent.results import (
BatchResult,
PublishResult,
BroadcastResult,
SubscribeResult,
Expand All @@ -36,7 +37,6 @@
ChannelOptionsOverride,
Disconnect,
)
from cent.results import BatchResult


T = TypeVar("T")
Expand All @@ -45,19 +45,21 @@
class AsyncClient:
def __init__(
self,
base_url: str,
api_url: str,
api_key: str,
session: Optional[BaseAsyncSession] = None,
request_timeout: Optional[float] = 10.0,
) -> None:
"""
:param base_url: Centrifuge base_url
:param api_key: Centrifuge API key
:param session: Custom Session instance
:param api_url: Centrifugo API URL
:param api_key: Centrifugo API key
:param request_timeout: base timeout for all requests.
"""

self._base_url = base_url
self.api_key = api_key
self.session = session or AiohttpSession(base_url=base_url)
self._base_url = api_url
self._api_key = api_key
self._session = AiohttpSession(
base_url=api_url,
timeout=request_timeout,
)

async def publish(
self,
Expand Down Expand Up @@ -250,11 +252,16 @@ async def batch(
call = BatchRequest.model_construct(commands=commands)
return await self(call, request_timeout=request_timeout)

async def __call__(self, method: CentRequest[T], request_timeout: Optional[float] = None) -> T:
async def close(self) -> None:
await self._session.close()

async def __call__(
self, request: CentRequest[T], request_timeout: Optional[float] = None
) -> T:
"""
Call API method
:param method: Centrifugo method
:param request: Centrifugo request
:return: Centrifugo response
"""
return await self.session(self, method, timeout=request_timeout)
return await self._session(self._api_key, request, timeout=request_timeout)
Loading

0 comments on commit a0f5c16

Please sign in to comment.