Skip to content

Commit

Permalink
Update the handler to match style of golang-http-template
Browse files Browse the repository at this point in the history
- Created two new templates, python3-http & python3-http-armhf
- Added waitress as the production server for new templates

Signed-off-by: Kevin Turcios <kevin_turcios@outlook.com>
  • Loading branch information
kturcios authored and alexellis committed Apr 4, 2019
1 parent 070bba5 commit 461c2be
Show file tree
Hide file tree
Showing 14 changed files with 421 additions and 20 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
*.pyc
build
164 changes: 145 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,158 @@
# python-flask-template
OpenFaaS Python Flask Templates
=============================================

Python OpenFaaS template with Flask
The Python Flask templates make use of the incubator project [of-watchdog](https://github.com/openfaas-incubator/of-watchdog) to handle higher throughput than the [classic watchdog](https://github.com/openfaas/faas/tree/master/watchdog) due to the process being kept warm.

To try this out with either Python 2.7 or Python 3.6:

```bash
faas template pull https://github.com/openfaas-incubator/python-flask-template
faas new --list
Languages available as templates:
Templates available under this repository:
- python27-flask
- python3-flask
```
- python3-flask-armhf
- python3-http*
- python3-http-armhf*

Generate a function with one of the languages:
Notes:
- The templates listed with an asterik are the only ones that support additional control over the HTTP response and provide access to HTTP request details.
- To build and deploy a function for Raspberry Pi or ARMv7 in general, use the language templates ending in *-armhf*

```bash
faas new --lang python3-flask myfunction
mv myfunction.yml stack.yml
## Downloading the templates
```
$ faas template pull https://github.com/openfaas-incubator/python-flask-template
```

Followed by the usual flow:
# Using the python27-flask/python3-flask templates
Create a new function
```
$ faas new --lang python27-flask <fn-name>
```
Build, push, and deploy
```
$ faas up -f <fn-name>.yml
```
Test the new function
```
$ echo -n content | faas invoke <fn-name>
```

# Using the python3-http templates
Create a new function
```
$ faas new --lang python3-http <fn-name>
```
Build, push, and deploy
```
$ faas up -f <fn-name>.yml
```
Set your OpenFaaS gateway URL. For example:
```
faas build \
&& faas deploy
&& faas list --verbose
$ OPENFAAS_URL=http://127.0.0.1:8080
```
Test the new function
```
$ curl -i $OPENFAAS_URL/function/<fn-name>
```

## Event and Context Data
The function handler is passed two arguments, *event* and *context*.

*event* contains data about the request, including:
- body
- headers
- method
- query
- path

*context* contains basic information about the function, including:
- hostname

## Response Bodies
By default, flask will automatically attempt to set the correct Content-Type header for you based on the type of response.

# Wait a couple of seconds then:
For example, returning a dict object type will automatically attach the header `Content-Type: application/json` and returning a string type will automatically attach the `Content-Type: text/html, charset=utf-8` for you.

echo -n content | faas invoke myfunction
## Example usage
### Custom Status Codes and Response Bodies
Successful response status code and JSON response body
```python
def handle(event, context):
return {
"statusCode": 200,
"body": {
"key": "value"
}
}
```
Successful response status code and string response body
```python
def handle(event, context):
return {
"statusCode": 201,
"body": "Object successfully created"
}
```
Failure response status code and JSON error message
```python
def handle(event, context):
return {
"statusCode": 400,
"body": {
"error": "Bad request"
}
}
```
### Custom Response Headers
Setting custom response headers
```python
def handle(event, context):
return {
"statusCode": 200,
"body": {
"key": "value"
},
"headers": {
"Location": "https://www.example.com/"
}
}
```
### Accessing Event Data
Accessing request body
```python
def handle(event, context):
return {
"statusCode": 200,
"body": "You said: " + str(event.body)
}
```
Accessing request method
```python
def handle(event, context):
if event.method == 'GET':
return {
"statusCode": 200,
"body": "GET request"
}
else:
return {
"statusCode": 405,
"body": "Method not allowed"
}
```
Accessing request query string arguments
```python
def handle(event, context):
return {
"statusCode": 200,
"body": {
"name": event.query['name']
}
}
```
Accessing request headers
```python
def handle(event, context):
return {
"statusCode": 200,
"body": {
"content-type-received": event.headers['Content-Type']
}
}
```
48 changes: 48 additions & 0 deletions template/python3-http-armhf/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
FROM armhf/python:3.6-alpine

ARG ADDITIONAL_PACKAGE
# Alternatively use ADD https:// (which will not be cached by Docker builder)
RUN apk --no-cache add curl ${ADDITIONAL_PACKAGE} \
&& echo "Pulling watchdog binary from Github." \
&& curl -sSLf https://github.com/openfaas-incubator/of-watchdog/releases/download/0.4.6/of-watchdog-armhf > /usr/bin/fwatchdog \
&& chmod +x /usr/bin/fwatchdog \
&& apk del curl --no-cache

# Add non root user
RUN addgroup -S app && adduser app -S -G app
RUN chown app /home/app

USER app

ENV PATH=$PATH:/home/app/.local/bin

WORKDIR /home/app/

COPY index.py .
COPY requirements.txt .
USER root
RUN pip install -r requirements.txt
USER app

RUN mkdir -p function
RUN touch ./function/__init__.py
WORKDIR /home/app/function/
COPY function/requirements.txt .
RUN pip install --user -r requirements.txt

WORKDIR /home/app/

USER root
COPY function function
RUN chown -R app:app ./
USER app

# Set up of-watchdog for HTTP mode
ENV fprocess="python index.py"
ENV cgi_headers="true"
ENV mode="http"
ENV upstream_url="http://127.0.0.1:5000"

HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1

CMD ["fwatchdog"]
6 changes: 6 additions & 0 deletions template/python3-http-armhf/function/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def handle(event, context):
# TODO implement
return {
"statusCode": 200,
"body": "Hello from OpenFaaS!"
}
Empty file.
68 changes: 68 additions & 0 deletions template/python3-http-armhf/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from flask import Flask, request, jsonify
from waitress import serve
import os

from function import handler

app = Flask(__name__)

class Event:
def __init__(self):
self.body = request.get_data()
self.headers = request.headers
self.method = request.method
self.query = request.args
self.path = request.path

class Context:
def __init__(self):
self.hostname = os.environ['HOSTNAME']

def format_status_code(resp):
if 'statusCode' in resp:
return resp['statusCode']

return 200

def format_body(resp):
if 'body' not in resp:
return ""
elif type(resp['body']) == dict:
return jsonify(resp['body'])
else:
return str(resp['body'])

def format_headers(resp):
if 'headers' not in resp:
return []
elif type(resp['headers']) == dict:
headers = []
for key in resp['headers'].keys():
header_tuple = (key, resp['headers'][key])
headers.append(header_tuple)
return headers

return resp['headers']

def format_response(resp):
if resp == None:
return ('', 200)

statusCode = format_status_code(resp)
body = format_body(resp)
headers = format_headers(resp)

return (body, statusCode, headers)

@app.route('/', defaults={'path': ''}, methods=['GET', 'PUT', 'POST', 'PATCH', 'DELETE'])
@app.route('/<path:path>', methods=['GET', 'PUT', 'POST', 'PATCH' 'DELETE'])
def call_handler(path):
event = Event()
context = Context()
response_data = handler.handle(event, context)

resp = format_response(response_data)
return resp

if __name__ == '__main__':
serve(app, host='0.0.0.0', port=5000)
2 changes: 2 additions & 0 deletions template/python3-http-armhf/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask
waitress
14 changes: 14 additions & 0 deletions template/python3-http-armhf/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
language: python3-http-armhf
fprocess: python index.py
build_options:
- name: dev
packages:
- make
- automake
- gcc
- g++
- subversion
- python3-dev
- musl-dev
- libffi-dev
- git
48 changes: 48 additions & 0 deletions template/python3-http/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
FROM python:3.7-alpine

ARG ADDITIONAL_PACKAGE
# Alternatively use ADD https:// (which will not be cached by Docker builder)
RUN apk --no-cache add curl ${ADDITIONAL_PACKAGE} \
&& echo "Pulling watchdog binary from Github." \
&& curl -sSLf https://github.com/openfaas-incubator/of-watchdog/releases/download/0.4.6/of-watchdog > /usr/bin/fwatchdog \
&& chmod +x /usr/bin/fwatchdog \
&& apk del curl --no-cache

# Add non root user
RUN addgroup -S app && adduser app -S -G app
RUN chown app /home/app

USER app

ENV PATH=$PATH:/home/app/.local/bin

WORKDIR /home/app/

COPY index.py .
COPY requirements.txt .
USER root
RUN pip install -r requirements.txt
USER app

RUN mkdir -p function
RUN touch ./function/__init__.py
WORKDIR /home/app/function/
COPY function/requirements.txt .
RUN pip install --user -r requirements.txt

WORKDIR /home/app/

USER root
COPY function function
RUN chown -R app:app ./
USER app

# Set up of-watchdog for HTTP mode
ENV fprocess="python index.py"
ENV cgi_headers="true"
ENV mode="http"
ENV upstream_url="http://127.0.0.1:5000"

HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1

CMD ["fwatchdog"]
6 changes: 6 additions & 0 deletions template/python3-http/function/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def handle(event, context):
# TODO implement
return {
"statusCode": 200,
"body": "Hello from OpenFaaS!"
}
Empty file.

0 comments on commit 461c2be

Please sign in to comment.