Coder Social home page Coder Social logo

nickjj / flask-static-digest Goto Github PK

View Code? Open in Web Editor NEW
153.0 4.0 25.0 69 KB

Flask extension to help make your static files production ready by md5 tagging and gzipping them.

License: MIT License

Python 88.94% HTML 10.51% CSS 0.55%
flask flask-extension python static-files md5-digest gzipped-files

flask-static-digest's Introduction

What is Flask-Static-Digest? CI

It is a Flask extension that will help make your static files production ready with very minimal effort on your part. It does this by creating md5 tagged versions and gzip and / or brotli compressed versions of your static files by running a flask digest compile command that this extension adds to your Flask app.

It should be the last thing you do to your static files before uploading them to your server or CDN. Speaking of which, if you're using a CDN this extension optionally lets you configure a host URL that will get prepended to your static file paths. If you're not using a CDN, no problem everything will work as you would expect by default.

Other web frameworks like Django, Ruby on Rails and Phoenix all have this feature built into their framework, and now with this extension Flask does too.

This extension will work if you're not using any asset build tools but at the same time it also works with esbuild, Webpack, Grunt, Gulp or any other build tool you can think of. This tool does not depend on or compete with existing asset build tools.

If you're already using Webpack or a similar tool, that's great. Webpack takes care of bundling your assets and helps convert things like SASS to CSS and ES6+ JS to browser compatible JS. That is solving a completely different problem than what this extension solves. This extension will further optimize your static files after your build tool produces its output files.

This extension does things that Webpack alone cannot do because in order for things like md5 tagging to work Flask needs to be aware of how to map those hashed file names back to regular file names you would reference in your Jinja 2 templates.

How does it work?

There's 3 pieces to this extension:

  1. It adds a custom Flask CLI command to your project. When you run this command it looks at your static files and then generates an md5 tagged version of each file along with optionally compressing them with gzip and / or brotli.

  2. When the above command finishes it creates a cache_manifest.json file in your static folder which maps the regular file names, such as images/flask.png to images/flask-f86b271a51b3cfad5faa9299dacd987f.png.

  3. It adds a new template helper called static_url_for which uses Flask's url_for under the hood but is aware of the cache_manifest.json file so it knows how to resolve images/flask.png to the md5 tagged file name.

Demo video

This 25 minute video goes over using this extension but it also spends a lot of time on the "why" where we cover topics like cache busting and why IMO you might want to use this extension in all of your Flask projects.

If you prefer reading instead of video, this README file covers installing, configuring and using this extension too.

Demo Video

Changes since this video

  • FLASK_STATIC_DIGEST_HOST_URL has been added to configure an optional external host, aka. CDN (explained here)
  • If your blueprints have static files they will get digested now too (including nested blueprints!)
  • Optional Brotli support has been added
  • FLASK_STATIC_DIGEST_COMPRESSION has been added to control compression (explained here)

Table of Contents

Installation

You'll need to be running Python 3.6+ and using Flask 1.0 or greater.

pip install Flask-Static-Digest

To install with Brotli support:

pip install Flask-Static-Digest[brotli]

Example directory structure for a 'hello' app

├── hello
│   ├── __init__.py
│   ├── app.py
│   └── static
│       └── css
│           ├── app.css
└── requirements.txt

Flask app factory example using this extension

from flask import Flask
from flask_static_digest import FlaskStaticDigest

flask_static_digest = FlaskStaticDigest()


def create_app():
    app = Flask(__name__)

    flask_static_digest.init_app(app)

    @app.route("/")
    def index():
        return "Hello, World!"

    return app

A more complete example app can be found in the tests/ directory.

Using the newly added Flask CLI command

You'll want to make sure to at least set the FLASK_APP environment variable:

export FLASK_APP=hello.app
export FLASK_ENV=development

Then run the flask binary to see its help menu:

Usage: flask [OPTIONS] COMMAND [ARGS]...

  ...

Options:
  --version  Show the flask version
  --help     Show this message and exit.

Commands:
  digest  md5 tag and compress static files.
  routes  Show the routes for the app.
  run     Run a development server.
  shell   Run a shell in the app context.

If all went as planned you should see the new digest command added to the list of commands.

Going over the Flask CLI commands

Running flask digest will produce this help menu:

Usage: flask digest [OPTIONS] COMMAND [ARGS]...

  md5 tag and compress static files.

Options:
  --help  Show this message and exit.

Commands:
  clean    Remove generated static files and cache manifest.
  compile  Generate optimized static files and a cache manifest.

Each command is labeled, but here's a bit more information on what they do.

compile

Inspects your Flask app's and blueprint's static_folder and uses that as both the input and output path of where to look for and create the newly digested and compressed files.

At a high level it recursively loops over all of the files it finds in that directory and then generates the md5 tagged and compressed versions of each file. It also creates a cache_manifest.json file in the root of your static_folder.

That manifest file is machine generated meaning you should not edit it unless you really know what you're doing.

This file maps the human readable file name of let's say images/flask.png to the digested file name. It's a simple key / value set up. It's basically a Python dictionary in JSON format.

In the end it means if your static folder looked like this originally:

  • css/app.css
  • js/app.js
  • images/flask.png

And you decided to run the compile command, it would now look like this:

  • css/app.css
  • css/app.css.gz
  • css/app-5d41402abc4b2a76b9719d911017c592.css
  • css/app-5d41402abc4b2a76b9719d911017c592.css.gz
  • js/app.js
  • js/app.js.gz
  • js/app-098f6bcd4621d373cade4e832627b4f6.js
  • js/app-098f6bcd4621d373cade4e832627b4f6.js.gz
  • images/flask.png
  • images/flask.png.gz
  • images/flask-f86b271a51b3cfad5faa9299dacd987f.png
  • images/flask-f86b271a51b3cfad5faa9299dacd987f.png.gz
  • cache_manifest.json

Your md5 hashes will be different because it depends on what the contents of the file are.

clean

Inspects your Flask app's and blueprint's static_folder and uses that as the input path of where to look for digested and compressed files.

It will recursively delete files that have a file extension of .gz or .br and files that have been digested. It determines if a file has been digested based on its file name. In other words, it will delete files that match this regexp r"-[a-f\d]{32}".

In the end that means if you had these 6 files in your static folder:

  • images/flask.png
  • images/flask.png.gz
  • images/flask.png.br
  • images/flask-f86b271a51b3cfad5faa9299dacd987f.png
  • images/flask-f86b271a51b3cfad5faa9299dacd987f.png.gz
  • images/flask-f86b271a51b3cfad5faa9299dacd987f.png.br

And you decided to run the clean command, the last 5 files would be deleted leaving you with the original images/flask.png.

Configuring this extension

By default this extension will create md5 tagged versions of all files it finds in your configured static_folder. It will also create gzip'ed versions of each file and it won't prefix your static files with an external host.

If you don't like any of this behavior or you wish to enable brotli you can optionally configure:

FLASK_STATIC_DIGEST_BLACKLIST_FILTER = []
# If you want specific extensions to not get md5 tagged you can add them to
# the list, such as: [".htm", ".html", ".txt"]. Make sure to include the ".".

FLASK_STATIC_DIGEST_COMPRESSION = ["gzip"]
# Optionally compress your static files, supported values are:
#   []                 avoids any compression
#   ["gzip"]           uses gzip
#   ["brotli"]         uses brotli (prefer either gzip or both)
#   ["gzip", "brotli"] uses both

FLASK_STATIC_DIGEST_HOST_URL = None
# When set to a value such as https://cdn.example.com and you use static_url_for
# it will prefix your static path with this URL. This would be useful if you
# host your files from a CDN. Make sure to include the protocol (aka. https://).

You can override these defaults in your Flask app's config file.

Modifying your templates to use static_url_for instead of url_for

We're all familiar with this code right?

<img src="{{ url_for('static', filename='images/flask.png') }}"
     width="480" height="188" alt="Flask logo" />

When you put the above code into a Flask powered Jinja 2 template, it turns into this:

<img src="images/flask.png"
     width="480" height="188" alt="Flask logo" />

The path might vary depending on how you configured your Flask app's static_folder but you get the idea.

Using static_url_for instead of url_for

Let's use the same example as above:

<img src="{{ static_url_for('static', filename='images/flask.png') }}"
     width="480" height="188" alt="Flask logo" />

But now take a look at the output this produces:

<img src="/images/flask-f86b271a51b3cfad5faa9299dacd987f.png"
     width="480" height="188" alt="Flask logo" />

Or if you set FLASK_STATIC_DIGEST_HOST_URL = "https://cdn.example.com" it would produce:

<img src="https://cdn.example.com/images/flask-f86b271a51b3cfad5faa9299dacd987f.png"
     width="480" height="188" alt="Flask logo" />

Instead of using url_for you would use static_url_for. This uses Flask's url_for under the hood so things like _external=True and everything else url_for supports is available to use with static_url_for.

That means to use this extension you don't have to do anything other than install it, optionally run the CLI command to generate the manifest and then rename your static file references to use static_url_for instead of url_for.

If your editor supports performing a find / replace across multiple files you can quickly make the change by finding url_for('static' and replacing that with static_url_for('static'. If you happen to use double quotes instead of single quotes you'll want to adjust for that too.

Potentially updating your .gitignore file

If you're using something like Webpack then chances are you're already git ignoring the static files it produces as output. It's a common pattern to commit your Webpack source static files but ignore the compiled static files it produces.

But if you're not using Webpack or another asset build tool then the static files that are a part of your project might have the same source and destination directory. If that's the case, chances are you'll want to git ignore the md5 tagged files as well as the compressed and cache_manifest.json files from version control.

For clarity, you want to ignore them because you'll be generating them on your server at deploy time or within a Docker image if you're using Docker. They don't need to be tracked in version control.

Add this to your .gitignore file to ignore certain files this extension creates:

*-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].*
*.gz
cache_manifest.json

This allows your original static files but ignores everything else this extension creates. I am aware at how ridiculous that ignore rule is for the md5 hash but using [0-9a-f]{32} does not work. If you know of a better way, please open a PR!

FAQ

What about development vs production and performance implications?

You would typically only run the CLI command to prepare your static files for production. Running flask digest compile would become a part of your build process -- typically after you pip install your dependencies.

In development when the cache_manifest.json likely doesn't exist static_url_for calls url_for directly. This allows the static_url_for helper to work in both development and production without any fuss.

It's also worth pointing out the CLI command is expected to be run before you even start your Flask server (or gunicorn / etc.), so there's no perceivable run time performance hit. It only involves doing 1 extra dictionary lookup at run time which is many orders of magnitude faster than even the most simple database query.

In other words, this extension is not going to negatively impact the performance of your web application. If anything it's going to speed it up and save you money on hosting.

That's because compressed files can be upwards of 5-10x smaller so there's less bytes to transfer over the network.

Also with md5 tagging each file it means you can configure your web server such as nginx to cache each file forever. That means if a user visits your site a second time in the future, nginx will be smart enough to load it from their local browser's cache without even contacting your server. It's a 100% local look up.

This is as efficient as it gets. You can't do this normally without md5 tagging each file because if the file changes in the future, nginx will continue serving the old file until the cache expires so users will never see your updates. But due to how md5 hashing works, if the contents of a file changes it will get generated with a new name and nginx will serve the uncached new file.

This tactic is commonly referred to as "cache busting" and it's a very good idea to do this in production. You can even go 1 step further and serve your static files using a CDN. Using this cache busting strategy makes configuring your CDN a piece of cake since you don't need to worry about ever expiring your cache manually.

Why bother compressing your static files here instead of with nginx?

You would still be using nginx's gzip / brotli features, but now instead of nginx having to compress your files on the fly at run time you can configure nginx to use the pre-made compressed files that this extension creates.

This way you can benefit from having maximum compression without having nginx waste precious CPU cycles compressing files on the fly. This gives you the best of both worlds -- the highest compression ratio with no noticeable run time performance penalty.

How do you use this extension with Webpack or another build tool?

It works out of the box with no extra configuration or plugins needed for Webpack or your build tool of choice.

Typically the Webpack (or another build tool) work flow would look like this:

  • You configure Webpack with your source static files directory
  • You configure Webpack with your destination static files directory
  • Webpack processes your files in the source directory and copies them to the destination directory
  • Flask is configured to serve static files from that destination directory

For example, your source directory might be assets/ inside of your project and the destination might be myapp/static.

This extension will look at your Flask configuration for the static_folder and determine it's set to myapp/static so it will md5 tag and compress those files. Your Webpack source files will not get digested and compressed.

Migrating from Flask-Webpack

Flask-Webpack is another extension I wrote a long time ago which was specific to Webpack but had a similar idea to this extension. Flask-Webpack is now deprecated in favor of Flask-Static-Digest. Migrating is fairly painless. There are a number of changes but on the bright side you get to delete more code than you add!

Dependency / Flask app changes

  • Remove Flask-Webpack from requirements.txt
  • Remove all references to Flask-Webpack from your Flask app and config
  • Remove manifest-revision-webpack-plugin from package.json
  • Remove all references to this webpack plugin from your webpack config
  • Add Flask-Static-Digest to requirements.txt
  • Add the Flask-Static-Digest extension to your Flask app

Jinja 2 template changes

  • Replace stylesheet_tag('main_css') | safe with static_url_for('static', filename='css/main.css')
  • Replace javascript_tag('main_js') | safe with static_url_for('static', filename='js/main.js')
  • Replace any occurrences of asset_url_for('foo.png') with static_url_for('static', filename='images/foo.png')

How do you use this extension with Docker?

It's really no different than without Docker, but instead of running flask digest compile on your server directly at deploy time you would run it inside of your Docker image at build time. This way your static files are already set up and ready to go by the time you pull and use your Docker image in production.

You can see a fully working example of this in the open source version of my Build a SAAS App with Flask course. It leverages Docker's build arguments to only compile the static files when FLASK_ENV is set to production. The key files to look at are the Dockerfile, docker-compose.yml and .env files. That wires up the build arguments and env variables to make it work.

How do you use this extension with Heroku?

If you're deploying to Heroku using the Python buildpack you can follow these 2 steps:

  1. Create a bin/post_compile file in your project's source code
  2. Copy the lines below into the bin/post_compile file, save it and commit the changes
#!/usr/bin/env bash

set -e

echo "-----> Digesting static files"
cd "${1}" && flask digest compile

The next time you push your code this script will run after your pip dependencies are installed. It will run before your slug is compiled which ensures that the digested files are available before any traffic is served to your Dyno.

You can view how this file gets executed by Heroku in their Python buildpack's source code.

What about user uploaded files?

Let's say that besides having static files like your logo and CSS / JavaScript bundles you also have files uploaded by users. This could be things like a user avatar, blog post images or anything else.

You would still want to md5 tag and compress these files but now we've run into a situation. The flask digest compile command is meant to be run at deploy time and it could potentially be run from your dev box, inside of a Docker image, on a CI server or your production server. In these cases you wouldn't have access to the user uploaded files.

But at the same time you have users uploading files at run time. They are changing all the time.

Needless to say you can't use the flask digest compile command to digest user uploaded files. The cache_manifest.json file should be reserved for files that exist in your code repo (such as your CSS / JS bundles, maybe a logo, fonts, etc.).

The above files do not change at run time and align well with running the flask digest compile command at deploy time.

For user uploaded content you wouldn't ever write these entries to the manifest JSON file. Instead, you would typically upload your files to disk, S3 or somewhere else and then save the file name of the file you uploaded into your local database.

So now when you reference a user uploaded file (let's say an avatar), you would loop over your users from the database and reference the file name from the DB.

There's no need for a manifest file to store the user uploaded files because the database has a reference to the real name and then you are dynamically referencing that in your template helper (static_url_for), so it's never a hard coded thing that changes at the template level.

What's cool about this is you already did the database query to retrieve the record(s) from the database, so there's no extra database work to do. All you have to do is reference the file name field that's a part of your model.

But that doesn't fully solve the problem. You'll still want to md5 tag and compress your user uploaded content at run time and you would want to do this before you save the uploaded file into its final destination (local file system, S3, etc.).

This can be done completely separate from this extension and it's really going to vary depending on where you host your user uploaded content. For example some CDNs will automatically create compressed files for you and they use things like an ETag header in the response to include a unique file name (and this is what you can store in your DB).

So maybe md5 hashing and maybe compressing your user uploaded content becomes an app specific responsibility, although I'm not opposed to maybe creating helper functions you can use but that would need to be thought out carefully.

However the implementation is not bad. It's really only about 5 lines of code to do both things. Feel free to CTRL + F around the code base for hashlib, gzip and brotli and you'll find the related code.

So with that said, here's a work flow you can do to deal with this today:

  • User uploads file
  • Your Flask app potentially md5 tags / gzips the file if necessary
  • Your Flask app saves the file name + compressed file to its final destination (local file system, S3, etc.)
  • Your Flask app saves the final unique file name to your database

That final unique file name would be the md5 tagged version of the file that you created or the unique file name that your CDN returned back to you. I hope that clears up how to deal with user uploaded files and efficiently serving them!

About the author

If you're interested in learning Flask I have a 17+ hour video course called Build a SAAS App with Flask. It's a course where we build a real world SAAS app. Everything about the course and demo videos of what we build is on the site linked above.

flask-static-digest's People

Contributors

mattaw avatar nickjj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

flask-static-digest's Issues

has not working

i have code like this:

<script src="{{ static_url_for('static', filename='loader/loader.js') }}"></script>

But in html page showing default name wizhout md5 name, how can i fix it?

image

Blacklist Filter Array - Default Setting

Hey all,

When I installed Flask-Static-Digest the flask digest clean command worked perfectly and it found my static folder. However, when trying flask digest compile I ran into this error (keep in mind I'm on Windows):

File c:\users\maxde\desktop\mycodingfiles\environments\mflask\lib\site-packages\flask_static_digest\digester.py", line 95, in _is_compiled_file
    or file_extension in digest_blacklist_filter
TypeError: argument of type 'NoneType' is not iterable

However I was able to fix this by specifying the blacklist array in my app.py in the code block below. I took a peek at the __init__.py file and it seems like it should be defaulting to an empty array, but for some reason it wasn't the case for me. Just want to help out future devs who run into the same issue!

app.config['FLASK_STATIC_DIGEST_BLACKLIST_FILTER'] = []

Btw thanks Nick for this awesome repo!

-Max

There is no possibility to customize path to static_folder.

I have "bundle" and "user_files" subdirectories in my flask "static_folder" diectory.

The "bundle" subdirectory contains the static content of the site (js, css, images), compiled using webpack.

The "user_files" subdirectory contains files uploaded by users, such as avatars and illustrations for blog posts.

I'd like the flask-static-digest extension to only apply to the "static_folder/bundle" subdirectory, not "static_folder/user_files".
At the moment, I don't see a way to set up flask_static_digest like this.

Option to allow specify output folder for digested files?

Hello

the digested files have been placing in the same folder as the original files.
In case I use CDN, It's supposed that I only need to upload all digested files to my CDN server. as I don't want my original files also being upload to the CDN server.

Is there an option to allow to specify the output folder for digested files?
(Imagine if static files are in many different folders, It could be quite a task...)

It dosen't work in windows.

I found that this extension does not work properly under Windows. After reading the source code, I found out that the filename saved in cache_manifest.json is 'xx\xx', but the filename passed under windows is 'xx/xx', in cache_manifest.json. I can't find the corresponding value, so I submitted the PR and added support for windows.

Getting Permission Error on flask digest compile (Ubuntu 20.4)

Hey Nick, I just moved my dev environment to Ubuntu 20.4 from Mac and I'm not able to used flask digest compile from a the app's docker container as usual.

This is the traceback I'm seeing:

Traceback (most recent call last):
File "/usr/local/bin/flask", line 8, in
sys.exit(main())
File "/usr/local/lib/python3.8/site-packages/flask/cli.py", line 966, in main
cli.main(prog_name="python -m flask" if as_module else None)
File "/usr/local/lib/python3.8/site-packages/flask/cli.py", line 586, in main
return super(FlaskGroup, self).main(*args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/usr/local/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/usr/local/lib/python3.8/site-packages/click/core.py", line 1259, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "/usr/local/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/usr/local/lib/python3.8/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/click/decorators.py", line 21, in new_func
return f(get_current_context(), *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/flask/cli.py", line 426, in decorator
return __ctx.invoke(f, *args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/usr/local/lib/python3.8/site-packages/flask_static_digest/cli.py", line 20, in compile
_compile(current_app.static_folder,
File "/usr/local/lib/python3.8/site-packages/flask_static_digest/digester.py", line 35, in compile
manifest = _generate_manifest(files, gzip_files, output_path)
File "/usr/local/lib/python3.8/site-packages/flask_static_digest/digester.py", line 113, in _generate_manifest
_write_to_disk(file, digested_file_path, gzip_files, output_path)
File "/usr/local/lib/python3.8/site-packages/flask_static_digest/digester.py", line 141, in _write_to_disk
shutil.copy2(file, full_digested_file_path)
File "/usr/local/lib/python3.8/shutil.py", line 426, in copy2
copyfile(src, dst, follow_symlinks=follow_symlinks)
File "/usr/local/lib/python3.8/shutil.py", line 259, in copyfile
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
PermissionError: [Errno 13] Permission denied: '/app/APP_NAME/../public/js/app-350139f5b599102893ae61f5dbd798f3.js'

For the look of it, the container doesn't have proper permission to modify files on my host, but this should be managed automatically by Docker, shouldn't it?

As I mentioned above, same code works perfectly on Mac, but is giving me this permission error on Ubuntu 20.4.

Thanks in advance Nick!

Cannot read .env variable due to Flask static digest

Hi Nick,

I'm using your Build A SaaS app with Flask framework. The course was great.
I build my own API app on top of it. I started adding extra environment parameters to the .env file and to the settings.py. I can build the web docker container when Flask is in 'development'. However when I set Flask to 'production' than I get the following error:

 ---> 374360990a86
Step 13/15 : RUN if [ "${FLASK_ENV}" != "development" ]; then   flask digest compile; fi
 ---> Running in 69f4c0d52b38
 * Tip: There are .env or .flaskenv files present. Do "pip install python-dotenv" to use them.
Traceback (most recent call last):
  File "/usr/local/bin/flask", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 966, in main
    cli.main(prog_name="python -m flask" if as_module else None)
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 586, in main
    return super(FlaskGroup, self).main(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/click/decorators.py", line 21, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 425, in decorator
    with __ctx.ensure_object(ScriptInfo).load_app().app_context():
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 388, in load_app
    app = locate_app(self, import_name, name)
  File "/usr/local/lib/python3.7/site-packages/flask/cli.py", line 240, in locate_app
    __import__(module_name)
  File "/app/snakeeyes/app.py", line 23, in <module>
    from snakeeyes.blueprints.admin import admin
  File "/app/snakeeyes/blueprints/admin/__init__.py", line 1, in <module>
    from snakeeyes.blueprints.admin.views import admin
  File "/app/snakeeyes/blueprints/admin/views.py", line 11, in <module>
    from snakeeyes.blueprints.admin.models import Dashboard
  File "/app/snakeeyes/blueprints/admin/models.py", line 3, in <module>
    from snakeeyes.blueprints.user.models import db, User
  File "/app/snakeeyes/blueprints/user/__init__.py", line 1, in <module>
    from snakeeyes.blueprints.user.views import user
  File "/app/snakeeyes/blueprints/user/views.py", line 18, in <module>
    from snakeeyes.blueprints.user.models import User
  File "/app/snakeeyes/blueprints/user/models.py", line 17, in <module>
    from snakeeyes.blueprints.billing.models.credit_card import CreditCard
  File "/app/snakeeyes/blueprints/billing/__init__.py", line 1, in <module>
    from snakeeyes.blueprints.billing.views.billing import billing
  File "/app/snakeeyes/blueprints/billing/views/billing.py", line 14, in <module>
    from config import settings
  File "/app/config/settings.py", line 193, in <module>
    'MONEYBIRD_TAX_RATE_ID_NO_VAT', None))
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
ERROR: Service 'web' failed to build: The command '/bin/sh -c if [ "${FLASK_ENV}" != "development" ]; then   flask digest compile; fi' returned a non-zero code: 1

If I comment out the code below in the dockerfile, it builds again in production:

RUN if [ "${FLASK_ENV}" != "development" ]; then \
  flask digest compile; fi

Do you have any suggestions?

Version:
Flask-Static-Digest==0.1.2

Update python_requires to >=3.6

First off, thank you for this extension. It seems almost perfect for the project I'm working on.

Unfortunately, I need to support Python 3.5 at a minimum for the time being, and Flask-Static-Digest fails to start due to the use of formatted string literals in digester.py, as these were introduced in 3.6. Everything else works without any problem I can see.

I can maintain a private fork until the systems I support are upgraded, but I would ask you to update your python_requires so no one else is affected.

README suggestion: how to use in Heroku

Hello! I had a hard time googling how to do static asset compilation in Heroku, but eventually found that there's a hook program that gets run after all of the packages are installed. I created bin/post_compile with these contents:

#!/usr/bin/env bash
set -e

cd "$1"  # the build dir
echo "-----> Compiling static assets"
flask digest compile

And that runs the compilation on Heroku. It's helpful because Heroku deploys with a git push, so without it you'd have to add the compiled files to git, which would be annoying. Not sure if you'll feel it's worth cluttering your README with this, but thought I would suggest it.

Also, thanks for this package! Very easy to integrate, works very well so far. Awesome!

References: https://github.com/heroku/heroku-buildpack-python/blob/main/bin/steps/hooks/post_compile https://vitorbaptista.com/how-to-compile-sass-scss-files-when-deploying-django-to-heroku

Asset server (webpack-dev-server)

I started working on a flask project using https://github.com/cookiecutter-flask/cookiecutter-flask as a template.

I see that Flask-Webpack is now deprecated in favour of this project. Can you give me guidance how can I achieve the same thing with this project?

Webpack already creates properly hashed files I'd like to serve them, and during development I'd like to serve them from the asset webpack server.

stylesheet_tag in the original project created the proper url.

Thank you!

I think hash is long, can i go i short hash

now , i got a filename

dist/js/main-d41d8cd98f00b204e9800998ecf8427e.js

but too long filename is not good for looked.

i want to got a short hash, just like:

dist/js/main-d41d8cd9.js

static_url_for returning 500

Hi Nick,

On using static_url_for for images, sometimes if an image is not available, it results in a 500 error and the entire page errors out.

For now we have resorted to quickly changing them on the fly to url_for until the developer hunts for the image.

Is there anything we can do such that static_url_for could return, for example, a 404 instead of 500 response, as this would help the page load without the image and with an alt-text at least.

Regards,
Mitalee

Flask Static Digest + Create React App

Hi,
I'm trying to configure Create React App to work with Flask and stumbled upon this package.

Can you give any hints on how to configure this plugin to consume already compiled manifest? Or maybe you can recommend another approach.

Thanks!

The result of npm build command of CRA looks like this

├── asset-manifest.json
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── precache-manifest.b8aaeab7f259072963f46661b376837a.js
├── robots.txt
├── service-worker.js
└── static
    ├── css
    │   ├── main.87c0ef5b.chunk.css
    │   └── main.87c0ef5b.chunk.css.map
    ├── js
    │   ├── 2.856bb494.chunk.js
    │   ├── 2.856bb494.chunk.js.LICENSE.txt
    │   ├── 2.856bb494.chunk.js.map
    │   ├── main.f1dc1e54.chunk.js
    │   ├── main.f1dc1e54.chunk.js.map
    │   ├── runtime-main.a026a1da.js
    │   └── runtime-main.a026a1da.js.map
    └── media
        └── logo.8dad8028.svg

and asset-manifest.json looks like this

{
  "files": {
    "main.css": "/static/css/main.87c0ef5b.chunk.css",
    "main.js": "/static/js/main.f1dc1e54.chunk.js",
    "main.js.map": "/static/js/main.f1dc1e54.chunk.js.map",
    "runtime-main.js": "/static/js/runtime-main.a026a1da.js",
    "runtime-main.js.map": "/static/js/runtime-main.a026a1da.js.map",
    "static/js/2.856bb494.chunk.js": "/static/js/2.856bb494.chunk.js",
    "static/js/2.856bb494.chunk.js.map": "/static/js/2.856bb494.chunk.js.map",
    "index.html": "/index.html",
    "precache-manifest.b8aaeab7f259072963f46661b376837a.js": "/precache-manifest.b8aaeab7f259072963f46661b376837a.js",
    "service-worker.js": "/service-worker.js",
    "static/css/main.87c0ef5b.chunk.css.map": "/static/css/main.87c0ef5b.chunk.css.map",
    "static/js/2.856bb494.chunk.js.LICENSE.txt": "/static/js/2.856bb494.chunk.js.LICENSE.txt",
    "static/media/logo.svg": "/static/media/logo.8dad8028.svg"
  },
  "entrypoints": [
    "static/js/runtime-main.a026a1da.js",
    "static/js/2.856bb494.chunk.js",
    "static/css/main.87c0ef5b.chunk.css",
    "static/js/main.f1dc1e54.chunk.js"
  ]
}

Working with Blueprints

Hey, thanks for this extensions. I wonder how I would work with many blueprints. I tried some things but was no able to make it work

Basically I have an app like that:

└── flaskr
├── app.py
├── my_blueprint
│   ├── blueprint.py (this is where my blueprint its configured and with the routes passing the view_func)
│   └── views.py (with the handler functions that call render_template)
├── static
│   └── my_blueprint
│   ├── css
│   │   └── stylesheet.css
│   ├── images
│   │   └── image.png
│   └── js
│   └── main.js
└── templates
└── my_blueprint
└── index.html

With this organization I can have many blueprint and each blueprint with its own templates and static folder (similar to what Django does). Do you have any example like that? thanks a lot!!

Statics no in FLASK_STATIC_DIGEST_HOST_URL root

I have noted that FLASK_STATIC_DIGEST_HOST_URL only work's if the statics are put on the root of the CDN, for example in a conventional/normal app, with the statics in the static folder, when i put FLASK_STATIC_DIGEST_HOST_URL='https://assets.example.com/myapp/static/' an URL for static/myimg.png will be https://assets.example.com/static/myimg.png and not https://assets.example.com/myapp/static/myimg.png as intended. So no sharing?

I ask in the event that ones needs to share the same CDN host for several app's. I know that the file names will include the hashes and that if two files conflict it means it is the same file, but i wold like to maintain the separation. Any thoughts ?

a idea: dev envirienment with webpack

# -*- coding: utf-8 -*-
from urllib.parse import urljoin

from flask_static_digest import FlaskStaticDigest


class WebpackFlaskStaticDigest(FlaskStaticDigest):
    """
    FLASK_WEBPACK_DEV_HOST
    """

    def init_app(self, app):
        self.webpack_dev_host = app.config.get("FLASK_WEBPACK_DEV_HOST")
        super().init_app(app)

    def static_url_for(self, endpoint, **values):
        if self.app.debug and self.webpack_dev_host:
            return self._prepend_webpack_dev_host_url(values.get('filename'))

        return super().static_url_for(endpoint, **values)

    def _prepend_webpack_dev_host_url(self, filename):
        return urljoin(self.webpack_dev_host, filename)

now, i can config FLASK_WEBPACK_DEV_HOST in dev env.

app.config['FLASK_WEBPACK_DEV_HOST'] = 'http://localhost:8383'
app.debug = True

use webpack, i can use:

  1. sass to css
  2. js module
  3. live hot
    ...

i think, this a good a idea.

Adding a collect command to copy static assets (including from nested blueprints) to a target dir

[Briefly discussed here :https://github.com//pull/35#issuecomment-1546711780]

My workflow is:

  1. webpack to compile and bundle js and css (Tailwind) into my flask app's static folder.
  2. flask digest compile to create md5 tagged and compressed versions

In production the static assets are served by Apache / Nginx. However blueprints and nested blueprints present an annoyance, as their static assets (and their md5 + compressed counterparts) are scattered in directories through my app's source tree.

I would like to reproduce the directory tree from the Flask app source tree with only the static assets and their md5 + compressed versions in it to a target directory of my choice so I can simplify the configuration of Apache/Nginx down to serving one directory tree vs. one per blueprint. This may involve some complexity or similar to create a single manifest.

Proposed Enhancements

Hi, @nickjj:

Wondering if you might be able to take a look at cookiecutter-flask/cookiecutter-flask#630. The user who submitted has a few issues that are specific to how he uses Webpack, but I think there are a few broader ideas in there for potential enhancements to flask-static-digest.

In particular:

  • Ability to serve the tagged & gzipped files separately from the main flask app
  • Ability to auto-reload cache_manifest.json (I haven't validated, but it sounds like the app may need to be restarted after running flask digest compile for the changes to be discovered)

What are your thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.