diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d13d2aec0..72bafc661 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,23 +5,28 @@ title: '[BUG]' labels: 'kind/bug' assignees: '' --- + **Description** + **Expected Behavior** + **Screenshots** + **Environment:** -- Host OS: -- Browser: ---- +- Host OS: +- Browser: +--- [Optional] **Additional Context** + ---- \ No newline at end of file +--- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 06d77568d..8c59f4026 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,17 +5,23 @@ title: '[Feature Request]' labels: 'kind/enhancement' assignees: '' --- + **Current Behavior** + **Desired Behavior** + --- + **Screenshots / Mockups** + **Alternatives** + ---- \ No newline at end of file +--- diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ed6e79b87..f555c445a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,12 +3,12 @@ This PR fixes # \ No newline at end of file +--> diff --git a/CHANGELOG.md b/CHANGELOG.md index 12168cb56..151112123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,7 +76,7 @@ **Closed issues:** -- \[Feature Request\] Add list of sponsors on the project website [\#182](https://github.com/sansyrox/robyn/issues/182) +- \[Feature Request\] Add list of sponsors on the project website [\#182](https://github.com/sansyrox/robyn/issues/182) - Optional build feature for io\_uring [\#177](https://github.com/sansyrox/robyn/issues/177) - Create Custom headers for the response. [\#174](https://github.com/sansyrox/robyn/issues/174) @@ -169,7 +169,7 @@ **Closed issues:** -- Benchmarks to BjΓΆrn, uvicorn etc. [\#142](https://github.com/sansyrox/robyn/issues/142) +- Benchmarks to BjΓΆrn, uvicorn etc. [\#142](https://github.com/sansyrox/robyn/issues/142) - Add Python linter setup [\#129](https://github.com/sansyrox/robyn/issues/129) - Add fixtures in testing [\#84](https://github.com/sansyrox/robyn/issues/84) @@ -180,11 +180,11 @@ **Closed issues:** - Add PyPI classifiers [\#127](https://github.com/sansyrox/robyn/issues/127) -- Robyn version 0.9.0 doesn't work on Mac M1 Models [\#120](https://github.com/sansyrox/robyn/issues/120) -- Inconsistency in steps mentioned in Readme to run locally [\#119](https://github.com/sansyrox/robyn/issues/119) +- Robyn version 0.9.0 doesn't work on Mac M1 Models [\#120](https://github.com/sansyrox/robyn/issues/120) +- Inconsistency in steps mentioned in Readme to run locally [\#119](https://github.com/sansyrox/robyn/issues/119) - Async web socket support [\#116](https://github.com/sansyrox/robyn/issues/116) - Reveal Logo to be removed from Future Roadmap [\#107](https://github.com/sansyrox/robyn/issues/107) -- Dead Link for Test Drive Button on Robyn Landing Page [\#106](https://github.com/sansyrox/robyn/issues/106) +- Dead Link for Test Drive Button on Robyn Landing Page [\#106](https://github.com/sansyrox/robyn/issues/106) - Add issue template, pr template and community guidelines [\#105](https://github.com/sansyrox/robyn/issues/105) - For v0.7.0 [\#72](https://github.com/sansyrox/robyn/issues/72) - Add better support for requests and response! [\#13](https://github.com/sansyrox/robyn/issues/13) @@ -329,7 +329,7 @@ - Make html file serving more robust [\#45](https://github.com/sansyrox/robyn/issues/45) - Try to serve individual static files using vanilla rust [\#43](https://github.com/sansyrox/robyn/issues/43) -- Error on import [\#16](https://github.com/sansyrox/robyn/issues/16) +- Error on import [\#16](https://github.com/sansyrox/robyn/issues/16) - Add multiple process sharing [\#2](https://github.com/sansyrox/robyn/issues/2) ## [v0.5.0](https://github.com/sansyrox/robyn/tree/v0.5.0) (2021-07-01) @@ -392,6 +392,4 @@ - Improve async runtime [\#3](https://github.com/sansyrox/robyn/issues/3) - - -\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* +\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a8994019..713ccca50 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,33 +1,33 @@ ## Contributing Guidelines -First off, thank you for considering contributing to ```Robyn```. This guide details all the general information that one should know before contributing to the project. -Please stick as close as possible to the guidelines. That way we ensure that you have a smooth experience contributing to this project. +First off, thank you for considering contributing to `Robyn`. This guide details all the general information that one should know before contributing to the project. +Please stick as close as possible to the guidelines. That way, we ensure that you have a smooth experience contributing to this project. -### General Rules : -These are in general rules that you should be following while contributing to an Open Source project : +### General Rules: + +These are, in general, rules that you should be following while contributing to an Open-Source project : - Be Nice, Be Respectful (BNBR) - Check if the Issue you created, exists or not. -- While creating a new issue make sure you describe the issue clearly. +- While creating a new issue, make sure you describe the issue clearly. - Make proper commit messages and document your PR well. -- Always add Comments in your Code and explain it at points, if possible add Doctest. +- Always add comments in your Code and explain it at points if possible, add Doctest. - Always create a Pull Request from a Branch; Never from the Main. - Follow proper code conventions because writing clean code is important. -- Issues would be assigned on a "First Come, First Serve" basis. -- Do mention (@maintainer) the project maintainer if your PR isn't reviewed within few days. - -## First time contributors : +- Issues would be assigned on a "First Come, First Served" basis. +- Do mention (@maintainer) the project maintainer if your PR isn't reviewed within a few days. -Pushing files in your own repository is easy but how to contribute to someone else's project? If you have the same question then below are the steps that you can follow -to make your first contribution in this repoitory. +## First time contributors: +Pushing files in your own repository is easy, but how to contribute to someone else's project? If you have the same question, then below are the steps that you can follow +to make your first contribution in this repository. ### Pull Request -**1.** The very first step includes forking the project. Click on the ```fork``` button as shown below to fork the project. +**1.** The very first step includes forking the project. Click on the `fork` button as shown below to fork the project.


-**2.** Clone the forked repository. Open up the GitBash/Command Line and type +**2.** Clone the forked repository. Open up the GitBash/Command Line and type ``` git clone https://github.com//robyn.git @@ -38,13 +38,14 @@ git clone https://github.com//robyn.git ``` cd robyn ``` + **4.** Add a reference to the original repository. ``` git remote add upstream https://github.com/sansyrox/robyn.git ``` -**5.** See latest changes to the repo using +**5.** See latest changes to the repo using ``` git remote -v @@ -56,67 +57,62 @@ git remote -v git checkout -b ``` -**7.** Always take a pull from the upstream repository to your main branch to keep it even with the main project. This will save your from frequent merge conflicts. +**7.** Always take a pull from the upstream repository to your main branch to keep it even with the main project. This will save you from frequent merge conflicts. ``` git pull upstream main ``` -**8.** You can make the required changes now. Make appropriate commits with proper commit messages. +**8.** You can make the required changes now. Make appropriate commits with proper commit messages. **9.** Add and then commit your changes. ``` git add . ``` + ``` git commit -m "" ``` - **10.** Push your local branch to the remote repository. ``` git push -u origin ``` -**11.** Once you have pushed the changes to your repository, go to your forked repository. Click on the ```Compare & pull request``` button as shown below. +**11.** Once you have pushed the changes to your repository, go to your forked repository. Click on the `Compare & pull request` button as shown below.


- -**12.** The image below is how the new page would look like. Give a proper title to your PR and describe the changes made by you in the description box.(Note - Sometimes there are PR templates which is to be filled as instructed.) +**12.** The image below is what the new page would look like. Give a proper title to your PR and describe the changes made by you in the description box.(Note - Sometimes there are PR templates which are to be filled as instructed.)


+**13.** Open a pull request by clicking the `Create pull request` button. -**13.** Open a pull request by clicking the ```Create pull request``` button. - -```Voila, you have made your first contribution to this project``` +`Voila, you have made your first contribution to this project` -## Issue +## Issue -- Issues can be used to keep track of bugs, enhancements, or other requests. Creating an issue to let the project maintainers know about the changes your are - planning to make before raising a PR is a good open source practice. -
+- Issues can be used to keep track of bugs, enhancements, or other requests. Creating an issue to let the project maintainers know about the changes you are planning to make before raising a PR is a good open-source practice. +
-Lets walkthrough the steps to create an issue: +Let's walk through the steps to create an issue: -**1.** On GitHub navigate to the main page of the repository. [Here](https://github.com/sansyrox/robyn.git) in this case. +**1.** On GitHub, navigate to the main page of the repository. [Here](https://github.com/sansyrox/robyn.git) in this case. - -**2.** Under your repository name, click on the ```Issues``` button. +**2.** Under your repository name, click on the `Issues` button.


- -**3.** Click on the ```New issue``` button. +**3.** Click on the `New issue` button.


**4.** Select one of the Issue Templates to get started.


-**4.** Fill in the appropriate ```Title``` and ```Issue description``` and click on ```Submit new issue```. +**4.** Fill in the appropriate `Title` and `Issue description` and click on `Submit new issue`.


-### Tutorials that may help you : +### Tutorials that may help you: - [Git & GitHub Tutorial](https://www.youtube.com/watch?v=RGOj5yH7evk) -- [Resolve merge conflict](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github) \ No newline at end of file +- [Resolve merge conflict](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github) diff --git a/README.md b/README.md index 7bb59b235..939da195b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - Robyn Logo # Robyn @@ -11,12 +10,10 @@ [![License](https://img.shields.io/badge/License-BSD_2.0-black)](#license) [![Discord](https://img.shields.io/discord/999782964143603713?label=discord&logo=discord&logoColor=white&style=for-the-badge&color=blue)](https://discord.gg/rkERZ5eNU8) - [![view - Documentation](https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge)](https://sansyrox.github.io/robyn/#/) Robyn is a fast async Python web server with a runtime written in Rust. - Check out the talk at **PyCon Sweden 2021** about [Robyn: An async python web framework with a Rust runtime](https://www.youtube.com/watch?v=DK9teAs72Do) ## πŸ“¦ Installation @@ -36,7 +33,6 @@ conda install -c conda-forge robyn ## πŸ€” Usage ```python - from robyn import Robyn app = Robyn(__file__) @@ -46,10 +42,10 @@ async def h(request): return "Hello, world!" app.start(port=5000) - ``` ## πŸ’‘ Features + - Under active development! - Written in Rust, btw xD - A multithreaded Runtime @@ -63,7 +59,6 @@ app.start(port=5000) - Hot Reloading - Community First and truly FOSS! - ## πŸ—’οΈ Contributor Guidelines Feel free to open an issue for any clarification or for any suggestions. @@ -76,7 +71,7 @@ If you're feeling curious. You can take a look at a more detailed architecture [ 1. Install the pre-commit git hooks: `pre-commit install` -1. Add more routes in the `integration_tests/base_routes.py` file(if you like). +1. Add more routes in the `integration_tests/base_routes.py` file (if you like). 1. Run `maturin develop` or `maturin develop --cargo-extra-args="--features=io-uring"` (if you want to run the experimental version). @@ -99,7 +94,6 @@ optional arguments: --log-level LEVEL : this flag allows you to set the log level ``` - ## ✨ Contributors/Supporters To contribute to Robyn, make sure to first go through the [CONTRIBUTING.md](./CONTRIBUTING.md). @@ -110,8 +104,7 @@ Thanks to all the contributors of the project. Robyn will not be what it is with - -Special thanks to the [ PyO3 ](https://pyo3.rs/v0.13.2/) community and [ Andrew from PyO3-asyncio ](https://github.com/awestlake87/pyo3-asyncio) for their amazing libraries and their support for my queries. πŸ’– +Special thanks to the [PyO3](https://pyo3.rs/v0.13.2/) community and [Andrew from PyO3-asyncio](https://github.com/awestlake87/pyo3-asyncio) for their amazing libraries and their support for my queries. πŸ’– ## ✨ Sponsors @@ -119,7 +112,4 @@ These sponsors help us make the magic happen! [![DigitalOcean Referral Badge](https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%201.svg)](https://www.digitalocean.com/?refcode=3f2b9fd4968d&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) -- [ Shivay Lamba ](https://github.com/shivaylamba) - - - +- [Shivay Lamba](https://github.com/shivaylamba) diff --git a/docs/README.md b/docs/README.md index 3625c0d67..a98fc2283 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,3 @@ - Robyn Logo # Robyn @@ -11,12 +10,10 @@ [![License](https://img.shields.io/badge/License-BSD_2.0-black)](#license) [![Discord](https://img.shields.io/discord/999782964143603713?label=discord&logo=discord&logoColor=white&style=for-the-badge&color=blue)](https://discord.gg/qKF5sSnC) - [![view - Documentation](https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge)](https://sansyrox.github.io/robyn/#/) Robyn is an async Python backend server with a runtime written in Rust, btw. - ## πŸ“¦ Installation You can simply use Pip for installation. @@ -34,20 +31,21 @@ conda install -c conda-forge robyn ## πŸ€” Usage ```python - from robyn import Robyn app = Robyn(__file__) + @app.get("/") async def h(request): return "Hello, world!" -app.start(port=5000) +app.start(port=5000) ``` ## πŸ’‘ Features + - Under active development! - Written in Rust, btw xD - A multithreaded Runtime @@ -61,7 +59,6 @@ app.start(port=5000) - Hot Reloading - Community First and truly FOSS! - ## πŸ—’οΈ Contributor Guidelines Feel free to open an issue for any clarification or for any suggestions. @@ -97,7 +94,6 @@ optional arguments: --log-level LEVEL : this flag allows you to set the log level ``` - ## ✨ Contributors/Supporters To contribute to Robyn, make sure to first go through the [CONTRIBUTING.md](./CONTRIBUTING.md). @@ -108,8 +104,7 @@ Thanks to all the contributors of the project. Robyn will not be what it is with - -Special thanks to the [ PyO3 ](https://pyo3.rs/v0.13.2/) community and [ Andrew from PyO3-asyncio ](https://github.com/awestlake87/pyo3-asyncio) for their amazing libraries and their support for my queries. πŸ’– +Special thanks to the [PyO3](https://pyo3.rs/v0.13.2/) community and [Andrew from PyO3-asyncio](https://github.com/awestlake87/pyo3-asyncio) for their amazing libraries and their support for my queries. πŸ’– ## ✨ Sponsors @@ -117,7 +112,4 @@ These sponsors help us make the magic happen! [![DigitalOcean Referral Badge](https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%201.svg)](https://www.digitalocean.com/?refcode=3f2b9fd4968d&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) -- [ Shivay Lamba ](https://github.com/shivaylamba) - - - +- [Shivay Lamba](https://github.com/shivaylamba) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 8dc6c65c6..0f327019d 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -15,4 +15,3 @@ - [Hosting](hosting.md) - [Templates](templates.md) - [Sponsors](sponsors.md) - diff --git a/docs/architecture.md b/docs/architecture.md index b66f86d68..fbdea36a7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,7 +2,7 @@ Robyn is a Python web server that uses the tokio runtime. -First of all, we have a worker event cycle that basically does all the dirty work. This part manages the runtime and passes all instructions to the rust code. +First of all, we have a worker event cycle that basically does all the dirty work. This part manages the runtime and passes all instructions to the rust code. This spawns the threading pool Then when we type the command `python3 app.py` the python code is converted to rust objects and then the router is populated. The incoming requests hit the router and then the rust objects are dispatched in the thread pool and executed depending on their types @@ -13,11 +13,9 @@ Now, we can have multiple workers as well as multiple processes in Robyn. This a ![Multi Core Scaling](./assets/architecture/multi-processing.png) - - ## Const Requests -Const Requests is a feature that is unique to Robyn. +Const Requests is a feature that is unique to Robyn. What if we could execute the function only once and store the response in the rust response. This would help us save a lot of overhead of calling the router. @@ -27,8 +25,6 @@ This is exactly what const requests tries to achieve. ![Const Request Optimisation](./assets/architecture/const-request-optimisation.png) - -       @@ -36,10 +32,8 @@ This is exactly what const requests tries to achieve.     - - - ### Old architecture + ![Diagram](https://i.ibb.co/cNV4DJX/image.png) ![Diagram of the final Architecture](https://i.ibb.co/GHwTTqk/Untitled-2021-02-25-0125-1.png) diff --git a/docs/community-resources.md b/docs/community-resources.md index 30f21860e..caf8f6444 100644 --- a/docs/community-resources.md +++ b/docs/community-resources.md @@ -11,6 +11,6 @@ - [Hello, Robyn!](https://www.sanskar.me/posts/hello-robyn) - ### Upcoming Talks + - Coming Soon... diff --git a/docs/comparison.md b/docs/comparison.md index c513d333f..6b63c8388 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -2,59 +2,68 @@ ## Read this before you scroll down -The comparison is not meant to defame any of the of the developers or the frameworks listed below. The names of the frameworks have been listed for a clear comparison. All of these frameworks are the reason for me having a high inclination towards the python web ecosystem and I hope to have not caused any offence (to anyone) by listing these frameworks. +The comparison is not meant to defame any of the developers or the frameworks listed below. The names of the frameworks have been listed for a clear comparison. All of these frameworks are the reason for me having a high inclination towards the python web ecosystem and I hope to have not caused any offense (to anyone) by listing these frameworks. **Also, these tests were done on my development machine and the numbers portrayed below are not absolute by any means. These numbers only indicate the relative performance of these frameworks.** I used [oha](https://github.com/hatoo/oha) to perform the testing of 10000 requests on the following frameworks and the results were as follows: 1. Flask(Gunicorn) + ``` - Total: 5.5254 secs - Slowest: 0.0784 secs - Fastest: 0.0028 secs - Average: 0.0275 secs - Requests/sec: 1809.8082 +Total: 5.5254 secs +Slowest: 0.0784 secs +Fastest: 0.0028 secs +Average: 0.0275 secs +Requests/sec: 1809.8082 ``` -2. FastAPI(Uvicorn) +1. FastAPI(Uvicorn) + ``` - Total: 4.1314 secs - Slowest: 0.0733 secs - Fastest: 0.0027 secs - Average: 0.0206 secs - Requests/sec: 2420.4851 +Total: 4.1314 secs +Slowest: 0.0733 secs +Fastest: 0.0027 secs +Average: 0.0206 secs +Requests/sec: 2420.4851 ``` -3. Django(Gunicorn) + +1. Django(Gunicorn) + ``` - Total: 13.5070 secs - Slowest: 0.3635 secs - Fastest: 0.0249 secs - Average: 0.0674 secs - Requests/sec: 740.3558 +Total: 13.5070 secs +Slowest: 0.3635 secs +Fastest: 0.0249 secs +Average: 0.0674 secs +Requests/sec: 740.3558 ``` -4. Robyn(Doesn't need a *SGI) + +1. Robyn(Doesn't need a *SGI) + ``` - Total: 1.8324 secs - Slowest: 0.0269 secs - Fastest: 0.0024 secs - Average: 0.0091 secs - Requests/sec: 5457.2339 +Total: 1.8324 secs +Slowest: 0.0269 secs +Fastest: 0.0024 secs +Average: 0.0091 secs +Requests/sec: 5457.2339 ``` -4. Robyn (5 workers) +1. Robyn (5 workers) + ``` - Total: 1.5592 secs - Slowest: 0.0211 secs - Fastest: 0.0017 secs - Average: 0.0078 secs - Requests/sec: 6413.6480 +Total: 1.5592 secs +Slowest: 0.0211 secs +Fastest: 0.0017 secs +Average: 0.0078 secs +Requests/sec: 6413.6480 ``` -Robyn is able to serve the 10k requests in 1.8 seconds followed by Flask and FastAPI, which take around 5 seconds(using 5 workers on a dual core machine). Finally, Django takes around 13.5070 seconds. +Robyn is able to serve the 10k requests in 1.8 seconds followed by Flask and FastAPI, which take around 5 seconds(using 5 workers on a dual-core machine). Finally, Django takes around 13.5070 seconds. ## Verbose Logs + Flask(Gunicorn) + ``` Summary: Success rate: 1.0000 @@ -99,6 +108,7 @@ Status code distribution: ``` FastAPI(Uvicorn) + ``` Summary: Success rate: 1.0000 @@ -143,6 +153,7 @@ Status code distribution: ``` Robyn + ``` Summary: Success rate: 1.0000 @@ -187,6 +198,7 @@ Status code distribution: ``` Django(Gunicorn) + ``` Summary: Success rate: 1.0000 @@ -230,8 +242,8 @@ Status code distribution: [200] 10000 responses ``` - Robyn(with 5 workers) + ``` Summary: Success rate: 1.0000 diff --git a/docs/env-variables.md b/docs/env-variables.md index c886e4ff7..bc4d714af 100644 --- a/docs/env-variables.md +++ b/docs/env-variables.md @@ -1,8 +1,8 @@ ## Environment Variables -There some environment variables that Robyn looks out for. e.g. `ROBYN_URL` and `ROBYN_PORT`. +There are some environment variables that Robyn looks out for. e.g. `ROBYN_URL` and `ROBYN_PORT`. -You can have a `robyn.env` file to load them automatically in your environment. +You can have a `robyn.env` file to load them automatically in your environment. The server will check for the `robyn.env` file in the root of the project. If it is able to find one, it will parse the environment variables and the set your environment. diff --git a/docs/examples.md b/docs/examples.md index bd0f12f3c..ccba88c9e 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -3,54 +3,56 @@ Below are a few examples of real life use cases of Robyn. ### Creating a Simple HTTP Service -```python +```python from robyn import Robyn app = Robyn(__file__) + @app.get("/") async def h(request): return "Hello, world!" -app.start(port=5000) +app.start(port=5000) ``` ### Serving simple HTML Files -```python +```python from robyn import Robyn, serve_html app = Robyn(__file__) + @app.get("/") async def h(request): return serve_html("./index.html") -app.start(port=5000) +app.start(port=5000) ``` ### Serving files to download -```python +```python from robyn import Robyn, serve_file app = Robyn(__file__) + @app.get("/") async def h(request): return serve_file("./index.html") -app.start(port=5000) +app.start(port=5000) ``` - ### Interaction with a Database -It should be fairly easy to make a crud app example. Here's a minimal example using Prisma(`pip install prisma-client-py`) with Robyn. +It should be fairly easy to make a crud app example. Here's a minimal example using Prisma (`pip install prisma-client-py`) with Robyn. ```python from robyn import Robyn @@ -60,15 +62,18 @@ from prisma.models import User app = Robyn(__file__) prisma = Prisma(auto_register=True) + @app.startup_handler async def startup_handler() -> None: await prisma.connect() + @app.shutdown_handler async def shutdown_handler() -> None: if prisma.is_connected(): await prisma.disconnect() + @app.get("/") async def h(): user = await User.prisma().create( @@ -78,6 +83,7 @@ async def h(): ) return user.json(indent=2) + app.start(port=5000) ``` @@ -100,8 +106,8 @@ model User { ``` ### Using Middleware -```python +```python @app.before_request("/") async def hello_before_request(request): print(request) @@ -110,11 +116,12 @@ async def hello_before_request(request): @app.after_request("/") def hello_after_request(request): print(request) - ``` ### A basic web socket chat app. + Coming Soon.... ### Using Robyn to send an email + Coming Soon.... diff --git a/docs/features.md b/docs/features.md index 64377526f..2502306cb 100644 --- a/docs/features.md +++ b/docs/features.md @@ -1,11 +1,10 @@ ## Features - ## Synchronous Requests + Robyn supports both sync methods and async methods for fetching requests. Every method gets a request object from the routing decorator. ```python - @app.get("/") def h(request): return "Hello, world" @@ -19,7 +18,6 @@ async def h(request): return "Hello, world" ``` - ## All kinds of HTTP Requests The request object contains the `body` in PUT/POST/PATCH. The `header`s are available in every request object. @@ -50,7 +48,6 @@ async def postreq(request): return bytearray(request["body"]).decode("utf-8") ``` - #### PATCH Request ```python @@ -59,7 +56,6 @@ async def postreq(request): return bytearray(request["body"]).decode("utf-8") ``` - #### DELETE Request ```python @@ -68,7 +64,6 @@ async def postreq(request): return bytearray(request["body"]).decode("utf-8") ``` - #### Directory Serving ```python @@ -80,6 +75,7 @@ app.add_directory( ``` ## Dynamic Routes + You can add params in the routes and access them from the request object. ```python @@ -90,20 +86,24 @@ async def json(request): ``` ## Returning a JSON Response + You can also serve JSON responses when serving HTTP request using the following way. ```python from robyn import jsonify + @app.post("/jsonify") async def json(request): return jsonify({"hello": "world"}) ``` ## Format of the Response -Robyn supports several kind of Response for your routes + +Robyn supports several kinds of Response for your routes #### Dictionary + Robyn accepts dictionaries to build a response for the route: ```python @@ -118,18 +118,21 @@ async def dictionary(request): ``` #### Response object + Robyn provides a `Response` object to help you build a valid response. ```python from robyn.robyn import Response + @app.get("/response") async def response(request): return Response(status_code=200, headers={}, body="OK") ``` #### Other types -Whenever you want to use an other type for your routes, the `str` method will be called on it and it will be stored in the body of the response. Here is an example that returns a string: + +Whenever you want to use another type for your routes, the `str` method will be called on it, and it will be stored in the body of the response. Here is an example that returns a string: ```python @app.get("/") @@ -138,14 +141,15 @@ async def hello(request): ``` ## Global Headers + You can also add global headers for every request. ```python app.add_request_header("server", "robyn") - ``` ## Per route headers + You can also add headers for every route. ```python @@ -157,10 +161,8 @@ async def request_headers(): "type": "text", "headers": {"Header": "header_value"}, } - ``` - ## Query Params You can access query params from every HTTP method. @@ -176,18 +178,18 @@ async def query_get(request): return jsonify(query_data) ``` - ## Events -You can add startup and shutdown events in robyn. These events will execute before the requests have started serving and after the serving has been completed. +You can add startup and shutdown events in Robyn. These events will execute before the requests have started serving and after the serving has been completed. ```python - async def startup_handler(): print("Starting up") + app.startup_handler(startup_handler) + @app.shutdown_handler def shutdown_handler(): print("Shutting down") @@ -213,34 +215,35 @@ Now, you can define 3 methods for every web_socket for their life cycle, they ar @websocket.on("message") def connect(): global i - i+=1 - if i==0: + i += 1 + if i == 0: return "Whaaat??" - elif i==1: + elif i == 1: return "Whooo??" - elif i==2: + elif i == 2: return "*chika* *chika* Slim Shady." - elif i==3: - i= -1 + elif i == 3: + i = -1 return "" + @websocket.on("close") def close(): return "Goodbye world, from ws" + @websocket.on("connect") def message(): return "Hello world, from ws" - ``` The three methods: - - "message" is called when the socket receives a message - - "close" is called when the socket is disconnected - - "connect" is called when the socket connects -To see a complete service in action, you can go to the folder [../integration_tests/base_routes.py](../integration_tests/base_routes.py) +- "message" is called when the socket receives a message +- "close" is called when the socket is disconnected +- "connect" is called when the socket connects +To see a complete service in action, you can go to the folder [../integration_tests/base_routes.py](../integration_tests/base_routes.py) #### Web Socket Usage @@ -248,25 +251,26 @@ To see a complete service in action, you can go to the folder [../integration_te @websocket.on("message") async def connect(): global i - i+=1 - if i==0: + i += 1 + if i == 0: return "Whaaat??" - elif i==1: + elif i == 1: return "Whooo??" - elif i==2: + elif i == 2: return "*chika* *chika* Slim Shady." - elif i==3: - i= -1 + 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" - ``` ## Middlewares @@ -290,8 +294,6 @@ To run Robyn across multiple cores, you can use the following command: `python app.py --workers=N --processes=N` - - ## Const Requests You can pre-compute the response for each route. This will compute the response even before execution. This will improve the response time bypassing the need to access the router. @@ -304,30 +306,29 @@ async def h(): ## Templates -You can render templates in Robyn. We ship `Jinja2` as our out of the box solution. If you would like to add support for other templating engines you can create your own renderer too! Read more at [templating](/templates.md) documentation. +You can render templates in Robyn. We ship `Jinja2` as our out-of-the-box solution. If you would like to add support for other templating engines you can create your own renderer too! Read more at [templating](/templates.md) documentation. Here is a sample below. main.py + ```python from robyn.templating import JinjaTemplate current_file_path = pathlib.Path(__file__).parent.resolve() JINJA_TEMPLATE = JinjaTemplate(os.path.join(current_file_path, "templates")) + @app.get("/template_render") def template_render(): - context = { - "framework": "Robyn", - "templating_engine": "Jinja2" - } + context = {"framework": "Robyn", "templating_engine": "Jinja2"} template = JINJA_TEMPLATE.render_template(template_name="test.html", **context) return template - ``` templates/test.html + ```html {# templates/test.html #} @@ -342,7 +343,6 @@ templates/test.html

{{framework}} 🀝 {{templating_engine}}

- ``` ### Understanding the code diff --git a/docs/getting-started.md b/docs/getting-started.md index 3b41775d4..4f10830b1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -4,7 +4,7 @@ We will go through the process of creating a "Hello, World!" app. ### Step 1: Creating a virtualenv -To ensure that there are isolated dependencies, we will use virtualenvironments. +To ensure that there are isolated dependencies, we will use virtual environments. ``` python3 -m venv venv @@ -13,11 +13,13 @@ python3 -m venv venv ### Step 2: Activate the virtualenv and install Robyn #### Activating the virtualenv + ``` source venv/bin/activate ``` #### Installing Robyn + ``` pip install robyn ``` @@ -26,20 +28,20 @@ pip install robyn - Create a file called `app.py`. -- In your favourite editor, open `app.py` and write the following. +- In your favorite editor, open `app.py` and write the following. ```python - from robyn import Robyn app = Robyn(__file__) + @app.get("/") -async def h(request): # request is an optional parameter +async def h(request): # request is an optional parameter return "Hello, world!" -app.start(port=5000, url="0.0.0.0") # url is optional, defaults to 127.0.0.1 +app.start(port=5000, url="0.0.0.0") # url is optional, defaults to 127.0.0.1 ``` Let us try to decipher the usage line by line. @@ -54,7 +56,7 @@ Here, we are creating the app object. We require the `__file__` object to mount ### Step 4. Running the service -You can just use the command +You can just use the command ``` python3 app.py @@ -65,5 +67,5 @@ if you want to run the production version, and ``` python3 app.py --dev=true ``` -if you want to enable hot reloading or the development version. +if you want to enable hot reloading or the development version. diff --git a/docs/graphql-integration.md b/docs/graphql-integration.md index d54c9ee4e..3e5ef556c 100644 --- a/docs/graphql-integration.md +++ b/docs/graphql-integration.md @@ -13,11 +13,13 @@ python3 -m venv venv ### Step 2: Activate the virtualenv and install Robyn #### Activating the virtualenv + ```bash source venv/bin/activate ``` #### Installing Robyn and Strawberry + ```bash pip install robyn strawberry-graphql ``` @@ -25,7 +27,6 @@ pip install robyn strawberry-graphql ### Step 3: Coding the App ```python - from typing import List, Optional from robyn import Robyn, jsonify import json @@ -39,6 +40,7 @@ import strawberry.utils.graphiql class User: name: str + @strawberry.type class Query: @strawberry.field @@ -50,13 +52,15 @@ schema = strawberry.Schema(Query) app = Robyn(__file__) + @app.get("/", const=True) async def get(): return strawberry.utils.graphiql.get_graphiql_html() - + + @app.post("/") async def post(request): - body = json.loads( bytearray(request["body"]).decode("utf-8") ) + body = json.loads(bytearray(request["body"]).decode("utf-8")) query = body["query"] variables = body.get("variables", None) context_value = {"request": request} @@ -71,21 +75,22 @@ async def post(request): operation_name, ) - return jsonify( { - "data": ( data.data ), - **({"errors": data.errors} if data.errors else {}), - **({"extensions": data.extensions} if data.extensions else {}) - }) + return jsonify( + { + "data": (data.data), + **({"errors": data.errors} if data.errors else {}), + **({"extensions": data.extensions} if data.extensions else {}), + } + ) -if __name__=="__main__": - app.start() +if __name__ == "__main__": + app.start() ``` Let us try to decipher the usage line by line. ```python - from typing import List, Optional from robyn import Robyn, jsonify @@ -96,21 +101,21 @@ import strawberry import strawberry.utils.graphiql ``` -These statements just import the dependencies. - +These statements just import the dependencies. ```python - @strawberry.type class User: name: str + @strawberry.type class Query: @strawberry.field def user(self) -> Optional[User]: return User(name="Hello") + schema = strawberry.Schema(Query) ``` @@ -118,30 +123,24 @@ Here, we are creating a base `User` type with a `name` property. We are then creating a GraphQl `Query` that returns the `User`. - ```python app = Robyn(__file__) - ``` -Now, we are initializing a Robyn app. For us, to serve a GraphQl app, we need to have a `get` route to return the `GraphiQL`(ide) and then a post route to process the `GraphQl` request. +Now, we are initializing a Robyn app. For us, to serve a GraphQl app, we need to have a `get` route to return the `GraphiQL`(ide) and then a post route to process the `GraphQl` request. ```python - @app.get("/", const=True) async def get(): return strawberry.utils.graphiql.get_graphiql_html() - ``` We are populating the html page with the GraphiQL IDE using `strawberry`. We are using `const=True` to precompute this population. Essentially, making it very fast and bypassing the execution overhead in this get request. - ```python - @app.post("/") async def post(request): - body = json.loads( bytearray(request["body"]).decode("utf-8") ) + body = json.loads(bytearray(request["body"]).decode("utf-8")) query = body["query"] variables = body.get("variables", None) context_value = {"request": request} @@ -156,12 +155,15 @@ async def post(request): operation_name, ) - return jsonify( { - "data": ( data.data ), - **({"errors": data.errors} if data.errors else {}), - **({"extensions": data.extensions} if data.extensions else {}) - }) + return jsonify( + { + "data": (data.data), + **({"errors": data.errors} if data.errors else {}), + **({"extensions": data.extensions} if data.extensions else {}), + } + ) ``` + Finally, we are getting params(body, query, variables, context_value, root_value, operation_name) from the `request` object. The above is the example for just one route. You can do the same for as many as you like. :) diff --git a/docs/hosting.md b/docs/hosting.md index cdc23244b..64ddaf607 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -2,12 +2,11 @@ The process of hosting a Robyn app on various cloud providers. - ### Railway We will be deploying the app on [Railway](https://railway.app/). -A GitHub account is needed as a mandatory pre-requisite. +A GitHub account is needed as a mandatory prerequisite. We will deploy a sample "Hello World," demonstrating a simple GET route and serving an HTML file. @@ -18,13 +17,12 @@ app folder/ main.py requirements.txt index.html - ``` -Note - Railway looks for a `main.py` as an entrypoint instead of `app.py`. The build process will fail if there is no `main.py` file. +Note - Railway looks for a `main.py` as an entry point instead of `app.py`. The build process will fail if there is no `main.py` file. +_main.py_ -*main.py* ```python from robyn import Robyn, serve_html @@ -37,28 +35,27 @@ async def h(request): print(request) return "Hello, world!" + @app.get("/") async def get_page(request): return serve_html("./index.html") -if __name__=="__main__": - app.start(url="0.0.0.0", port=PORT) +if __name__ == "__main__": + app.start(url="0.0.0.0", port=PORT) ``` - -*index.html* +_index.html_ ```html

Hello World, this is Robyn framework!

- ``` - ### Exposing Ports + The Railway documentation says the following about the listening to ports: -> The easiest way to get up and running is to have your application listen on 0.0.0.0:$PORT, where PORT is a Railway-provided environment variable. +> The easiest way to get up and running is to have your application listen on 0.0.0.0:$PORT, where PORT is a Railway-provided environment variable. So, passing the URL as `0.0.0.0` to `app.start()` as an argument is necessary. @@ -89,5 +86,4 @@ We can generate a temporary domain under the "Domains" tab. ![image](https://user-images.githubusercontent.com/70811425/202870735-6b955752-c5a6-48d5-acbc-1a4ea6fd7574.png) - We can go to our domain `/hello` and confirm that the message "Hello World" is displayed. diff --git a/docs/plugins.md b/docs/plugins.md index 2757dc6b0..fc38b6deb 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -1,5 +1,5 @@ ## Plugin Design -Robyn is an extensible web framework that allows anyone to make plugins over the top of robyn. +Robyn is an extensible web framework that allows anyone to make plugins over the top of Robyn. ### Tutorial coming soon.. diff --git a/docs/sponsors.md b/docs/sponsors.md index 564c41164..106b08ac2 100644 --- a/docs/sponsors.md +++ b/docs/sponsors.md @@ -4,4 +4,4 @@ These sponsors help us make the magic happen! [![DigitalOcean Referral Badge](https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%201.svg)](https://www.digitalocean.com/?refcode=3f2b9fd4968d&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) -- [ Shivay Lamba ](https://github.com/shivaylamba) +- [Shivay Lamba](https://github.com/shivaylamba) diff --git a/docs/templates.md b/docs/templates.md index cac1db385..ad471bf13 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -8,7 +8,6 @@ To do that, you need to import the `TemplateInterface` from `robyn.templating` ```python from robyn.templating import TemplateInterface - ``` You need to have a `render_template` method inside your implementation. So, an example would look like the following: @@ -16,9 +15,12 @@ You need to have a `render_template` method inside your implementation. So, an e ```python class JinjaTemplate(TemplateInterface): def __init__(self, directory, encoding="utf-8", followlinks=False): - self.env = Environment(loader=FileSystemLoader(searchpath=directory, encoding=encoding, followlinks=followlinks)) + self.env = Environment( + loader=FileSystemLoader( + searchpath=directory, encoding=encoding, followlinks=followlinks + ) + ) def render_template(self, template_name, **kwargs): return self.env.get_template(template_name).render(**kwargs) - ``` diff --git a/integration_tests/base_routes.py b/integration_tests/base_routes.py index fd047417a..68b8e3e29 100644 --- a/integration_tests/base_routes.py +++ b/integration_tests/base_routes.py @@ -1,13 +1,12 @@ -from robyn import Robyn, serve_file, jsonify, WS, serve_html -from robyn.robyn import Response - -from robyn.templating import JinjaTemplate - -from robyn.log_colors import Colors import asyncio +import logging import os import pathlib -import logging + +from robyn import WS, Robyn, jsonify, serve_file, serve_html +from robyn.log_colors import Colors +from robyn.robyn import Response +from robyn.templating import JinjaTemplate app = Robyn(__file__) websocket = WS(app, "/web_socket") diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 2c7fb18e1..c0a178465 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -1,11 +1,12 @@ +import os +import pathlib import signal +import subprocess import sys +import time from typing import List + import pytest -import subprocess -import pathlib -import os -import time def spawn_process(command: List[str]) -> subprocess.Popen: @@ -24,12 +25,13 @@ def kill_process(process: subprocess.Popen) -> None: process.send_signal(signal.CTRL_BREAK_EVENT) process.kill() return - + try: os.killpg(os.getpgid(process.pid), signal.SIGKILL) except ProcessLookupError: pass + @pytest.fixture(scope="session") def session(): os.environ["ROBYN_URL"] = "127.0.0.1" diff --git a/integration_tests/test_base_url.py b/integration_tests/test_base_url.py index 709ba804d..58c90bb0c 100644 --- a/integration_tests/test_base_url.py +++ b/integration_tests/test_base_url.py @@ -1,6 +1,7 @@ -import requests import os +import requests + def test_default_url_index_request(default_session): BASE_URL = "http://127.0.0.1:5000" diff --git a/integration_tests/test_env_populator.py b/integration_tests/test_env_populator.py index 41fe0c9d7..76e23f53d 100644 --- a/integration_tests/test_env_populator.py +++ b/integration_tests/test_env_populator.py @@ -1,9 +1,10 @@ # from integration_tests.conftest import test_session -from robyn.env_populator import load_vars, parser -import pathlib import os +import pathlib + import pytest +from robyn.env_populator import load_vars, parser path = pathlib.Path(__file__).parent @@ -27,5 +28,5 @@ def test_env_population(test_session, env_file): load_vars(variables=parser(config_path=env_path)) PORT = os.environ["ROBYN_PORT"] HOST = os.environ["ROBYN_URL"] - assert PORT == '8080' + assert PORT == "8080" assert HOST == "127.0.0.1" diff --git a/integration_tests/test_web_sockets.py b/integration_tests/test_web_sockets.py index 157cb3206..a3dbc7841 100644 --- a/integration_tests/test_web_sockets.py +++ b/integration_tests/test_web_sockets.py @@ -1,6 +1,7 @@ -from websockets import connect import asyncio +from websockets import connect + BASE_URL = "ws://127.0.0.1:5000" diff --git a/robyn/__init__.py b/robyn/__init__.py index c8a9389d3..9c3870afa 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -11,6 +11,7 @@ from robyn.argument_parser import ArgumentParser from robyn.dev_event_handler import EventHandler +from robyn.env_populator import load_vars from robyn.events import Events from robyn.log_colors import Colors from robyn.processpool import spawn_process @@ -19,7 +20,6 @@ from robyn.router import MiddlewareRouter, Router, WebSocketRouter from robyn.types import Directory, Header from robyn.ws import WS -from robyn.env_populator import load_vars logger = logging.getLogger(__name__) @@ -83,7 +83,9 @@ def add_directory( index_file: Optional[str] = None, show_files_listing: bool = False, ): - self.directories.append(Directory(route, directory_path, index_file, show_files_listing)) + self.directories.append( + Directory(route, directory_path, index_file, show_files_listing) + ) def add_request_header(self, key: str, value: str) -> None: self.request_headers.append(Header(key, value)) diff --git a/robyn/processpool.py b/robyn/processpool.py index 7f36e806e..6fa9c9e37 100644 --- a/robyn/processpool.py +++ b/robyn/processpool.py @@ -1,13 +1,12 @@ import asyncio import sys -from typing import Dict, Tuple, List +from typing import Dict, List, Tuple from robyn.events import Events from robyn.robyn import FunctionInfo, Server, SocketHeld from robyn.router import MiddlewareRoute, Route -from robyn.ws import WS from robyn.types import Directory, Header - +from robyn.ws import WS def initialize_event_loop(): diff --git a/robyn/robyn.pyi b/robyn/robyn.pyi index 942aa0f18..2b940f4a7 100644 --- a/robyn/robyn.pyi +++ b/robyn/robyn.pyi @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass from typing import Callable, Optional class SocketHeld: diff --git a/robyn/router.py b/robyn/router.py index 86b7a7b1f..cc41faed8 100644 --- a/robyn/router.py +++ b/robyn/router.py @@ -1,12 +1,12 @@ from abc import ABC, abstractmethod -from functools import wraps from asyncio import iscoroutinefunction +from functools import wraps from inspect import signature -from typing import Callable, Dict, List, Tuple, Union from types import CoroutineType -from robyn.robyn import FunctionInfo, Response -from robyn.responses import jsonify +from typing import Callable, Dict, List, Tuple, Union +from robyn.responses import jsonify +from robyn.robyn import FunctionInfo, Response from robyn.ws import WS Route = Tuple[str, str, Callable, bool] diff --git a/robyn/templating.py b/robyn/templating.py index 5a1bd0988..2799d3cc0 100644 --- a/robyn/templating.py +++ b/robyn/templating.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod + from jinja2 import Environment, FileSystemLoader diff --git a/robyn/types.py b/robyn/types.py index 32d61d51a..4548921ae 100644 --- a/robyn/types.py +++ b/robyn/types.py @@ -10,7 +10,12 @@ class Directory: show_files_listing: bool def as_list(self): - return [self.route, self.directory_path, self.index_file, self.show_files_listing] + return [ + self.route, + self.directory_path, + self.index_file, + self.show_files_listing, + ] @dataclass @@ -20,5 +25,3 @@ class Header: def as_list(self): return [self.key, self.val] - -