From 4eff6d4274fb462d306f914f5ae9c2b1baf43616 Mon Sep 17 00:00:00 2001 From: Alex Kennedy Date: Tue, 14 Jun 2022 20:41:20 -0700 Subject: [PATCH] PyBlack codebase for future PR diffs (#32) --- .github/workflows/codeql-analysis.yml | 4 +- Makefile | 5 +- README.md | 4 +- src/app.py | 53 +++++++---- src/auth.py | 60 ++++++------ src/base_list_view.py | 80 ++++++++-------- src/cache.py | 1 - src/config.py | 16 ++-- src/gogo.py | 129 ++++++++++++++------------ src/models.py | 4 +- src/search.py | 32 +++---- 11 files changed, 202 insertions(+), 186 deletions(-) delete mode 100644 src/cache.py diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8de5e6c..f454b88 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [ main ] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ main ] schedule: - cron: '21 3 * * 6' diff --git a/Makefile b/Makefile index 04dc5ea..4df3d7b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ -SHELL=/bin/bash SHA1 := $(shell git rev-parse --short HEAD) DOCKER_IMAGE ?= gogo @@ -42,9 +41,9 @@ test: stop venv: @echo "Creating and updating venv." - @python3.6 -m venv .venv + @python3 -m venv .venv @if [ "$$(cat resources/requirements.txt | sort)" != "$$(.venv/bin/pip freeze)" ]; then \ - .venv/bin/pip install -r resources/requirements.txt; \ + .venv/bin/pip install -Ur resources/requirements.txt; \ fi # Database. diff --git a/README.md b/README.md index 1d2472b..92f83e1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Built with Flask, Python 3.6, with Postgres and Google OAuth. Uses docker. -Run `make postgres` and `make psql-populate` to initialize the database first. +Run `make db` to initialize the database first. OAuth is required. Secrets can be provided either by direct environment variables or by KMS blobs in environment variables. If using KMS, you'll need to be authenticated to AWS to decrypt the blobs. `~/.aws` is volume mounted. @@ -57,10 +57,10 @@ OAuth: One of Built-In Google OAuth or Header-Based Auth must be used. | `GOOGLE_CLIENT_ID` | Your Google App ID. | | `GOOGLE_CLIENT_SECRET` | Your Google Client Secret. | | `GOOGLE_CLIENT_SECRET_KMS_BLOB` | Alternative to `GOOGLE_CLIENT_SECRET` - KMS Encrypted Google Client Secret. | -| `AWS_DEFAULT_REGION` | Will be used to determine the AWS region in which to decrypt `*_KMS_BLOB` secrets. | | `HOSTED_DOMAIN` | The email domain to use for Google accounts, like 'nextdoor.com'. | | `SESSION_SECRET_KEY` | The secret key to encrypt the session cookie with. | | `SESSION_SECRET_KEY_KMS_BLOB` | Alternative to `SESSION_SECRET_KEY` - KMS Encrypted Secret Key. | +| `AWS_DEFAULT_REGION` | Will be used to determine the AWS region in which to decrypt `*_KMS_BLOB` secrets. | ### If using Header-Based Auth: diff --git a/src/app.py b/src/app.py index be21d5d..26c1040 100644 --- a/src/app.py +++ b/src/app.py @@ -1,6 +1,6 @@ import os -from flask import g, Flask +from flask import Flask, g from werkzeug.contrib.fixers import ProxyFix from werkzeug.routing import BaseConverter @@ -9,15 +9,19 @@ import search from models import db -app = Flask('gogo', template_folder='../templates', static_url_path='/static', - static_folder='../static') +app = Flask( + "gogo", + template_folder="../templates", + static_url_path="/static", + static_folder="../static", +) -app.config.from_object(os.environ['APP_SETTINGS']) -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config.from_object(os.environ["APP_SETTINGS"]) +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False -app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URI'] +app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URI"] -if app.config['BEHIND_PROXY']: +if app.config["BEHIND_PROXY"]: app.wsgi_app = ProxyFix(app.wsgi_app) @@ -27,29 +31,38 @@ def __init__(self, url_map, *items): self.regex = items[0] -app.url_map.converters['regex'] = RegexConverter +app.url_map.converters["regex"] = RegexConverter -if app.config['USE_GOOGLE_AUTH']: +if app.config["USE_GOOGLE_AUTH"]: # If using Google Auth, the session cookie is the source of truth, so it should be encrypted. - app.secret_key = os.getenv('SESSION_SECRET_KEY') + app.secret_key = os.getenv("SESSION_SECRET_KEY") # Register OAuth2 Callback URL for Google Auth. - app.add_url_rule('/oauth2/callback', view_func=auth.OAuth2Callback.as_view('oauth2_callback')) + app.add_url_rule( + "/oauth2/callback", view_func=auth.OAuth2Callback.as_view("oauth2_callback") + ) -app.add_url_rule('/healthz', view_func=gogo.Healthz.as_view('healthz')) +app.add_url_rule("/healthz", view_func=gogo.Healthz.as_view("healthz")) -app.add_url_rule('/', view_func=gogo.DashboardView.as_view('dashboard')) -app.add_url_rule('/_list', view_func=gogo.ListView.as_view('list')) -app.add_url_rule('/_create', view_func=gogo.CreateShortcutView.as_view('create_shortcut')) -app.add_url_rule('/_delete', view_func=gogo.DeleteShortcutView.as_view('delete_shortcut')) -app.add_url_rule('/_edit', view_func=gogo.EditShortcutView.as_view('edit_shortcut')) -app.add_url_rule('/', view_func=gogo.ShortcutRedirectView.as_view('shortcut_redirect')) +app.add_url_rule("/", view_func=gogo.DashboardView.as_view("dashboard")) +app.add_url_rule("/_list", view_func=gogo.ListView.as_view("list")) +app.add_url_rule( + "/_create", view_func=gogo.CreateShortcutView.as_view("create_shortcut") +) +app.add_url_rule( + "/_delete", view_func=gogo.DeleteShortcutView.as_view("delete_shortcut") +) +app.add_url_rule("/_edit", view_func=gogo.EditShortcutView.as_view("edit_shortcut")) +app.add_url_rule( + '/', + view_func=gogo.ShortcutRedirectView.as_view("shortcut_redirect"), +) -app.add_url_rule('/_ajax/search', view_func=search.SearchView.as_view('search')) +app.add_url_rule("/_ajax/search", view_func=search.SearchView.as_view("search")) db.init_app(app) -if __name__ == '__main__': +if __name__ == "__main__": db.init_app(app) auth.init_app(app) app.run() diff --git a/src/auth.py b/src/auth.py index 58c0cb7..7b1909e 100644 --- a/src/auth.py +++ b/src/auth.py @@ -1,22 +1,23 @@ from functools import wraps -import httplib2 import flask -from flask.views import MethodView +import httplib2 from apiclient import discovery +from flask.views import MethodView from oauth2client import client flow = None def init_app(app): - if app.config['USE_GOOGLE_AUTH']: + if app.config["USE_GOOGLE_AUTH"]: global flow flow = client.flow_from_clientsecrets( - '/app/resources/client_secrets.json', - scope=['https://www.googleapis.com/auth/userinfo.email'], - redirect_uri=app.config['REDIRECT_URI']) - flow.user_agent = 'Go Link Shortener' + "/app/resources/client_secrets.json", + scope=["https://www.googleapis.com/auth/userinfo.email"], + redirect_uri=app.config["REDIRECT_URI"], + ) + flow.user_agent = "Go Link Shortener" class NoLoginSetupConfigured(Exception): @@ -26,23 +27,23 @@ class NoLoginSetupConfigured(Exception): def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): - if flask.current_app.config['SKIP_AUTH']: + if flask.current_app.config["SKIP_AUTH"]: return f(*args, **kwargs) - elif flask.current_app.config['USE_HEADER_AUTH']: + elif flask.current_app.config["USE_HEADER_AUTH"]: # Header login flow. current_user = get_current_user_from_header() if current_user is None: - return 'Unauthorized', 401 + return "Unauthorized", 401 return f(*args, **kwargs) - elif flask.current_app.config['USE_GOOGLE_AUTH']: + elif flask.current_app.config["USE_GOOGLE_AUTH"]: # Google login flow. current_user = get_current_user_from_session() if current_user is None: # Redirect to oauth. - flask.session['after_auth'] = flask.request.url + flask.session["after_auth"] = flask.request.url return flask.redirect(flow.step1_get_authorize_url()) - if 'after_auth' in flask.session: - return flask.redirect(flask.session.pop('after_auth')) + if "after_auth" in flask.session: + return flask.redirect(flask.session.pop("after_auth")) else: raise NoLoginSetupConfigured return f(*args, **kwargs) @@ -54,41 +55,42 @@ class OAuth2Callback(MethodView): """Google OAuth2 Callback.""" def get(self): - code = flask.request.args.get('code') + code = flask.request.args.get("code") if code is None: return flask.redirect(flow.step1_get_authorize_url()) credentials = flow.step2_exchange(code) try: - flask.session['user'] = query_user( - credentials, flask.current_app.config['HOSTED_DOMAIN']) + flask.session["user"] = query_user( + credentials, flask.current_app.config["HOSTED_DOMAIN"] + ) except ValueError: - flask.session.pop('after_auth') - return 'Unauthorized', 401 - return flask.redirect('/') + flask.session.pop("after_auth") + return "Unauthorized", 401 + return flask.redirect("/") def query_user(credentials, hosted_domain): http = credentials.authorize(httplib2.Http()) - service = discovery.build('oauth2', 'v2', http=http) + service = discovery.build("oauth2", "v2", http=http) result = service.userinfo().get().execute() - email = result['email'] - if f'@{hosted_domain}' not in email: - raise ValueError(f'Must be a {hosted_domain} domain.') - return email.split(f'@{hosted_domain}')[0] + email = result["email"] + if f"@{hosted_domain}" not in email: + raise ValueError(f"Must be a {hosted_domain} domain.") + return email.split(f"@{hosted_domain}")[0] def get_current_user(): - if flask.current_app.config['USE_HEADER_AUTH']: + if flask.current_app.config["USE_HEADER_AUTH"]: return get_current_user_from_header() - elif flask.current_app.config['USE_GOOGLE_AUTH']: + elif flask.current_app.config["USE_GOOGLE_AUTH"]: return get_current_user_from_session() # Shouldn't get here. return None def get_current_user_from_header(): - return flask.request.headers.get(flask.current_app.config['AUTH_HEADER_NAME']) + return flask.request.headers.get(flask.current_app.config["AUTH_HEADER_NAME"]) def get_current_user_from_session(): - return flask.session.get('user') + return flask.session.get("user") diff --git a/src/base_list_view.py b/src/base_list_view.py index 5b3c603..0bf8da0 100644 --- a/src/base_list_view.py +++ b/src/base_list_view.py @@ -1,6 +1,6 @@ import urllib -from flask import current_app, request, render_template +from flask import current_app, render_template, request from flask.views import View from sqlalchemy import asc, desc @@ -8,21 +8,21 @@ # Keys map to values in list.html for the sortColumn class element val attribute. SORT_MAP = { - 'hits': 'hits', - 'name': 'name', - 'owner': 'owner', - 'dateCreated': 'created_at', - 'url': 'url', - 'secondaryUrl': 'secondary_url' + "hits": "hits", + "name": "name", + "owner": "owner", + "dateCreated": "created_at", + "url": "url", + "secondaryUrl": "secondary_url", } class BaseListView(View): - DEFAULT_SORT = 'name' - DEFAULT_ORDER = 'asc' + DEFAULT_SORT = "name" + DEFAULT_ORDER = "asc" DEFAULT_LIMIT = 20 - ORDERS = ['asc', 'desc'] + ORDERS = ["asc", "desc"] template = None @@ -40,26 +40,24 @@ def __init__(self): def _load_params(self): """Load up common URL parameters.""" # MESSAGING - self.created = request.args.get('created') - self.edited = request.args.get('edited') - self.deleted = request.args.get('deleted') - self.to = request.args.get('to') - self.error = request.args.get('error') + self.created = request.args.get("created") + self.edited = request.args.get("edited") + self.deleted = request.args.get("deleted") + self.to = request.args.get("to") + self.error = request.args.get("error") # SORT - self.sort = SORT_MAP.get( - request.args.get('sort'), - self.DEFAULT_SORT) - self.order = request.args.get('order', self.DEFAULT_ORDER) + self.sort = SORT_MAP.get(request.args.get("sort"), self.DEFAULT_SORT) + self.order = request.args.get("order", self.DEFAULT_ORDER) if self.order not in self.ORDERS: self.order = self.DEFAULT_ORDER # LIMIT (page_size for backwards compatibility) - page_size = int(request.args.get('page_size', self.DEFAULT_LIMIT)) - self.limit = int(request.args.get('limit', page_size)) + 1 + page_size = int(request.args.get("page_size", self.DEFAULT_LIMIT)) + self.limit = int(request.args.get("limit", page_size)) + 1 # OFFSET - self.offset = int(request.args.get('offset', 0)) + self.offset = int(request.args.get("offset", 0)) def get_previous_offset(self): return max(self.offset - self.limit, 0) @@ -69,16 +67,16 @@ def get_next_offset(self): def get_next_url(self): params = { - 'offset': self.get_next_offset(), - 'limit': self.limit, + "offset": self.get_next_offset(), + "limit": self.limit, } params.update(self.get_sort_params()) return urllib.parse.urlencode(params) def get_previous_url(self): params = { - 'offset': self.get_previous_offset(), - 'limit': self.limit, + "offset": self.get_previous_offset(), + "limit": self.limit, } params.update(self.get_sort_params()) return urllib.parse.urlencode(params) @@ -87,9 +85,9 @@ def get_shortcuts(self): raise NotImplementedError() def get_edit_url(self, shortcut): - return 'Edit here: http://go/_edit?%s' % urllib.parse.urlencode({ - 'name': shortcut.name, - }) + return "Edit here: http://go/_edit?%s" % urllib.parse.urlencode( + {"name": shortcut.name,} + ) def get_template_values(self): shortcuts = self.get_shortcuts() @@ -103,17 +101,17 @@ def get_template_values(self): shortcuts = shortcuts[:-1] return { - 'title': current_app.config['TITLE'], - 'offset': self.offset, - 'previous': self.get_previous_url(), - 'next': self.get_next_url(), - 'shortcuts': shortcuts, - 'has_next': has_next, - 'created': self.created, - 'edited': self.edited, - 'deleted': self.deleted, - 'to': self.to, - 'error': self.error, + "title": current_app.config["TITLE"], + "offset": self.offset, + "previous": self.get_previous_url(), + "next": self.get_next_url(), + "shortcuts": shortcuts, + "has_next": has_next, + "created": self.created, + "edited": self.edited, + "deleted": self.deleted, + "to": self.to, + "error": self.error, } def get_sort_params(self): @@ -125,7 +123,7 @@ def get_sort_params(self): return params def get_order_by(self): - if self.order == 'asc': + if self.order == "asc": return asc(self.sort) else: return desc(self.sort) diff --git a/src/cache.py b/src/cache.py deleted file mode 100644 index 0d1665a..0000000 --- a/src/cache.py +++ /dev/null @@ -1 +0,0 @@ -# TODO Redis diff --git a/src/config.py b/src/config.py index 9c6d827..8c7e2aa 100644 --- a/src/config.py +++ b/src/config.py @@ -4,15 +4,15 @@ class Config(object): DEBUG = False CSRF_ENABLED = True - TITLE = os.environ['TITLE'] + TITLE = os.environ["TITLE"] - REDIRECT_URI = os.getenv('REDIRECT_URI') - HOSTED_DOMAIN = os.getenv('HOSTED_DOMAIN') - AUTH_HEADER_NAME = os.getenv('AUTH_HEADER_NAME') - USE_HEADER_AUTH = os.getenv('AUTH_HEADER_NAME') is not None - USE_GOOGLE_AUTH = os.getenv('AUTH_HEADER_NAME') is None - SKIP_AUTH = os.getenv('SKIP_AUTH').lower() in ['1', 'true', 't'] - BEHIND_PROXY = os.getenv('BEHIND_PROXY', '0').lower() in ['1', 'true', 't'] + REDIRECT_URI = os.getenv("REDIRECT_URI") + HOSTED_DOMAIN = os.getenv("HOSTED_DOMAIN") + AUTH_HEADER_NAME = os.getenv("AUTH_HEADER_NAME") + USE_HEADER_AUTH = os.getenv("AUTH_HEADER_NAME") is not None + USE_GOOGLE_AUTH = os.getenv("AUTH_HEADER_NAME") is None + SKIP_AUTH = os.getenv("SKIP_AUTH").lower() in ["1", "true", "t"] + BEHIND_PROXY = os.getenv("BEHIND_PROXY", "0").lower() in ["1", "true", "t"] class ProductionConfig(Config): diff --git a/src/gogo.py b/src/gogo.py index ee5c661..4731e46 100644 --- a/src/gogo.py +++ b/src/gogo.py @@ -4,124 +4,123 @@ import flask from flask.views import MethodView -from base_list_view import BaseListView -from models import db, Shortcut - import auth +from base_list_view import BaseListView +from models import Shortcut, db # Shortcuts may not use these names. -RESERVED_NAMES = {'_create', '_delete', '_edit', '_list', '_ajax'} -HTTPS_REDIRECT_URL = os.getenv('HTTPS_REDIRECT_URL', 'https://localhost:8443') +RESERVED_NAMES = {"_create", "_delete", "_edit", "_list", "_ajax"} +HTTPS_REDIRECT_URL = os.getenv("HTTPS_REDIRECT_URL", "https://localhost:8443") class DashboardView(BaseListView): - template = 'dashboard.html' + template = "dashboard.html" def get_shortcuts(self): return ( - Shortcut.query - .filter(Shortcut.owner == auth.get_current_user()) - .order_by(self.get_order_by()) - .offset(self.offset) - .limit(self.limit) - .all() + Shortcut.query.filter(Shortcut.owner == auth.get_current_user()) + .order_by(self.get_order_by()) + .offset(self.offset) + .limit(self.limit) + .all() ) class ListView(BaseListView): - template = 'list.html' + template = "list.html" def get_shortcuts(self): return ( - Shortcut.query - .filter() - .order_by(self.get_order_by()) - .offset(self.offset) - .limit(self.limit) - .all() + Shortcut.query.filter() + .order_by(self.get_order_by()) + .offset(self.offset) + .limit(self.limit) + .all() ) class CreateShortcutView(MethodView): @auth.login_required def post(self): - name = flask.request.form.get('name') - url = flask.request.form.get('url') - secondary_url = flask.request.form.get('secondary_url') + name = flask.request.form.get("name") + url = flask.request.form.get("url") + secondary_url = flask.request.form.get("secondary_url") if not name or not url: return '"name" and "url" params required', 400 if name in RESERVED_NAMES: - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?error={name}+is+reserved') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?error={name}+is+reserved") shortcut = Shortcut.query.filter(Shortcut.name == name).first() if shortcut: - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?error={name}+already+exists') - - shortcut = Shortcut(name=name, - url=url, - secondary_url=secondary_url, - owner=auth.get_current_user(), - hits=0) + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?error={name}+already+exists") + + shortcut = Shortcut( + name=name, + url=url, + secondary_url=secondary_url, + owner=auth.get_current_user(), + hits=0, + ) db.session.add(shortcut) db.session.commit() - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?created={name}') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?created={name}") class DeleteShortcutView(MethodView): @auth.login_required def get(self): - name = flask.request.args.get('name') + name = flask.request.args.get("name") if name is None: return '"name" param is required', 400 shortcut = Shortcut.query.filter(Shortcut.name == name).first() if not shortcut: - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist") template_values = { - 'name': name, + "name": name, } - return flask.render_template('delete.html', **template_values) + return flask.render_template("delete.html", **template_values) @auth.login_required def post(self): - name = flask.request.form.get('name') + name = flask.request.form.get("name") shortcut = Shortcut.query.filter(Shortcut.name == name).first() if not shortcut: - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist") db.session.delete(shortcut) db.session.commit() - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?deleted={name}') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?deleted={name}") class EditShortcutView(MethodView): @auth.login_required def get(self): - name = flask.request.args.get('name') + name = flask.request.args.get("name") if name is None: return '"name" param is required', 400 shortcut = Shortcut.query.filter(Shortcut.name == name).first() if not shortcut: - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist") template_values = { - 'name': name, - 'url': shortcut.url, - 'secondary_url': shortcut.secondary_url, + "name": name, + "url": shortcut.url, + "secondary_url": shortcut.secondary_url, } - return flask.render_template('edit.html', **template_values) + return flask.render_template("edit.html", **template_values) @auth.login_required def post(self): - name = flask.request.form.get('name') - url = flask.request.form.get('url') - secondary_url = flask.request.form.get('secondary_url') + name = flask.request.form.get("name") + url = flask.request.form.get("url") + secondary_url = flask.request.form.get("secondary_url") if name is None or url is None: return '"name" and "url" params required', 400 shortcut = Shortcut.query.filter(Shortcut.name == name).first() if not shortcut: - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?error={name}+does+not+exist") shortcut.url = url shortcut.secondary_url = secondary_url @@ -133,15 +132,15 @@ def post(self): db.session.add(shortcut) db.session.commit() - return flask.redirect(f'{HTTPS_REDIRECT_URL}/?edited={name}') + return flask.redirect(f"{HTTPS_REDIRECT_URL}/?edited={name}") class ShortcutRedirectView(MethodView): @auth.login_required def get(self, name): secondary_arg = None - if '/' in name: - name, secondary_arg = name.split('/', 1) + if "/" in name: + name, secondary_arg = name.split("/", 1) if name in RESERVED_NAMES: flask.abort(404) @@ -154,26 +153,34 @@ def get(self, name): shortcut.hits += 1 db.session.add(shortcut) db.session.commit() - if secondary_arg and shortcut.secondary_url and '%s' in shortcut.secondary_url: + if ( + secondary_arg + and shortcut.secondary_url + and "%s" in shortcut.secondary_url + ): response = flask.make_response( - flask.redirect(str(shortcut.secondary_url).replace('%s', secondary_arg))) + flask.redirect( + str(shortcut.secondary_url).replace("%s", secondary_arg) + ) + ) else: response = flask.make_response( - flask.redirect(str(shortcut.url), code=301)) - response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' + flask.redirect(str(shortcut.url), code=301) + ) + response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" return response template_values = { - 'name': name, + "name": name, } - return flask.render_template('create.html', **template_values) + return flask.render_template("create.html", **template_values) class Healthz(MethodView): def get(self): try: - db.engine.execute('SELECT 1') - return 'OK' + db.engine.execute("SELECT 1") + return "OK" except Exception as e: - print('Healthz failed: %s' % e) - return 'Fail', 500 + print("Healthz failed: %s" % e) + return "Fail", 500 diff --git a/src/models.py b/src/models.py index 598d40f..541ccec 100644 --- a/src/models.py +++ b/src/models.py @@ -4,7 +4,7 @@ class Shortcut(db.Model): - __tablename__ = 'shortcut' + __tablename__ = "shortcut" id = db.Column(db.Integer, primary_key=True, nullable=False) created_at = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) @@ -22,4 +22,4 @@ def __init__(self, name, owner, url, secondary_url, hits): self.hits = hits def __repr__(self): - return ''.format(self.id) + return "".format(self.id) diff --git a/src/search.py b/src/search.py index b776e37..30b76f1 100644 --- a/src/search.py +++ b/src/search.py @@ -1,10 +1,9 @@ import flask -from flask.views import MethodView import sqlalchemy - -from models import Shortcut +from flask.views import MethodView import auth +from models import Shortcut class SearchView(MethodView): @@ -14,22 +13,21 @@ class SearchView(MethodView): @auth.login_required def get(self): try: - name = flask.request.args.get('name') - url = flask.request.args.get('url') - result_limit = int(flask.request.args.get('limit', self.DEFAULT_RESULT_LIMIT)) + name = flask.request.args.get("name") + url = flask.request.args.get("url") + result_limit = int( + flask.request.args.get("limit", self.DEFAULT_RESULT_LIMIT) + ) if name is None and url is None: raise ValueError('One of params "name", "url" is required.') result_limit = min(result_limit, self.MAX_RESULT_LIMIT) query = Shortcut.query - name_like_filter = Shortcut.name.ilike('%{}%'.format(name)) - url_like_filter = Shortcut.url.ilike('%{}%'.format(url)) + name_like_filter = Shortcut.name.ilike("%{}%".format(name)) + url_like_filter = Shortcut.url.ilike("%{}%".format(url)) if name is not None and url is not None: - query = query.filter(sqlalchemy.or_( - name_like_filter, - url_like_filter - )) + query = query.filter(sqlalchemy.or_(name_like_filter, url_like_filter)) elif name is not None: query = query.filter(name_like_filter) elif url is not None: @@ -38,11 +36,11 @@ def get(self): results = query.limit(result_limit).all() results = [ { - 'name': result.name, - 'owner': result.owner, - 'url': result.url, - 'secondary_url': result.secondary_url, - 'hits': result.hits, + "name": result.name, + "owner": result.owner, + "url": result.url, + "secondary_url": result.secondary_url, + "hits": result.hits, } for result in results ]