| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Zero is a simple Python framework (RPC like) to build fast and high performance microservices or distributed servers
Features:
Philosophy behind Zero:
The documentation can be found here.
Ensure Python 3.9+
pip install zeroapi pip install "zeroapi[uvloop]" # for better async performance on linux and mac-os pip install "zeroapi[pydantic]" # for pydantic support pip install "zeroapi[tornado]" # for windows async support pip install "zeroapi[all]" # for all extras
Create a server.py
from zero import ZeroServer
app = ZeroServer(port=5559)
@app.register_rpc
def echo(msg: str) -> str:
return msg
@app.register_rpc
async def hello_world() -> str:
return "hello world"
if __name__ == "__main__":
app.run()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.
Run the server
python -m serverCall the rpc methods
from zero import ZeroClient
zero_client = ZeroClient("localhost", 5559)
def echo():
resp = zero_client.call("echo", "Hi there!")
print(resp)
def hello():
resp = zero_client.call("hello_world", None)
print(resp)
if __name__ == "__main__":
echo()
hello()Or using async client -
import asyncio
from zero import AsyncZeroClient
zero_client = AsyncZeroClient("localhost", 5559)
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)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(echo())
loop.run_until_complete(hello())By default Zero uses ZeroMQ for communication. But if you want to use raw TCP, you can use the protocol parameter.
from zero import ZeroServer
from zero.protocols.tcp import TCPServer
app = ZeroServer(port=5559, protocol=TCPServer) # <-- Note the protocol parameter
@app.register_rpc
def echo(msg: str) -> str:
return msg
@app.register_rpc
async def hello_world() -> str:
return "hello world"
if __name__ == "__main__":
app.run()In that case the client should also use TCP protocol.
import asyncio
from zero import AsyncZeroClient
from zero import ZeroClient
from zero.protocols.tcp import AsyncTCPClient
zero_client = ZeroClient("localhost", 5559, protocol=AsyncTCPClient) # <-- Note the protocol parameter
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)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(echo())
loop.run_until_complete(hello())TCP has better performance and throughput than ZeroMQ. We might make it the default protocol in future releases.
Msgspec is the default serializer. So msgspec.Struct (for high performance) or dataclass or any supported types can be used easily to pass complex arguments, i.e.
from dataclasses import dataclass
from msgspec import Struct
from zero import ZeroServer
app = ZeroServer()
class Person(Struct):
name: str
age: int
dob: datetime
@dataclass
class Order:
id: int
amount: float
created_at: datetime
@app.register_rpc
def save_person(person: Person) -> bool:
# save person to db
...
@app.register_rpc
def save_order(order: Order) -> bool:
# save order to db
...Pydantic models are also supported out of the box. Just use pydantic.BaseModel as the argument or return type and install zero with pydantic extra.
pip install zeroapi[pydantic]
If you want to use a custom serializer, you can create your own serializer by implementing the Encoder interface.
class MyCustomEncoder(Encoder):
def encode(self, obj: Any) -> bytes:
# implement your custom serialization logic here
...
def decode(self, data: bytes, type_hint: Type[Any]) -> Any:
# implement your custom deserialization logic here
...Then pass the serializer to both* server and client.
from zero import ZeroServer, ZeroClient
from my_custom_encoder import MyCustomEncoder
app = ZeroServer(port=5559, encoder=MyCustomEncoder)
zero_client = ZeroClient("localhost", 5559, encoder=MyCustomEncoder)The return type of the RPC function can be any of the supported types. If return_type is set in the client call method, then the return type will be converted to that type.
@dataclass
class Order:
id: int
amount: float
created_at: datetime
def get_order(id: str) -> Order:
return zero_client.call("get_order", id, return_type=Order)Easy to use code generation tool is also provided with schema support!
After running the server, like above, you can generate client code using the zero.generate_client module.
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, --port, and --protocol options.
python -m zero.generate_client --host localhost --port 5559 --protocol zmq --overwrite-dir ./my_clientIt will generate client like this -
from dataclasses import dataclass
from msgspec import Struct
from datetime import datetime
from zero import ZeroClient
zero_client = ZeroClient("localhost", 5559)
class Person(Struct):
name: str
age: int
dob: datetime
@dataclass
class Order:
id: int
amount: float
created_at: datetime
class RpcClient:
def __init__(self, zero_client: ZeroClient):
self._zero_client = zero_client
def save_person(self, person: Person) -> bool:
return self._zero_client.call("save_person", person)
def save_order(self, order: Order) -> bool:
return self._zero_client.call("save_order", order)Check the schemas are copied!
Use the client -
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()))To generate async client code, use the --async flag.
python -m zero.generate_client --host localhost --port 5559 --protocol zmq --overwrite-dir ./my_async_client --async*tcp protocol will always generate async client.
Zero is all about inter service communication. In most real life scenarios, we need to call another microservice.
So we will be testing a gateway calling another server for some data. Check the benchmark/dockerize folder for details.
There are two endpoints in every tests,
Compare the results! 👇
13th Gen Intel® Core™ i9-13900HK @ 5.40GHz, 14 cores, 20 threads, 32GB RAM (Docker in Ubuntu 22.04.2 LTS)
(Sorted alphabetically)
| Framework | "hello world" (req/s) | 99% latency (ms) | redis save (req/s) | 99% latency (ms) |
|---|---|---|---|---|
| aiohttp | 33167.69 | 11.89 | 17959.46 | 12.76 |
| aiozmq | 25174.24 | 6.13 | 8850.15 | 10.19 |
| blacksheep | 38025.53 | 8.41 | 16324.19 | 13.54 |
| fastApi | 19682.99 | 9.09 | 12775.97 | 16.28 |
| sanic | 58811.27 | 4.43 | 23622.69 | 9.22 |
| zero(sync) | 27570.85 | 6.65 | 10269.1 | 23.71 |
| zero(async) | 41091.96 | 4.41 | 23996.18 | 8.64 |
| zero(tcp) | 100752.12 | 2.33 | 35812.88 | 13.48 |
Contributors are welcomed 🙏
Please leave a star ⭐ if you like Zero!
| Back | FazBrowse Home | New Git URL |