Skip to content

Commit

Permalink
feat: add Python client (#73)
Browse files Browse the repository at this point in the history
* feat: add python Client (#65)

* feat: ci and documentation for python client (#69)

* fix: add missing self for builder

* fix: header usage in example

* added type hints for better warnings during type mismatch

* added test for checking headers

* added formatting

* reduce python requirement to 3.9

---------

Co-authored-by: Ravi Suhag <[email protected]>
  • Loading branch information
punit-kulal and ravisuhag authored Jul 31, 2024
1 parent d8a60ef commit 9d8f2c1
Show file tree
Hide file tree
Showing 35 changed files with 1,252 additions and 1 deletion.
28 changes: 28 additions & 0 deletions .github/workflows/release-python-client.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Release Client - Python

on:
release:
types: [published]
workflow_dispatch:

jobs:
publish-python-client:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install dependencies
working-directory: clients/python
run: poetry install --with=dev
- name: Build
working-directory: clients/python
run: |
poetry version $(cat ../../version.txt)
poetry publish --build -u $PYPI_USERNAME -p $PYPI_PASSWORD --dry-run
61 changes: 61 additions & 0 deletions .github/workflows/test-python-client.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Test Raccoon Python Client
on:
push:
paths:
- "clients/python/**"
branches:
- main
pull_request:
paths:
- "clients/python/**"
jobs:
format-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install dependencies
working-directory: clients/python
run: poetry install --with=dev
- name: Format
working-directory: clients/python
run: poetry run python -m black . --check
lint-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install dependencies
working-directory: clients/python
run: poetry install --with=dev
- name: Lint
working-directory: clients/python
run: |
poetry run python -m ruff check raccoon_client tests
poetry run python -m pylint raccoon_client tests
test-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install dependencies
working-directory: clients/python
run: poetry install
- name: Unit Test
working-directory: clients/python
run: poetry run python -m unittest discover -p '*_test.py'
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ coverage
.vscode
*.env
*.idea/
raccoon
.temp
clients/python/venv
clients/python/poetry.lock
__pycache__
raccoon
!clients/python/raccoon_client/protos/raystack/raccoon/v1beta1/
node_modules
__debug.*

Expand Down
5 changes: 5 additions & 0 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ plugins:
- plugin: buf.build/grpc/go:v1.3.0
out: proto
opt: paths=source_relative,require_unimplemented_servers=true
- plugin: buf.build/protocolbuffers/python:v23.4
out: clients/python/raccoon_client/protos
- plugin: buf.build/protocolbuffers/pyi:v23.4
out: clients/python/raccoon_client/protos

41 changes: 41 additions & 0 deletions clients/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Python Client

### Setup
- The project uses poetry for build, and virutal env management.
- The client was created with 3.11 as it's python environment. Hence 3.11 can be considered it's minimum requirement. It's also stated in the pyproject.toml file.
- Make sure to install poetry via https://python-poetry.org/docs/#installing-manually
- After installing poetry you can activate the env by `poetry env use`
- Install all dependencies using `poetry install --no-root --with=dev` (no-root tells that the package is not at the root of the directory)
- For setting up in IDE, make sure to setup the interpreter to use the virtual environment that was created when you activated poetry env.

### Lint and Formatting
- We use black for formatting of python files and pylint, ruff for linting the python files.
- You can check the command for running lint and formating by referring to `test-python-client.yml` workflow.

### Usage
- You can use the raccoon by installing it from PyPi by the following command
- From Pypi
```pip install raccoon_client```
- From Github
```pip install raccoon_client@git+https://github.com/raystack/raccoon@$VERSION#subdirectory=clients/python```
where $VERSION is a git tag.
- An example on how to use the client is under the [examples](examples) package.

### Confiugration
The client supports the following configuration:

| Name | Description | Type | Default |
|---------|-----------------------------------------------------------------------------------|-----------------------------------|---------|
| url | The remote server url to connect to | string | "" |
| retries | The max number of retries to be attempted before an event is considered a failure | int (<10) | 3 |
| timeout | The number of seconds to wait before timing out the request | float | 1.0 |
| serialiser | The format to which event field of client.Event serialises it's data to | Serialiser Enum(JSON or PROTOBUF) | JSON |
|wire_type | The format in which the request payload should be sent to server | Wire Type Enum(JSON or PROTOBUF) | JSON |
| headers | HTTP header key value pair to be sent along with each request | dict | {} |


Note:
- During development, make sure to open just the python directory, otherwise the IDE misconfigures the imports.
- The protos package contain generated code and should not be edited manually.
- It's recommended not to use JSON serialiser, when using proto generated classes as your events due to JSON encoding incompatibility. [Issue](https://github.com/raystack/raccoon/issues/67)

Binary file not shown.
Empty file.
73 changes: 73 additions & 0 deletions clients/python/examples/rest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from raccoon_client.client import Event
from raccoon_client.protos.raystack.raccoon.v1beta1.raccoon_pb2 import SendEventRequest
from raccoon_client.rest.client import RestClient
from raccoon_client.rest.option import RestClientConfigBuilder
from raccoon_client.serde.enum import Serialiser, WireType


def example_json_serialiser_json_wire():
event_data = {"a": "field a", "b": "field b"}

config = (
RestClientConfigBuilder()
.with_url("http://localhost:8080/api/v1/events")
.with_serialiser(Serialiser.JSON)
.with_wire_type(WireType.JSON)
.build()
) # other parameters supported by the config builder can be checked in its method definition.
rest_client = RestClient(config)
topic_to_publish_to = "test_topic_2"
e = Event(topic_to_publish_to, event_data)
req_id, response, raw = rest_client.send([e])
return req_id, response, raw


def example_protobuf_serialiser_protobuf_wire():
event_data = (
SendEventRequest()
) # sample generated proto class which is an event to send to raccoon
event_data.sent_time = 1000
event_data.req_guid = "some string"

config = (
RestClientConfigBuilder()
.with_url("http://localhost:8080/api/v1/events")
.with_serialiser(Serialiser.PROTOBUF)
.with_wire_type(WireType.PROTOBUF)
.with_timeout(10.0)
.with_retry_count(3)
.with_headers({"Authorization": "TOKEN"})
.build()
) # other parameters supported by the config builder can be checked in its method definition.
rest_client = RestClient(config)
topic_to_publish_to = "test_topic_2"
e = Event(topic_to_publish_to, event_data)
req_id, response, raw = rest_client.send([e])
return req_id, response, raw


def example_protobuf_serialiser_json_wire():
event_data = (
SendEventRequest()
) # sample generated proto class which is an event to send to raccoon
event_data.sent_time = 1000
event_data.req_guid = "some string"

config = (
RestClientConfigBuilder()
.with_url("http://localhost:8080/api/v1/events")
.with_serialiser(Serialiser.PROTOBUF)
.with_wire_type(WireType.JSON)
.build()
) # other parameters supported by the config builder can be checked in its method definition.
rest_client = RestClient(config)
topic_to_publish_to = "test_topic_2"
e = Event(topic_to_publish_to, event_data)
req_id, response, raw = rest_client.send([e])
return req_id, response, raw


if __name__ == "__main__":
example_json_serialiser_json_wire()
example_protobuf_serialiser_protobuf_wire()
example_protobuf_serialiser_json_wire()
44 changes: 44 additions & 0 deletions clients/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[tool.poetry]
name = "raccoon-client"
version = "v0.2.1"
description = "A python client to serve requests to raccoon server"
authors = ["Punit Kulal <[email protected]>"]
readme = "README.md"
packages = [{include = "raccoon_client"}]

[tool.poetry.dependencies]
python = "^3.9"
requests = "^2.31.0"
protobuf = "^4.23.4"
google = "^3.0.0"

[tool.poetry.group.dev.dependencies]
requests = "^2.31.0"
black = "^23.7.0"
pylint = "^2.17.5"
ruff = "^0.0.285"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.black]
line-length = 88
extend-exclude = '.*_pb2.py|.*_pb2.pyi'

[tool.ruff]
ignore = ["E501"]

[tool.pylint.'MESSAGES CONTROL']
disable = [
'no-name-in-module',
'line-too-long',
'missing-module-docstring',
'bad-indentation',
'missing-class-docstring',
'missing-function-docstring',
'protected-access'
]

[tool.pylint.MASTER]
ignore-patterns = '.*_pb2.py|.*_pb2.pyi'
Empty file.
23 changes: 23 additions & 0 deletions clients/python/raccoon_client/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from dataclasses import dataclass

from raccoon_client.protos.raystack.raccoon.v1beta1.raccoon_pb2 import (
SendEventResponse,
)


@dataclass
class RaccoonResponseError(IOError):
def __init__(self, status_code, msg):
super().__init__(msg)
self.status_code = status_code


@dataclass
class Event:
type: str
event: object


class Client: # pylint: disable=too-few-public-methods
def send(self, events: [Event]) -> (str, SendEventResponse, RaccoonResponseError):
raise NotImplementedError()
Empty file.
Empty file.
Empty file.
Empty file.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9d8f2c1

Please sign in to comment.