-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Stalled] Provide an Elsa class that can be used programmatically #39
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
from ._cli import cli | ||
from ._cli import Elsa, cli | ||
|
||
__all__ = ['cli'] | ||
__all__ = ['Elsa', 'cli'] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,132 +11,203 @@ | |
from ._shutdown import ShutdownableFreezer, inject_shutdown | ||
|
||
|
||
def port_option(): | ||
return click.option( | ||
'--port', type=int, default=8003, | ||
help='Port to listen at') | ||
|
||
|
||
def cname_option(): | ||
return click.option( | ||
'--cname/--no-cname', default=True, | ||
help='Whether to create the CNAME file, default is to create it') | ||
|
||
|
||
def path_option(app): | ||
return click.option( | ||
'--path', default=os.path.join(app.root_path, '_build'), | ||
help='Input path, default _build') | ||
|
||
|
||
def freeze_app(app, freezer, path, base_url): | ||
if not base_url: | ||
raise click.UsageError('No base URL provided, use --base-url') | ||
print('Generating HTML...') | ||
app.config['FREEZER_DESTINATION'] = path | ||
app.config['FREEZER_BASE_URL'] = base_url | ||
app.config['SERVER_NAME'] = urllib.parse.urlparse(base_url).netloc | ||
|
||
# make sure Frozen Flask warnings are treated as errors | ||
warnings.filterwarnings('error', category=flask_frozen.FrozenFlaskWarning) | ||
|
||
try: | ||
freezer.freeze() | ||
except flask_frozen.FrozenFlaskWarning as w: | ||
print('Error:', w, file=sys.stderr) | ||
sys.exit(1) | ||
|
||
|
||
def inject_cname(app): | ||
"""Create CNAME route for GitHub pages""" | ||
@app.route('/CNAME') | ||
def cname(): | ||
return Response(app.config['SERVER_NAME'], | ||
mimetype='application/octet-stream') | ||
class Elsa: | ||
"""Elsa freezes websites written in Flask""" | ||
|
||
DEFAULT_PORT = 8003 | ||
DEFAULT_REMOTE = 'origin' | ||
|
||
def __init__(self, app, *, freezer=None, base_url=None): | ||
"""Initialize Elsa. | ||
|
||
Arguments: | ||
|
||
* app: and instance of Flask WSGI application | ||
* freezer: flask_frozen.Freezer-like instance (optional) | ||
* base_url: URL for the application, used for external links (optional) | ||
""" | ||
self.app = app | ||
self.freezer = freezer or ShutdownableFreezer(app) | ||
self.base_url = base_url | ||
|
||
def _base_url_help(self, *, freeze=False): | ||
freeze = ' with --freeze' if freeze else '' | ||
url = self.base_url | ||
default = 'default {}'.format(url) if url else 'mandatory' + freeze | ||
return 'URL for the application, used for external links, ' + default | ||
|
||
def _inject_cname(self): | ||
"""Create CNAME route for GitHub pages""" | ||
@self.app.route('/CNAME') | ||
def cname(): | ||
return Response(self.app.config['SERVER_NAME'], | ||
mimetype='application/octet-stream') | ||
|
||
def _inject_shutdown(self): | ||
"""Create a shutdown route""" | ||
inject_shutdown(self.app) | ||
|
||
def freeze(self, path=None, base_url=None): | ||
"""Freeze the app | ||
|
||
Arguments: | ||
* path: location of frozen website (tries FREEZER_DESTINATION app | ||
config if not provided) | ||
* base_url: URL for the application, used for external links | ||
(tries self.base_url and FREEZER_BASE_URL app config | ||
if not provided)""" | ||
base_url = (base_url or self.base_url or | ||
self.app.config.get('FREEZER_BASE_URL')) | ||
if not base_url: | ||
raise ValueError('No base URL provided!') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think errors don't use punctuation by convention |
||
self.app.config['FREEZER_BASE_URL'] = base_url | ||
|
||
path = path or self.app.config.get('FREEZER_DESTINATION') | ||
self.app.config['FREEZER_DESTINATION'] = path | ||
self.app.config['SERVER_NAME'] = urllib.parse.urlparse(base_url).netloc | ||
|
||
if not path: | ||
raise ValueError('No path provided!') | ||
|
||
# make sure Frozen Flask warnings are treated as errors | ||
warnings.filterwarnings('error', | ||
category=flask_frozen.FrozenFlaskWarning) | ||
|
||
print('Generating HTML...') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should an API like this print on its own? Perhaps logging as INFO would be better, since a logger is already in use. Alternatively, a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, it definitively should not, I say that in the PR description. A logger is already in use? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed that, sorry. Also, I didn't realize |
||
self.freezer.freeze() | ||
|
||
def freeze_fail_exit(self, path=None, base_url=None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this useful? Usually libraries don't quit on their own. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to do it twice, so I created a function. Maybe it should be underscored, not to be part of the API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can only see you using it once right now. If it's only used in the CLI, I'd say it could be defined in there, but your CLI is intertwined with the Elsa class anyway (probably not really a bad thing). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good catch. Now I use it twice :D |
||
"""Like freeze() but exists on exception""" | ||
try: | ||
self.freeze(path, base_url) | ||
except flask_frozen.FrozenFlaskWarning as w: | ||
print('Error:', w, file=sys.stderr) | ||
sys.exit(1) | ||
|
||
def serve(self, port=DEFAULT_PORT): | ||
"""Serve the frozen app using the freezers ability to serve what was | ||
frozen""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *freezer's |
||
return self.freezer.serve(port=port) | ||
|
||
def deploy(self, *, path=None, remote=None, push=False, show_err=False): | ||
"""Deploy to GitHub pages, expects to be already frozen | ||
|
||
Arguments: | ||
* path: Where to find frozen HTML, defaults to FREEZER_DESTINATION app | ||
config | ||
* remote: (optional) git remote | ||
* push: whether to push to git | ||
* show_err: whether to display git push failure stderr""" | ||
path = path or self.app.config.get('FREEZER_DESTINATION') | ||
remote = remote or self.DEFAULT_REMOTE | ||
return deploy_(path, remote=remote, push=push, | ||
show_err=show_err) | ||
|
||
@classmethod | ||
def _port_option(cls): | ||
return click.option( | ||
'--port', type=int, default=cls.DEFAULT_PORT, | ||
help='Port to listen at') | ||
|
||
@classmethod | ||
def _cname_option(cls): | ||
return click.option( | ||
'--cname/--no-cname', default=True, | ||
help='Whether to create the CNAME file, default is to create it') | ||
|
||
def _path_option(self): | ||
return click.option( | ||
'--path', default=os.path.join(self.app.root_path, '_build'), | ||
help='Input path, default _build') | ||
|
||
def cli(self): | ||
"""Get a cli() function""" | ||
|
||
@click.group(context_settings=dict(help_option_names=['-h', '--help']), | ||
help=__doc__) | ||
def command(): | ||
pass | ||
|
||
@command.command() | ||
@self._port_option() | ||
@self._cname_option() | ||
def serve(port, cname): | ||
"""Run a debug server""" | ||
|
||
# Workaround for https://github.com/pallets/flask/issues/1907 | ||
auto_reload = self.app.config.get('TEMPLATES_AUTO_RELOAD') | ||
if auto_reload or auto_reload is None: | ||
self.app.jinja_env.auto_reload = True | ||
|
||
self._inject_shutdown() | ||
if cname: | ||
self._inject_cname() | ||
|
||
self.app.run(host='0.0.0.0', port=port, debug=True) | ||
|
||
@command.command() | ||
@self._path_option() | ||
@click.option('--base-url', default=self.base_url, | ||
help=self._base_url_help()) | ||
@click.option('--serve/--no-serve', | ||
help='After building the site, run a server with it') | ||
@self._port_option() | ||
@self._cname_option() | ||
def freeze(path, base_url, serve, port, cname): | ||
"""Build a static site""" | ||
if cname: | ||
self._inject_cname() | ||
|
||
if not base_url: | ||
raise click.UsageError('No base URL provided, use --base-url') | ||
self.freeze_fail_exit(path, base_url) | ||
|
||
if serve: | ||
self.serve(port=port) | ||
|
||
@command.command() | ||
@self._path_option() | ||
@click.option('--base-url', default=self.base_url, | ||
help=self._base_url_help(freeze=True)) | ||
@click.option('--remote', default=self.DEFAULT_REMOTE, | ||
help='The name of the remote to push to, ' | ||
'default origin') | ||
@click.option('--push/--no-push', default=None, | ||
help='Whether to push the gh-pages branch, ' | ||
'deprecated default is to push') | ||
@click.option('--freeze/--no-freeze', default=True, | ||
help='Whether to freeze the site before deploying, ' | ||
'default is to freeze') | ||
@click.option('--show-git-push-stderr', is_flag=True, | ||
help='Show the stderr output of `git push` failure, ' | ||
'might be dangerous if logs are public') | ||
@self._cname_option() | ||
def deploy(path, base_url, remote, push, freeze, | ||
show_git_push_stderr, cname): | ||
"""Deploy the site to GitHub pages""" | ||
if push is None: | ||
warnings.simplefilter('always') | ||
msg = ('Using deploy without explicit --push/--no-push is ' | ||
'deprecated. Assuming --push for now. In future ' | ||
'versions of elsa, the deploy command will not push to ' | ||
'the remote server by default. Use --push explicitly ' | ||
'to maintain current behavior.') | ||
warnings.warn(msg, DeprecationWarning) | ||
push = True | ||
if freeze: | ||
if cname: | ||
self._inject_cname() | ||
if not base_url: | ||
raise click.UsageError('No base URL provided, use ' | ||
'--base-url') | ||
self.freeze(path, base_url) | ||
|
||
deploy_(path, remote=remote, push=push, | ||
show_err=show_git_push_stderr) | ||
|
||
return command() | ||
|
||
|
||
def cli(app, *, freezer=None, base_url=None): | ||
"""Get a cli() function for provided app""" | ||
if not freezer: | ||
freezer = ShutdownableFreezer(app) | ||
|
||
@click.group(context_settings=dict(help_option_names=['-h', '--help']), | ||
help=__doc__) | ||
def command(): | ||
pass | ||
|
||
@command.command() | ||
@port_option() | ||
@cname_option() | ||
def serve(port, cname): | ||
"""Run a debug server""" | ||
|
||
# Workaround for https://github.com/pallets/flask/issues/1907 | ||
auto_reload = app.config.get('TEMPLATES_AUTO_RELOAD') | ||
if auto_reload or auto_reload is None: | ||
app.jinja_env.auto_reload = True | ||
|
||
inject_shutdown(app) | ||
if cname: | ||
inject_cname(app) | ||
|
||
app.run(host='0.0.0.0', port=port, debug=True) | ||
|
||
@command.command() | ||
@path_option(app) | ||
@click.option('--base-url', default=base_url, | ||
help='URL for the application, used for external links, ' + | ||
('default {}'.format(base_url) if base_url else 'mandatory')) | ||
@click.option('--serve/--no-serve', | ||
help='After building the site, run a server with it') | ||
@port_option() | ||
@cname_option() | ||
def freeze(path, base_url, serve, port, cname): | ||
"""Build a static site""" | ||
if cname: | ||
inject_cname(app) | ||
|
||
freeze_app(app, freezer, path, base_url) | ||
|
||
if serve: | ||
freezer.serve(port=port) | ||
|
||
@command.command() | ||
@path_option(app) | ||
@click.option('--base-url', default=base_url, | ||
help='URL for the application, used for external links, ' + | ||
('default {}'.format(base_url) if base_url else 'mandatory' | ||
' with --freeze')) | ||
@click.option('--remote', default='origin', | ||
help='The name of the remote to push to, ' | ||
'default origin') | ||
@click.option('--push/--no-push', default=None, | ||
help='Whether to push the gh-pages branch, ' | ||
'deprecated default is to push') | ||
@click.option('--freeze/--no-freeze', default=True, | ||
help='Whether to freeze the site before deploying, ' | ||
'default is to freeze') | ||
@click.option('--show-git-push-stderr', is_flag=True, | ||
help='Show the stderr output of `git push` failure, ' | ||
'might be dangerous if logs are public') | ||
@cname_option() | ||
def deploy(path, base_url, remote, push, freeze, | ||
show_git_push_stderr, cname): | ||
"""Deploy the site to GitHub pages""" | ||
if push is None: | ||
warnings.simplefilter('always') | ||
msg = ('Using deploy without explicit --push/--no-push is ' | ||
'deprecated. Assuming --push for now. In future versions ' | ||
'of elsa, the deploy command will not push to the remote ' | ||
'server by default. Use --push explicitly to maintain ' | ||
'current behavior.') | ||
warnings.warn(msg, DeprecationWarning) | ||
push = True | ||
if freeze: | ||
if cname: | ||
inject_cname(app) | ||
freeze_app(app, freezer, path, base_url) | ||
|
||
deploy_(path, remote=remote, push=push, show_err=show_git_push_stderr) | ||
|
||
return command() | ||
return Elsa(app, freezer=freezer, base_url=base_url).cli() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*an instance of a Flask WSGI application
(typo and missing article)