Releases: sparckles/Robyn
v0.12.0 - Events added
Robyn now supports startup and shutdown events and is now backed with a more stable build backend.
Sample Usage
You can either use the decorator syntax or the functional call syntax to organise your code.
async def startup_handler():
logger.log(logging.INFO, "Starting up")
app.startup_handler(startup_handler)
@app.shutdown_handler
def shutdown_handler():
logger.log(logging.INFO, "Shutting down")
What's Changed
Special Thanks
- @klaa97 for making robyn's PR at TechEmpoweredBenchmark's repo!
Full Changelog: v0.11.1...v0.12.0
v0.11.1 - Make Robyn work with docker containers
This is a minor release. An input statement was being used instead of a print statement. This will help with docker containers and also Tech-Empowered benchmarking.
What's Changed
New Contributors
Full Changelog: v0.11.0...v0.11.1
Special thanks to @klaa97 for identifying the bug and fixing it! 🥳
v0.11.0 - Add support for query params across EVERY HTTP method and dev server fix
What's Changed
- Add project-wide flake8 settings by @sansyrox in #143
- URL queries by @patchgamestudio in #146
- Fix dev server by @sansyrox in #148
New Contributors
- @patchgamestudio made their first contribution in #146
Full Changelog: v0.10.0...v0.11.0
Summary
You can make a request to http://localhost:5000/query?a=b
and can access the params in the following way:
@app.get("/query")
async def query_get(request):
query_data = request["queries"]
return jsonify(query_data)
The hot reloading server is now fixed and works on Linux and OSX(i.e. on all important operating systems). Windows support for dev server is still WIP.
Special thanks to @patchgamestudio for their contribution with the query params 🥳
v0.10.0 - Async Functions now supported in Web Sockets
The latest version of Robyn supports async functions in WebSockets now!
Usage
@websocket.on("message")
async def connect():
global i
i+=1
if i==0:
return "Whaaat??"
elif i==1:
return "Whooo??"
elif i==2:
return "*chika* *chika* Slim Shady."
elif i==3:
i= -1
return ""
@websocket.on("close")
async def close():
return "Goodbye world, from ws"
@websocket.on("connect")
async def message():
return "Hello world, from ws"
Important Note:
Async functions execute out of order. i.e. They do not block the actor in order to provide concurrent execution. However, if the order is essential for you while using async functions, try using sync functions for now or wait for a future release :D
What's Changed
- FIX : Wrong link for Test Drive by @shivaylamba in #117
- [FEAT] Open Source Contribution Templates by @shivaylamba in #118
- Release v0.9.0 Changelog by @sansyrox in #121
- Fix readme documentation by @sansyrox in #122
- Update comparison.md by @Kludex in #123
- Update comparison.md by @Kludex in #124
- Apply Python highlight on api.md by @Kludex in #126
- Add help messages and simplify 'dev' option by @Kludex in #128
- Add async support in WS by @sansyrox in #134
New Contributors
- @shivaylamba made their first contribution in #117
- @Kludex made their first contribution in #123
Full Changelog: v0.9.0...v0.10.0
Thanks
Thanks to @Kludex @awestlake87 @ShadowJonathan @shivaylamba @messense for their help(Issues/PRs) with this release. 😄
v0.9.0 | WebSockets
Websocket Intergration
PR Links
Full Changelog: v0.8.0...v0.9.0
Robyn supports WebSockets now.
from robyn import Robyn, WS
app = Robyn(__file__)
websocket = WS(app, "/web_socket")
i = -1
@websocket.on("message")
def connect():
global i
i+=1
if i==0:
return "Whaaat??"
elif i==1:
return "Whooo??"
elif i==2:
i = -1
return "*chika* *chika* Slim Shady."
@websocket.on("close")
def close():
print("Hello world")
return "Hello world, from ws"
@websocket.on("connect")
def message():
print("Hello world")
return "Hello world, from ws"
You can have 3 methods for every web socket:
"message", "close" and "connect" for responding to the message received, connection closed and connection initiated.
v0.8.1 (Minor Bug fix)
What's Changed
Full Changelog: v0.8.0...v0.8.1
app.start(url='127.0.0.1')
no longer defaults to 0.0.0.0
and the defaults are also set to their original value.
v0.8.0
The latest version of Robyn now scales across multiple cores!!
PR LInks
Full Changelog: https://github.com/sansyrox/robyn/blob/main/CHANGELOG.md#v080-2021-11-10
You can select the number of processes and workers now:
python3 app.py --workers=5 --processes=5
Performance Comparison [CPU under very heavy strain]
Robyn - 5 Processes and 5 Workers (v0.8.0)
╰─ oha -n 500000 -c 50 http://localhost:5000/ --no-tui ─╯
Summary:
Success rate: 1.0000
Total: 74.1781 secs
Slowest: 0.1318 secs
Fastest: 0.0005 secs
Average: 0.0074 secs
Requests/sec: 6740.5379
Total data: 21.93 MiB
Size/request: 46 B
Size/sec: 302.80 KiB
Response time histogram:
0.002 [49062] |■■■■■■■■■■■■■
0.004 [115710] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.006 [105245] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.008 [72753] |■■■■■■■■■■■■■■■■■■■■
0.010 [54286] |■■■■■■■■■■■■■■■
0.012 [36697] |■■■■■■■■■■
0.014 [22683] |■■■■■■
0.016 [14015] |■■■
0.018 [8920] |■■
0.020 [5815] |■
0.022 [14814] |■■■■
Latency distribution:
10% in 0.0025 secs
25% in 0.0037 secs
50% in 0.0060 secs
75% in 0.0095 secs
90% in 0.0136 secs
95% in 0.0171 secs
99% in 0.0278 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0026 secs, 0.0022 secs, 0.0031 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0001 secs
Status code distribution:
[200] 500000 responses
Robyn - 1 Process and 1 Worker (v0.7.1)
╰─ oha -n 500000 -c 50 http://localhost:5000/ --no-tui ─╯
Summary:
Success rate: 1.0000
Total: 101.4573 secs
Slowest: 0.0858 secs
Fastest: 0.0011 secs
Average: 0.0101 secs
Requests/sec: 4928.1797
Total data: 8.95 MiB
Size/request: 18 B
Size/sec: 90.37 KiB
Response time histogram:
0.003 [617] |
0.005 [23214] |■■■
0.008 [151605] |■■■■■■■■■■■■■■■■■■■■■
0.011 [228487] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.013 [55292] |■■■■■■■
0.016 [32058] |■■■■
0.019 [7509] |■
0.021 [747] |
0.024 [153] |
0.027 [52] |
0.029 [266] |
Latency distribution:
10% in 0.0075 secs
25% in 0.0086 secs
50% in 0.0097 secs
75% in 0.0111 secs
90% in 0.0138 secs
95% in 0.0155 secs
99% in 0.0178 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0034 secs, 0.0019 secs, 0.0043 secs
DNS-lookup: 0.0001 secs, 0.0000 secs, 0.0011 secs
Status code distribution:
[200] 500000 responses
Conclusion
It saves around 27 seconds under high strain conditions. The default configs are at 1 worker and 1 process. You can config for whatever works best for you.
Contributors
Special thanks to @JackThomson2 @messense @awestlake87 for the help with the socket sharing feature! 🥳 ✨
Release v0.7.1 [Bug Fixes]
This version contains major fixes of the previous version:
- Request objects are optional in every route handler now.
- Robyn's test suite is now fixed. Now you can expect more reliable shipments!
example below
@app.get("/jsonify")
async def json_get():
return jsonify({"hello": "world"})
v0.7.0
v0.6.1
This new release contains some major developments.
- Add the base of http requests #78 (sansyrox)
- Add default port and a variable url #77 (sansyrox)
- Make the request object accessible in every route #76 (sansyrox)
- Add the basics for circle ci and testing framework #67 (sansyrox)
- Update to pyo3 v0.14 #65 (sansyrox)
- Add the static directory serving #64 (sansyrox)
- Create a request object #61 (sansyrox)
- Add the ability to add body in PUT, PATCH and DELETE #60 (sansyrox)
- Implement a working dev server #40 (sansyrox)
- Use Actix as base #35 (JackThomson2)
Test use:
from robyn import Robyn, static_file, jsonify
import asyncio
app = Robyn(__file__)
callCount = 0
@app.get("/")
async def h(request):
print(request)
global callCount
callCount += 1
message = "Called " + str(callCount) + " times"
return message
@app.get("/test")
async def test():
import os
path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "index.html"))
return static_file(path)
@app.post("/jsonify")
async def json(request):
print(request)
return jsonify({"hello": "world"})
@app.post("/post")
async def postreq(request):
return bytearray(request["body"]).decode("utf-8")
@app.put("/put")
async def putreq(request):
return bytearray(request["body"]).decode("utf-8")
@app.delete("/delete")
async def deletereq(request):
return bytearray(request["body"]).decode("utf-8")
@app.patch("/patch")
async def patchreq(request):
return bytearray(request["body"]).decode("utf-8")
@app.get("/sleep")
async def sleeper():
await asyncio.sleep(5)
return "sleep function"
@app.get("/blocker")
def blocker():
import time
time.sleep(10)
return "blocker function"
if __name__ == "__main__":
app.add_header("server", "robyn")
app.add_directory(route="/test_dir",directory_path="./test_dir/build", index_file="index.html")
app.start(port=5000)