Skip to content

Commit

Permalink
Update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Ananto30 committed Jul 12, 2024
1 parent 0691f88 commit 5e0b1b1
Showing 1 changed file with 110 additions and 101 deletions.
211 changes: 110 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,103 +27,101 @@

**Features**:

* Zero provides **faster communication** (see [benchmarks](https://github.com/Ananto30/zero#benchmarks-)) between the microservices using [zeromq](https://zeromq.org/) under the hood.
* Zero uses messages for communication and traditional **client-server** or **request-reply** pattern is supported.
* Support for both **async** and **sync**.
* The base server (ZeroServer) **utilizes all cpu cores**.
* **Code generation**! See [example](https://github.com/Ananto30/zero#code-generation-) πŸ‘‡
* Zero provides **faster communication** (see [benchmarks](https://github.com/Ananto30/zero#benchmarks-)) between the microservices using [zeromq](https://zeromq.org/) under the hood.
* Zero uses messages for communication and traditional **client-server** or **request-reply** pattern is supported.
* Support for both **async** and **sync**.
* The base server (ZeroServer) **utilizes all cpu cores**.
* **Code generation**! See [example](https://github.com/Ananto30/zero#code-generation-) πŸ‘‡

**Philosophy** behind Zero:

* **Zero learning curve**: The learning curve is tends to zero. Just add functions and spin up a server, literally that's it! The framework hides the complexity of messaging pattern that enables faster communication.
* **ZeroMQ**: An awesome messaging library enables the power of Zero.
* **Zero learning curve**: The learning curve is tends to zero. Just add functions and spin up a server, literally that's it! The framework hides the complexity of messaging pattern that enables faster communication.
* **ZeroMQ**: An awesome messaging library enables the power of Zero.

Let's get started!

# Getting started πŸš€

*Ensure Python 3.8+*

```
pip install zeroapi
```
pip install zeroapi

**For Windows**, [tornado](https://pypi.org/project/tornado/) needs to be installed separately (for async operations). It's not included with `zeroapi` because for linux and mac-os, tornado is not needed as they have their own event loops.

* Create a `server.py`
* Create a `server.py`

```python
from zero import ZeroServer
```python
from zero import ZeroServer

app = ZeroServer(port=5559)
app = ZeroServer(port=5559)

@app.register_rpc
def echo(msg: str) -> str:
return msg
@app.register_rpc
def echo(msg: str) -> str:
return msg

@app.register_rpc
async def hello_world() -> str:
return "hello world"
@app.register_rpc
async def hello_world() -> str:
return "hello world"


if __name__ == "__main__":
app.run()
```
if __name__ == "__main__":
app.run()
```

* The **RPC functions only support one argument** (`msg`) for now.
* The **RPC functions only support one argument** (`msg`) for now.

* Also note that server **RPC functions are type hinted**. Type hint is **must** in Zero server. Supported types can be found [here](/zero/utils/type_util.py#L11).
* Also note that server **RPC functions are type hinted**. Type hint is **must** in Zero server. Supported types can be found [here](/zero/utils/type_util.py#L11).

* Run the server
* Run the server

```shell
python -m server
```
```shell
python -m server
```

* Call the rpc methods
* Call the rpc methods

```python
from zero import ZeroClient
```python
from zero import ZeroClient

zero_client = ZeroClient("localhost", 5559)
zero_client = ZeroClient("localhost", 5559)

def echo():
resp = zero_client.call("echo", "Hi there!")
print(resp)
def echo():
resp = zero_client.call("echo", "Hi there!")
print(resp)

def hello():
resp = zero_client.call("hello_world", None)
print(resp)
def hello():
resp = zero_client.call("hello_world", None)
print(resp)


if __name__ == "__main__":
echo()
hello()
```
if __name__ == "__main__":
echo()
hello()
```

* Or using async client -
* Or using async client -

```python
import asyncio
```python
import asyncio

from zero import AsyncZeroClient
from zero import AsyncZeroClient

zero_client = AsyncZeroClient("localhost", 5559)
zero_client = AsyncZeroClient("localhost", 5559)

async def echo():
resp = await zero_client.call("echo", "Hi there!")
print(resp)
async def echo():
resp = await zero_client.call("echo", "Hi there!")
print(resp)

async def hello():
resp = await zero_client.call("hello_world", None)
print(resp)
async def hello():
resp = await zero_client.call("hello_world", None)
print(resp)


if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(echo())
loop.run_until_complete(hello())
```
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(echo())
loop.run_until_complete(hello())
```

# Serialization πŸ“¦

Expand Down Expand Up @@ -160,7 +158,7 @@ def save_order(order: Order) -> bool:
...
```

## Return type
## Return type on client

The return type of the RPC function can be any of the [supported types](https://jcristharif.com/msgspec/supported-types.html). If `return_type` is set in the client `call` method, then the return type will be converted to that type.

Expand All @@ -177,59 +175,76 @@ def get_order(id: str) -> Order:

# Code Generation πŸ€–

Easy to use code generation tool is also provided!
Easy to use code generation tool is also provided with schema support!

After running the server, like above, call the server to get the client code. This makes it easy to know what functions are available in the local or remote server.
* After running the server, like above, it calls the server to get the client code.

This makes it easy to get the latest schemas on live servers and not to maintain other file sharing approach to manage schemas.

Using `zero.generate_client` generate client code for even remote servers using the `--host` and `--port` options.
Using `zero.generate_client` generate client code for even remote servers using the `--host` and `--port` options.

```shell
python -m zero.generate_client --host localhost --port 5559 --overwrite-dir ./my_client
```
```shell
python -m zero.generate_client --host localhost --port 5559 --overwrite-dir ./my_client
```

It will generate client like this -
* It will generate client like this -

```python
import typing # remove this if not needed
from typing import List, Dict, Union, Optional, Tuple # remove this if not needed
from zero import ZeroClient
```python
from dataclasses import dataclass
from msgspec import Struct
from datetime import datetime

from zero import ZeroClient

zero_client = ZeroClient("localhost", 5559)

zero_client = ZeroClient("localhost", 5559)

class RpcClient:
def __init__(self, zero_client: ZeroClient):
self._zero_client = zero_client
class Person(Struct):
name: str
age: int
dob: datetime

def echo(self, msg: str) -> str:
return self._zero_client.call("echo", msg)

def hello_world(self, msg: str) -> str:
return self._zero_client.call("hello_world", msg)
```
@dataclass
class Order:
id: int
amount: float
created_at: datetime

Use the client -

```python
from my_client import RpcClient, zero_client
class RpcClient:
def __init__(self, zero_client: ZeroClient):
self._zero_client = zero_client

client = RpcClient(zero_client)
def save_person(self, person: Person) -> bool:
return self._zero_client.call("save_person", person)

if __name__ == "__main__":
client.echo("Hi there!")
client.hello_world(None)
```
def save_order(self, order: Order) -> bool:
return self._zero_client.call("save_order", order)
```

Currently, the code generation tool supports only `ZeroClient` and not `AsyncZeroClient`.
Check the schemas are copied!

*WIP - Generate models from server code.*
* Use the client -

```python
from my_client import RpcClient, zero_client

client = RpcClient(zero_client)

if __name__ == "__main__":
client.save_person(Person(name="John", age=25, dob=datetime.now()))
client.save_order(Order(id=1, amount=100.0, created_at=datetime.now()))
```

*If you want a async client just replace `ZeroClient` with `AsyncZeroClient` in the generated code, and update the methods to be async. (Next version will have async client generation, hopefully πŸ˜…)*

# Important notes! πŸ“

* `ZeroServer` should always be run under `if __name__ == "__main__":`, as it uses multiprocessing.
* `ZeroServer` creates the workers in different processes, so anything global in your code will be instantiated N times where N is the number of workers. So if you want to initiate them once, put them under `if __name__ == "__main__":`. But recommended to not use global vars. And Databases, Redis, other clients, creating them N times in different processes is fine and preferred.
* The methods which are under `register_rpc()` in `ZeroServer` should have **type hinting**, like `def echo(msg: str) -> str:`
## For multiprocessing

* `ZeroServer` should always be run under `if __name__ == "__main__":`, as it uses multiprocessing.
* `ZeroServer` creates the workers in different processes, so anything global in your code will be instantiated N times where N is the number of workers. So if you want to initiate them once, put them under `if __name__ == "__main__":`. But recommended to not use global vars. And Databases, Redis, other clients, creating them N times in different processes is fine and preferred.

# Let's do some benchmarking! 🏎

Expand All @@ -239,8 +254,8 @@ So we will be testing a gateway calling another server for some data. Check the

There are two endpoints in every tests,

* `/hello`: Just call for a hello world response πŸ˜…
* `/order`: Save a Order object in redis
* `/hello`: Just call for a hello world response πŸ˜…
* `/order`: Save a Order object in redis

Compare the results! πŸ‘‡

Expand All @@ -262,12 +277,6 @@ Compare the results! πŸ‘‡

Seems like blacksheep is faster on hello world, but in more complex operations like saving to redis, zero is the winner! πŸ†

# Roadmap πŸ—Ί

* \[x] Make msgspec as default serializer
* \[ ] Add support for async server (currently the sync server runs async functions in the eventloop, which is blocking)
* \[ ] Add pub/sub support

# Contribution

Contributors are welcomed πŸ™
Expand Down

0 comments on commit 5e0b1b1

Please sign in to comment.