0

I am having an issue where a Flask app using flask_assets can't find lessc after a modify a certain .less file, and even changing it back to the original does not help.

I have been trying to use this tutorial to learn about using Blueprints to organize a Flask application: https://hackersandslackers.com/flask-blueprints/

app structure

├── application
│   ├── assets.py
│   ├── home
│   │   ├── home.py
│   │   ├── __pycache__
│   │   │   └── home.cpython-38.pyc
│   │   ├── static
│   │   │   └── less
│   │   │       ├── home.less
│   │   │       └── variables.less
│   │   └── templates
│   │       └── index.jinja2
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── assets.cpython-38.pyc
│   │   └── __init__.cpython-38.pyc
│   ├── static
│   │   ├── dist
│   │   │   └── css
│   │   │       ├── home.css
│   │   │       ├── products.css
│   │   │       ├── profile.css
│   │   │       └── style.css
│   │   ├── img
│   │   │   ├── avatar.png
│   │   │   ├── favicon.png
│   │   │   └── logo.png
│   │   └── src
│   │       └── less
│   │           ├── nav.less
│   │           ├── style.less
│   │           └── variables.less
│   └── templates
│       ├── analytics.jinja2
│       ├── layout.jinja2
│       └── navigation.jinja2
└── wsgi.py

init.py

"""Initialize Flask app."""
from flask import Flask
from flask_assets import Environment


def create_app():
    """Create Flask application."""
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object('config.Config')
    assets = Environment()
    assets.init_app(app)

    with app.app_context():
        # Import parts of our application
        from .home import home
        from .assets import compile_static_assets

        # Register Blueprints
        app.register_blueprint(home.home_bp)

        # Compile static assets
        compile_static_assets(assets)

        return app
        ```



        assets.py
        ```
        from flask import current_app as app
from flask_assets import Bundle


def compile_static_assets(assets):
    assets.auto_build = True
    assets.debug = False
    common_less_bundle = Bundle('src/less/*.less',
                                filters='less,cssmin',
                                output='dist/css/style.css',
                                extra={'rel': 'stylesheet/less'})
    home_less_bundle = Bundle('home_bp/less/home.less',
                              filters='less,cssmin',
                              output='dist/css/home.css',
                              extra={'rel': 'stylesheet/less'})

    assets.register('common_less_bundle', common_less_bundle)
    assets.register('home_less_bundle', home_less_bundle)
    if app.config['ENV'] == 'development':  # Only rebuild bundles in development
        common_less_bundle.build()
        home_less_bundle.build()
    return assets

home.py

"""General page routes."""
from flask import Blueprint, render_template
from flask import current_app as app

# Blueprint Configuration
home_bp = Blueprint('home_bp', __name__,
                    template_folder='templates',
                    static_folder='static')


@home_bp.route('/', methods=['GET'])
def home():
    """Homepage."""
    products ="example"
    return render_template('index.jinja2',
                           title='Flask Blueprint Demo',
                           subtitle='Demonstration of Flask blueprints in action.',
                           template='home-template',
                           products=products)

and the problem file, home.less

@import 'variables.less';

.home-template {

  &.page {
    .products {
      display: none !important;
    }
  }

  .container {
    .resource-links {
      margin-top: 10px;

      .resource-link {
        margin: 4px 0;
        color: #8a91a7;
        line-height: 1;
      }
    }

    .products {
      display: flex;
      justify-content: space-between;
      flex-wrap: wrap;
      margin-top: 30px;

      .product-preview {
        padding: 2%;
        box-shadow: 0 0 5px rgba(65, 67, 144, 0.15);
        max-width: 28%;
        width: 30%;
        background: white;
        margin-bottom: 20px;
        display: block;
        text-align: center;
        transition: @transition;

        &:hover {
          background: #5dbad7;
          color: white !important;
          opacity: 1;

          * {
            color: white !important;
          }
        }

        .product-image {
          height: 110px;
          margin: 0 auto 20px;
          display: block;
          @media (max-width: 600px) {
            width: 90%;
            height: unset;
          }
        }

        .name {
          -webkit-line-clamp: 2;
          -webkit-box-orient: vertical;
          display: -webkit-box;
          width: -webkit-fill-available;
          width: -moz-available;
          width: stretch;
          overflow: hidden;
          text-overflow: ellipsis;
          font-weight: 500;
          font-size: .9em;
          height: 42px;
        }

        .price {
          color: #5f6988;
          margin-top: 5px;
          font-size: 1.1em;
          font-weight: 600;
        }
      }
    }
  }

  h2 {
    margin-bottom: 0 !important;
  }
}

When home.less is freshly pasted from the git download to my simplified version, the app runs as expected.

$ python3 wsgi.py
 * Serving Flask app "application" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5001/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 213-702-582
127.0.0.1 - - [21/May/2020 14:26:01] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [21/May/2020 14:26:02] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [21/May/2020 14:26:02] "GET /static/dist/css/home.css?99706b81 HTTP/1.1" 304 -
127.0.0.1 - - [21/May/2020 14:26:02] "GET /static/img/logo.png HTTP/1.1" 304 -
127.0.0.1 - - [21/May/2020 14:26:02] "GET /favicon.ico HTTP/1.1" 404 -

Now, what really wierds me out, is that if I make any modification to home.less, even just adding a space, saving, removing the space, and then saving again, the less filter cannot be found:

 * Serving Flask app "application" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:5001/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 213-702-582
127.0.0.1 - - [21/May/2020 14:28:09] "GET / HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/__init__.py", line 510, in subprocess
    proc = subprocess.Popen(
  File "/usr/lib/python3.8/subprocess.py", line 854, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.8/subprocess.py", line 1702, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'lessc'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/home/home.py", line 15, in home
    return render_template('index.jinja2',
  File "/usr/lib/python3.8/site-packages/flask/templating.py", line 137, in render_template
    return _render(
  File "/usr/lib/python3.8/site-packages/flask/templating.py", line 120, in _render
    rv = template.render(context)
  File "/usr/lib/python3.8/site-packages/jinja2/environment.py", line 1090, in render
    self.environment.handle_exception()
  File "/usr/lib/python3.8/site-packages/jinja2/environment.py", line 832, in handle_exception
    reraise(*rewrite_traceback_stack(source=source))
  File "/usr/lib/python3.8/site-packages/jinja2/_compat.py", line 28, in reraise
    raise value.with_traceback(tb)
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/home/templates/index.jinja2", line 1, in top-level template code
    {% extends "layout.jinja2" %}
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/templates/layout.jinja2", line 9, in top-level template code
    {% block pagestyles %}{% endblock %}
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/home/templates/index.jinja2", line 4, in block "pagestyles"
    {% assets "home_less_bundle" %}
  File "/home/james/.local/lib/python3.8/site-packages/webassets/ext/jinja2.py", line 187, in _render_assets
    urls = bundle.urls(calculate_sri=True)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 833, in urls
    urls.extend(bundle._urls(new_ctx, extra_filters, *args, **kwargs))
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 767, in _urls
    self._build(ctx, extra_filters=extra_filters, force=False,
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 618, in _build
    hunk = self._merge_and_apply(
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 543, in _merge_and_apply
    hunk = filtertool.apply(hunk, filters_to_run, 'input',
  File "/home/james/.local/lib/python3.8/site-packages/webassets/merge.py", line 280, in apply
    return self._wrap_cache(key, func)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/merge.py", line 222, in _wrap_cache
    content = func().getvalue()
  File "/home/james/.local/lib/python3.8/site-packages/webassets/merge.py", line 255, in func
    getattr(filter, type)(data, out, **kwargs_final)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/less.py", line 139, in input
    self._apply_less(_in, out, source_path)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/less.py", line 131, in _apply_less
    self.subprocess(args, out, in_)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/__init__.py", line 520, in subprocess
    raise FilterError('Program file not found: %s.' % argv[0])
webassets.exceptions.FilterError: Program file not found: lessc.
127.0.0.1 - - [21/May/2020 14:28:10] "GET / HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/__init__.py", line 510, in subprocess
    proc = subprocess.Popen(
  File "/usr/lib/python3.8/subprocess.py", line 854, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.8/subprocess.py", line 1702, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'lessc'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/home/home.py", line 15, in home
    return render_template('index.jinja2',
  File "/usr/lib/python3.8/site-packages/flask/templating.py", line 137, in render_template
    return _render(
  File "/usr/lib/python3.8/site-packages/flask/templating.py", line 120, in _render
    rv = template.render(context)
  File "/usr/lib/python3.8/site-packages/jinja2/environment.py", line 1090, in render
    self.environment.handle_exception()
  File "/usr/lib/python3.8/site-packages/jinja2/environment.py", line 832, in handle_exception
    reraise(*rewrite_traceback_stack(source=source))
  File "/usr/lib/python3.8/site-packages/jinja2/_compat.py", line 28, in reraise
    raise value.with_traceback(tb)
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/home/templates/index.jinja2", line 1, in top-level template code
    {% extends "layout.jinja2" %}
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/templates/layout.jinja2", line 9, in top-level template code
    {% block pagestyles %}{% endblock %}
  File "/home/james/Downloads/flask-blueprint-tutorial-master_reduced/application/home/templates/index.jinja2", line 4, in block "pagestyles"
    {% assets "home_less_bundle" %}
  File "/home/james/.local/lib/python3.8/site-packages/webassets/ext/jinja2.py", line 187, in _render_assets
    urls = bundle.urls(calculate_sri=True)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 833, in urls
    urls.extend(bundle._urls(new_ctx, extra_filters, *args, **kwargs))
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 767, in _urls
    self._build(ctx, extra_filters=extra_filters, force=False,
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 618, in _build
    hunk = self._merge_and_apply(
  File "/home/james/.local/lib/python3.8/site-packages/webassets/bundle.py", line 543, in _merge_and_apply
    hunk = filtertool.apply(hunk, filters_to_run, 'input',
  File "/home/james/.local/lib/python3.8/site-packages/webassets/merge.py", line 280, in apply
    return self._wrap_cache(key, func)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/merge.py", line 222, in _wrap_cache
    content = func().getvalue()
  File "/home/james/.local/lib/python3.8/site-packages/webassets/merge.py", line 255, in func
    getattr(filter, type)(data, out, **kwargs_final)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/less.py", line 139, in input
    self._apply_less(_in, out, source_path)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/less.py", line 131, in _apply_less
    self.subprocess(args, out, in_)
  File "/home/james/.local/lib/python3.8/site-packages/webassets/filter/__init__.py", line 520, in subprocess
    raise FilterError('Program file not found: %s.' % argv[0])
webassets.exceptions.FilterError: Program file not found: lessc.

What could be going on here? At first I thought it might be a cache issue, but deleting the __pycache__ did not help.

Stonecraft
  • 860
  • 1
  • 12
  • 30
  • you need to install lessc so you can compile your less files it looks like – Joran Beasley May 21 '20 at 18:51
  • Then why would it have worked the first time around? Why would adding and then removing a space from the .less file result in lessc not being found (or why did it work without lessc before)? – Stonecraft May 21 '20 at 18:52
  • I dont have any of those answers... maybe initially it used a different method to build less -> css, or maybe initially the css is really there(but your ide or something is hiding it and showing you the less file only) ... are you using babel to translate less to css directly in the html? or are you including css files? but the error message clearly states it cannot find lessc (which it uses to compile less -> css) – Joran Beasley May 21 '20 at 18:59
  • 1
    I would recommend reading the docs for https://webassets.readthedocs.io/en/latest/ as that looks like whats trying to compile the less files – Joran Beasley May 21 '20 at 19:02
  • Thanks. I am just including css files. Do you know how I can get the app to print the path it is looking for lessc on each time it is called? – Stonecraft May 21 '20 at 20:19
  • its looking in `%PATH%` or `$PATH` (depending on os) you can probably do `npm install -g lessc` – Joran Beasley May 21 '20 at 20:20
  • 1
    But that shouldn't change no matter what I do to with the python and css files, right? I cannot understand why re-saving a file would make the difference between lessc being found or not. Is it possible that in the case where it works, lessc isn't even being called? – Stonecraft May 21 '20 at 20:23

1 Answers1

0

OK, well, I found a solution, but I really don't understand why it works.

If I change assets.py to explicitly create the environment, trivial modifications to home.less no longer result in the inability to find lessc.

New assets.py

from flask import Flask, current_app as app
from flask_assets import Environment, Bundle


def compile_static_assets(assets):
    app = Flask(__name__)
    assets = Environment(app)
    assets.auto_build = True
    assets.debug = False
    common_less_bundle = Bundle('src/less/*.less',
                                filters='less,cssmin',
                                output='dist/css/style.css',
                                extra={'rel': 'stylesheet/less'})
    home_less_bundle = Bundle('home_bp/less/home.less',
                              filters='less,cssmin',
                              output='dist/css/home.css',
                              extra={'rel': 'stylesheet/less'})

    assets.register('common_less_bundle', common_less_bundle)
    assets.register('home_less_bundle', home_less_bundle)
    if app.config['ENV'] == 'development':  # Only rebuild bundles in development
        common_less_bundle.build()
        home_less_bundle.build()
    return assets

It makes sense to me that instantiating the environment with the right packages loaded is necessary for the less filter to work. But I am still mystified by the fact that this only became a problem after adding (and then removing) a single space from less.css.

I think I must not understand the pipeline of how flask_assets applies filters. I've taken a gander at the docs, but it is not obvious to me.

Stonecraft
  • 860
  • 1
  • 12
  • 30