Skip to content

Commit

Permalink
feat: 0.3.0 pre-release
Browse files Browse the repository at this point in the history
  • Loading branch information
RaRhAeu authored Jul 29, 2024
1 parent eebddea commit 008ca7a
Show file tree
Hide file tree
Showing 48 changed files with 3,587 additions and 12 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

name: Tests
on:
push:
Expand Down Expand Up @@ -26,6 +25,11 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install Hatch
run: pipx install hatch
- name: Install types bacport
if: matrix.python-version == '3.9'
run: pip install eval-type-backport
- name: Ruff
if: matrix.python-version == '3.12'
run: hatch run default:check

- name: Pytest
run: hatch run default:test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ site/
.mypy_cache
tmp.db
.python-version
asyncapi.yaml
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repos:
args: [--allow-multiple-documents]

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.1.13"
rev: "v0.5.2"
hooks:
- id: ruff
args: ["--fix"]
Expand All @@ -30,3 +30,9 @@ repos:
rev: v1.2.0
hooks:
- id: mypy
args:
[
"--install-types",
"--non-interactive",
"--enable-incomplete-feature=Unpack",
]
142 changes: 142 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,145 @@
# eventiq

Publish/Subscribe asyncio framework for Python

## Installation
```shell
pip install eventiq
```
or
```shell
poetry add eventiq
```

### Installing optional dependencies

```shell
pip install 'eventiq[broker]'
```

### Available brokers

- `nats`
- `rabbitmq`
- `kafka`
- `redis`

## Features

- Modern, `asyncio` based python 3.8+ syntax
- Minimal external dependencies
- Automatic message parsing based on type annotations using `pydantic`
- Code hot-reload
- Highly scalable: each service can process hundreds of tasks concurrently,
all messages are load balanced between all instances by default
- Resilient - at least once delivery for all messages by default
- Customizable & pluggable message encoders (json by default)
- Multiple broker support
- Nats
- Kafka
- Rabbitmq
- Redis
- Easily extensible via Middlewares
- Cloud Events standard as base message structure (no more python specific `*args` and `**kwargs` in messages)
- AsyncAPI documentation generation from code
- Twelve factor app approach - stdout logging, configuration through environment variables
- Available extensions for integrating with Prometheus (metrics) and OpenTelemetry (tracing, metrics)

## Basic Usage

```Python
import asyncio
from eventiq import Service, Middleware, CloudEvent
from eventiq.backends.nats import JetStreamBroker

class SendMessageMiddleware(Middleware):
async def after_broker_connect(self, *, service: Service):
print(f"After service start, running with {service.broker}")
await asyncio.sleep(10)
for i in range(100):
message = CloudEvent(topic="test.topic", data={"counter": i})
await service.publish(message)
print("Published messages(s)")

broker = JetStreamBroker(url="nats://localhost:4222")

service = Service(
name="example-service",
broker=broker,
middlewares=[SendMessageMiddleware()]
)


@service.subscribe(topic="test.topic")
async def example_run(message: CloudEvent):
print(f"Received Message {message.id} with data: {message.data}")
```

Run with

```shell
eventiq run app:service --log-level=info
```


## Watching for changes

```shell
eventiq run app:service --log-level=info --reload=.
```

## Testing

`StubBroker` class is provided as in memory replacement for running unit tests

```python
import os


def get_broker(**kwargs):
if os.getenv('ENV') == 'TEST':
from eventiq.backends.stub import StubBroker
return StubBroker()
else:
from eventiq.backends.rabbitmq import RabbitmqBroker
return RabbitmqBroker(**kwargs)

broker = get_broker()

```

Furthermore, subscribers are just regular python coroutines, so it's possible to test them simply by invocation

```python

# main.py
@service.subscribe(topic="test.topic")
async def my_subscriber(message: CloudEvent):
return 42

# tests.py
from main import my_subscriber

async def test_my_subscriber():
result = await my_subscriber(None)
assert result == 42

```

## CLI

Getting help:
```shell
eventiq --help
```

Installing shell autocompletion:
```shell
eventiq --install-completion [bash|zsh|fish|powershell|pwsh]
```

### Basic commands

- `run` - run service
- `docs` - generate AsyncAPI docs
- `send` - send message to broker
47 changes: 47 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
version: "3.8"
services:
nats:
image: nats:latest
command: "-js"
ports:
- "4222:4222"
volumes:
- nats-jetstream:/tmp/nats/jetstream
- nats-data:/data
redis:
image: redis:alpine
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq:3.8-management
environment:
RABBITMQ_DEFAULT_USER: rabbitmq
RABBITMQ_DEFAULT_PASS: rabbitmq
ports:
- "5672:5672"
- "1567:1567"
- "15672:15672"
zookeeper:
image: docker.io/bitnami/zookeeper:3.8
ports:
- "2181:2181"
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
kafka:
image: docker.io/bitnami/kafka:3.2
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_CFG_ZOOKEEPER_CONNECT: "zookeeper:2181"
ALLOW_PLAINTEXT_LISTENER: "yes"
KAFKA_CREATE_TOPICS: "test.topic:1:1"
KAFKA_ADVERTISED_HOST_NAME: localhost:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
depends_on:
- zookeeper

volumes:
nats-jetstream:
nats-data:
1 change: 1 addition & 0 deletions eventiq/__about__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.3.0rc0"
20 changes: 20 additions & 0 deletions eventiq/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from .__about__ import __version__
from .broker import Broker
from .consumer import Consumer, ConsumerGroup, GenericConsumer
from .middleware import Middleware
from .models import CloudEvent, Publishes
from .results import Result
from .service import Service

__all__ = [
"__version__",
"Broker",
"Consumer",
"ConsumerGroup",
"CloudEvent",
"Publishes",
"GenericConsumer",
"Middleware",
"Result",
"Service",
]
4 changes: 4 additions & 0 deletions eventiq/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .cli import cli

if __name__ == "__main__":
cli()
Loading

0 comments on commit 008ca7a

Please sign in to comment.