├── .env ├── .gitignore ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── api ├── model │ ├── __init__.py │ └── welcome.py ├── route │ └── home.py └── schema │ ├── __init__.py │ └── welcome.py ├── app.py ├── config.py └── test ├── __init__.py └── route ├── __init__.py └── test_home.py /.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bajcmartinez/flask-api-starter-kit/2e690ebd63a8a194f8a07d3adaf48789e9f056ad/.env -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Live Code Stream 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 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | flask = "*" 8 | flasgger = "*" 9 | flask-marshmallow = "*" 10 | apispec = "*" 11 | python-dotenv = "*" 12 | 13 | [dev-packages] 14 | 15 | [requires] 16 | python_version = "3.8" 17 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "22353aa3a48240d0f6fc7f8ced35b46dc72052682407a47a21c6a521d73d217e" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.python.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "apispec": { 20 | "hashes": [ 21 | "sha256:419d0564b899e182c2af50483ea074db8cb05fee60838be58bb4542095d5c08d", 22 | "sha256:9bf4e51d56c9067c60668b78210ae213894f060f85593dc2ad8805eb7d875a2a" 23 | ], 24 | "index": "pypi", 25 | "version": "==3.3.0" 26 | }, 27 | "attrs": { 28 | "hashes": [ 29 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 30 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 31 | ], 32 | "version": "==19.3.0" 33 | }, 34 | "click": { 35 | "hashes": [ 36 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 37 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 38 | ], 39 | "version": "==7.1.2" 40 | }, 41 | "flasgger": { 42 | "hashes": [ 43 | "sha256:37137b3292738580c42e03662bfb8731656a11d636e76f76d30e572c1fa5bd0d", 44 | "sha256:7c187f7a7caeb42645f7de652335b794375925467407e40322620fb9d401b38c" 45 | ], 46 | "index": "pypi", 47 | "version": "==0.9.4" 48 | }, 49 | "flask": { 50 | "hashes": [ 51 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", 52 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" 53 | ], 54 | "index": "pypi", 55 | "version": "==1.1.2" 56 | }, 57 | "flask-marshmallow": { 58 | "hashes": [ 59 | "sha256:6e6aec171b8e092e0eafaf035ff5b8637bf3a58ab46f568c4c1bab02f2a3c196", 60 | "sha256:a1685536e7ab5abdc712bbc1ac1a6b0b50951a368502f7985e7d1c27b3c21e59" 61 | ], 62 | "index": "pypi", 63 | "version": "==0.12.0" 64 | }, 65 | "itsdangerous": { 66 | "hashes": [ 67 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 68 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 69 | ], 70 | "version": "==1.1.0" 71 | }, 72 | "jinja2": { 73 | "hashes": [ 74 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 75 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 76 | ], 77 | "version": "==2.11.2" 78 | }, 79 | "jsonschema": { 80 | "hashes": [ 81 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 82 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 83 | ], 84 | "version": "==3.2.0" 85 | }, 86 | "markupsafe": { 87 | "hashes": [ 88 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 89 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 90 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 91 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 92 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 93 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 94 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 95 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 96 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 97 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 98 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 99 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 100 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 101 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 102 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 103 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 104 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 105 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 106 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 107 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 108 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 109 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 110 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 111 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 112 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 113 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 114 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 115 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 116 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 117 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 118 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 119 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 120 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 121 | ], 122 | "version": "==1.1.1" 123 | }, 124 | "marshmallow": { 125 | "hashes": [ 126 | "sha256:c2673233aa21dde264b84349dc2fd1dce5f30ed724a0a00e75426734de5b84ab", 127 | "sha256:f88fe96434b1f0f476d54224d59333eba8ca1a203a2695683c1855675c4049a7" 128 | ], 129 | "version": "==3.6.0" 130 | }, 131 | "mistune": { 132 | "hashes": [ 133 | "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", 134 | "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4" 135 | ], 136 | "version": "==0.8.4" 137 | }, 138 | "pyrsistent": { 139 | "hashes": [ 140 | "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3" 141 | ], 142 | "version": "==0.16.0" 143 | }, 144 | "pyyaml": { 145 | "hashes": [ 146 | "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", 147 | "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", 148 | "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", 149 | "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", 150 | "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", 151 | "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", 152 | "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", 153 | "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", 154 | "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", 155 | "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", 156 | "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" 157 | ], 158 | "version": "==5.3.1" 159 | }, 160 | "six": { 161 | "hashes": [ 162 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 163 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 164 | ], 165 | "version": "==1.15.0" 166 | }, 167 | "werkzeug": { 168 | "hashes": [ 169 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 170 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 171 | ], 172 | "version": "==1.0.1" 173 | } 174 | }, 175 | "develop": {} 176 | } 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask API Starter Kit 2 | 3 | Sample API layout structure to be used as a baseline for other apps 4 | 5 | ## Dependencies 6 | 7 | - [flask](https://palletsprojects.com/p/flask/): Python server of choise 8 | - [flasgger](https://github.com/flasgger/flasgger): Used to generate the swagger documentation 9 | - [flask-marshmallow](https://flask-marshmallow.readthedocs.io/en/latest/): My favourite serializer 10 | - [apispec](https://apispec.readthedocs.io/en/latest/): Required for the integration between marshmallow and flasgger 11 | 12 | ## Set Up 13 | 14 | 1. Check out the code 15 | 2. Install requirements 16 | ``` 17 | pipenv install 18 | ``` 19 | 3. Start the server with: 20 | ``` 21 | pipenv run python -m flask run 22 | ``` 23 | 24 | 4. Visit http://localhost/api for the home api 25 | 26 | 4. Visit http://localhost/apidocs for the swagger documentation 27 | 28 | ## Tests 29 | 30 | The code is covered by tests, to run the tests please execute 31 | 32 | ``` 33 | pipenv run python -m unittest 34 | ``` -------------------------------------------------------------------------------- /api/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bajcmartinez/flask-api-starter-kit/2e690ebd63a8a194f8a07d3adaf48789e9f056ad/api/model/__init__.py -------------------------------------------------------------------------------- /api/model/welcome.py: -------------------------------------------------------------------------------- 1 | class WelcomeModel: 2 | def __init__(self): 3 | self.message = "Hello World!" 4 | -------------------------------------------------------------------------------- /api/route/home.py: -------------------------------------------------------------------------------- 1 | from http import HTTPStatus 2 | from flask import Blueprint 3 | from flasgger import swag_from 4 | from api.model.welcome import WelcomeModel 5 | from api.schema.welcome import WelcomeSchema 6 | 7 | home_api = Blueprint('api', __name__) 8 | 9 | 10 | @home_api.route('/') 11 | @swag_from({ 12 | 'responses': { 13 | HTTPStatus.OK.value: { 14 | 'description': 'Welcome to the Flask Starter Kit', 15 | 'schema': WelcomeSchema 16 | } 17 | } 18 | }) 19 | def welcome(): 20 | """ 21 | 1 liner about the route 22 | A more detailed description of the endpoint 23 | --- 24 | """ 25 | result = WelcomeModel() 26 | return WelcomeSchema().dump(result), 200 27 | -------------------------------------------------------------------------------- /api/schema/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bajcmartinez/flask-api-starter-kit/2e690ebd63a8a194f8a07d3adaf48789e9f056ad/api/schema/__init__.py -------------------------------------------------------------------------------- /api/schema/welcome.py: -------------------------------------------------------------------------------- 1 | from flask_marshmallow import Schema 2 | from marshmallow.fields import Str 3 | 4 | 5 | class WelcomeSchema(Schema): 6 | class Meta: 7 | # Fields to expose 8 | fields = ["message"] 9 | 10 | message = Str() -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flasgger import Swagger 3 | from api.route.home import home_api 4 | 5 | def create_app(): 6 | app = Flask(__name__) 7 | 8 | app.config['SWAGGER'] = { 9 | 'title': 'Flask API Starter Kit', 10 | } 11 | swagger = Swagger(app) 12 | ## Initialize Config 13 | app.config.from_pyfile('config.py') 14 | app.register_blueprint(home_api, url_prefix='/api') 15 | 16 | return app 17 | 18 | 19 | if __name__ == '__main__': 20 | from argparse import ArgumentParser 21 | 22 | parser = ArgumentParser() 23 | parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on') 24 | args = parser.parse_args() 25 | port = args.port 26 | 27 | app = create_app() 28 | 29 | app.run(host='0.0.0.0', port=port) 30 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | """[General Configuration Params] 2 | """ 3 | from os import environ, path 4 | from dotenv import load_dotenv 5 | 6 | basedir = path.abspath(path.dirname(__file__)) 7 | load_dotenv(path.join(basedir, '.env')) 8 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bajcmartinez/flask-api-starter-kit/2e690ebd63a8a194f8a07d3adaf48789e9f056ad/test/__init__.py -------------------------------------------------------------------------------- /test/route/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bajcmartinez/flask-api-starter-kit/2e690ebd63a8a194f8a07d3adaf48789e9f056ad/test/route/__init__.py -------------------------------------------------------------------------------- /test/route/test_home.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from app import create_app 3 | 4 | 5 | class TestWelcome(TestCase): 6 | def setUp(self): 7 | self.app = create_app().test_client() 8 | 9 | def test_welcome(self): 10 | """ 11 | Tests the route screen message 12 | """ 13 | rv = self.app.get('/api/') 14 | 15 | # If we recalculate the hash on the block we should get the same result as we have stored 16 | self.assertEqual({"message": 'Hello World!'}, rv.get_json()) 17 | --------------------------------------------------------------------------------