Skip to content

Feature: Custom Param Decorators (@Body, @Param, @Query, @Headers, createParamDecorator) #115

Description

@ItayTheDar

Overview

PyNest route handlers today rely entirely on FastAPI's implicit parameter binding — function signature type hints are parsed by FastAPI directly. This means there is no PyNest-native way to:

  1. Explicitly declare where a parameter comes from (@Body, @Param, @Query)
  2. Build reusable domain-specific decorators like @CurrentUser() or @TenantId()
  3. Transform or validate individual parameters before they reach the handler

This feature request proposes a full suite of built-in param decorators and a public createParamDecorator factory API.


Motivation

```python

Today — implicit and ambiguous:

@post('/users')
def create_user(self, user_data: CreateUserDto, token: str = Header(None)):
...

With this feature — explicit and composable:

@post('/users')
def create_user(self, @Body() user_data: CreateUserDto, @currentuser() user: User):
...
```

Explicit param decorators also enable:


Built-in Param Decorators

@Body(key?: str)

```python
@post('/users')
def create_user(self, @Body() body: CreateUserDto):
# body is the full request body, validated against CreateUserDto
...

@post('/upload')
def upload(self, @Body('file') file: UploadFile):
...
```

@Param(name?: str)

```python
@get('/:user_id/posts/:post_id')
def get_post(self, @PARAM('user_id') uid: int, @PARAM('post_id') pid: int):
...

@get('/:user_id')
def get_user(self, @PARAM() params: dict):
# params = {"user_id": "123"}
...
```

@Query(name?: str)

```python
@get('/search')
def search(self, @query('q') query: str, @query('limit') limit: int = 20):
...
```

@Headers(name?: str)

```python
@get('/me')
def get_me(self, @headers('authorization') auth: str):
...

@get('/debug')
def debug(self, @headers() all_headers: dict):
...
```

@Req() and @Res()

```python
@get('/raw')
def raw(self, @Req() request: Request, @res() response: Response):
...
```

@Ip() and @HostParam()

```python
@post('/audit')
def audit(self, @ip() ip: str):
...
```


createParamDecorator — Custom Param Decorators

The real power: a public factory for building reusable param decorators with arbitrary extraction logic.

```python
from nest.common.decorators import createParamDecorator, ExecutionContext

Define once:

CurrentUser = createParamDecorator(
lambda data, ctx: ctx.switch_to_http().get_request().state.user
)

TenantId = createParamDecorator(
lambda data, ctx: ctx.switch_to_http().get_request().headers.get("x-tenant-id")
)

Pagination = createParamDecorator(
lambda data, ctx: {
"page": int(ctx.switch_to_http().get_request().query_params.get("page", 1)),
"limit": int(ctx.switch_to_http().get_request().query_params.get("limit", 20)),
}
)

Use anywhere:

@get('/posts')
def list_posts(self, @currentuser() user: User, @Pagination() pagination: dict):
return self.service.get_posts(user.id, **pagination)

@delete('/:id')
def delete_item(self, @PARAM('id') item_id: int, @TenantID() tenant: str):
...
```

data argument — parametrized decorators

```python
UserProperty = createParamDecorator(
lambda data, ctx: getattr(ctx.switch_to_http().get_request().state.user, data, None)
)

@get('/profile')
def profile(self, @userproperty('email') email: str):
...
```


Integration with Pipes (Feature #5)

Param decorators should accept an optional pipe argument for per-parameter transformation:

```python
@get('/:id')
def get(self, @PARAM('id', ParseIntPipe) id: int):
...

@post('/')
def create(self, @Body(ValidationPipe) body: CreateDto):
...
```


Implementation Approach

Since Python does not have native parameter-level decorators, this should be implemented via:

  1. A ParamMetadata descriptor or a sentinel object returned by each decorator
  2. The controller decorator (@Controller) or a route processing wrapper that inspects function signatures for these sentinels
  3. FastAPI Depends() under the hood for actual extraction and injection

Acceptance Criteria

  • @Body(key?), @Param(name?), @Query(name?), @Headers(name?) built-in decorators
  • @Req(), @Res(), @Ip(), @HostParam() built-in decorators
  • createParamDecorator(factory) public API
  • Custom decorators support a data argument for parameterized use
  • Param decorators compose with pipes (@Param('id', ParseIntPipe))
  • ExecutionContext with switch_to_http() available inside factory functions
  • Full backward compatibility — existing routes without param decorators still work
  • Unit tests for all built-in decorators
  • Documentation page with createParamDecorator examples

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions