├── .gitignore ├── LICENSE ├── README.md ├── app.py ├── db.py ├── models.py ├── requirements.txt ├── resources.py └── settings.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | main.db 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Max Mautner 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Demo RESTful HTTP API using [Flask](https://github.com/pallets/flask), [Flask-Restful](https://github.com/flask-restful/flask-restful) and [SQLAlchemy](https://github.com/zzzeek/sqlalchemy) 2 | =================== 3 | 4 | 1. Install requisite packages: 5 | ```shell 6 | $ pip install -r requirements.txt 7 | ``` 8 | 2. Create tables: 9 | ```shell 10 | $ ./models.py 11 | ``` 12 | 3. Run service: 13 | ``` 14 | $ python app.py 15 | ``` 16 | 4. Give it a try: 17 | ```shell 18 | >> import requests, json 19 | >> requests.get('http://localhost:5000/todos').json() 20 | [] 21 | >> requests.post('http://localhost:5000/todos', 22 | headers={'Content-Type': 'application/json'}, 23 | data=json.dumps({'task': 'go outside!'})).json() 24 | {u'id': 1, u'task': u'go outside!', u'uri': u'http://localhost:5000/todos/1'} 25 | >> requests.get('http://localhost:5000/todos/1').json() 26 | {u'id': 1, u'task': u'go outside!', u'uri': u'http://localhost:5000/todos/1'} 27 | >> requests.put('http://localhost:5000/todos/1', 28 | headers={'Content-Type': 'application/json'}, 29 | data=json.dumps({'task': 'go to the gym'})).json() 30 | {u'id': 1, u'task': u'go to the gym', u'uri': u'http://localhost:5000/todos/1'} 31 | >> requests.delete('http://localhost:5000/todos/1') 32 | >> requests.get('http://localhost:5000/todos').json() 33 | [] 34 | ``` 35 | 36 | Don't forget that you must pass a "Content-Type: application/json" header along with your request! 37 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from flask import Flask 4 | from flask.ext.restful import Api 5 | 6 | app = Flask(__name__) 7 | api = Api(app) 8 | 9 | from resources import TodoListResource 10 | from resources import TodoResource 11 | 12 | api.add_resource(TodoListResource, '/todos', endpoint='todos') 13 | api.add_resource(TodoResource, '/todos/', endpoint='todo') 14 | 15 | if __name__ == '__main__': 16 | app.run(debug=True) 17 | -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | 2 | from sqlalchemy import create_engine 3 | from sqlalchemy.orm import scoped_session 4 | from sqlalchemy.orm import sessionmaker 5 | from settings import DB_URI 6 | 7 | Session = sessionmaker(autocommit=False, 8 | autoflush=False, 9 | bind=create_engine(DB_URI)) 10 | session = scoped_session(Session) 11 | 12 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from sqlalchemy import Column 4 | from sqlalchemy import Integer 5 | from sqlalchemy import String 6 | from sqlalchemy.ext.declarative import declarative_base 7 | 8 | Base = declarative_base() 9 | 10 | class Todo(Base): 11 | __tablename__ = 'todos' 12 | 13 | id = Column(Integer, primary_key=True) 14 | task = Column(String(255)) 15 | 16 | if __name__ == "__main__": 17 | from sqlalchemy import create_engine 18 | from settings import DB_URI 19 | engine = create_engine(DB_URI) 20 | Base.metadata.drop_all(engine) 21 | Base.metadata.create_all(engine) 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask-restful 3 | sqlalchemy 4 | -------------------------------------------------------------------------------- /resources.py: -------------------------------------------------------------------------------- 1 | 2 | from models import Todo 3 | from db import session 4 | 5 | from flask.ext.restful import reqparse 6 | from flask.ext.restful import abort 7 | from flask.ext.restful import Resource 8 | from flask.ext.restful import fields 9 | from flask.ext.restful import marshal_with 10 | 11 | todo_fields = { 12 | 'id': fields.Integer, 13 | 'task': fields.String, 14 | 'uri': fields.Url('todo', absolute=True), 15 | } 16 | 17 | parser = reqparse.RequestParser() 18 | parser.add_argument('task', type=str) 19 | 20 | class TodoResource(Resource): 21 | @marshal_with(todo_fields) 22 | def get(self, id): 23 | todo = session.query(Todo).filter(Todo.id == id).first() 24 | if not todo: 25 | abort(404, message="Todo {} doesn't exist".format(id)) 26 | return todo 27 | 28 | def delete(self, id): 29 | todo = session.query(Todo).filter(Todo.id == id).first() 30 | if not todo: 31 | abort(404, message="Todo {} doesn't exist".format(id)) 32 | session.delete(todo) 33 | session.commit() 34 | return {}, 204 35 | 36 | @marshal_with(todo_fields) 37 | def put(self, id): 38 | parsed_args = parser.parse_args() 39 | todo = session.query(Todo).filter(Todo.id == id).first() 40 | todo.task = parsed_args['task'] 41 | session.add(todo) 42 | session.commit() 43 | return todo, 201 44 | 45 | 46 | class TodoListResource(Resource): 47 | @marshal_with(todo_fields) 48 | def get(self): 49 | todos = session.query(Todo).all() 50 | return todos 51 | 52 | @marshal_with(todo_fields) 53 | def post(self): 54 | parsed_args = parser.parse_args() 55 | todo = Todo(task=parsed_args['task']) 56 | session.add(todo) 57 | session.commit() 58 | return todo, 201 59 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | DB_URI = 'sqlite:///./main.db' 2 | --------------------------------------------------------------------------------