Skip to content

Instantly share code, notes, and snippets.

@mmerickel
Last active June 5, 2023 09:09
Show Gist options
  • Save mmerickel/6e484657f2244fc6876e6bfb8ed97bc3 to your computer and use it in GitHub Desktop.
Save mmerickel/6e484657f2244fc6876e6bfb8ed97bc3 to your computer and use it in GitHub Desktop.
render an ini file based on env vars
##############################################################################
# Settings made available in site.ini.in.
#
# After making changes re-run render-config
#
# Override settings by making a .env.local file.
##############################################################################
DEBUG=yes
WEB_CONCURRENCY=10
TRUSTED_PROXY=""
TRUSTED_PROXY_HEADERS=""
TRUSTED_PROXY_COUNT=1
LOG_UNTRUSTED_PROXY_HEADERS=yes
BIND_HOST=127.0.0.1
# heroku defines the PORT variable but we rename it to BIND_PORT
PORT=10001
BIND_PORT=${PORT}
DATABASE_URL=...
RETRY_ATTEMPTS=3
from dotenv import load_dotenv
import jinja2
from jinja2.ext import Extension
from jinja2.lexer import Token
from markupsafe import Markup
import os
from pyramid.settings import asbool
import textwrap
def asbool_filter(value):
return asbool(value)
def multiline_filter(value):
# strip only the leading whitespaces from the first line
value = value.strip(' ').strip('\n')
if value:
lines = textwrap.dedent(value).split('\n')
return '\n ' + '\n '.join(lines)
return ''
def ini_filter(value):
if isinstance(value, Markup):
return value
if not isinstance(value, str):
return value
return value.replace('%', '%%')
class IniEverythingExtension(Extension):
"""
Insert a `|ini` filter at the end of every variable substitution.
This will ensure that all injected values are converted to INI.
"""
def filter_stream(self, stream):
# This is based on
# https://github.com/indico/indico/blob/master/indico/web/flask/templating.py.
for token in stream:
if token.type == 'variable_end':
yield Token(token.lineno, 'pipe', '|')
yield Token(token.lineno, 'name', 'ini')
yield token
def render_template(path, context):
base, name = os.path.split(path)
if not base:
base = os.getcwd()
env = jinja2.Environment(
loader=jinja2.FileSystemLoader([base]),
undefined=jinja2.StrictUndefined,
extensions=[IniEverythingExtension],
)
env.filters['bool'] = asbool_filter
env.filters['multiline'] = multiline_filter
env.filters['ini'] = ini_filter
template = env.get_template(name)
return template.render(context)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--env')
parser.add_argument('--env-file')
parser.add_argument('--template', default='site.ini.in')
parser.add_argument('-o', '--output-file', default='site.ini')
def main(cli, args):
if args.env_file:
load_dotenv(args.env_file)
else:
override = args.env or os.environ.get('DOTENV')
for path in (
f'.env.{override}.local' if override else None,
f'.env.{override}' if override else None,
'.env.local',
'.env',
):
if path and os.path.exists(path):
load_dotenv(path)
result = render_template(args.template, {
'env': os.environ,
})
with open(args.output_file, 'w', encoding='utf8') as fp:
fp.write(result.rstrip() + '\n')
{%- set DEBUG = env.DEBUG | bool -%}
[alembic]
script_location = myapp.alembic:migrations
file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s
sqlalchemy.url = {{ env.DATABASE_URL }}
[app:api]
use = egg:myapp
sqlalchemy.url = {{ env.DATABASE_URL }}
retry.attempts = {{ env.RETRY_ATTEMPTS }}
tm.annotate_user = no
pyramid.reload_assets = {{ 'yes' if DEBUG else 'no' }}
pyramid.reload_templates = {{ 'yes' if DEBUG else 'no' }}
pyramid.debug_authorization = no
pyramid.debug_notfound = no
pyramid.debug_routematch = no
pyramid.default_locale_name = en
{%- if DEBUG %}
pyramid.includes =
pyramid_debugtoolbar
debugtoolbar.hosts = 0.0.0.0/0
debugtoolbar.show_on_exc_only = yes
debugtoolbar.active_panels = performance
{%- endif %}
[filter:tracker]
use = egg:request-id
exclude_prefixes =
/static/
/_debug_toolbar/
[pipeline:main]
pipeline =
tracker
api
[server:main]
use = egg:waitress#main
host = {{ env.BIND_HOST }}
port = {{ env.BIND_PORT }}
threads = {{ env.WEB_CONCURRENCY }}
{%- if env.TRUSTED_PROXY %}
trusted_proxy = {{ env.TRUSTED_PROXY }}
trusted_proxy_headers = {{ env.TRUSTED_PROXY_HEADERS }}
trusted_proxy_count = {{ env.TRUSTED_PROXY_COUNT }}
{%- endif %}
log_untrusted_proxy_headers = {{ env.LOG_UNTRUSTED_PROXY_HEADERS }}
clear_untrusted_proxy_headers = yes
[loggers]
keys = root, alembic, sqlalchemy, myapp, translogger
[handlers]
keys = console, translogger
[formatters]
keys = generic, minimal
[logger_root]
level = INFO
handlers = console
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy
[logger_myapp]
level = DEBUG
handlers =
qualname = myapp
[logger_translogger]
level = INFO
handlers = translogger
qualname = request_id
propagate = 0
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[handler_translogger]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = minimal
[formatter_generic]
class = Formatter
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
[formatter_minimal]
class = Formatter
format = %(message)s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment