├── .gitignore ├── LICENSE ├── README.md ├── app.py ├── app ├── __init__.py ├── models.py ├── routes.py └── schema.py ├── config.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Code Editor 2 | .vscode 3 | 4 | ### Database ### 5 | *.accdb 6 | *.db 7 | *.dbf 8 | *.mdb 9 | *.pdb 10 | *.sqlite 11 | *.sqlite3 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | MANIFEST 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .nox/ 56 | .coverage 57 | .coverage.* 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | *.cover 62 | *.py,cover 63 | .hypothesis/ 64 | .pytest_cache/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Django stuff: 71 | *.log 72 | local_settings.py 73 | db.sqlite3 74 | db.sqlite3-journal 75 | 76 | # Flask stuff: 77 | instance/ 78 | .webassets-cache 79 | 80 | # Scrapy stuff: 81 | .scrapy 82 | 83 | # Sphinx documentation 84 | docs/_build/ 85 | 86 | # PyBuilder 87 | target/ 88 | 89 | # Jupyter Notebook 90 | .ipynb_checkpoints 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 107 | __pypackages__/ 108 | 109 | # Celery stuff 110 | celerybeat-schedule 111 | celerybeat.pid 112 | 113 | # SageMath parsed files 114 | *.sage.py 115 | 116 | # Environments 117 | .env 118 | .venv 119 | env/ 120 | venv/ 121 | ENV/ 122 | env.bak/ 123 | venv.bak/ 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | 143 | # migrations 144 | */migrations/* 145 | **/migrations 146 | **/migrations/__init__.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fatema T. Zuhora 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-SQLAlchemy-RESTful-CRUD 2 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/Django.svg) [![MIT license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/fatematzuhora/Flask-SQLAlchemy-RESTful-CRUD) 3 | 4 | Simple RESTful CRUD API application using [Flask](http://flask.pocoo.org) & [SQLAlchemy](http://www.sqlalchemy.org), and connecting the both using [Flask-SQLAlchemy](http://flask-sqlalchemy.pocoo.org) library. 5 | 6 | In this application we are connecting a MySQL database in a python flask file in which there are THREE tables Category, Author, and Books. The Book table contains One-to-Many relationship with Categories and Authors. We use to add a new category, author, book in the database, retrieve them. And later we can update and delete from the database. 7 | 8 | 9 | ## Getting started 10 | * At first you'll need to get the source code of the project. Do this by cloning the [Flask-SQLAlchemy-RESTful-CRUD repository](https://github.com/fatematzuhora/Flask-SQLAlchemy-RESTful-CRUD). 11 | ``` 12 | $ git clone https://github.com/fatematzuhora/Flask-SQLAlchemy-RESTful-CRUD.git 13 | $ cd Flask-SQLAlchemy-RESTful-CRUD 14 | ``` 15 | 16 | * Create a virtual environment for this project and install dependencies 17 | ``` 18 | $ virtualenv .venv 19 | ``` 20 | 21 | * Activate the virtual environment 22 | ``` 23 | $ source .venv/bin/activate 24 | ``` 25 | 26 | * Install the dependencies 27 | ``` 28 | $ pip install -r requirements.txt 29 | ``` 30 | 31 | * Create a environment file and configure it 32 | ``` 33 | $ touch .env 34 | ``` 35 | 36 | #### Sample .env File 37 | ``` 38 | SECRET_KEY="w&8s%5^5vuhy2-gvkyi=gg4e*tso*51mb$l!=%o(@$a2tmq6o+Flask-SQLAlchemy-RESTful-CRUD" 39 | DEBUG=TRUE 40 | SQLALCHEMY_DATABASE_URI="mysql://YOUR_DB_USER_NAME:YOUR_DB_PASS@localhost:3306/YOUR_DB_NAME" 41 | SQLALCHEMY_TRACK_MODIFICATIONS=FALSE 42 | ``` 43 | 44 | * Update `SQLALCHEMY_DATABASE_URI` at the `.env` file according to your MySQL database information 45 | 46 | 47 | ## Running the App 48 | 49 | #### 1) With Database Migration 50 | 51 | ``` 52 | $ export FLASK_APP=app.py 53 | $ flask db init 54 | ``` 55 | 56 | * Create a migration file for all tables 57 | ``` 58 | $ flask db migrate -m tables 59 | ``` 60 | 61 | * Upgrade the database with migration file 62 | ``` 63 | $ flask db upgrade 64 | ``` 65 | 66 | * Run the app 67 | ``` 68 | $ flask run 69 | ``` 70 | 71 | And finally, the application will run on the following URL: http://127.0.0.1:5000 72 | 73 | 74 | #### 2) Without Migration 75 | 76 | * Simply run the following command, it will create database tables and run the project on the following URL: http://0.0.0.0:8087 77 | * And the DEBUG mode will be ON 78 | 79 | ``` 80 | $ python app.py 81 | ``` 82 | 83 | If you want to change the PORT go to the [app.py](https://github.com/fatematzuhora/Flask-SQLAlchemy-RESTful-CRUD/blob/master/app.py) file and edit on the following line of code. 84 | ``` 85 | app.run(host='0.0.0.0', port=8087, debug=True) 86 | ``` 87 | 88 | 89 | ## API Documentation 90 | 91 | #### 1. Create Category 92 | 93 | **Request** 94 | ``` 95 | POST /category 96 | ``` 97 | 98 | **Parameters** 99 | Name|Type|Description|Required 100 | :-:|:-:|:-:|:-: 101 | `name`|`string`|category name|`True` 102 | `short_desc`|`string`|category short description|`False` 103 | 104 | **Request Body** 105 | ``` 106 | { 107 | "name": "Python", 108 | "short_desc": "A python programming language blog" 109 | } 110 | ``` 111 | 112 | **Response** 113 | ``` 114 | { 115 | "data": { 116 | "id": 1, 117 | "name": "Python", 118 | "short_desc": "A python programming language blog" 119 | }, 120 | "message": "New Category Created!", 121 | "status": 201 122 | } 123 | ``` 124 | 125 | #### 2. Category List 126 | 127 | **Request** 128 | ``` 129 | GET /category 130 | ``` 131 | 132 | **Response** 133 | ``` 134 | { 135 | "data": [ 136 | { 137 | "id": 1, 138 | "name": "Python", 139 | "short_desc": "A python programming language blog" 140 | }, 141 | .... 142 | ], 143 | "message": "All Categories!", 144 | "status": 200 145 | } 146 | ``` 147 | 148 | #### 3. Category Detail 149 | 150 | **Request** 151 | ``` 152 | GET /category/:id 153 | ``` 154 | 155 | **Response** 156 | ``` 157 | { 158 | "data": { 159 | "id": 1, 160 | "name": "Python", 161 | "short_desc": "A python programming language blog" 162 | }, 163 | "message": "Category Info!", 164 | "status": 200 165 | } 166 | ``` 167 | 168 | #### 4. Update Category 169 | 170 | **Request** 171 | ``` 172 | PATCH /category/:id 173 | ``` 174 | 175 | **Parameters** 176 | Name|Type|Description|Required 177 | :-:|:-:|:-:|:-: 178 | `name`|`string`|category name|`False` 179 | `short_desc`|`string`|category short description|`False` 180 | 181 | #### 5. Delete Category 182 | 183 | **Request** 184 | ``` 185 | DELETE /category/:id 186 | ``` 187 | 188 | **Response** 189 | ``` 190 | { 191 | "message": "Category Deleted!", 192 | "status": 200 193 | } 194 | ``` 195 | 196 | #### 6. Create Author 197 | 198 | **Request** 199 | ``` 200 | POST /author 201 | ``` 202 | 203 | **Parameters** 204 | Name|Type|Description|Required 205 | :-:|:-:|:-:|:-: 206 | `name`|`string`|author name|`True` 207 | `about`|`string`| short description about author|`False` 208 | 209 | **Request Body** 210 | ``` 211 | { 212 | "name": "Fatema T. Zuhora", 213 | "about": "Full Stack Software Engineer" 214 | } 215 | ``` 216 | 217 | **Response** 218 | ``` 219 | { 220 | "data": { 221 | "about": "Full Stack Software Engineer", 222 | "id": 1, 223 | "name": "Fatema T. Zuhora" 224 | }, 225 | "message": "New Author Created!", 226 | "status": 201 227 | } 228 | ``` 229 | 230 | #### 7. Author List 231 | 232 | **Request** 233 | ``` 234 | GET /author 235 | ``` 236 | 237 | **Response** 238 | ``` 239 | { 240 | "data": [ 241 | { 242 | "about": "Full Stack Software Engineer", 243 | "id": 1, 244 | "name": "Fatema T. Zuhora" 245 | }, 246 | .... 247 | ], 248 | "message": "All Authors!", 249 | "status": 200 250 | } 251 | ``` 252 | 253 | #### 8. Author Detail 254 | 255 | **Request** 256 | ``` 257 | GET /author/:id 258 | ``` 259 | 260 | **Response** 261 | ``` 262 | { 263 | "data": { 264 | "about": "Full Stack Software Engineer", 265 | "id": 1, 266 | "name": "Fatema T. Zuhora" 267 | }, 268 | "message": "Author Info!", 269 | "status": 200 270 | } 271 | ``` 272 | 273 | #### 9. Update Author 274 | 275 | **Request** 276 | ``` 277 | PATCH /author/:id 278 | ``` 279 | 280 | **Parameters** 281 | Name|Type|Description|Required 282 | :-:|:-:|:-:|:-: 283 | `name`|`string`|author name|`False` 284 | `about`|`string`| short description about author|`False` 285 | 286 | #### 10. Delete Author 287 | 288 | **Request** 289 | ``` 290 | DELETE /author/:id 291 | ``` 292 | 293 | **Response** 294 | ``` 295 | { 296 | "message": "Author Deleted!", 297 | "status": 200 298 | } 299 | ``` 300 | 301 | #### 11. Create Book 302 | 303 | **Request** 304 | ``` 305 | POST /book 306 | ``` 307 | 308 | **Parameters** 309 | Name|Type|Description|Required 310 | :-:|:-:|:-:|:-: 311 | `name`|`string`|book name|`True` 312 | `tagline`|`string`|book tagline|`True` 313 | `category_id`|`int`|category id of book|`True` 314 | `author_id`|`int`|author id of book|`True` 315 | `short_desc`|`string`| short description of book|`False` 316 | 317 | **Request Body** 318 | ``` 319 | { 320 | "name": "Code In Python", 321 | "tagline": "A python programming language blog!", 322 | "category_id": 1, 323 | "author_id": 1 324 | } 325 | ``` 326 | 327 | **Response** 328 | ``` 329 | { 330 | "data": { 331 | "author_id": 1, 332 | "category_id": 1, 333 | "is_published": false, 334 | "name": "Code In Python", 335 | "short_desc": "", 336 | "tagline": "A python programming language blog!", 337 | "uuid": "edf5b0e0-7a3b-4ea6-8890-b6bb84d2318f" 338 | }, 339 | "message": "New Book Created!", 340 | "status": 201 341 | } 342 | ``` 343 | 344 | #### 12. Book List 345 | 346 | **Request** 347 | ``` 348 | GET /book 349 | ``` 350 | 351 | **Response** 352 | ``` 353 | { 354 | "data": [ 355 | { 356 | "author_id": 1, 357 | "category_id": 1, 358 | "is_published": false, 359 | "name": "Code In Python", 360 | "short_desc": "", 361 | "tagline": "A python programming language blog!", 362 | "uuid": "edf5b0e0-7a3b-4ea6-8890-b6bb84d2318f" 363 | }, 364 | .... 365 | ], 366 | "message": "All Books!", 367 | "status": 200 368 | } 369 | ``` 370 | 371 | #### 13. Book Detail 372 | 373 | **Request** 374 | ``` 375 | GET /book/:uuid 376 | ``` 377 | 378 | **Response** 379 | ``` 380 | { 381 | "data": { 382 | "author_id": 1, 383 | "category_id": 1, 384 | "is_published": false, 385 | "name": "Code In Python", 386 | "short_desc": "", 387 | "tagline": "A python programming language blog!", 388 | "uuid": "edf5b0e0-7a3b-4ea6-8890-b6bb84d2318f" 389 | }, 390 | "message": "Book Info!", 391 | "status": 200 392 | } 393 | ``` 394 | 395 | #### 14. Update Book 396 | 397 | **Request** 398 | ``` 399 | PATCH /book/:uuid 400 | ``` 401 | 402 | **Parameters** 403 | Name|Type|Description|Required 404 | :-:|:-:|:-:|:-: 405 | `name`|`string`|book name|`False` 406 | `tagline`|`string`|book tagline|`False` 407 | `category_id`|`int`|category id of book|`False` 408 | `author_id`|`int`|author id of book|`False` 409 | `is_published`|`boolean`| status of book|`False` 410 | `short_desc`|`string`| short description of book|`False` 411 | 412 | #### 15. Delete Book 413 | 414 | **Request** 415 | ``` 416 | DELETE /book/:uuid 417 | ``` 418 | 419 | **Response** 420 | ``` 421 | { 422 | "message": "Book Deleted!", 423 | "status": 200 424 | } 425 | ``` -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | """ 2 | main module of the server file 3 | """ 4 | from config import Config 5 | from app import app, db 6 | 7 | @app.before_first_request 8 | def create_tables(): 9 | print("Creating database tables...") 10 | db.create_all() 11 | print("Done!") 12 | 13 | if __name__ == '__main__': 14 | # app.run(debug=Config.DEBUG) 15 | app.secret_key=Config.SECRET_KEY 16 | app.run(host='0.0.0.0', port=8087, debug=True) -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_sqlalchemy import SQLAlchemy 3 | from flask_migrate import Migrate 4 | 5 | 6 | 7 | # app config file 8 | from config import Config 9 | 10 | # create the application instance 11 | app = Flask(__name__) 12 | app.config.from_object(Config) 13 | 14 | # create the application database instance 15 | db = SQLAlchemy(app) 16 | migrate = Migrate(app, db) 17 | 18 | 19 | from app import routes, models -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from app import db 2 | from sqlalchemy_utils import ChoiceType 3 | 4 | from uuid import uuid4 5 | import datetime, enum 6 | 7 | # method to generate uuid 8 | def generate_uuid(): 9 | return str(uuid4()) 10 | 11 | # enum choice class for is_published field in book model 12 | class BookIsPublishedEnum(enum.Enum): 13 | yes = True 14 | no = False 15 | 16 | 17 | """ 18 | ============= 19 | model classes 20 | ============= 21 | """ 22 | # See http://flask-sqlalchemy.pocoo.org/2.0/models/#simple-example 23 | # for details on the column types. 24 | 25 | class Book(db.Model): 26 | __tablename__ = "books" 27 | 28 | uuid = db.Column(db.String(255), nullable=False, unique=True, default=generate_uuid, primary_key=True) 29 | name = db.Column(db.String(255), nullable=False) 30 | tagline = db.Column(db.String(255), nullable=False) 31 | short_desc = db.Column(db.Text()) 32 | is_published = db.Column( 33 | db.Boolean(), 34 | ChoiceType(BookIsPublishedEnum), 35 | default=False, 36 | nullable=False 37 | ) 38 | category_id = db.Column(db.Integer, db.ForeignKey("category.id"), nullable=False) 39 | author_id = db.Column(db.Integer, db.ForeignKey("author.id"), nullable=False) 40 | 41 | created = db.Column(db.DateTime(timezone=True), default=db.func.current_timestamp(), nullable=False) 42 | modified = db.Column(db.DateTime(timezone=True), default=db.func.current_timestamp(), onupdate=db.func.current_timestamp(), nullable=False) 43 | 44 | 45 | def __init__(self, name, tagline, short_desc, category_id, author_id): 46 | self.name = name 47 | self.tagline = tagline 48 | self.short_desc = short_desc 49 | self.category_id = category_id 50 | self.author_id = author_id 51 | 52 | 53 | 54 | class Category(db.Model): 55 | __tablename__ = "category" 56 | 57 | id = db.Column(db.Integer, nullable=False, primary_key=True) 58 | name = db.Column(db.String(100), nullable=False) 59 | short_desc = db.Column(db.String(255)) 60 | 61 | created = db.Column(db.DateTime(timezone=True), default=db.func.current_timestamp(), nullable=False) 62 | modified = db.Column(db.DateTime(timezone=True), default=db.func.current_timestamp(), onupdate=db.func.current_timestamp(), nullable=False) 63 | 64 | books = db.relationship(Book, backref='category', lazy=True) 65 | 66 | 67 | def __init__(self, name, short_desc): 68 | self.name = name 69 | self.short_desc = short_desc 70 | 71 | 72 | 73 | class Author(db.Model): 74 | __tablename__ = "author" 75 | 76 | id = db.Column(db.Integer, nullable=False, primary_key=True) 77 | name = db.Column(db.String(100), nullable=False) 78 | about = db.Column(db.Text()) 79 | 80 | created = db.Column(db.DateTime(timezone=True), default=db.func.current_timestamp(), nullable=False) 81 | modified = db.Column(db.DateTime(timezone=True), default=db.func.current_timestamp(), onupdate=db.func.current_timestamp(), nullable=False) 82 | 83 | books = db.relationship(Book, backref='author', lazy=True) 84 | 85 | 86 | def __init__(self, name, about): 87 | self.name = name 88 | self.about = about 89 | -------------------------------------------------------------------------------- /app/routes.py: -------------------------------------------------------------------------------- 1 | from app import app, db 2 | 3 | from app.models import Author, Book, Category 4 | from app.schema import author_schema, authors_schema, book_schema, books_schema, category_schema, categories_schema 5 | 6 | from flask import request, jsonify, make_response 7 | 8 | 9 | """ 10 | =========================== 11 | endpoints for Category CRUD 12 | =========================== 13 | """ 14 | 15 | # endpoint to CREATE category 16 | @app.route("/category", methods=["POST"]) 17 | def create_category(): 18 | 19 | name = request.json['name'] 20 | 21 | if 'short_desc' in request.json: 22 | short_desc = request.json['short_desc'] 23 | else: 24 | short_desc = "" 25 | 26 | new_category = Category(name, short_desc) 27 | 28 | db.session.add(new_category) 29 | db.session.commit() 30 | 31 | result = category_schema.dump(new_category) 32 | 33 | data = { 34 | 'message': 'New Category Created!', 35 | 'status': 201, 36 | 'data': result 37 | } 38 | return make_response(jsonify(data)) 39 | 40 | 41 | 42 | # endpoint to GET all categories 43 | @app.route("/category", methods=["GET"]) 44 | def get_categories(): 45 | 46 | all_categories = Category.query.all() 47 | result = categories_schema.dump(all_categories) 48 | 49 | data = { 50 | 'message': 'All Categories!', 51 | 'status': 200, 52 | 'data': result 53 | } 54 | return make_response(jsonify(data)) 55 | 56 | 57 | 58 | # endpoint to GET category detail by id 59 | @app.route("/category/", methods=["GET"]) 60 | def get_category(id): 61 | 62 | category = Category.query.get(id) 63 | 64 | if(category): 65 | result = category_schema.dump(category) 66 | data = { 67 | 'message': 'Category Info!', 68 | 'status': 200, 69 | 'data': result 70 | } 71 | else: 72 | data = { 73 | 'message': 'Invalid Category ID!', 74 | 'status': 200 75 | } 76 | return make_response(jsonify(data)) 77 | 78 | 79 | 80 | # endpoint to UPDATE category 81 | @app.route("/category/", methods=["PATCH"]) 82 | def update_category(id): 83 | 84 | category = Category.query.get(id) 85 | 86 | if(category): 87 | if 'name' in request.json: 88 | category.name = request.json['name'] 89 | if 'short_desc' in request.json: 90 | category.short_desc = request.json['short_desc'] 91 | 92 | db.session.commit() 93 | result = category_schema.dump(category) 94 | 95 | data = { 96 | 'message': 'Category Info Edited!', 97 | 'status': 200, 98 | 'data': result 99 | } 100 | 101 | else: 102 | data = { 103 | 'message': 'Invalid Category ID!', 104 | 'status': 200 105 | } 106 | return make_response(jsonify(data)) 107 | 108 | 109 | 110 | # endpoint to DELETE category 111 | @app.route("/category/", methods=["DELETE"]) 112 | def delete_category(id): 113 | 114 | category = Category.query.get(id) 115 | 116 | if(category): 117 | db.session.delete(category) 118 | db.session.commit() 119 | 120 | data = { 121 | 'message': 'Category Deleted!', 122 | 'status': 200 123 | } 124 | else: 125 | data = { 126 | 'message': 'Invalid Category ID!', 127 | 'status': 200 128 | } 129 | return make_response(jsonify(data)) 130 | 131 | 132 | 133 | 134 | """ 135 | =========================== 136 | endpoints for Author CRUD 137 | =========================== 138 | """ 139 | 140 | # endpoint to CREATE author 141 | @app.route("/author", methods=["POST"]) 142 | def create_author(): 143 | 144 | name = request.json['name'] 145 | 146 | if 'about' in request.json: 147 | about = request.json['about'] 148 | else: 149 | about = "" 150 | 151 | new_author = Author(name, about) 152 | 153 | db.session.add(new_author) 154 | db.session.commit() 155 | 156 | result = author_schema.dump(new_author) 157 | 158 | data = { 159 | 'message': 'New Author Created!', 160 | 'status': 201, 161 | 'data': result 162 | } 163 | return make_response(jsonify(data)) 164 | 165 | 166 | 167 | # endpoint to GET all authors 168 | @app.route("/author", methods=["GET"]) 169 | def get_authors(): 170 | 171 | all_author = Author.query.all() 172 | result = authors_schema.dump(all_author) 173 | 174 | data = { 175 | 'message': 'All Authors!', 176 | 'status': 200, 177 | 'data': result 178 | } 179 | return make_response(jsonify(data)) 180 | 181 | 182 | 183 | # endpoint to GET author detail by id 184 | @app.route("/author/", methods=["GET"]) 185 | def get_author(id): 186 | 187 | author = Author.query.get(id) 188 | 189 | if(author): 190 | result = author_schema.dump(author) 191 | data = { 192 | 'message': 'Author Info!', 193 | 'status': 200, 194 | 'data': result 195 | } 196 | else: 197 | data = { 198 | 'message': 'Invalid Author ID!', 199 | 'status': 200 200 | } 201 | return make_response(jsonify(data)) 202 | 203 | 204 | 205 | # endpoint to UPDATE author 206 | @app.route("/author/", methods=["PATCH"]) 207 | def update_author(id): 208 | 209 | author = Author.query.get(id) 210 | 211 | if(author): 212 | if 'name' in request.json: 213 | author.name = request.json['name'] 214 | if 'about' in request.json: 215 | author.about = request.json['about'] 216 | 217 | db.session.commit() 218 | result = author_schema.dump(author) 219 | 220 | data = { 221 | 'message': 'Author Info Edited!', 222 | 'status': 200, 223 | 'data': result 224 | } 225 | 226 | else: 227 | data = { 228 | 'message': 'Invalid Author ID!', 229 | 'status': 200 230 | } 231 | return make_response(jsonify(data)) 232 | 233 | 234 | 235 | # endpoint to DELETE author 236 | @app.route("/author/", methods=["DELETE"]) 237 | def delete_author(id): 238 | 239 | author = Author.query.get(id) 240 | 241 | if(author): 242 | db.session.delete(author) 243 | db.session.commit() 244 | 245 | data = { 246 | 'message': 'Author Deleted!', 247 | 'status': 200 248 | } 249 | else: 250 | data = { 251 | 'message': 'Invalid Author ID!', 252 | 'status': 200 253 | } 254 | return make_response(jsonify(data)) 255 | 256 | 257 | 258 | 259 | """ 260 | =========================== 261 | endpoints for Book CRUD 262 | =========================== 263 | """ 264 | 265 | # endpoint to CREATE book 266 | @app.route("/book", methods=["POST"]) 267 | def create_book(): 268 | 269 | name = request.json['name'] 270 | tagline = request.json['tagline'] 271 | category_id = request.json['category_id'] 272 | author_id = request.json['author_id'] 273 | 274 | if 'short_desc' in request.json: 275 | short_desc = request.json['short_desc'] 276 | else: 277 | short_desc = "" 278 | 279 | new_book = Book(name, tagline, short_desc, category_id, author_id) 280 | 281 | db.session.add(new_book) 282 | db.session.commit() 283 | 284 | result = book_schema.dump(new_book) 285 | 286 | data = { 287 | 'message': 'New Book Created!', 288 | 'status': 201, 289 | 'data': result 290 | } 291 | return make_response(jsonify(data)) 292 | 293 | 294 | 295 | # endpoint to GET all books 296 | @app.route("/book", methods=["GET"]) 297 | def get_books(): 298 | 299 | all_book = Book.query.all() 300 | result = books_schema.dump(all_book) 301 | 302 | data = { 303 | 'message': 'All Books!', 304 | 'status': 200, 305 | 'data': result 306 | } 307 | return make_response(jsonify(data)) 308 | 309 | 310 | 311 | # endpoint to GET book detail by uuid 312 | @app.route("/book/", methods=["GET"]) 313 | def get_book(uuid): 314 | 315 | book = Book.query.get(uuid) 316 | 317 | if(book): 318 | result = book_schema.dump(book) 319 | data = { 320 | 'message': 'Book Info!', 321 | 'status': 200, 322 | 'data': result 323 | } 324 | else: 325 | data = { 326 | 'message': 'Invalid Book ID!', 327 | 'status': 200 328 | } 329 | return make_response(jsonify(data)) 330 | 331 | 332 | 333 | # endpoint to UPDATE book 334 | @app.route("/book/", methods=["PATCH"]) 335 | def update_book(uuid): 336 | 337 | book = Book.query.get(uuid) 338 | 339 | if(book): 340 | if 'name' in request.json: 341 | book.name = request.json['name'] 342 | if 'short_desc' in request.json: 343 | book.short_desc = request.json['short_desc'] 344 | if 'tagline' in request.json: 345 | book.tagline = request.json['tagline'] 346 | if 'is_published' in request.json: 347 | book.is_published = request.json['is_published'] 348 | if 'category_id' in request.json: 349 | book.category_id = request.json['category_id'] 350 | if 'author_id' in request.json: 351 | book.author_id = request.json['author_id'] 352 | 353 | db.session.commit() 354 | result = book_schema.dump(book) 355 | 356 | data = { 357 | 'message': 'Book Info Edited!', 358 | 'status': 200, 359 | 'data': result 360 | } 361 | 362 | else: 363 | data = { 364 | 'message': 'Invalid Book ID!', 365 | 'status': 200 366 | } 367 | return make_response(jsonify(data)) 368 | 369 | 370 | 371 | # endpoint to DELETE author 372 | @app.route("/book/", methods=["DELETE"]) 373 | def delete_book(uuid): 374 | 375 | book = Book.query.get(uuid) 376 | 377 | if(book): 378 | db.session.delete(book) 379 | db.session.commit() 380 | 381 | data = { 382 | 'message': 'Book Deleted!', 383 | 'status': 200 384 | } 385 | else: 386 | data = { 387 | 'message': 'Invalid Book ID!', 388 | 'status': 200 389 | } 390 | return make_response(jsonify(data)) 391 | -------------------------------------------------------------------------------- /app/schema.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | 3 | from app.models import Author, Book, Category 4 | from flask_marshmallow import Marshmallow 5 | 6 | ma = Marshmallow(app) 7 | 8 | 9 | """ 10 | ============= 11 | schema classes 12 | ============= 13 | """ 14 | 15 | class CategorySchema(ma.ModelSchema): 16 | class Meta: 17 | model = Category 18 | fields = ('id', 'name', 'short_desc') # fields to expose 19 | 20 | category_schema = CategorySchema() 21 | categories_schema = CategorySchema(many=True) 22 | 23 | 24 | 25 | class AuthorSchema(ma.ModelSchema): 26 | class Meta: 27 | model = Author 28 | fields = ('id', 'name', 'about') # fields to expose 29 | 30 | author_schema = AuthorSchema() 31 | authors_schema = AuthorSchema(many=True) 32 | 33 | 34 | 35 | class BookSchema(ma.ModelSchema): 36 | class Meta: 37 | model = Book 38 | fields = ('uuid', 'name', 'tagline', 'short_desc', 'is_published', 'category_id', 'author_id') # fields to expose 39 | 40 | book_schema = BookSchema() 41 | books_schema = BookSchema(many=True) -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from os import environ, path 2 | 3 | BASE_DIR = path.abspath(path.dirname(__file__)) 4 | 5 | 6 | 7 | # config class 8 | class Config(object): 9 | """set Flask configuration variables from .env file.""" 10 | 11 | # general 12 | DEBUG = environ.get('DEBUG') 13 | SECRET_KEY = environ.get('SECRET_KEY') 14 | 15 | # sqlalchemy 16 | SQLALCHEMY_DATABASE_URI = environ.get('SQLALCHEMY_DATABASE_URI') or 'sqlite:///' + path.join(BASE_DIR, 'db.sqlite') # if you do not create an environment file then it will create a sqlite database 17 | SQLALCHEMY_TRACK_MODIFICATIONS = environ.get('SQLALCHEMY_TRACK_MODIFICATIONS') -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==1.3.2 2 | Click==7.0 3 | Flask==1.1.1 4 | flask-marshmallow==0.10.1 5 | Flask-Migrate==2.5.2 6 | Flask-SQLAlchemy==2.4.1 7 | itsdangerous==1.1.0 8 | Jinja2==2.11.3 9 | Mako==1.1.0 10 | MarkupSafe==1.1.1 11 | marshmallow==3.3.0 12 | marshmallow-sqlalchemy==0.21.0 13 | mysqlclient==1.4.6 14 | PyMySQL==0.9.3 15 | python-dateutil==2.8.1 16 | python-dotenv==0.10.4 17 | python-editor==1.0.4 18 | six==1.14.0 19 | SQLAlchemy==1.3.12 20 | SQLAlchemy-Utils==0.36.1 21 | Werkzeug==0.16.0 --------------------------------------------------------------------------------