diff --git a/.pylintrc b/.pylintrc new file mode 100755 index 0000000..1349af4 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,5 @@ +[FORMAT] +max-line-length=140 + +[MESSAGES CONTROL] +disable=import-error \ No newline at end of file diff --git a/src/index.py b/src/index.py index 3f91708..5347255 100755 --- a/src/index.py +++ b/src/index.py @@ -1,16 +1,17 @@ #!/usr/bin/python3 -from flask import Flask, render_template, Response import traceback from os import environ import threading -from requests import post import logging +from requests import post +from flask import Flask, render_template app = Flask(__name__) -import projects -import contact +# These imports need to come after our app is defined as they add routes to it. +import projects # pylint: disable=wrong-import-position,unused-import +import contact # pylint: disable=wrong-import-position,unused-import class DiscordLogger(logging.Handler): ''' Simple logging handler to send a message to Discord ''' @@ -45,11 +46,13 @@ discord_logger = DiscordLogger(environ['DISCORD_ERR_HOOK']) app.logger.addHandler(discord_logger) @app.route('/') -def index(): +def index() -> str: + ''' Load the homepage ''' return render_template('index.html') @app.route('/error/') -def error(code): +def error(code) -> str: + ''' Render a nicer error page for a given code ''' error_definitions = { 400: 'Bad Request', 403: 'Forbidden', @@ -69,6 +72,6 @@ def error(code): 505: 'Your browser tried to use a HTTP version I don\'t support. Check it is up to date.' } - return render_template('error.html', + return render_template('error.html', error=f'{code}: {error_definitions.get(int(code))}', description=error_desc.get(int(code))) diff --git a/src/projects.py b/src/projects.py index dee9385..b8556dc 100755 --- a/src/projects.py +++ b/src/projects.py @@ -2,21 +2,22 @@ from os import path import json -from flask import Flask, render_template, Response, send_from_directory, request, make_response -from markdown import markdown -import frontmatter -from glob import glob from datetime import datetime -from index import app -from bs4 import BeautifulSoup -from PIL import Image, UnidentifiedImageError from io import BytesIO +from glob import glob +from PIL import Image, UnidentifiedImageError +import frontmatter +from markdown import markdown +from bs4 import BeautifulSoup +from flask import render_template, Response, send_from_directory, request, make_response +from index import app md_directory = path.join(path.realpath(path.dirname(__file__)), path.normpath('projects/')) @app.context_processor -def processor(): - def get_excerpt(post): +def processor() -> dict: + ''' Jninja processors ''' + def get_excerpt(post: frontmatter.Post) -> str: html = markdown(post.content) post_soup = BeautifulSoup(html, 'html.parser') all_text = ' '.join([x.get_text() for x in post_soup.findAll('p')]) @@ -25,31 +26,37 @@ def processor(): @app.template_filter('category_title') def category_title(category_id: str) -> str: - with open(path.join(md_directory, 'categories.json')) as categories_file: + ''' Jninja filter to get a category title by its ID ''' + with open(path.join(md_directory, 'categories.json'), encoding='utf8') as categories_file: categories = json.load(categories_file) return categories.get(category_id).get('title', '') @app.template_filter('human_date') def human_date(iso_date: str) -> str: + ''' Jninja filter to convert an ISO date to human readable. ''' try: return datetime.fromisoformat(str(iso_date)).strftime('%A %d %B %Y') except ValueError: return iso_date @app.template_filter('to_html') -def to_html(content): +def to_html(content: str) -> str: + ''' Jninja filter to wrap markdown ''' return markdown(content) def get_all_posts(directory: str) -> list: + ''' Get all posts in the posts directory ''' abs_paths = [path.join(directory, x) for x in glob(f'{directory}/*.md')] return [frontmatter.load(x) for x in abs_paths] -def get_by_meta_key(directory: str, key: str, value: str): +def get_by_meta_key(directory: str, key: str, value: str) -> list: + ''' Get posts by a metadata key value pair ''' return [x for x in get_all_posts(directory) if x.get(key) == value or type(x.get(key, [])) is list and value in x.get(key, [])] @app.route('/projects/') -def projects(): +def projects() -> str: + ''' Load the projects page ''' articles_to_return = sorted( get_all_posts( md_directory), @@ -62,7 +69,7 @@ def projects(): error='There\'s nothing here... yet.', description='I\'m still working on this page. Check back soon for some content.') try: - with open(path.join(md_directory, 'categories.json')) as categories_file: + with open(path.join(md_directory, 'categories.json'), encoding='utf8') as categories_file: categories = json.load(categories_file) except FileNotFoundError: return render_template('error.html', @@ -77,11 +84,12 @@ def projects(): description='A selection of projects I\'ve been involved in1') @app.route('/projects/category//') -def category(category): +def category(category_id: str) -> str: + ''' Load the page for a given category ''' try: - with open(path.join(md_directory, 'categories.json')) as categories_file: + with open(path.join(md_directory, 'categories.json'), encoding='utf8') as categories_file: categories = json.load(categories_file) - the_category = categories.get(category) + the_category = categories.get(category_id) except FileNotFoundError: return render_template('error.html', error='There\'s nothing here... yet.', @@ -92,7 +100,7 @@ def category(category): articles_to_return = sorted( get_by_meta_key( - md_directory, 'categories', category), + md_directory, 'categories', category_id), key=lambda d: d.metadata.get('date'), reverse=True ) @@ -110,8 +118,9 @@ def category(category): current_category=category) @app.route('/projects/
') -def article(article): - articles = get_by_meta_key(md_directory, 'id', article) +def article(article_id: str) -> str: + ''' Load a single article ''' + articles = get_by_meta_key(md_directory, 'id', article_id) if len(articles) == 0: return Response(status=404) @@ -124,23 +133,24 @@ def article(article): page_title=f'{the_article.metadata["title"]} - ') @app.route('/projects/image/') -def image(image): +def image(image_name: str) -> Response: + ''' Resize and return an image. ''' w = int(request.args.get('w', 0)) h = int(request.args.get('h', 0)) if w == 0 and h == 0: - return send_from_directory(md_directory, path.join('images', image)) + return send_from_directory(md_directory, path.join('images', image_name)) try: - the_image = Image.open(path.join(md_directory, 'images', image)) + the_image = Image.open(path.join(md_directory, 'images', image_name)) except FileNotFoundError: return Response(status=404) except UnidentifiedImageError: - return send_from_directory(md_directory, path.join('images', image)) + return send_from_directory(md_directory, path.join('images', image_name)) max_width, max_height = the_image.size if (w >= max_width and h >= max_height): - return send_from_directory(md_directory, path.join('images', image)) + return send_from_directory(md_directory, path.join('images', image_name)) req_size = [max_width, max_height] if w > 0: @@ -151,8 +161,7 @@ def image(image): resized_img = BytesIO() the_image.thumbnail(tuple(req_size)) the_image.save(resized_img, format=the_image.format) - + response = make_response(resized_img.getvalue()) response.headers.set('Content-Type', f'image/{the_image.format}') return response -