Skip to content

Commit

Permalink
feat: add dependency injections (#569)
Browse files Browse the repository at this point in the history
* add dependency injections
---------

Co-authored-by: Fernando Rodriguez <[email protected]>
Co-authored-by: Sanskar Jethi <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 3, 2023
1 parent ba0bd6c commit 8887036
Show file tree
Hide file tree
Showing 17 changed files with 569 additions and 82 deletions.
5 changes: 5 additions & 0 deletions docs_src/src/components/documentation/ApiDocs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ const guides = [
name: 'Advanced Features',
description: 'Learn about advanced features in Robyn.',
},
{
href: '/documentation/api_reference/dependency_injection',
name: 'Dependency Injection',
description: 'Learn about Dependency Injection in Robyn.',
},
]

export function ApiDocs() {
Expand Down
8 changes: 8 additions & 0 deletions docs_src/src/components/documentation/Navigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ export const navigation = [
href: '/documentation/api_reference/request_object',
title: 'The Request Object',
},
{
href: '/documentation/api_reference/robyn_env',
title: 'The Robyn Env file',
},
{
href: '/documentation/api_reference/middlewares',
title: 'Middlewares, Events and Websockets',
Expand Down Expand Up @@ -264,6 +268,10 @@ export const navigation = [
href: '/documentation/api_reference/views',
title: 'Code Organisation',
},
{
href: '/documentation/api_reference/dependency-injection',
title: 'Dependency Injection',
},

{
href: '/documentation/api_reference/exceptions',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
export const description =
'On this page, we will learn about dependency injection in Robyn.'


## Dependency Injection

Batman wanted to learn about dependency injection in Robyn. Robyn introduced him to the concept of dependency injection and how it can be used in Robyn.



Robyn has two types of dependency injection:
One is for the application level and the other is for the router level.

### Application Level Dependency Injection

<Row>
<Col>
Application level dependency injection is used to inject dependencies into the application. These dependencies are available to all the requests.
</Col>
<Col>

<CodeGroup title="Request" tag="GET" label="/hello_world">

```python {{ title: 'untyped' }}
from robyn import Robyn, ALLOW_CORS

app = Robyn(__file__)
GLOBAL_DEPENDENCY = "GLOBAL DEPENDENCY"

app.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)

@app.get("/sync/global_di")
def sync_global_di(request, global_dependencies):
return global_dependencies["GLOBAL_DEPENDENCY"]
```

```python {{ title: 'typed' }}
from robyn import Robyn, ALLOW_CORS

app = Robyn(__file__)
GLOBAL_DEPENDENCY = "GLOBAL DEPENDENCY"

app.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)

@app.get("/sync/global_di")
def sync_global_di(request, global_dependencies):
return global_dependencies["GLOBAL_DEPENDENCY"]

```
</CodeGroup>
</Col>
</Row>


### Router Level Dependency Injection

<Row>
<Col>
Router level dependency injection is used to inject dependencies into the router. These dependencies are available to all the requests of that router.
</Col>
<Col>

<CodeGroup title="Request" tag="GET" label="/hello_world">

```python {{ title: 'untyped' }}
from robyn import Robyn, ALLOW_CORS

app = Robyn(__file__)
ROUTER_DEPENDENCY = "ROUTER DEPENDENCY"

app.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)

@app.get("/sync/global_di")
def sync_global_di(request, router_dependencies):
return router_dependencies["ROUTER_DEPENDENCY"]
```

```python {{ title: 'typed' }}
from robyn import Robyn, ALLOW_CORS

app = Robyn(__file__)
ROUTER_DEPENDENCY = "ROUTER DEPENDENCY"

app.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)

@app.get("/sync/global_di")
def sync_global_di(request, router_dependencies):
return router_dependencies["ROUTER_DEPENDENCY"]
```
</CodeGroup>
</Col>
</Row>

<Row>
<Col>
Note: `router_dependencies`, `global_dependencies` , `request` are named parameters and **must** be named as such. The order of the parameters does not matter.
</Col>
</Row>

---


## What's next?

Batman, being the familiar with the dark side wanted to know about Exceptions!

Robyn introduced him to the concept of exceptions and how he can use them to handle errors in his application.

- [Exceptions](/documentation/api_reference/exceptions)

6 changes: 2 additions & 4 deletions docs_src/src/pages/documentation/api_reference/views.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,7 @@ The only thing to remember is that you need to add the subrouter to the main rou

## What's next?

Batman, being the familiar with the dark side wanted to know about Exceptions!
Now, that Batman knows how to organise his code, he wants to know how to add dependencies to his code. Robyn tells him that he can add dependencies at the global level, router level and the view level.

Robyn introduced him to the concept of exceptions and how he can use them to handle errors in his application.

- [Exceptions](/documentation/api_reference/exceptions)
- [Dependency Injection](/documentation/api_reference/dependency_injection)

64 changes: 51 additions & 13 deletions integration_tests/base_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,26 @@
from robyn.templating import JinjaTemplate

from integration_tests.views import SyncView, AsyncView
from integration_tests.subroutes import sub_router
from integration_tests.subroutes import sub_router, di_subrouter


app = Robyn(__file__)
websocket = WebSocket(app, "/web_socket")


# Creating a new WebSocket app to test json handling + to serve an example to future users of this lib
# while the original "raw" web_socket is used with benchmark tests
websocket_json = WebSocket(app, "/web_socket_json")

websocket_di = WebSocket(app, "/web_socket_di")

websocket_di.inject_global(GLOBAL_DEPENDENCY="GLOBAL DEPENDENCY")
websocket_di.inject(ROUTER_DEPENDENCY="ROUTER DEPENDENCY")

current_file_path = pathlib.Path(__file__).parent.resolve()
jinja_template = JinjaTemplate(os.path.join(current_file_path, "templates"))


# ===== Websockets =====

# Make it easier for multiple test runs
Expand All @@ -55,7 +62,7 @@ async def jsonws_message(ws, msg: str) -> str:


@websocket.on("message")
async def message(ws: WebSocketConnector, msg: str) -> str:
async def message(ws: WebSocketConnector, msg: str, global_dependencies) -> str:
global websocket_state
websocket_id = ws.id
state = websocket_state[websocket_id]
Expand All @@ -69,7 +76,6 @@ async def message(ws: WebSocketConnector, msg: str) -> str:
elif state == 2:
resp = "*chika* *chika* Slim Shady."
websocket_state[websocket_id] = (state + 1) % 3

return resp


Expand All @@ -93,6 +99,21 @@ def jsonws_connect():
return "Hello world, from ws"


@websocket_di.on("connect")
async def di_message_connect(global_dependencies, router_dependencies):
return global_dependencies["GLOBAL_DEPENDENCY"] + " " + router_dependencies["ROUTER_DEPENDENCY"]


@websocket_di.on("message")
async def di_message():
return ""


@websocket_di.on("close")
async def di_message_close():
return ""


# ===== Lifecycle handlers =====


Expand Down Expand Up @@ -190,13 +211,12 @@ def sync_middlewares_401():

# Hello world


@app.get("/")
async def hello_world():
return "Hello world"
app.inject(RouterDependency="Router Dependency")


# str
@app.get("/")
async def hello_world(request):
return "Hello, world!"


@app.get("/sync/str")
Expand Down Expand Up @@ -710,22 +730,22 @@ async def async_exception_get():


@app.put("/sync/exception/put")
def sync_exception_put(_: Request):
def sync_exception_put(request: Request):
raise ValueError("value error")


@app.put("/async/exception/put")
async def async_exception_put(_: Request):
async def async_exception_put(request: Request):
raise ValueError("value error")


@app.post("/sync/exception/post")
def sync_exception_post(_: Request):
def sync_exception_post(request: Request):
raise ValueError("value error")


@app.post("/async/exception/post")
async def async_exception_post(_: Request):
async def async_exception_post(request: Request):
raise ValueError("value error")


Expand Down Expand Up @@ -764,7 +784,24 @@ async def async_without_decorator():
app.add_route("PUT", "/async/put/no_dec", async_without_decorator)
app.add_route("POST", "/async/post/no_dec", async_without_decorator)

# ===== Main =====

# ===== Dependency Injection =====

GLOBAL_DEPENDENCY = "GLOBAL DEPENDENCY"
ROUTER_DEPENDENCY = "ROUTER DEPENDENCY"

app.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)
app.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)


@app.get("/sync/global_di")
def sync_global_di(request, router_dependencies, global_dependencies):
return global_dependencies["GLOBAL_DEPENDENCY"]


@app.get("/sync/router_di")
def sync_router_di(request, router_dependencies):
return router_dependencies["ROUTER_DEPENDENCY"]


def main():
Expand All @@ -778,6 +815,7 @@ def main():
app.add_view("/sync/view", SyncView)
app.add_view("/async/view", AsyncView)
app.include_router(sub_router)
app.include_router(di_subrouter)

class BasicAuthHandler(AuthenticationHandler):
def authenticate(self, request: Request) -> Optional[Identity]:
Expand Down
19 changes: 11 additions & 8 deletions integration_tests/subroutes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from robyn import SubRouter, jsonify, WebSocket

from subroutes.di_subrouter import di_subrouter

sub_router = SubRouter(__name__, prefix="/sub_router")

websocket = WebSocket(sub_router, "/ws")

__all__ = ["sub_router", "websocket", "di_subrouter"]


@websocket.on("connect")
async def connect(ws):
Expand All @@ -22,40 +25,40 @@ async def close(ws):


@sub_router.get("/foo")
def get_foo(name):
def get_foo():
return jsonify({"message": "foo"})


@sub_router.post("/foo")
def post_foo(name):
def post_foo():
return jsonify({"message": "foo"})


@sub_router.put("/foo")
def put_foo(name):
def put_foo():
return jsonify({"message": "foo"})


@sub_router.delete("/foo")
def delete_foo(name):
def delete_foo():
return jsonify({"message": "foo"})


@sub_router.patch("/foo")
def patch_foo(name):
def patch_foo():
return jsonify({"message": "foo"})


@sub_router.options("/foo")
def option_foo(name):
def option_foo():
return jsonify({"message": "foo"})


@sub_router.trace("/foo")
def trace_foo(name):
def trace_foo():
return jsonify({"message": "foo"})


@sub_router.head("/foo")
def head_foo(name):
def head_foo():
return jsonify({"message": "foo"})
17 changes: 17 additions & 0 deletions integration_tests/subroutes/di_subrouter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from robyn import SubRouter

di_subrouter = SubRouter(__file__, "/di_subrouter")
GLOBAL_DEPENDENCY = "GLOBAL DEPENDENCY OVERRIDE"
ROUTER_DEPENDENCY = "ROUTER DEPENDENCY"
di_subrouter.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)
di_subrouter.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)


@di_subrouter.get("/subrouter_router_di")
def sync_subrouter_route_dependency(router_dependencies):
return router_dependencies["ROUTER_DEPENDENCY"]


@di_subrouter.get("/subrouter_global_di")
def sync_subrouter_global_dependency(global_dependencies):
return global_dependencies["GLOBAL_DEPENDENCY"]
Loading

0 comments on commit 8887036

Please sign in to comment.