diff --git a/.github/workflows/python-CI.yml b/.github/workflows/python-CI.yml index 0888985e5..449f5bc16 100644 --- a/.github/workflows/python-CI.yml +++ b/.github/workflows/python-CI.yml @@ -32,4 +32,4 @@ jobs: pip install --no-index --find-links=dist/ robyn - name: Test with pytest run: | - pytest ./integration_tests + pytest diff --git a/integration_tests/base_routes.py b/integration_tests/base_routes.py index e761337de..6bd656812 100644 --- a/integration_tests/base_routes.py +++ b/integration_tests/base_routes.py @@ -5,6 +5,8 @@ from robyn.robyn import Response from robyn.templating import JinjaTemplate +from views import SyncView, AsyncView + app = Robyn(__file__) websocket = WS(app, "/web_socket") @@ -482,6 +484,29 @@ async def async_raise(): raise Exception() +# ===== Views ===== + + +@app.view("/sync/view/decorator") +def sync_decorator_view(): + def get(): + return "Hello, world!" + + def post(request): + body = bytearray(request["body"]).decode("utf-8") + return {"status_code": 200, "body": body} + + +@app.view("/async/view/decorator") +def async_decorator_view(): + async def get(): + return "Hello, world!" + + async def post(request): + body = bytearray(request["body"]).decode("utf-8") + return {"status_code": 200, "body": body} + + # ===== Main ===== @@ -493,4 +518,6 @@ async def async_raise(): index_file="index.html", ) app.startup_handler(startup_handler) + app.add_view("/sync/view", SyncView) + app.add_view("/async/view", AsyncView) app.start(port=8080) diff --git a/integration_tests/test_views.py b/integration_tests/test_views.py new file mode 100644 index 000000000..60aa48fbf --- /dev/null +++ b/integration_tests/test_views.py @@ -0,0 +1,41 @@ +from helpers.http_methods_helpers import get, post + + +def test_get_sync_view(session): + r = get("/sync/view") + assert r.text == "Hello, world!" + + +def test_post_sync_view(session): + r = post("/sync/view", data={"name": "John"}) + assert "John" in r.text + + +def test_get_sync_decorator_view(session): + r = get("/sync/view/decorator") + assert r.text == "Hello, world!" + + +def test_post_sync_decorator_view(session): + r = post("/sync/view/decorator", data={"name": "John"}) + assert "John" in r.text + + +def test_get_async_view(session): + r = get("/async/view") + assert r.text == "Hello, world!" + + +def test_post_async_view(session): + r = post("/async/view", data={"name": "John"}) + assert "John" in r.text + + +def test_get_async_decorator_view(session): + r = get("/async/view/decorator") + assert r.text == "Hello, world!" + + +def test_post_async_decorator_view(session): + r = post("/async/view/decorator", data={"name": "John"}) + assert "John" in r.text diff --git a/integration_tests/views/__init__.py b/integration_tests/views/__init__.py new file mode 100644 index 000000000..669f94bae --- /dev/null +++ b/integration_tests/views/__init__.py @@ -0,0 +1,4 @@ +from .sync_view import SyncView +from .async_view import AsyncView + +__all__ = ["SyncView", "AsyncView"] diff --git a/integration_tests/views/async_view.py b/integration_tests/views/async_view.py new file mode 100644 index 000000000..644674303 --- /dev/null +++ b/integration_tests/views/async_view.py @@ -0,0 +1,11 @@ +def AsyncView(): + async def get(): + return "Hello, world!" + + async def post(request): + body = bytes(request["body"]).decode("utf-8") + return { + "status": 200, + "body": body, + "headers": {"Content-Type": "text/json"}, + } diff --git a/integration_tests/views/sync_view.py b/integration_tests/views/sync_view.py new file mode 100644 index 000000000..2aec64f43 --- /dev/null +++ b/integration_tests/views/sync_view.py @@ -0,0 +1,11 @@ +def SyncView(): + def get(): + return "Hello, world!" + + def post(request): + body = bytes(request["body"]).decode("utf-8") + return { + "status": 200, + "body": body, + "headers": {"Content-Type": "text/json"}, + } diff --git a/pyproject.toml b/pyproject.toml index 529223046..5ae950d95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ name = "robyn" dependencies = [ 'watchdog == 2.2.1', 'multiprocess == 0.70.14', + 'nestd==0.3.1', # conditional 'uvloop == 0.17.0; sys_platform == "darwin"', 'uvloop == 0.17.0; platform_machine == "x86_64"', @@ -43,7 +44,6 @@ Changelog = "https://github.com/sansyrox/robyn/blob/main/CHANGELOG.md" [project.optional-dependencies] "templating" = ["jinja2 == 3.0.1"] - [tool.ruff] line-length = 160 exclude = ["src/*" , ".git" , "docs"] diff --git a/requirements.txt b/requirements.txt index 00cda3741..69ab33175 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ uvloop; platform_system!="Windows" watchdog==2.2.1 multiprocess==0.70.14 jinja2==3.1.2 +nestd==0.3.1 diff --git a/robyn/__init__.py b/robyn/__init__.py index bfc24987c..2b086a2ed 100644 --- a/robyn/__init__.py +++ b/robyn/__init__.py @@ -4,6 +4,7 @@ import os import signal from typing import Callable, List, Optional +from nestd import get_all_nested from watchdog.observers import Observer @@ -39,11 +40,11 @@ def __init__(self, file_object: str) -> None: def _add_route(self, route_type, endpoint, handler, is_const=False): """ - [This is base handler for all the decorators] + This is base handler for all the decorators - :param route_type [str]: [route type between GET/POST/PUT/DELETE/PATCH] - :param endpoint [str]: [endpoint for the route added] - :param handler [function]: [represents the sync or async function passed as a handler for the route] + :param route_type str: route type between GET/POST/PUT/DELETE/PATCH + :param endpoint str: endpoint for the route added + :param handler function: represents the sync or async function passed as a handler for the route """ """ We will add the status code here only @@ -165,6 +166,40 @@ def terminating_signal_handler(_sig, _frame): observer.stop() observer.join() + def add_view(self, endpoint: str, view: Callable, const: bool = False): + """ + This is base handler for the view decorators + + :param endpoint str: endpoint for the route added + :param handler function: represents the function passed as a parent handler for single route with different route types + """ + http_methods = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"} + + def get_functions(view): + functions = get_all_nested(view) + output = [] + for name, handler in functions: + route_type = name.upper() + if route_type in http_methods: + output.append((route_type, handler)) + return output + + handlers = get_functions(view) + for route_type, handler in handlers: + self._add_route(route_type, endpoint, handler, const) + + def view(self, endpoint: str, const: bool = False): + """ + The @app.view decorator to add a view with the GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS method + + :param endpoint str: endpoint to server the route + """ + + def inner(handler): + return self.add_view(endpoint, handler, const) + + return inner + def get(self, endpoint: str, const: bool = False): """ The @app.get decorator to add a route with the GET method