-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 381e435
Showing
19 changed files
with
402 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
*.pyc | ||
*.pyo | ||
*.swp | ||
*~ | ||
|
||
# Packages | ||
*.egg | ||
*.egg-info | ||
dist | ||
build/ | ||
eggs | ||
bin | ||
sdist | ||
|
||
#Sphinx builds | ||
_build | ||
|
||
# Installer logs | ||
pip-log.txt | ||
|
||
# Flake8 violation file | ||
violations.flake8.txt | ||
|
||
# Unit test / coverage reports | ||
.coverage | ||
.tox | ||
nosetests.xml | ||
__pycache__ | ||
database.db | ||
.vscode | ||
.pytest_cache | ||
|
||
# Sphinx | ||
docs/_build | ||
|
||
# Virtual environments | ||
env | ||
env* | ||
|
||
# Env Vars | ||
*.env* | ||
|
||
.webassets-cache | ||
*.cache | ||
.DS_Store | ||
*.sublime-* | ||
|
||
appname/static/public/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Create Flask API | ||
|
||
A boilerplate Flask application on which to build APIs using best practices. | ||
|
||
- ✅ RESTful API (with argument validation & output schemas) | ||
- ✅ 100% Code Coverage in Tests | ||
- ✅ Using Flask Best Practices | ||
- ✅ Support Heroku Deployment | ||
- ✅ With an example image API | ||
|
||
## Installation | ||
|
||
```bash | ||
$ git clone | ||
$ cd create-flask-api | ||
$ python3 -m venv env; source env/bin/activate # To set up an virtual env | ||
$ ./dev-server.sh # runs: FLASK_DEBUG="true" FLASK_APP="server:create_app" flask run | ||
``` | ||
|
||
### Key Files: | ||
|
||
The API Endpoints are defined in `server/api` and registered to specific routes in `server/api/__init__.py`. | ||
|
||
## Testing | ||
|
||
To test with a coverage report: | ||
|
||
`pytest --cov-report term-missing --cov=server` | ||
|
||
## Limitations | ||
|
||
This repo is designed to be the barebones for an API. If you want to hook into a database or do authentication, you should look into [Flask Ignite](https://github.com/sumukh/ignite) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"name": "Create Flask API", | ||
"description": "Flask API", | ||
"keywords": [ | ||
"flask", | ||
"api", | ||
"scaffolding", | ||
"ignite" | ||
], | ||
"website": "https://github.com/sumukh/create-flask-api/", | ||
"repository": "https://github.com/sumukh/create-flask-apiapi/", | ||
"logo": "https://github.com/Sumukh/Ignite/raw/master/appname/static/public/ignite/ignite-icon.png", | ||
"success_url": "/", | ||
"scripts": {}, | ||
"env": { | ||
"SECRET_TOKEN": { | ||
"description": "A secret key for verifying the integrity of signed cookies.", | ||
"generator": "secret" | ||
}, | ||
"FLASK_APP": { | ||
"description": "Where the flask app lives.", | ||
"value": "wsgi.py" | ||
}, | ||
"FLASK_ENV": { | ||
"description": "What environment is this in", | ||
"value": "prod" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import pytest | ||
|
||
from server import create_app | ||
|
||
@pytest.fixture | ||
def app(): | ||
return create_app('test') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
FLASK_DEBUG="true" FLASK_APP="server:create_app" flask run |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
Flask==1.1.1 | ||
jinja2==2.11.1 | ||
Werkzeug~=0.16.1 | ||
|
||
requests | ||
gunicorn | ||
|
||
Flask-Caching>=1.3.3 | ||
Flask-RESTful==0.3.8 | ||
Flask-Limiter | ||
|
||
# Flask-SocketIO>=3.1.0 # Realtime Websockets | ||
# python-engineio>=3.0.0 # Needed to fix startup error | ||
|
||
# Timezones | ||
pytz | ||
arrow | ||
|
||
# Other | ||
itsdangerous==1.1.0 | ||
hashids==1.2.0 | ||
humanize==2.0.0 | ||
|
||
# Testing | ||
pytest==5.4.1 | ||
pytest-cov==2.8.1 | ||
pytest-flask | ||
mccabe==0.6.1 | ||
flake8==3.7.9 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from flask import Flask | ||
from server.api import api_blueprint | ||
|
||
def create_app(object_name='server.settings.DevConfig'): | ||
""" | ||
An flask application factory, as explained here: | ||
http://flask.pocoo.org/docs/patterns/appfactories/ | ||
Arguments: | ||
object_name: the python path of the config object, | ||
e.g. appname.settings.ProdConfig | ||
""" | ||
|
||
app = Flask(__name__) | ||
app.config.from_object(object_name) | ||
app.register_blueprint(api_blueprint, url_prefix="/api") | ||
return app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
from flask import Blueprint, request | ||
import flask_restful as restful | ||
|
||
from server.api.info import ApiInfo | ||
from server.api.categories import CategoriesApi | ||
from server.api.wallpaper import (UnsplashSearchApi, UnsplashCategoryApi, | ||
RedditSearchApi) | ||
|
||
api_blueprint = Blueprint('api', __name__) | ||
api_blueprint.config = {} | ||
|
||
api = restful.Api(api_blueprint) | ||
|
||
@api_blueprint.record | ||
def record_params(setup_state): | ||
""" Load used app configs into local config on registration from | ||
server/__init__.py """ | ||
app = setup_state.app | ||
api_blueprint.config['tz'] = app.config.get('TIMEZONE', 'utc') | ||
api_blueprint.config['debug'] = app.debug | ||
|
||
@api.representation('application/json') | ||
def envelope_api(data, code, headers=None): | ||
""" API response envelope (for metadata/pagination). | ||
Optionally wraps JSON response in envelope. | ||
This is for successful requests only. | ||
data is the object returned by the API. | ||
code is the HTTP status code as an int | ||
""" | ||
if not request.args.get('envelope'): | ||
return restful.representations.json.output_json(data, code, headers) | ||
message = 'success' | ||
data = { | ||
'data': data, | ||
'code': code, | ||
'message': message | ||
} | ||
return restful.representations.json.output_json(data, code, headers) | ||
|
||
|
||
# If you want to version your API, you can do that by adding a prefix to the route here | ||
api.add_resource(ApiInfo, '/info') | ||
api.add_resource(CategoriesApi, '/categories') | ||
api.add_resource(UnsplashSearchApi, '/wallpaper/unsplash/search') | ||
api.add_resource(UnsplashCategoryApi, '/wallpaper/unsplash/category/<int:category_id>') | ||
api.add_resource(RedditSearchApi, '/wallpaper/reddit/<string:subreddit>') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from flask_restful import Resource, fields, marshal_with | ||
|
||
class Category: | ||
def __init__(self, id, name, state='featured'): | ||
self.id = id | ||
self.name = name | ||
self.state = state | ||
|
||
# Example of returning objects and using a schema to marshal out specific fields | ||
class CategoriesApi(Resource): | ||
get_fields = { | ||
'name': fields.String, | ||
'id': fields.Integer | ||
} | ||
|
||
@marshal_with(get_fields) | ||
def get(self): | ||
return [ | ||
Category(3330448, 'nature'), | ||
Category(3356570, 'travel'), | ||
Category(1065976, 'wallpapers'), | ||
Category(3330445, 'patterns'), | ||
Category(3694365, 'gradients'), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from flask_restful import Resource | ||
|
||
class ApiInfo(Resource): | ||
def get(self): | ||
return { | ||
'version': '1.0', | ||
'example_endpoints': ['/categories'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from flask_restful import Resource, reqparse | ||
from services.images import (search_unsplash, get_unsplash_collection, | ||
get_reddit_images) | ||
|
||
class UnsplashSearchApi(Resource): | ||
parser = reqparse.RequestParser() | ||
parser.add_argument('query', required=True) | ||
parser.add_argument('page', type=int, required=False) | ||
|
||
def get(self): | ||
args = self.parser.parse_args() | ||
response = search_unsplash(args['query'], page=args['page']) | ||
return response | ||
|
||
class UnsplashCategoryApi(Resource): | ||
parser = reqparse.RequestParser() | ||
parser.add_argument('page', required=False) | ||
|
||
def get(self, category_id=None): | ||
args = self.parser.parse_args() | ||
return get_unsplash_collection(collection_id=category_id, page=args['page']) | ||
|
||
class RedditSearchApi(Resource): | ||
def get(self, subreddit): | ||
response = get_reddit_images(subreddit) | ||
return response |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import os | ||
|
||
class BaseConfig: | ||
TESTING = False | ||
|
||
class DevConfig(BaseConfig): | ||
SERVICE_API_KEY = os.getenv('SERVICE_API_KEY', "ABC") | ||
|
||
class TestConfig(BaseConfig): | ||
TESTING = True | ||
SERVICE_API_KEY = os.getenv('SERVICE_API_KEY', "BDC") | ||
|
||
class ProdConfig(BaseConfig): | ||
SERVICE_API_KEY = os.getenv('SERVICE_API_KEY', "DEF") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import requests | ||
|
||
USER_AGENT = 'Create Flask Api 1.0' | ||
|
||
def get_reddit_images(subreddit): | ||
response = requests.get("https://reddit.com/r/{}.json".format(subreddit), | ||
headers={'User-agent': USER_AGENT}).json() | ||
if 'error' in response: | ||
return [] | ||
posts = response["data"]["children"] | ||
posts_with_image = [p["data"] for p in posts | ||
if p["data"].get("post_hint") == "image"] | ||
return [{ | ||
'url': p['url'], | ||
'title': p['title'], | ||
'source': "https://reddit.com{}".format(p['permalink']), | ||
'author': { | ||
'name': 'Reddit', | ||
'description': subreddit, | ||
'profile_pic': '', | ||
'link': "https://reddit.com/r/{}".format(subreddit), | ||
} | ||
} for p in posts_with_image if p and not p['over_18']] | ||
|
||
def format_unsplash_image(p): | ||
return { | ||
'url': p['urls']['raw'], | ||
'title': p['description'] or p['alt_description'], | ||
'source': p['links']['html'], | ||
'author': { | ||
'name': p['user']['name'], | ||
'description': p['user']['bio'], | ||
'profile_pic': p['user']['profile_image'].get('medium'), | ||
'link': p['user']['links']['html'], | ||
} | ||
} | ||
|
||
def get_unsplash_collection(collection_id=3330448, page=1): | ||
endpoint_url = 'https://unsplash.com/napi/collections/{}/photos'.format( | ||
collection_id | ||
) | ||
photos = requests.get(endpoint_url, {'page': page}, | ||
headers={'User-agent': USER_AGENT}).json() | ||
|
||
return [format_unsplash_image(p) for p in photos if p] | ||
|
||
def search_unsplash(search_term, page=1): | ||
response = requests.get('https://unsplash.com/napi/search/photos', { | ||
'query': search_term, | ||
'per_page': 25, | ||
'page': page, | ||
}, headers={'User-agent': USER_AGENT}) | ||
results = response.json().get('results', []) | ||
return [format_unsplash_image(p) for p in results] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import pytest | ||
|
||
def test_categories_endpoint(client): | ||
response = client.get('/api/categories') | ||
assert response.status_code == 200 | ||
data = response.json | ||
assert len(data) > 0 | ||
assert data[0]['id'] is not None | ||
assert data[0]['name'] is not None | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import pytest | ||
|
||
def test_info_endpoint(client): | ||
response = client.get('/api/info') | ||
assert response.status_code == 200 | ||
|
||
def test_envelope(client): | ||
response = client.get('/api/info?envelope=1') | ||
assert response.status_code == 200 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import pytest | ||
from server.settings import DevConfig, TestConfig, ProdConfig | ||
|
||
def test_testing_settings(): | ||
assert DevConfig.TESTING == False | ||
assert ProdConfig.TESTING == False | ||
assert TestConfig.TESTING == True |
Oops, something went wrong.