Any modification of a .less file breaks Flask app, resulting in Program file not found: lessc

427 views Asked by At

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.

1

There are 1 answers

0
Stonecraft On

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.