Skip to content

Commit

Permalink
Version 0 3 0 (#11)
Browse files Browse the repository at this point in the history
* added websockets dependency

* client and logger added

* documentos updated
  • Loading branch information
NathanDraco22 authored Nov 26, 2024
1 parent 7e03ff6 commit e77c995
Show file tree
Hide file tree
Showing 7 changed files with 425 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ nosetests.xml
zap
server.py
sanic_server.py
starlette_server.py
starlette_server.py.
client_demo.py
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,31 @@ async def disconnected_trigger(ctx: EventContext):
```
> Error details in `payload`
## Client

Zaptools provides a python client to connect with others zaptools server

```python
from zaptools.client import ZapClient

client = ZapClient()
await client.connect("ws://localhost:8000/") #Connect to the server

await client.send("event1", {"hello":"from client"}, {}) # send a event

# A generator with all event stream
# Receive all events
async for event in client.event_stream():
print(event.payload)


# A generator with all connection state
# Receive connection state
# ONLINE, OFFLINE, CONNNECTING and ERROR state
async for state in client.connection_state():
print(state)

```


## Contributions are wellcome
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "zaptools"
description = "A toolkit for Event-Driven websocket management"
readme = "README.md"
authors = [{name = "Nathan Mejia", email = "[email protected]"}]
version = "0.2.5"
version = "0.3.0"
requires-python = ">=3.10"
classifiers = [
'Development Status :: 4 - Beta',
Expand All @@ -17,6 +17,10 @@ classifiers = [
'Programming Language :: Python :: 3.10',
]
license = { file = "LICENSE" }
dependencies = [
"rich>=13.9.4",
"websockets>=14.1",
]

[project.urls]
"Homepage" = "https://github.com/NathanDraco22/zaptools-python"
Expand All @@ -25,3 +29,8 @@ license = { file = "LICENSE" }
dev = [
"pytest"
]

[dependency-groups]
dev = [
"build>=1.2.2.post1",
]
260 changes: 260 additions & 0 deletions uv.lock

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions zaptools/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import enum
import json
import asyncio
from typing import Any, AsyncGenerator
from websockets.asyncio.client import connect

from .tools import EventData
from .zap_logger import zap_logger

class ZapClientState(enum.Enum):
ONLINE = 1
OFFLINE = 2
CONNECTING = 3
ERROR = 4

class ZapClient:

_connection_state_queue = asyncio.Queue[ZapClientState]()
_current_state = ZapClientState.OFFLINE

async def connect(self, url: str) -> None:
await self._update_connection_state(ZapClientState.CONNECTING)
self._conn = await connect(url)
await self._update_connection_state(ZapClientState.ONLINE)
zap_logger.info_green(f"Connected to {url}")


async def send(
self,
event_name: str,
payload: dict[str, Any],
headers: dict[str, Any]|None
) -> None:
conn = self._conn
inner_header = headers if headers is not None else {}
event_data = EventData(event_name, payload, inner_header)
event_json = json.dumps(event_data.to_dict())
await conn.send(event_json)


async def event_stream(self) -> AsyncGenerator[EventData, None]:
conn = self._conn
while True:
try:
data = await conn.recv()
except Exception:
await self._update_connection_state(ZapClientState.ERROR)
zap_logger.error("Error receiving data from server")
break
data = json.loads(data)
try:
event_data = EventData(
data["eventName"],
data["payload"],
data["headers"]
)
yield event_data
except Exception:
await self._update_connection_state(ZapClientState.ERROR)
zap_logger.error("Error parsing event from server")
break
await self._update_connection_state(ZapClientState.OFFLINE)
zap_logger.warning("Disconnected from server")

async def connection_state(self) -> AsyncGenerator[ZapClientState, None]:
while True:
yield await self._connection_state_queue.get()

async def close(self) -> None:
await self._conn.close()

async def _update_connection_state(self, state: ZapClientState):
self._current_state = state
await self._connection_state_queue.put(state)




18 changes: 18 additions & 0 deletions zaptools/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
from typing import Callable, Any
from .protocols import ConnectionAdapter

class RequestInfo:
host: str
port: int
base_url: str

def __init__(self, host: str, port: int, base_url: str) -> None:
self.host = host
self.port = port
self.base_url = base_url


class Event:
def __init__(self, name:str, callback: Callable) -> None:
self.name = name
Expand All @@ -13,6 +24,13 @@ def __init__(self, event_name:str, payload:Any, headers: dict[str, Any]) -> None
self.event_name = event_name
self.payload = payload
self.headers = headers

def to_dict(self) -> dict[str, Any]:
return {
"eventName": self.event_name,
"payload": self.payload,
"headers": self.headers
}

class WebSocketConnection:

Expand Down
30 changes: 30 additions & 0 deletions zaptools/zap_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import logging
from rich.logging import RichHandler


FORMAT = "%(message)s"
class ZapLogger():
def __init__(self):
logging.basicConfig(
level= logging.INFO,
format=FORMAT, datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)]
)
self.logger = logging.getLogger('zap_logger')

def info(self, message):
self.logger.info(f"[Zap]{message}")

def info_green(self, message):
self.logger.info(f"[green][Zap]{message}", extra={"markup": True})

def warning(self, message):
self.logger.warning(f"[yellow][Zap]{message}", extra={"markup": True})

def error(self, message):
self.logger.error(f"[red][Zap]{message}", extra={"markup": True})


zap_logger = ZapLogger()


0 comments on commit e77c995

Please sign in to comment.