Skip to content

Commit

Permalink
feat: split request params (sparckles#952)
Browse files Browse the repository at this point in the history
* chore: remove jsonify from base routes

* add req as request

* map params based off of types

* raise syntax error for unexpected args

* fix(ci): use typing annotations

* docs: add docstrings

* test query params

* fix imports

---------

Co-authored-by: kliwongan <keanu.liwongan@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sanskar Jethi <29942790+sansyrox@users.noreply.github.com>
  • Loading branch information
4 people authored Oct 18, 2024
1 parent fd970fd commit 42f87f8
Show file tree
Hide file tree
Showing 10 changed files with 644 additions and 136 deletions.
83 changes: 75 additions & 8 deletions docs_src/src/pages/documentation/api_reference/getting_started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -244,19 +244,24 @@ Batman was curious about how to access path parameters and query parameters from
from robyn import jsonify

@app.post("/jsonify/:id")
async def json(request):
async def json(request, path_params):
print(request.path_params["id"])
print(path_params["id"])
assert request.path_params["id"] == path_params["id"]
return {"hello": "world"}


```

```python {{ title: 'typed' }}
from robyn import jsonify, Request
from robyn import jsonify
from robyn.types import PathParams

@app.post("/jsonify/:id")
async def json(request: Request):
print(request.path_params["id"])
async def json(req_obj: Request, path_parameters: PathParams):
print(req_obj.path_params["id"])
print(path_params["id"])
assert req_obj.path_params["id"] == path_parameters["id"]
return {"hello": "world"}

```
Expand All @@ -282,24 +287,86 @@ Batman was curious about how to access path parameters and query parameters from

```python {{ title: 'untyped' }}
@app.get("/query")
async def query_get(request):
query_data = request.query_params.to_dict()
async def query_get(request, query_params):
query_data = query_params.to_dict()
assert query_data == request.query_params.to_dict()
return jsonify(query_data)
```

```python {{ title: 'typed' }}
from robyn import Request
from robyn.robyn import QueryParams

@app.get("/query")
async def query_get(request: Request):
query_data = request.query_params.to_dict()
async def query_get(req_obj: Request, query_params: QueryParams):
query_data = query_params.to_dict()
assert query_data == req_obj.query_params.to_dict()
return jsonify(query_data)
```

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

<Row>
<Col>

Any request param can be used in the handler function either using type annotations or using the reserved names.

<b>
Do note that the type annotations will take precedence over the reserved names.
</b>

Robyn showed Batman example syntaxes of accessing the request params:

</Col>
<Col sticky>

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

```python
from robyn.robyn import QueryParams, Headers
from robyn.types import PathParams, RequestMethod, RequestBody, RequestURL

@app.get("/untyped/query_params")
def untyped_basic(query_params):
return query_params.to_dict()


@app.get("/typed/query_params")
def typed_basic(query_data: QueryParams):
return query_data.to_dict()


@app.get("/untyped/path_params/:id")
def untyped_path_params(query_params: PathParams):
return query_params # contains the path params since the type annotations takes precedence over the reserved names


@app.post("/typed_untyped/combined")
def typed_untyped_combined(
query_params,
method_data: RequestMethod,
body_data: RequestBody,
url: RequestURL,
headers_item: Headers,
):
return {
"body": body_data,
"query_params": query_params.to_dict(),
"method": method_data,
"url": url.path,
"headers": headers_item.get("server"),
}
```

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

Type Aliases: `Request`, `QueryParams`, `Headers`, `PathParams`, `RequestBody`, `RequestMethod`, `RequestURL`, `FormData`, `RequestFiles`, `RequestIP`, `RequestIdentity`

Reserved Names: `r`, `req`, `request`, `query_params`, `headers`, `path_params`, `body`, `method`, `url`, `ip_addr`, `identity`, `form_data`, `files`

---

Expand Down
57 changes: 27 additions & 30 deletions docs_src/src/pages/documentation/api_reference/openapi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ python app.py --disable-openapi

## How to use?

- Query Params: The typing for query params can be added as `def get(r: Request, query_params=GetRequestParams)` where `GetRequestParams` is a `TypedDict`
- Query Params: The typing for query params can be added as `def get(r: Request, query_params: GetRequestParams)` where `GetRequestParams` is a subclass of `QueryParams`
- Path Params are defaulted to string type (ref: https://en.wikipedia.org/wiki/Query_string)

<CodeGroup title="Basic App">

```python {{ title: 'untyped' }}
from typing import TypedDict

from robyn import Robyn, OpenAPI
from robyn.openapi import OpenAPIInfo, Contact, License, ExternalDocumentation, Components
from robyn import Robyn
from robyn.robyn import QueryParams

app = Robyn(
file_object=__file__,
Expand Down Expand Up @@ -61,13 +59,13 @@ async def welcome():
return "hi"


class GetRequestParams(TypedDict):
class GetRequestParams(QueryParams):
appointment_id: str
year: int


@app.get("/api/v1/name", openapi_name="Name Route", openapi_tags=["Name"])
async def get(r, query_params=GetRequestParams):
async def get(r, query_params: GetRequestParams):
"""Get Name by ID"""
return r.query_params

Expand All @@ -83,12 +81,11 @@ if __name__ == "__main__":
```

```python {{ title: 'typed' }}
from typing import TypedDict
from robyn.robyn import QueryParams

from robyn import Robyn, OpenAPI, Request
from robyn.openapi import OpenAPIInfo, Contact, License, ExternalDocumentation, Components
from robyn import Robyn, Request

app: Robyn = Robyn(
app = Robyn(
file_object=__file__,
openapi=OpenAPI(
info=OpenAPIInfo(
Expand Down Expand Up @@ -118,13 +115,13 @@ async def welcome():
return "hi"


class GetRequestParams(TypedDict):
class GetRequestParams(QueryParams):
appointment_id: str
year: int


@app.get("/api/v1/name", openapi_name="Name Route", openapi_tags=["Name"])
async def get(r: Request, query_params=GetRequestParams):
async def get(r: Request, query_params: GetRequestParams):
"""Get Name by ID"""
return r.query_params

Expand All @@ -146,9 +143,8 @@ if __name__ == "__main__":
<CodeGroup title="Subrouters">

```python {{ title: 'untyped' }}
from typing import TypedDict

from robyn import SubRouter
from robyn.robyn import QueryParams

subrouter = SubRouter(__name__, prefix="/sub")

Expand All @@ -159,13 +155,13 @@ async def subrouter_welcome():
return "hiiiiii subrouter"


class SubRouterGetRequestParams(TypedDict):
class SubRouterGetRequestParams(QueryParams):
_id: int
value: str


@subrouter.get("/name")
async def subrouter_get(r, query_params=SubRouterGetRequestParams):
async def subrouter_get(r, query_params: SubRouterGetRequestParams):
"""Get Name by ID"""
return r.query_params

Expand All @@ -180,7 +176,7 @@ app.include_router(subrouter)
```

```python {{ title: 'typed' }}
from typing import TypedDict
from robyn.robyn import QueryParams

from robyn import Request, SubRouter

Expand All @@ -193,13 +189,13 @@ async def subrouter_welcome():
return "hiiiiii subrouter"


class SubRouterGetRequestParams(TypedDict):
class SubRouterGetRequestParams(QueryParams):
_id: int
value: str


@subrouter.get("/name")
async def subrouter_get(r: Request, query_params=SubRouterGetRequestParams):
async def subrouter_get(r: Request, query_params: SubRouterGetRequestParams):
"""Get Name by ID"""
return r.query_params

Expand All @@ -222,20 +218,20 @@ We support all the params mentioned in the latest OpenAPI specifications (https:
<CodeGroup title="Request & Response Body">

```python {{ title: 'untyped' }}
from robyn.types import JSONResponse
from robyn.types import JSONResponse, Body

class Initial(TypedDict):
class Initial(Body):
is_present: bool
letter: Optional[str]


class FullName(TypedDict):
class FullName(Body):
first: str
second: str
initial: Initial


class CreateItemBody(TypedDict):
class CreateItemBody(Body):
name: FullName
description: str
price: float
Expand All @@ -248,37 +244,38 @@ class CreateResponse(JSONResponse):


@app.post("/")
def create_item(request: Request, body=CreateItemBody) -> CreateResponse:
def create_item(request: Request, body: CreateItemBody) -> CreateResponse:
return CreateResponse(success=True, items_changed=2)
```

```python {{ title: 'typed' }}
from robyn.types import JSONResponse
from robyn.types import JSONResponse, Body

class Initial(TypedDict):
class Initial(Body):
is_present: bool
letter: Optional[str]


class FullName(TypedDict):
class FullName(Body):
first: str
second: str
initial: Initial


class CreateItemBody(TypedDict):
class CreateItemBody(Body):
name: FullName
description: str
price: float
tax: float


class CreateResponse(JSONResponse):
success: bool
items_changed: int


@app.post("/")
def create_item(request: Request, body=CreateItemBody) -> CreateResponse:
def create_item(request: Request, body: CreateItemBody) -> CreateResponse:
return CreateResponse(success=True, items_changed=2)
```

Expand Down
Loading

0 comments on commit 42f87f8

Please sign in to comment.