aws / aws/chalice

[Proposal] Modeling

Open
#1,224 10 comments 8 reactions 0 assignees View on GitHub
proposals
Dominant language
Python
Stars
11.1k
Forks
1k
Avg merge
1d 22h
Merged PRs (30d)
2

Description

Proof of concept: #1223

Proposed interface
==============

Two additional keys on the route decorator. `input_model` and `output_model` (maybe request/response instead?) The value of these is either `None` (default) meaning no models are applied to this route. Or an instance of `ModelConfig`. Which is defined as such

```python
class ModelConfig(object):
def __init__(self, model, validate=False, cls=None):
self.model = model
self.validate = validate
self.cls = cls
...
```

Where `model` is a marshmallow schema (currently class, though I plan to change it to instance), `validate` is whether or not the model should be enforced, and `cls` is a concrete data type that can be deserialized from the input body, or serialized to a response body.

The request object has a new property `model_body` which will be an instance of `cls` created from deserializing the `json_body`. If the `input_model` `cls` is not set then it will raise an error.

An instance of the `cls` on the `output_model` can be returned from a view function and will be automatically serialized using the schema.

Validate works slightly differently for in put and output. For input it will create a body validator in API gateway and enforce validation at the API Gateway layer. Invalid requests will not trigger a lambda and will return a 400.
For output API gateway does not provide any validation (understandably), so we provide it ourselves using the marshmallow schema. Validate will be manually called, and if any errors are discovered an error will be raised and the validation errors will be logged. A 500 will be returned to the user.

Sample Project
===========

```
$ tree .
.
├── app.py
├── chalicelib
│ ├── __init__.py
│ └── models.py
└── requirements.txt

1 directory, 4 files
```

app.py
```python
from chalice import Chalice
from chalice import ModelConfig

from chalicelib import models

app = Chalice(app_name='model-test')

@app.route('/hi', methods=['POST'],
input_model=ModelConfig(
models.UserSchema, validate=True, cls=models.User))
def say_hi():
user = app.current_request.model_body
return user.make_greeting()

@app.route('/get-user', methods=['POST'],
output_model=ModelConfig(
models.UserSchema, cls=models.User))
def get_user():
totally_a_user_i_got_from_a_db = models.User(
first_name='William',
middle_name='Henry',
last_name='Harrison',
)
return totally_a_user_i_got_from_a_db

```

chalicelib/models.py
```python
import random

from marshmallow import Schema, fields

class UserSchema(Schema):
first_name = fields.String(required=True)
middle_name = fields.String()
last_name = fields.String(required=True)

class User(object):
def __init__(self, first_name, last_name, middle_name=None):
self.first_name = first_name
self.middle_name = middle_name
self.last_name = last_name

def make_greeting(self):
greeting = random.choice(['Hi', 'Hello', 'Sup'])
if self.middle_name is None:
return '%s %s %s' % (greeting, self.first_name, self.last_name)
return '%s %s %s %s' % (
greeting, self.first_name, self.middle_name, self.last_name)

```

Sample Calls
=========
first middle and last
```bash
$ echo '{"first_name": "george", "middle_name": "wilbur", "last_name": "francis"}' | http POST $(chalice url)hi
HTTP/1.1 200 OK

Hello george wilbur francis
```
first and last
```bash
$ echo '{"first_name": "george", "last_name": "francis"}' | http POST $(chalice url)hi
HTTP/1.1 200 OK

Hello george francis
```

just first
```bash
$ echo '{"first_name": "george"}' | http POST $(chalice url)hi
HTTP/1.1 400 Bad Request

{
"message": "Invalid request body"
}
```

example showing output serialization of `User` object
```bash
$ http POST $(chalice url)get-user
HTTP/1.1 200 OK

{
"first_name": "William",
"last_name": "Harrison",
"middle_name": "Henry"
}
```

Contributor guide

Open the contributing guide

Research direction

Review proof of concept #1223 and the proposed route decorator, request model_body property, marshmallow schemas, and API Gateway validation behavior. Use the sample app.py and chalicelib/models.py as the reference flow; done means input and output models, deserialization, validation, and serialization behave as shown in the examples.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, python
Domain
api, backend, cloud
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.