From 681e5e4ffaf192fd4fd381ac24aaf6008815e137 Mon Sep 17 00:00:00 2001 From: Alvin Duran Date: Thu, 19 Jan 2023 16:33:09 -0400 Subject: [PATCH 1/3] Compatibility issues Removing nose dependency Making access to _request_ctx_stack conditional --- .gitignore | 3 +++ .idea/flask-assets.iml | 4 ++-- requirements-dev.pip | 7 +++--- src/flask_assets.py | 36 ++++++++++++++++++++-------- tests/test_config.py | 22 ++++++++++-------- tests/test_env.py | 3 +-- tests/test_integration.py | 49 ++++++++++++++++++++++++++------------- tests/test_script.py | 9 ++++--- tox.ini | 10 ++++---- 9 files changed, 91 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index eb9ee8c..ebcd75c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ /build/ /dist/ /src/Flask_Assets.egg-info/ +venv +tests/static/out +tests/static/.webassets-cache \ No newline at end of file diff --git a/.idea/flask-assets.iml b/.idea/flask-assets.iml index 31840df..11ff9e0 100644 --- a/.idea/flask-assets.iml +++ b/.idea/flask-assets.iml @@ -6,9 +6,9 @@ + - - + \ No newline at end of file diff --git a/requirements-dev.pip b/requirements-dev.pip index 75a296f..72e54fb 100644 --- a/requirements-dev.pip +++ b/requirements-dev.pip @@ -1,5 +1,6 @@ -nose +packaging Flask-Script>=0.3.3 -webassets==0.11.1 +webassets~=2.0 PyYAML -pyScss>=1.1.5 +pyScss>=1.4.0 +pytest diff --git a/src/flask_assets.py b/src/flask_assets.py index 957ab9d..10338c4 100644 --- a/src/flask_assets.py +++ b/src/flask_assets.py @@ -5,7 +5,9 @@ import logging from os import path -from flask import _request_ctx_stack, current_app +from packaging import version + +import flask from flask.templating import render_template_string # We want to expose Bundle via this module. from webassets import Bundle @@ -270,9 +272,17 @@ def convert_item_to_flask_url(self, ctx, item, filepath=None): filename = rel_path flask_ctx = None - if not _request_ctx_stack.top: - flask_ctx = ctx.environment._app.test_request_context() - flask_ctx.push() + if version.parse(flask.__version__) < version.parse("2.2.0"): + if not flask._request_ctx_stack.top: + flask_ctx = ctx.environment._app.test_request_context() + flask_ctx.push() + else: + try: + flask.globals.request_ctx.request + except RuntimeError: + flask_ctx = ctx.environment._app.test_request_context() + flask_ctx.push() + try: url = url_for(endpoint, filename=filename) # In some cases, url will be an absolute url with a scheme and hostname. @@ -314,13 +324,20 @@ def _app(self): if self.app is not None: return self.app - ctx = _request_ctx_stack.top + if version.parse(flask.__version__) < version.parse("2.2.0"): + ctx = flask._request_ctx_stack.top + else: + ctx = flask.globals.request_ctx + if ctx is not None: return ctx.app try: - from flask import _app_ctx_stack - app_ctx = _app_ctx_stack.top + if version.parse(flask.__version__) < version.parse("2.2.0"): + from flask import _app_ctx_stack + app_ctx = _app_ctx_stack.top + else: + app_ctx = flask.globals.app_ctx if app_ctx is not None: return app_ctx.app except ImportError: @@ -329,8 +346,6 @@ def _app(self): raise RuntimeError('assets instance not bound to an application, '+ 'and no application in current context') - - # XXX: This is required because in a couple of places, webassets 0.6 # still access env.directory, at one point even directly. We need to # fix this for 0.6 compatibility, but it might be preferable to @@ -338,6 +353,7 @@ def _app(self): # like the cache directory and output files. def set_directory(self, directory): self.config['directory'] = directory + def get_directory(self): if self.config.get('directory') is not None: return self.config['directory'] @@ -475,7 +491,7 @@ def _webassets_cmd(cmd): logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) cmdenv = CommandLineEnvironment( - current_app.jinja_env.assets_environment, logger + flask.current_app.jinja_env.assets_environment, logger ) getattr(cmdenv, cmd)() diff --git a/tests/test_config.py b/tests/test_config.py index fad37b5..6121a20 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,12 +2,11 @@ """ from __future__ import absolute_import -from tests.helpers import check_warnings -from nose.tools import assert_raises +import pytest + from flask import Flask from flask_assets import Environment -from webassets.exceptions import ImminentDeprecationWarning try: from webassets.updater import BaseUpdater @@ -24,7 +23,7 @@ class TestConfigAppBound: """The extension is bound to a specific app. """ - def setup(self): + def setup_method(self): self.app = Flask(__name__) self.env = Environment(self.app) @@ -68,13 +67,14 @@ class TestConfigNoAppBound: """The application is not bound to a specific app. """ - def setup(self): + def setup_method(self): self.env = Environment() def test_no_app_available(self): """Without an application bound, we can't do much.""" - assert_raises(RuntimeError, setattr, self.env, 'debug', True) - assert_raises(RuntimeError, self.env.config.get, 'debug') + with pytest.raises(RuntimeError): + setattr(self.env, 'debug', True) + self.env.config.get('debug') def test_global_defaults(self): """We may set defaults even without an application, however.""" @@ -89,7 +89,8 @@ def test_multiple_separate_apps(self): self.env.init_app(app1) # With no app yet available... - assert_raises(RuntimeError, getattr, self.env, 'url') + with pytest.raises(RuntimeError): + getattr(self.env, 'url') # ...set a default self.env.config.setdefault('FOO', 'BAR') @@ -109,6 +110,7 @@ def test_key_error(self): """KeyError is raised if a config value doesn't exist. """ with Flask(__name__).test_request_context(): - assert_raises(KeyError, self.env.config.__getitem__, 'YADDAYADDA') + with pytest.raises(KeyError): + self.env.config.__getitem__('YADDAYADDA') # The get() helper, on the other hand, simply returns None - assert self.env.config.get('YADDAYADDA') == None + assert self.env.config.get('YADDAYADDA') is None diff --git a/tests/test_env.py b/tests/test_env.py index 066f703..d428934 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -1,12 +1,11 @@ import os -from nose.tools import assert_raises from flask import Flask from flask_assets import Environment, Bundle class TestEnv: - def setup(self): + def setup_method(self): self.app = Flask(__name__) self.env = Environment(self.app) self.env.debug = True diff --git a/tests/test_integration.py b/tests/test_integration.py index d995e75..6b27423 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,11 +1,14 @@ from __future__ import absolute_import -from nose.tools import assert_raises + +import flask +import pytest from flask import Flask from flask_assets import Environment, Bundle from webassets.bundle import get_all_bundle_files from tests.helpers import TempEnvironmentHelper, Module, Blueprint +from packaging import version def test_import(): # We want to expose these via the assets extension module. @@ -25,10 +28,17 @@ class TestUrlAndDirectory(TempEnvironmentHelper): Let's test the different scenarios to ensure everything works. """ - def setup(self): + def setup_method(self): TempEnvironmentHelper.setup(self) - self.app = Flask(__name__, static_path='/app_static') + kwargs = {} + if int(flask.__version__.split('.')[0]) < 1: + kwargs['static_path'] = '/app_static' + else: + kwargs['static_url_path'] = '/app_static' + + self.app = Flask(__name__, **kwargs) + from tests import test_module if not Blueprint: self.module = Module(test_module.__name__, name='module', @@ -42,10 +52,11 @@ def setup(self): self.env = Environment(self.app) def test_config_values_not_set_by_default(self): - assert not 'directory' in self.env.config - assert not 'url' in self.env.config - assert_raises(KeyError, self.env.config.__getitem__, 'directory') - assert_raises(KeyError, self.env.config.__getitem__, 'url') + assert 'directory' not in self.env.config + assert 'url' not in self.env.config + with pytest.raises(KeyError): + self.env.config.__getitem__('directory') + self.env.config.__getitem__('url') def test_directory_auto(self): """Test how we resolve file references through the Flask static @@ -65,15 +76,15 @@ def test_directory_auto(self): assert get_all_bundle_files(Bundle('./module/bar'), self.env) == [root + '/static/module/bar'] # Custom static folder - self.app.static_folder = '/' - assert get_all_bundle_files(Bundle('foo'), self.env) == ['/foo'] + self.app.static_folder = '/bar' + assert get_all_bundle_files(Bundle('foo'), self.env)[0].endswith('/bar/foo') def test_url_auto(self): """Test how urls are generated via the Flask static system by default (if no custom 'env.url' etc. values have been configured manually). """ - assert not 'url' in self.env.config + assert 'url' not in self.env.config assert Bundle('foo', env=self.env).urls() == ['/app_static/foo'] # Urls for files that point to a module use that module's url prefix. @@ -83,8 +94,14 @@ def test_url_auto(self): # [Regression] Ensure that any request context we may have added # to the stack has been removed. - from flask import _request_ctx_stack - assert _request_ctx_stack.top is None + + if version.parse(flask.__version__) < version.parse("2.2.0"): + assert not flask._request_ctx_stack.top + else: + from flask.globals import request_ctx + + with pytest.raises(RuntimeError): + assert not request_ctx.request def test_custom_load_path(self): """A custom load_path is configured - this will affect how @@ -96,7 +113,6 @@ def test_custom_load_path(self): # We do not recognize references to modules. assert get_all_bundle_files(Bundle('module/bar'), self.env) == [self.path('module/bar')] - assert Bundle('foo', env=self.env).urls() == ['/custom/foo'] assert Bundle('module/bar', env=self.env).urls() == ['/custom/module/bar'] @@ -149,8 +165,8 @@ class TestUrlAndDirectoryWithInitApp(object): values also work if the application is initialized via "init_app()". """ - def setup(self): - self.app = Flask(__name__, static_path='/initapp_static') + def setup_method(self): + self.app = Flask(__name__, static_url_path='/initapp_static') self.env = Environment() self.env.init_app(self.app) @@ -233,7 +249,8 @@ def test_blueprint_urls(self): def test_blueprint_no_static_folder(self): """Test dealing with a blueprint without a static folder.""" self.make_blueprint('module') - assert_raises(TypeError, self.mkbundle('module/foo').urls) + with pytest.raises(TypeError): + self.mkbundle('module/foo').urls() def test_cssrewrite(self): """Make sure cssrewrite works with Blueprints. diff --git a/tests/test_script.py b/tests/test_script.py index 38660ff..de8e237 100644 --- a/tests/test_script.py +++ b/tests/test_script.py @@ -1,22 +1,21 @@ from __future__ import absolute_import -from nose import SkipTest +import pytest # check for flask-script before importing things that fail if it's not present try: from flask_script import Manager except: - raise SkipTest() + pytest.skip(allow_module_level=True) import sys -from flask import Flask -from flask_assets import Environment, ManageAssets +from flask_assets import ManageAssets from webassets.script import GenericArgparseImplementation from tests.helpers import TempEnvironmentHelper # Flask-script seemingly no longer supports 2.6 if sys.version_info[:2] == (2, 6): - raise SkipTest() + pytest.skip(allow_module_level=True) # The CLI likes to log to stderr, which isn't nice to the test output. diff --git a/tox.ini b/tox.ini index 1d385d5..14263a3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,9 @@ [tox] -envlist = py26, py27, py33, pypy +envlist = py39, py310, pypy [testenv] -commands = nosetests tests -deps = - -r{toxinidir}/requirements-dev.pip +commands = + pytest -W ignore::DeprecationWarning +deps = + pytest + -r {toxinidir}/requirements-dev.pip From e58f08f84dcb6ab9234206f0678917a45ac94e4f Mon Sep 17 00:00:00 2001 From: Alvin Duran Date: Fri, 20 Jan 2023 16:38:53 -0400 Subject: [PATCH 2/3] Changing condition logic for creating flask context --- src/flask_assets.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/flask_assets.py b/src/flask_assets.py index 10338c4..f8383ca 100644 --- a/src/flask_assets.py +++ b/src/flask_assets.py @@ -272,16 +272,19 @@ def convert_item_to_flask_url(self, ctx, item, filepath=None): filename = rel_path flask_ctx = None + context_missing = False if version.parse(flask.__version__) < version.parse("2.2.0"): if not flask._request_ctx_stack.top: - flask_ctx = ctx.environment._app.test_request_context() - flask_ctx.push() + context_missing = True else: try: flask.globals.request_ctx.request except RuntimeError: - flask_ctx = ctx.environment._app.test_request_context() - flask_ctx.push() + context_missing = True + + if context_missing: + flask_ctx = ctx.environment._app.test_request_context() + flask_ctx.push() try: url = url_for(endpoint, filename=filename) From adf21b3b9676da3d1264661ee611c935dd04e021 Mon Sep 17 00:00:00 2001 From: Alvin Duran Date: Fri, 20 Jan 2023 17:05:44 -0400 Subject: [PATCH 3/3] Improving handling error with request context --- src/flask_assets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/flask_assets.py b/src/flask_assets.py index f8383ca..0e8c0a1 100644 --- a/src/flask_assets.py +++ b/src/flask_assets.py @@ -279,7 +279,9 @@ def convert_item_to_flask_url(self, ctx, item, filepath=None): else: try: flask.globals.request_ctx.request - except RuntimeError: + except RuntimeError as rte: + if not str(rte).startswith('Working outside of request context'): + raise context_missing = True if context_missing: