Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,8 @@ ENV/
# serverless
.serverless/
.requirements/

# codegen
sdk/

node_modules/
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

SDK_DIR=sdk

$(SDK_DIR):
mkdir -p $(SDK_DIR)

generate-openapi: $(SDK_DIR)
$(shell serverless invoke local -f openapi > $(SDK_DIR)/openapi.json)

generate-client: generate-openapi
ifdef lang
mkdir -p $(SDK_DIR)/$(lang)
docker run --rm -v "$(PWD)/$(SDK_DIR):/local" openapitools/openapi-generator-cli generate \
-i /local/openapi.json \
-g $(lang) \
-o /local/$(lang)
@echo "SDK output to $(SDK_DIR)/$(lang)"
else
@echo "Please specify language with lang="
@echo "Example: make lang=typescript-axios generate-client"
endif
37 changes: 26 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
# Serverless-Flask
### API edition.

The fastest way to a Flask application with [Serverless](https://github.com/serverless/serverless).

## Usage
This version includes APISpec and Marshmallow for easy declaration of input and output for API functions
and automatic generation of swagger. CORS is enabled and can be customized.

## Usage
* [Install yarn](https://yarnpkg.com/lang/en/docs/install/#mac-stable) if you don't have it
```
$ npm install -g serverless
$ serverless install --url https://github.com/alexdebrie/serverless-flask --name my-flask-app
$ cd my-flask-app && npm run setup
<answer prompts>
$ serverless deploy
yarn global install serverless
sls install --url https://github.com/revmischa/serverless-flask --name my-flask-app
cd my-flask-app
yarn setup
# <answer prompts>
sls deploy
```

Once the deploy is complete, run `sls info` to get the endpoint:
Expand All @@ -19,8 +24,8 @@ $ sls info
Service Information
<snip>
endpoints:
ANY - https://abc6defghi.execute-api.us-east-1.amazonaws.com/dev <-- Endpoint
ANY - https://abc6defghi.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
ANY - https://abc6defghi.execute-api.eu-central-1.amazonaws.com/dev <-- Endpoint
ANY - https://abc6defghi.execute-api.eu-central-1.amazonaws.com/dev/{proxy+}
```

Copy paste into your browser, and _voila_!
Expand All @@ -44,13 +49,23 @@ sls wsgi serve
* Debugger is active!
```

Navigate to [localhost:5000](http://localhost:5000) to see your app running locally.
Navigate to [localhost:5000](http://localhost:5000) to see swagger UI for your API.


## Client CodeGen

Want to generate a client library for your API? No problem.

* Run `make generate-client lang=$LANG`

Where `$LANG` can be any language supported by [OpenAPI-Generator](https://github.com/openapitools/openapi-generator#overview).

e.g. `lang=go` or `lang=typescript-axios`.


## Configuration

The `postsetup.js` prompt will walk you through some setup questions that may be
custom to your use case. This includes:

- Python runtime version;
- Whether you have Docker setup, which assists in packaging dependencies. For more info, check out [this post on managing your Python packages with Serverless](https://serverless.com/blog/serverless-python-packaging/);
- Whether you want to set up a custom domain that you own, rather than a random assigned one from AWS. For more details on that, look at [this post on setting up a custom domain with API Gateway and Serverless](https://serverless.com/blog/serverless-api-gateway-domain/).
1 change: 1 addition & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""API endpoints."""
26 changes: 26 additions & 0 deletions api/doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Generate OpenAPI documentation."""
from flask import Flask
from flask_apispec.extension import FlaskApiSpec, make_apispec


class ServerlessOpenAPI:
def __init__(self, app: Flask) -> None:
app.config.update({
'APISPEC_SPEC': make_apispec(title='Flask API', version='v1'),
'APISPEC_SWAGGER_URL': '/swagger/',
})
self.apispec = FlaskApiSpec(app)

def register_all(self):
"""Generate documentation.

Call after all views loaded.
"""
self.apispec.register_existing_resources()


# lambda function
def get_openapi(event, context):
"""Get raw OpenAPI v2 API description."""
from app import docs
return docs.apispec.spec.to_dict()
16 changes: 16 additions & 0 deletions api/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from flask_apispec import use_kwargs, marshal_with
from marshmallow import Schema, fields
from app import app
from typing import Optional


class IndexSchema(Schema):
name: Optional[str] = fields.Str(required=False)


@app.route('/api/', methods=['POST'])
@use_kwargs(IndexSchema(strict=True))
@marshal_with(IndexSchema)
def api_index(name: str = None):
"""Main API endpoint."""
return {'name': f'you entered {name}'}
18 changes: 16 additions & 2 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
from flask import Flask, render_template
"""Lambda entry point."""
from flask import Flask, redirect, url_for
from flask_cors import CORS
from werkzeug.contrib.fixers import ProxyFix
from api.doc import ServerlessOpenAPI

# create app
app = Flask(__name__)
CORS(app)
app.wsgi_app = ProxyFix(app.wsgi_app)
docs = ServerlessOpenAPI(app)

# set up views
import api.index
docs.register_all()


@app.route("/")
def main():
return render_template('index.html')
"""Index page."""
return redirect(url_for('flask-apispec.swagger-ui'))


if __name__ == "__main__":
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"setup": "npm install",
"setup": "yarn install",
"postsetup": "node postsetup.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand All @@ -14,7 +14,9 @@
"chalk": "^2.1.0",
"inquirer": "^3.2.3",
"js-yaml": "^3.9.1",
"serverless-python-requirements": "^2.5.0",
"serverless-domain-manager": "^2.6.6",
"serverless-python-requirements": "^4.2.5",
"serverless-wsgi": "^1.3.0"
}
},
"dependencies": {}
}
14 changes: 2 additions & 12 deletions postsetup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Check to see if the user has Docker installed and which version of Python they prefer.
// Check to see if the user has Docker installed

'use strict';
var inquirer = require('inquirer');
Expand All @@ -9,15 +9,6 @@ var fs = require('fs');
console.log(chalk.yellow('Hi, a few quick questions before we start:'));

var questions = [
{
type: 'list',
name: 'python',
message: 'What Python version?',
choices: ['python2.7', 'python3.6'],
filter: function (val) {
return val.toLowerCase();
}
},
{
type: 'confirm',
name: 'docker',
Expand All @@ -41,10 +32,9 @@ var questions = [
]

inquirer.prompt(questions).then(function (answers) {

var doc = YAML.safeLoad(fs.readFileSync('serverless.yml', 'utf8'));
doc.custom.pythonRequirements.dockerizePip = answers.docker;
doc.provider.runtime = answers.python;
if (answers.wantsDomain) {
doc.plugins.push('serverless-domain-manager');
doc.custom.customDomain = createCustomDomain(answers.domainName);
Expand Down
14 changes: 8 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
click==6.7
Flask==0.12.2
itsdangerous==0.24
Jinja2==2.9.6
MarkupSafe==1.0
Werkzeug==0.12.2
Flask
flask_cors
boto3
# flask_apispec
# use fixed SERVER_NAME https://github.com/revmischa/flask-apispec/commits/script_root
git+https://github.com/revmischa/flask-apispec.git@7cf50d33189873f498b4474610e32896247a1260#egg=flask_apispec
apispec
marshmallow
8 changes: 6 additions & 2 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ package:

provider:
name: aws
runtime: python2.7
runtime: python3.6
stage: dev
region: us-east-1
region: eu-central-1

functions:
app:
handler: wsgi.handler
events:
- http: ANY /
- http: 'ANY {proxy+}'
openapi:
handler: api.doc.get_openapi
events:
- http: GET /openapi
Loading