Getting things ready to containerize CodeSmigen
- Update the Dockerfile to pull the correct package - Update the README.rst with environment variables - Add typing annotations - Refactor config to be loaded via environment variables as well as a file
This commit is contained in:
parent
204fa1bde2
commit
62029b9acd
@ -1,6 +1,6 @@
|
|||||||
FROM python:3.11
|
FROM python:3.11
|
||||||
|
|
||||||
RUN pip install stickynotes --index-url https://git.snyman.info/packages/raoul/index
|
RUN pip install --extra-index-url https://git.snyman.info/api/packages/raoul/pypi/simple/ CodeSmidgen hypercorn
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
CMD ["hypercorn", "stickynotes.app"]
|
CMD ["hypercorn", "codesmidgen.app"]
|
||||||
|
@ -25,7 +25,8 @@ The easiest way to install CodeSmidgen is via Docker and Docker Compose. Here's
|
|||||||
app:
|
app:
|
||||||
image: git.snyman.info/raoul/codesmidgen:latest
|
image: git.snyman.info/raoul/codesmidgen:latest
|
||||||
env:
|
env:
|
||||||
- SQLALCHEMY_URL=postgres://codesmidgen:codesmidgen@postgres/codesmidgen
|
- SMIDGEN_SECRET_KEY=yoursecrethere
|
||||||
|
- SQLALCHEMY_DATABASE_URL=postgres://codesmidgen:codesmidgen@postgres/codesmidgen
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8000:8000"
|
- "127.0.0.1:8000:8000"
|
||||||
|
@ -2,48 +2,25 @@
|
|||||||
"""
|
"""
|
||||||
CodeSmidgen, yet another paste bin
|
CodeSmidgen, yet another paste bin
|
||||||
"""
|
"""
|
||||||
from configparser import ConfigParser
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import quart_flask_patch # noqa: F401
|
import quart_flask_patch # noqa: F401
|
||||||
from quart import Quart
|
from quart import Quart
|
||||||
|
|
||||||
|
from codesmidgen.config import get_config
|
||||||
from codesmidgen.db import db
|
from codesmidgen.db import db
|
||||||
from codesmidgen.views import views
|
from codesmidgen.views import views
|
||||||
|
|
||||||
|
|
||||||
def read_config(config_path=None):
|
def make_app() -> Quart:
|
||||||
"""
|
|
||||||
Read the configuration file and return the values in a dictionary
|
|
||||||
"""
|
|
||||||
if config_path:
|
|
||||||
config_file = config_path / 'codesmidgen.cfg'
|
|
||||||
else:
|
|
||||||
config_file = Path(__file__).parent / '..' / 'codesmidgen.cfg'
|
|
||||||
if not config_file.exists():
|
|
||||||
return {}
|
|
||||||
config_parser = ConfigParser()
|
|
||||||
config_parser.read(config_file)
|
|
||||||
config = {}
|
|
||||||
for option in config_parser.options('codesmidgen'):
|
|
||||||
config[option.upper()] = config_parser.get('codesmidgen', option)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def make_app(config_path=None):
|
|
||||||
"""
|
"""
|
||||||
Create the application object
|
Create the application object
|
||||||
"""
|
"""
|
||||||
app = Quart(__name__)
|
app = Quart(__name__)
|
||||||
# Load the config file
|
app.config.update(get_config())
|
||||||
config = read_config(config_path)
|
|
||||||
app.config.update(config)
|
|
||||||
app.config.update({'SQLALCHEMY_TRACK_MODIFICATIONS': False})
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
app.register_blueprint(views)
|
app.register_blueprint(views)
|
||||||
|
|
||||||
@app.before_first_request
|
@app.before_first_request
|
||||||
async def setup_db():
|
async def setup_db() -> None:
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
"""
|
"""
|
||||||
This is the entry point for the WSGI server
|
This is the entry point for the WSGI server
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
|
||||||
from codesmidgen import make_app
|
from codesmidgen import make_app
|
||||||
|
|
||||||
application = make_app(Path(__file__).parent.parent)
|
application = make_app()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
application.run(debug=True)
|
application.run(debug=True)
|
||||||
|
62
codesmidgen/config.py
Normal file
62
codesmidgen/config.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
CONFIG_DEFAULTS = {
|
||||||
|
'SQLALCHEMY_TRACK_MODIFICATIONS': False
|
||||||
|
}
|
||||||
|
CONFIG_PREFIXES = ['SMIDGEN_', 'SQLALCHEMY_']
|
||||||
|
STRIPPED_PREFIXES = ['SMIDGEN_']
|
||||||
|
|
||||||
|
|
||||||
|
def read_from_file() -> dict:
|
||||||
|
"""Read the configuration file and return the values in a dictionary"""
|
||||||
|
config_file = Path(__file__).parent / '..' / 'codesmidgen.cfg'
|
||||||
|
if not config_file.exists():
|
||||||
|
return {}
|
||||||
|
config_parser = ConfigParser()
|
||||||
|
config_parser.read(config_file)
|
||||||
|
config: dict[str, Any] = {}
|
||||||
|
for option in config_parser.options('codesmidgen'):
|
||||||
|
config[option.upper()] = config_parser.get('codesmidgen', option)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def read_from_envvars() -> dict:
|
||||||
|
"""Read the configuration from environment variables"""
|
||||||
|
config: dict[str, Any] = {}
|
||||||
|
for key in sorted(os.environ):
|
||||||
|
if not any([key.startswith(prefix) for prefix in CONFIG_PREFIXES]):
|
||||||
|
continue
|
||||||
|
value = os.environ[key]
|
||||||
|
try:
|
||||||
|
value = json.loads(value)
|
||||||
|
except Exception:
|
||||||
|
# If the value is not JSON parseable, just leave it as a string
|
||||||
|
pass
|
||||||
|
for prefix in STRIPPED_PREFIXES:
|
||||||
|
key = key.replace(prefix, '')
|
||||||
|
# Check if there are any "nested" values
|
||||||
|
if '__' not in key:
|
||||||
|
config[key] = value
|
||||||
|
continue
|
||||||
|
# Navigate the nested values and build the structure
|
||||||
|
*parents, child = key.split('__')
|
||||||
|
item = config
|
||||||
|
for parent in parents:
|
||||||
|
if parent not in item:
|
||||||
|
item[parent] = {}
|
||||||
|
item = item[parent]
|
||||||
|
item[child] = value
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_config() -> dict:
|
||||||
|
"""Read configuration from files, environment variables, etc."""
|
||||||
|
config = {}
|
||||||
|
config.update(CONFIG_DEFAULTS)
|
||||||
|
config.update(read_from_file())
|
||||||
|
config.update(read_from_envvars())
|
||||||
|
return config
|
@ -7,7 +7,7 @@ import secrets
|
|||||||
import string
|
import string
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
from quart import Blueprint, redirect, request, flash, make_response, current_app, render_template
|
from quart import Blueprint, Response, redirect, request, flash, make_response, current_app, render_template
|
||||||
from pygments import highlight
|
from pygments import highlight
|
||||||
from pygments.formatters.html import HtmlFormatter
|
from pygments.formatters.html import HtmlFormatter
|
||||||
from pygments.lexers import get_lexer_by_name, get_all_lexers
|
from pygments.lexers import get_lexer_by_name, get_all_lexers
|
||||||
@ -30,7 +30,7 @@ EXPIRY_DELTAS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _generate_short_url():
|
def _generate_short_url() -> str:
|
||||||
"""
|
"""
|
||||||
Encode the URL
|
Encode the URL
|
||||||
"""
|
"""
|
||||||
@ -40,7 +40,7 @@ def _generate_short_url():
|
|||||||
|
|
||||||
|
|
||||||
@views.route('/', methods=['GET'])
|
@views.route('/', methods=['GET'])
|
||||||
async def index():
|
async def index() -> str:
|
||||||
"""
|
"""
|
||||||
Add a new sticky note
|
Add a new sticky note
|
||||||
"""
|
"""
|
||||||
@ -51,7 +51,7 @@ async def index():
|
|||||||
|
|
||||||
|
|
||||||
@views.route('/notes', methods=['GET'])
|
@views.route('/notes', methods=['GET'])
|
||||||
async def notes():
|
async def notes() -> str:
|
||||||
"""
|
"""
|
||||||
Show a list of recent notes
|
Show a list of recent notes
|
||||||
"""
|
"""
|
||||||
@ -64,7 +64,7 @@ async def notes():
|
|||||||
|
|
||||||
|
|
||||||
@views.route('/about', methods=['GET'])
|
@views.route('/about', methods=['GET'])
|
||||||
async def about():
|
async def about() -> str:
|
||||||
"""
|
"""
|
||||||
Show the about page
|
Show the about page
|
||||||
"""
|
"""
|
||||||
@ -72,7 +72,7 @@ async def about():
|
|||||||
|
|
||||||
|
|
||||||
@views.route('/', methods=['POST'])
|
@views.route('/', methods=['POST'])
|
||||||
async def save():
|
async def save() -> Response:
|
||||||
"""
|
"""
|
||||||
Save a sticky note
|
Save a sticky note
|
||||||
"""
|
"""
|
||||||
@ -107,7 +107,7 @@ async def save():
|
|||||||
|
|
||||||
|
|
||||||
@views.route('/<string:note_url>', methods=['GET'])
|
@views.route('/<string:note_url>', methods=['GET'])
|
||||||
async def view(note_url):
|
async def view(note_url: str) -> Response | str:
|
||||||
"""
|
"""
|
||||||
Show a sticky note
|
Show a sticky note
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ async def view(note_url):
|
|||||||
|
|
||||||
|
|
||||||
@views.route('/raw/<string:note_url>', methods=['GET'])
|
@views.route('/raw/<string:note_url>', methods=['GET'])
|
||||||
async def raw(note_url):
|
async def raw(note_url: str) -> Response | str:
|
||||||
"""
|
"""
|
||||||
Show the raw version of a sticky note
|
Show the raw version of a sticky note
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ async def raw(note_url):
|
|||||||
|
|
||||||
|
|
||||||
@views.route('/pygments.css', methods=['GET'])
|
@views.route('/pygments.css', methods=['GET'])
|
||||||
async def pygments_css():
|
async def pygments_css() -> Response:
|
||||||
"""
|
"""
|
||||||
Return the Pygments CSS to the browser
|
Return the Pygments CSS to the browser
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user