diff --git a/.github/workflows/lean.yml b/.github/workflows/lean.yml new file mode 100644 index 0000000..e1e6123 --- /dev/null +++ b/.github/workflows/lean.yml @@ -0,0 +1,40 @@ +name: Test Deploy to LeanEngine + +on: + push: + branches: [ master ] + paths-ignore: + - '**.md' + + pull_request: + branches: [ master ] + paths-ignore: + - '**.md' + +jobs: + build: + + runs-on: ubuntu-latest + steps: + - name: Install lean-cli + run: | + wget --quiet -O lean https://github.com/leancloud/lean-cli/releases/download/v1.0.0/lean-linux-x64 + sudo mv lean /usr/local/bin/lean + chmod a+x /usr/local/bin/lean + + - name: Login + env: + TOKEN: ${{ secrets.ACCESS_TOKEN }} + run: lean login --region us-w1 --token "$TOKEN" + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Connect + env: + APPID: ${{ secrets.PYTHON_FLASK_CI }} + run: lean switch --region US --group web "$APPID" + + - name: Deploy + run: lean deploy --prod + diff --git a/.gitignore b/.gitignore index 39c39eb..82f10fb 100644 --- a/.gitignore +++ b/.gitignore @@ -70,9 +70,6 @@ target/ # IPython Notebook .ipynb_checkpoints -# pyenv -.python-version - # celery beat schedule file celerybeat-schedule diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..d20cc2b --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.8.10 diff --git a/.pyup.yml b/.pyup.yml new file mode 100644 index 0000000..a0833af --- /dev/null +++ b/.pyup.yml @@ -0,0 +1,4 @@ +# autogenerated pyup.io config file +# see https://pyup.io/docs/configuration/ for all available options + +schedule: every day diff --git a/README.md b/README.md index 59addd7..4d5046d 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,20 @@ # Flask-getting-started -一个简单的使用 Flask 的 Python 应用。 -可以运行在 LeanEngine Python 运行时环境。 +A simple Python application based on Flask for LeanEngine Python runtime. -## 本地运行 +## Documentation -首先确认本机已经安装 [Python](http://python.org/) 运行环境。然后执行下列指令: +* [Python Web Hosting Guide](https://docs.leancloud.app/leanengine_webhosting_guide-python.html) +* [Python Cloud Function Guide](https://docs.leancloud.app/leanengine_cloudfunction_guide-python.html) +* [LeanStorage Python Guide](https://docs.leancloud.app/leanstorage_guide-python.html) +* [Python SDK API](https://leancloud.github.io/python-sdk/) +* [lean-cli Guide](https://docs.leancloud.app/leanengine_cli.html) -## 一键部署 -[![Deploy to LeanEngine](http://ac-32vx10b9.clouddn.com/109bd02ee9f5875a.png)](https://leancloud.cn/1.1/functions/_ops/deploy-button) +## Supported Python Versions -## 本地运行 +This project supports the following Python versions (the same as [LeanCloud Python SDK][sdk]): -首先确认本机已经安装 [Python](http://python.org/) 运行环境和 [LeanCloud 命令行工具](https://www.leancloud.cn/docs/leanengine_cli.html),然后执行下列指令: +- Python 2.7 +- Python 3.6, 3.7, 3.8, 3.9 -``` -$ git clone git@github.com:leancloud/python-getting-started.git -$ cd python-getting-started -``` - -### 安装依赖: - -``` -pip install -r requirements.txt -``` - -### 关联应用: - -``` -lean app add origin -``` - -这里的 appId 填上你在 LeanCloud 上创建的某一应用的 appId 即可。origin 则有点像 Git 里的 remote 名称。 - -### 启动项目: - -``` -lean up -``` - -应用即可启动运行:[localhost:3000](http://localhost:3000) - -## 部署到 LeanEngine - -部署到预备环境(若无预备环境则直接部署到生产环境): -``` -lean deploy -``` - -将预备环境的代码发布到生产环境: -``` -lean publish -``` - -## 相关文档 - -* [LeanEngine 指南](https://leancloud.cn/docs/leanengine_guide.html) -* [Python SDK 指南](https://leancloud.cn/docs/python_guide.html) -* [Python SDK API](https://leancloud.cn/docs/api/python/index.html) -* [命令行工具详解](https://leancloud.cn/docs/cloud_code_commandline.html) -* [LeanEngine FAQ](https://leancloud.cn/docs/cloud_code_faq.html) +[sdk]: https://github.com/leancloud/python-sdk \ No newline at end of file diff --git a/app.py b/app.py index aa7fdbf..91a9fcc 100644 --- a/app.py +++ b/app.py @@ -1,17 +1,19 @@ # coding: utf-8 - +import sys from datetime import datetime -from flask import Flask +import leancloud +from flask import Flask, jsonify, request from flask import render_template from flask_sockets import Sockets +from leancloud import LeanCloudError from views.todos import todos_view app = Flask(__name__) sockets = Sockets(app) -# 动态路由 +# routing app.register_blueprint(todos_view, url_prefix='/todos') @@ -25,8 +27,93 @@ def time(): return str(datetime.now()) +@app.route('/version') +def print_version(): + import sys + return sys.version + + @sockets.route('/echo') def echo_socket(ws): while True: message = ws.receive() ws.send(message) + + +# REST API example +class BadGateway(Exception): + status_code = 502 + + def __init__(self, message, status_code=None, payload=None): + Exception.__init__(self) + self.message = message + if status_code is not None: + self.status_code = status_code + self.payload = payload + + def to_json(self): + rv = dict(self.payload or ()) + rv['message'] = self.message + return jsonify(rv) + + +class BadRequest(Exception): + status_code = 400 + + def __init__(self, message, status_code=None, payload=None): + Exception.__init__(self) + self.message = message + if status_code is not None: + self.status_code = status_code + self.payload = payload + + def to_json(self): + rv = dict(self.payload or ()) + rv['message'] = self.message + return jsonify(rv) + + +@app.errorhandler(BadGateway) +def handle_bad_gateway(error): + response = error.to_json() + response.status_code = error.status_code + return response + + +@app.errorhandler(BadRequest) +def handle_bad_request(error): + response = error.to_json() + response.status_code = error.status_code + return response + + +@app.route('/api/python-version', methods=['GET']) +def python_version(): + return jsonify({"python-version": sys.version}) + + +@app.route('/api/todos', methods=['GET', 'POST']) +def todos(): + if request.method == 'GET': + try: + todo_list = leancloud.Query(leancloud.Object.extend('Todo')).descending('createdAt').find() + except LeanCloudError as e: + if e.code == 101: # Class does not exist on the cloud. + return jsonify([]) + else: + raise BadGateway(e.error, e.code) + else: + return jsonify([todo.dump() for todo in todo_list]) + elif request.method == 'POST': + try: + content = request.get_json()['content'] + except KeyError: + raise BadRequest('''receives malformed POST content (proper schema: '{"content": "TODO CONTENT"}')''') + todo = leancloud.Object.extend('Todo')() + todo.set('content', content) + try: + todo.save() + except LeanCloudError as e: + raise BadGateway(e.error, e.code) + else: + return jsonify(success=True) diff --git a/cloud.py b/cloud.py index 3d0a506..504ea82 100644 --- a/cloud.py +++ b/cloud.py @@ -3,10 +3,7 @@ from leancloud import Engine from leancloud import LeanEngineError -from app import app - - -engine = Engine(app) +engine = Engine() @engine.define @@ -21,6 +18,6 @@ def hello(**params): def before_todo_save(todo): content = todo.get('content') if not content: - raise LeanEngineError('内容不能为空') + raise LeanEngineError('Content cannot be empty!') if len(content) >= 240: todo.set('content', content[:240] + ' ...') diff --git a/leanengine.yaml b/leanengine.yaml new file mode 100644 index 0000000..37c0964 --- /dev/null +++ b/leanengine.yaml @@ -0,0 +1 @@ +run: python wsgi.py diff --git a/requirements.txt b/requirements.txt index 6f5d2d7..201f3e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ -gevent>=1.0.2,<2.0.0 gevent-websocket>=0.9.5,<1.0.0 -leancloud-sdk>=1.0.9,<=2.0.0 -Werkzeug>=0.11.11,<1.0.0 -Flask>=0.10.1,<1.0.0 +leancloud>=2.9.4,<3.0.0 +markupsafe<=2.0.1 +Flask>=1.0.0 Flask-Sockets>=0.1,<1.0 diff --git a/runtime.txt b/runtime.txt deleted file mode 100644 index 16e8214..0000000 --- a/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-2.7 diff --git a/static/style.css b/static/style.css index 6ce461d..d3f4169 100644 --- a/static/style.css +++ b/static/style.css @@ -1,4 +1,7 @@ -#container { - margin: 0 auto; - width: 960px; +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} +a { + color: #00b7ff; } diff --git a/templates/index.html b/templates/index.html index 0a2cbae..210a296 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,9 +7,10 @@

LeanEngine

-

这是 LeanEngine 的示例应用

-

一个简单的动态路由示例

-

一个简单的「TODO 列表」示例

+

This is a LeanEngine demo application.

+

Routing example

+

Example of having REST API return JSON object showing Python version

+

A simple todo demo

diff --git a/templates/todos.html b/templates/todos.html index 7df12b6..345ff74 100644 --- a/templates/todos.html +++ b/templates/todos.html @@ -9,7 +9,7 @@

{{ title }}

- +
+
+

REST API

+
+        GET /api/todos
+        POST /api/todos {"content": "TODO CONTENT"}
+      
+
diff --git a/views/todos.py b/views/todos.py index 5e7bb54..d68cce2 100644 --- a/views/todos.py +++ b/views/todos.py @@ -21,7 +21,7 @@ def show(): try: todos = Query(Todo).descending('createdAt').find() except LeanCloudError as e: - if e.code == 101: # 服务端对应的 Class 还没创建 + if e.code == 101: # Class does not exist on the cloud. todos = [] else: raise e diff --git a/wsgi.py b/wsgi.py index 78b4579..44e37cd 100644 --- a/wsgi.py +++ b/wsgi.py @@ -16,25 +16,28 @@ PORT = int(os.environ['LEANCLOUD_APP_PORT']) leancloud.init(APP_ID, app_key=APP_KEY, master_key=MASTER_KEY) -# 如果需要使用 master key 权限访问 LeanCLoud 服务,请将这里设置为 True +# Set this to be True if you need to access LeanCloud services with Master Key. leancloud.use_master_key(False) -application = engine - +# Uncomment the following line to redirect HTTP requests to HTTPS. +# app = leancloud.HttpsRedirectMiddleware(app) +app = engine.wrap(app) +application = app if __name__ == '__main__': - # 只在本地开发环境执行的代码 from gevent.pywsgi import WSGIServer from geventwebsocket.handler import WebSocketHandler - from werkzeug.serving import run_with_reloader - from werkzeug.debug import DebuggedApplication - @run_with_reloader - def run(): - global application - app.debug = True - application = DebuggedApplication(application, evalex=True) - server = WSGIServer(('localhost', PORT), application, handler_class=WebSocketHandler) + env = os.environ['LEANCLOUD_APP_ENV'] + if env == 'production': + server = WSGIServer(('0.0.0.0', PORT), application, log=None, handler_class=WebSocketHandler) server.serve_forever() + else: + from werkzeug.serving import run_with_reloader + from werkzeug.debug import DebuggedApplication - run() + app.debug = True + application = DebuggedApplication(application, evalex=True) + address = 'localhost' if env == 'development' else '0.0.0.0' + server = WSGIServer((address, PORT), application, handler_class=WebSocketHandler) + run_with_reloader(server.serve_forever)