├── .github └── workflows │ └── lean.yml ├── .gitignore ├── .leanignore ├── .python-version ├── .pyup.yml ├── LICENSE ├── README.md ├── app.py ├── cloud.py ├── leanengine.yaml ├── requirements.txt ├── static └── style.css ├── templates ├── index.html └── todos.html ├── views ├── __init__.py └── todos.py └── wsgi.py /.github/workflows/lean.yml: -------------------------------------------------------------------------------- 1 | name: Test Deploy to LeanEngine 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '**.md' 8 | 9 | pull_request: 10 | branches: [ master ] 11 | paths-ignore: 12 | - '**.md' 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Install lean-cli 20 | run: | 21 | wget --quiet -O lean https://github.com/leancloud/lean-cli/releases/download/v1.0.0/lean-linux-x64 22 | sudo mv lean /usr/local/bin/lean 23 | chmod a+x /usr/local/bin/lean 24 | 25 | - name: Login 26 | env: 27 | TOKEN: ${{ secrets.ACCESS_TOKEN }} 28 | run: lean login --region us-w1 --token "$TOKEN" 29 | 30 | - name: Checkout code 31 | uses: actions/checkout@v2 32 | 33 | - name: Connect 34 | env: 35 | APPID: ${{ secrets.PYTHON_FLASK_CI }} 36 | run: lean switch --region US --group web "$APPID" 37 | 38 | - name: Deploy 39 | run: lean deploy --prod 40 | 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # celery beat schedule file 74 | celerybeat-schedule 75 | 76 | # dotenv 77 | .env 78 | 79 | # virtualenv 80 | venv/ 81 | ENV/ 82 | 83 | # Spyder project settings 84 | .spyderproject 85 | 86 | # Rope project settings 87 | .ropeproject 88 | 89 | # LeanCloud settings 90 | .leancloud/ 91 | .avoscloud/ 92 | -------------------------------------------------------------------------------- /.leanignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .avoscloud/ 3 | .leancloud/ 4 | venv 5 | *.pyc 6 | __pycache__/ 7 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.8.10 2 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # autogenerated pyup.io config file 2 | # see https://pyup.io/docs/configuration/ for all available options 3 | 4 | schedule: every day 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Asaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-getting-started 2 | 3 | A simple Python application based on Flask for LeanEngine Python runtime. 4 | 5 | ## Documentation 6 | 7 | * [Python Web Hosting Guide](https://docs.leancloud.app/leanengine_webhosting_guide-python.html) 8 | * [Python Cloud Function Guide](https://docs.leancloud.app/leanengine_cloudfunction_guide-python.html) 9 | * [LeanStorage Python Guide](https://docs.leancloud.app/leanstorage_guide-python.html) 10 | * [Python SDK API](https://leancloud.github.io/python-sdk/) 11 | * [lean-cli Guide](https://docs.leancloud.app/leanengine_cli.html) 12 | 13 | ## Supported Python Versions 14 | 15 | This project supports the following Python versions (the same as [LeanCloud Python SDK][sdk]): 16 | 17 | - Python 2.7 18 | - Python 3.6, 3.7, 3.8, 3.9 19 | 20 | [sdk]: https://github.com/leancloud/python-sdk -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import sys 3 | from datetime import datetime 4 | 5 | import leancloud 6 | from flask import Flask, jsonify, request 7 | from flask import render_template 8 | from flask_sockets import Sockets 9 | from leancloud import LeanCloudError 10 | 11 | from views.todos import todos_view 12 | 13 | app = Flask(__name__) 14 | sockets = Sockets(app) 15 | 16 | # routing 17 | app.register_blueprint(todos_view, url_prefix='/todos') 18 | 19 | 20 | @app.route('/') 21 | def index(): 22 | return render_template('index.html') 23 | 24 | 25 | @app.route('/time') 26 | def time(): 27 | return str(datetime.now()) 28 | 29 | 30 | @app.route('/version') 31 | def print_version(): 32 | import sys 33 | return sys.version 34 | 35 | 36 | @sockets.route('/echo') 37 | def echo_socket(ws): 38 | while True: 39 | message = ws.receive() 40 | ws.send(message) 41 | 42 | 43 | # REST API example 44 | class BadGateway(Exception): 45 | status_code = 502 46 | 47 | def __init__(self, message, status_code=None, payload=None): 48 | Exception.__init__(self) 49 | self.message = message 50 | if status_code is not None: 51 | self.status_code = status_code 52 | self.payload = payload 53 | 54 | def to_json(self): 55 | rv = dict(self.payload or ()) 56 | rv['message'] = self.message 57 | return jsonify(rv) 58 | 59 | 60 | class BadRequest(Exception): 61 | status_code = 400 62 | 63 | def __init__(self, message, status_code=None, payload=None): 64 | Exception.__init__(self) 65 | self.message = message 66 | if status_code is not None: 67 | self.status_code = status_code 68 | self.payload = payload 69 | 70 | def to_json(self): 71 | rv = dict(self.payload or ()) 72 | rv['message'] = self.message 73 | return jsonify(rv) 74 | 75 | 76 | @app.errorhandler(BadGateway) 77 | def handle_bad_gateway(error): 78 | response = error.to_json() 79 | response.status_code = error.status_code 80 | return response 81 | 82 | 83 | @app.errorhandler(BadRequest) 84 | def handle_bad_request(error): 85 | response = error.to_json() 86 | response.status_code = error.status_code 87 | return response 88 | 89 | 90 | @app.route('/api/python-version', methods=['GET']) 91 | def python_version(): 92 | return jsonify({"python-version": sys.version}) 93 | 94 | 95 | @app.route('/api/todos', methods=['GET', 'POST']) 96 | def todos(): 97 | if request.method == 'GET': 98 | try: 99 | todo_list = leancloud.Query(leancloud.Object.extend('Todo')).descending('createdAt').find() 100 | except LeanCloudError as e: 101 | if e.code == 101: # Class does not exist on the cloud. 102 | return jsonify([]) 103 | else: 104 | raise BadGateway(e.error, e.code) 105 | else: 106 | return jsonify([todo.dump() for todo in todo_list]) 107 | elif request.method == 'POST': 108 | try: 109 | content = request.get_json()['content'] 110 | except KeyError: 111 | raise BadRequest('''receives malformed POST content (proper schema: '{"content": "TODO CONTENT"}')''') 112 | todo = leancloud.Object.extend('Todo')() 113 | todo.set('content', content) 114 | try: 115 | todo.save() 116 | except LeanCloudError as e: 117 | raise BadGateway(e.error, e.code) 118 | else: 119 | return jsonify(success=True) 120 | -------------------------------------------------------------------------------- /cloud.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from leancloud import Engine 4 | from leancloud import LeanEngineError 5 | 6 | engine = Engine() 7 | 8 | 9 | @engine.define 10 | def hello(**params): 11 | if 'name' in params: 12 | return 'Hello, {}!'.format(params['name']) 13 | else: 14 | return 'Hello, LeanCloud!' 15 | 16 | 17 | @engine.before_save('Todo') 18 | def before_todo_save(todo): 19 | content = todo.get('content') 20 | if not content: 21 | raise LeanEngineError('Content cannot be empty!') 22 | if len(content) >= 240: 23 | todo.set('content', content[:240] + ' ...') 24 | -------------------------------------------------------------------------------- /leanengine.yaml: -------------------------------------------------------------------------------- 1 | run: python wsgi.py 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gevent-websocket>=0.9.5,<1.0.0 2 | leancloud>=2.9.4,<3.0.0 3 | markupsafe<=2.0.1 4 | Flask>=1.0.0 5 | Flask-Sockets>=0.1,<1.0 6 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | a { 6 | color: #00b7ff; 7 | } 8 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |This is a LeanEngine demo application.
11 | 12 |Example of having REST API return JSON object showing Python version
13 | 14 |23 | GET /api/todos 24 | POST /api/todos {"content": "TODO CONTENT"} 25 |26 |