diff --git a/requirements/base.txt b/requirements/base.txt index ca143b20e627..e504429fc5e6 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -35,7 +35,7 @@ cffi==1.15.1 # via # cryptography # pynacl -click==8.0.4 +click==8.1.3 # via # apache-superset # celery @@ -70,7 +70,7 @@ dnspython==2.1.0 # via email-validator email-validator==1.1.3 # via flask-appbuilder -flask==2.1.3 +flask==2.2.5 # via # apache-superset # flask-appbuilder @@ -134,11 +134,11 @@ importlib-resources==5.12.0 # via limits isodate==0.6.0 # via apache-superset -itsdangerous==2.1.1 +itsdangerous==2.1.2 # via # flask # flask-wtf -jinja2==3.0.3 +jinja2==3.1.2 # via # flask # flask-babel @@ -158,6 +158,7 @@ markupsafe==2.1.1 # via # jinja2 # mako + # werkzeug # wtforms marshmallow==3.13.0 # via @@ -295,8 +296,9 @@ wcwidth==0.2.5 # via prompt-toolkit webencodings==0.5.1 # via bleach -werkzeug==2.1.2 +werkzeug==2.3.3 # via + # apache-superset # flask # flask-jwt-extended # flask-login diff --git a/requirements/docker.txt b/requirements/docker.txt index 0338f43fd8f8..1b122d50fc13 100644 --- a/requirements/docker.txt +++ b/requirements/docker.txt @@ -12,8 +12,10 @@ # -r requirements/docker.in gevent==21.8.0 # via -r requirements/docker.in -greenlet==1.1.3.post0 - # via gevent +greenlet==2.0.2 + # via + # -r requirements/docker.in + # gevent psycopg2-binary==2.9.5 # via apache-superset zope-event==4.5.0 diff --git a/requirements/integration.txt b/requirements/integration.txt index a0243d6d0dd0..7da1e7432aca 100644 --- a/requirements/integration.txt +++ b/requirements/integration.txt @@ -9,7 +9,7 @@ build==0.8.0 # via pip-tools cfgv==3.3.0 # via pre-commit -click==8.0.4 +click==8.1.3 # via # pip-compile-multi # pip-tools diff --git a/setup.py b/setup.py index 7124e70f75d7..d7d028ba9323 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ def get_git_sha() -> str: "cron-descriptor", "cryptography>=39.0.0,<40", "deprecation>=2.1.0, <2.2.0", - "flask>=2.1.3, <2.2", + "flask>=2.2.5, <3.0.0", "flask-appbuilder>=4.3.0, <5.0.0", "flask-caching>=1.10.1, <1.11", "flask-compress>=1.13, <2.0", @@ -123,6 +123,7 @@ def get_git_sha() -> str: "tabulate>=0.8.9, <0.9", "typing-extensions>=4, <5", "waitress; sys_platform == 'win32'", + "werkzeug>=2.3.3, <3", "wtforms>=2.3.3, <4", "wtforms-json", "xlsxwriter>=3.0.7, <3.1", diff --git a/tests/integration_tests/async_events/api_tests.py b/tests/integration_tests/async_events/api_tests.py index a63f540dd0f8..5c12b29af49c 100644 --- a/tests/integration_tests/async_events/api_tests.py +++ b/tests/integration_tests/async_events/api_tests.py @@ -33,6 +33,7 @@ def fetch_events(self, last_id: Optional[str] = None): @mock.patch("uuid.uuid4", return_value=UUID) def test_events(self, mock_uuid4): + app._got_first_request = False async_query_manager.init_app(app) self.login(username="admin") with mock.patch.object(async_query_manager._redis, "xrange") as mock_xrange: @@ -46,6 +47,7 @@ def test_events(self, mock_uuid4): @mock.patch("uuid.uuid4", return_value=UUID) def test_events_last_id(self, mock_uuid4): + app._got_first_request = False async_query_manager.init_app(app) self.login(username="admin") with mock.patch.object(async_query_manager._redis, "xrange") as mock_xrange: @@ -59,6 +61,7 @@ def test_events_last_id(self, mock_uuid4): @mock.patch("uuid.uuid4", return_value=UUID) def test_events_results(self, mock_uuid4): + app._got_first_request = False async_query_manager.init_app(app) self.login(username="admin") with mock.patch.object(async_query_manager._redis, "xrange") as mock_xrange: @@ -107,6 +110,7 @@ def test_events_results(self, mock_uuid4): self.assertEqual(response, expected) def test_events_no_login(self): + app._got_first_request = False async_query_manager.init_app(app) rv = self.fetch_events() assert rv.status_code == 401 diff --git a/tests/integration_tests/charts/data/api_tests.py b/tests/integration_tests/charts/data/api_tests.py index 83fb7281fbc7..891578a3f8d8 100644 --- a/tests/integration_tests/charts/data/api_tests.py +++ b/tests/integration_tests/charts/data/api_tests.py @@ -28,10 +28,7 @@ from flask import Response from tests.integration_tests.conftest import with_feature_flags from superset.models.sql_lab import Query -from tests.integration_tests.base_tests import ( - SupersetTestCase, - test_client, -) +from tests.integration_tests.base_tests import SupersetTestCase, test_client from tests.integration_tests.annotation_layers.fixtures import create_annotation_layers from tests.integration_tests.fixtures.birth_names_dashboard import ( load_birth_names_dashboard_with_slices, @@ -602,6 +599,7 @@ def test_when_where_parameter_is_template_and_query_result_type__query_is_templa @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_chart_data_async(self): self.logout() + app._got_first_request = False async_query_manager.init_app(app) self.login("admin") rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data") @@ -619,6 +617,7 @@ def test_chart_data_async_cached_sync_response(self): Chart data API: Test chart data query returns results synchronously when results are already cached. """ + app._got_first_request = False async_query_manager.init_app(app) class QueryContext: @@ -648,6 +647,7 @@ def test_chart_data_async_results_type(self): """ Chart data API: Test chart data query non-JSON format (async) """ + app._got_first_request = False async_query_manager.init_app(app) self.query_context_payload["result_type"] = "results" rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data") @@ -659,6 +659,7 @@ def test_chart_data_async_invalid_token(self): """ Chart data API: Test chart data query (async) """ + app._got_first_request = False async_query_manager.init_app(app) test_client.set_cookie( "localhost", app.config["GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME"], "foo" @@ -974,6 +975,7 @@ def test_chart_data_cache(self, cache_loader): """ Chart data cache API: Test chart data async cache request """ + app._got_first_request = False async_query_manager.init_app(app) cache_loader.load.return_value = self.query_context_payload orig_run = ChartDataCommand.run @@ -1000,6 +1002,7 @@ def test_chart_data_cache_run_failed(self, cache_loader): """ Chart data cache API: Test chart data async cache request with run failure """ + app._got_first_request = False async_query_manager.init_app(app) cache_loader.load.return_value = self.query_context_payload rv = self.get_assert_metric( @@ -1017,8 +1020,9 @@ def test_chart_data_cache_no_login(self, cache_loader): """ Chart data cache API: Test chart data async cache request (no login) """ - self.logout() + app._got_first_request = False async_query_manager.init_app(app) + self.logout() cache_loader.load.return_value = self.query_context_payload orig_run = ChartDataCommand.run @@ -1039,6 +1043,7 @@ def test_chart_data_cache_key_error(self): """ Chart data cache API: Test chart data async cache request with invalid cache key """ + app._got_first_request = False async_query_manager.init_app(app) rv = self.get_assert_metric( f"{CHART_DATA_URI}/test-cache-key", "data_from_cache" @@ -1156,10 +1161,10 @@ def test_data_cache_default_timeout( def test_chart_cache_timeout( + load_energy_table_with_slice: List[Slice], test_client, login_as_admin, physical_query_context, - load_energy_table_with_slice: List[Slice], ): # should override datasource cache timeout @@ -1178,7 +1183,6 @@ def test_chart_cache_timeout( db.session.commit() physical_query_context["form_data"] = {"slice_id": slice_with_cache_timeout.id} - rv = test_client.post(CHART_DATA_URI, json=physical_query_context) assert rv.json["result"][0]["cache_timeout"] == 20 diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py index f036f18bf6aa..2e9e28762041 100644 --- a/tests/integration_tests/core_tests.py +++ b/tests/integration_tests/core_tests.py @@ -1088,6 +1088,7 @@ def test_explore_json_async(self): "groupby": ["gender"], "row_limit": 100, } + app._got_first_request = False async_query_manager.init_app(app) self.login(username="admin") rv = self.client.post( @@ -1119,6 +1120,7 @@ def test_explore_json_async_results_format(self): "groupby": ["gender"], "row_limit": 100, } + app._got_first_request = False async_query_manager.init_app(app) self.login(username="admin") rv = self.client.post( diff --git a/tests/integration_tests/dashboards/permalink/api_tests.py b/tests/integration_tests/dashboards/permalink/api_tests.py index 40a312ef855a..ad19d13cd43c 100644 --- a/tests/integration_tests/dashboards/permalink/api_tests.py +++ b/tests/integration_tests/dashboards/permalink/api_tests.py @@ -66,7 +66,7 @@ def permalink_salt() -> Iterator[str]: def test_post( - test_client, login_as_admin, dashboard_id: int, permalink_salt: str + dashboard_id: int, permalink_salt: str, test_client, login_as_admin ) -> None: resp = test_client.post(f"api/v1/dashboard/{dashboard_id}/permalink", json=STATE) assert resp.status_code == 201 @@ -93,14 +93,14 @@ def test_post_access_denied(test_client, login_as, dashboard_id: int): assert resp.status_code == 404 -def test_post_invalid_schema(test_client, login_as_admin, dashboard_id: int): +def test_post_invalid_schema(dashboard_id: int, test_client, login_as_admin): resp = test_client.post( f"api/v1/dashboard/{dashboard_id}/permalink", json={"foo": "bar"} ) assert resp.status_code == 400 -def test_get(test_client, login_as_admin, dashboard_id: int, permalink_salt: str): +def test_get(dashboard_id: int, permalink_salt: str, test_client, login_as_admin): key = test_client.post( f"api/v1/dashboard/{dashboard_id}/permalink", json=STATE ).json["key"] diff --git a/tests/integration_tests/datasource_tests.py b/tests/integration_tests/datasource_tests.py index 52bd9ec244cc..2e42c32c8b19 100644 --- a/tests/integration_tests/datasource_tests.py +++ b/tests/integration_tests/datasource_tests.py @@ -543,7 +543,7 @@ def test_get_samples_with_filters(test_client, login_as_admin, virtual_dataset): f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table" ) rv = test_client.post(uri, json=None) - assert rv.status_code == 400 + assert rv.status_code == 415 rv = test_client.post(uri, json={}) assert rv.status_code == 200 diff --git a/tests/integration_tests/db_engine_specs/base_engine_spec_tests.py b/tests/integration_tests/db_engine_specs/base_engine_spec_tests.py index 6048ac8f1996..2650ca1b6d94 100644 --- a/tests/integration_tests/db_engine_specs/base_engine_spec_tests.py +++ b/tests/integration_tests/db_engine_specs/base_engine_spec_tests.py @@ -447,17 +447,19 @@ def test_validate_parameters_missing(): "query": {}, } } - errors = BasicParametersMixin.validate_parameters(properties) - assert errors == [ - SupersetError( - message=( - "One or more parameters are missing: " "database, host, port, username" + with app.app_context(): + errors = BasicParametersMixin.validate_parameters(properties) + assert errors == [ + SupersetError( + message=( + "One or more parameters are missing: " + "database, host, port, username" + ), + error_type=SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR, + level=ErrorLevel.WARNING, + extra={"missing": ["database", "host", "port", "username"]}, ), - error_type=SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR, - level=ErrorLevel.WARNING, - extra={"missing": ["database", "host", "port", "username"]}, - ), - ] + ] @mock.patch("superset.db_engine_specs.base.is_hostname_valid") @@ -474,21 +476,22 @@ def test_validate_parameters_invalid_host(is_hostname_valid): "query": {"sslmode": "verify-full"}, } } - errors = BasicParametersMixin.validate_parameters(properties) - assert errors == [ - SupersetError( - message="One or more parameters are missing: port", - error_type=SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR, - level=ErrorLevel.WARNING, - extra={"missing": ["port"]}, - ), - SupersetError( - message="The hostname provided can't be resolved.", - error_type=SupersetErrorType.CONNECTION_INVALID_HOSTNAME_ERROR, - level=ErrorLevel.ERROR, - extra={"invalid": ["host"]}, - ), - ] + with app.app_context(): + errors = BasicParametersMixin.validate_parameters(properties) + assert errors == [ + SupersetError( + message="One or more parameters are missing: port", + error_type=SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR, + level=ErrorLevel.WARNING, + extra={"missing": ["port"]}, + ), + SupersetError( + message="The hostname provided can't be resolved.", + error_type=SupersetErrorType.CONNECTION_INVALID_HOSTNAME_ERROR, + level=ErrorLevel.ERROR, + extra={"invalid": ["host"]}, + ), + ] @mock.patch("superset.db_engine_specs.base.is_hostname_valid") @@ -507,17 +510,18 @@ def test_validate_parameters_port_closed(is_port_open, is_hostname_valid): "query": {"sslmode": "verify-full"}, } } - errors = BasicParametersMixin.validate_parameters(properties) - assert errors == [ - SupersetError( - message="The port is closed.", - error_type=SupersetErrorType.CONNECTION_PORT_CLOSED_ERROR, - level=ErrorLevel.ERROR, - extra={ - "invalid": ["port"], - "issue_codes": [ - {"code": 1008, "message": "Issue 1008 - The port is closed."} - ], - }, - ) - ] + with app.app_context(): + errors = BasicParametersMixin.validate_parameters(properties) + assert errors == [ + SupersetError( + message="The port is closed.", + error_type=SupersetErrorType.CONNECTION_PORT_CLOSED_ERROR, + level=ErrorLevel.ERROR, + extra={ + "invalid": ["port"], + "issue_codes": [ + {"code": 1008, "message": "Issue 1008 - The port is closed."} + ], + }, + ) + ] diff --git a/tests/integration_tests/explore/permalink/api_tests.py b/tests/integration_tests/explore/permalink/api_tests.py index 3a07bd977af3..b9b1bfd0fbcd 100644 --- a/tests/integration_tests/explore/permalink/api_tests.py +++ b/tests/integration_tests/explore/permalink/api_tests.py @@ -68,7 +68,7 @@ def permalink_salt() -> Iterator[str]: def test_post( - test_client, login_as_admin, form_data: Dict[str, Any], permalink_salt: str + form_data: Dict[str, Any], permalink_salt: str, test_client, login_as_admin ): resp = test_client.post(f"api/v1/explore/permalink", json={"formData": form_data}) assert resp.status_code == 201 @@ -81,14 +81,14 @@ def test_post( db.session.commit() -def test_post_access_denied(test_client, login_as, form_data): +def test_post_access_denied(form_data, test_client, login_as): login_as("gamma") resp = test_client.post(f"api/v1/explore/permalink", json={"formData": form_data}) assert resp.status_code == 403 def test_get_missing_chart( - test_client, login_as_admin, chart, permalink_salt: str + chart, permalink_salt: str, test_client, login_as_admin ) -> None: from superset.key_value.models import KeyValueEntry @@ -125,7 +125,7 @@ def test_post_invalid_schema(test_client, login_as_admin) -> None: def test_get( - test_client, login_as_admin, form_data: Dict[str, Any], permalink_salt: str + form_data: Dict[str, Any], permalink_salt: str, test_client, login_as_admin ) -> None: resp = test_client.post(f"api/v1/explore/permalink", json={"formData": form_data}) data = json.loads(resp.data.decode("utf-8")) diff --git a/tests/integration_tests/reports/commands_tests.py b/tests/integration_tests/reports/commands_tests.py index 8d6a76c14f67..a6720d9c46ea 100644 --- a/tests/integration_tests/reports/commands_tests.py +++ b/tests/integration_tests/reports/commands_tests.py @@ -663,7 +663,7 @@ def test_email_chart_report_schedule( ) # assert that the link sent is correct assert ( - 'Explore in Superset' in email_mock.call_args[0][2] ) @@ -718,7 +718,7 @@ def _screenshot_side_effect(user: User) -> Optional[bytes]: # assert that the link sent is correct assert ( - 'Explore in Superset' in email_mock.call_args[0][2] ) @@ -763,7 +763,7 @@ def test_email_chart_report_schedule_force_screenshot( ) # assert that the link sent is correct assert ( - 'Explore in Superset' in email_mock.call_args[0][2] ) @@ -800,7 +800,7 @@ def test_email_chart_alert_schedule( notification_targets = get_target_from_report_schedule(create_alert_email_chart) # assert that the link sent is correct assert ( - 'Explore in Superset' in email_mock.call_args[0][2] ) @@ -872,7 +872,7 @@ def test_email_chart_report_schedule_with_csv( ) # assert that the link sent is correct assert ( - 'Explore in Superset' in email_mock.call_args[0][2] ) @@ -1303,7 +1303,7 @@ def test_slack_chart_report_schedule_with_text( | 1 | c21 | c22 | c23 |""" assert table_markdown in post_message_mock.call_args[1]["text"] assert ( - f"" + f"" in post_message_mock.call_args[1]["text"] ) diff --git a/tests/integration_tests/security/analytics_db_safety_tests.py b/tests/integration_tests/security/analytics_db_safety_tests.py index 7e36268e3028..9c40050c0ad9 100644 --- a/tests/integration_tests/security/analytics_db_safety_tests.py +++ b/tests/integration_tests/security/analytics_db_safety_tests.py @@ -21,6 +21,7 @@ from superset.exceptions import SupersetSecurityException from superset.security.analytics_db_safety import check_sqlalchemy_uri +from tests.integration_tests.test_app import app @pytest.mark.parametrize( @@ -83,9 +84,10 @@ def test_check_sqlalchemy_uri( sqlalchemy_uri: str, error: bool, error_message: Optional[str] ): - if error: - with pytest.raises(SupersetSecurityException) as excinfo: + with app.app_context(): + if error: + with pytest.raises(SupersetSecurityException) as excinfo: + check_sqlalchemy_uri(make_url(sqlalchemy_uri)) + assert str(excinfo.value) == error_message + else: check_sqlalchemy_uri(make_url(sqlalchemy_uri)) - assert str(excinfo.value) == error_message - else: - check_sqlalchemy_uri(make_url(sqlalchemy_uri)) diff --git a/tests/integration_tests/tasks/async_queries_tests.py b/tests/integration_tests/tasks/async_queries_tests.py index 20d0f39eea0f..6beda4a2248e 100644 --- a/tests/integration_tests/tasks/async_queries_tests.py +++ b/tests/integration_tests/tasks/async_queries_tests.py @@ -46,6 +46,7 @@ class TestAsyncQueries(SupersetTestCase): @mock.patch.object(async_query_manager, "update_job") @mock.patch.object(async_queries, "set_form_data") def test_load_chart_data_into_cache(self, mock_set_form_data, mock_update_job): + app._got_first_request = False async_query_manager.init_app(app) query_context = get_query_context("birth_names") user = security_manager.find_user("gamma") @@ -68,6 +69,7 @@ def test_load_chart_data_into_cache(self, mock_set_form_data, mock_update_job): ) @mock.patch.object(async_query_manager, "update_job") def test_load_chart_data_into_cache_error(self, mock_update_job, mock_run_command): + app._got_first_request = False async_query_manager.init_app(app) query_context = get_query_context("birth_names") user = security_manager.find_user("gamma") @@ -90,6 +92,7 @@ def test_load_chart_data_into_cache_error(self, mock_update_job, mock_run_comman def test_soft_timeout_load_chart_data_into_cache( self, mock_update_job, mock_run_command ): + app._got_first_request = False async_query_manager.init_app(app) user = security_manager.find_user("gamma") form_data = {} @@ -114,6 +117,7 @@ def test_soft_timeout_load_chart_data_into_cache( @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") @mock.patch.object(async_query_manager, "update_job") def test_load_explore_json_into_cache(self, mock_update_job): + app._got_first_request = False async_query_manager.init_app(app) table = self.get_table(name="birth_names") user = security_manager.find_user("gamma") @@ -145,6 +149,7 @@ def test_load_explore_json_into_cache(self, mock_update_job): def test_load_explore_json_into_cache_error( self, mock_set_form_data, mock_update_job ): + app._got_first_request = False async_query_manager.init_app(app) user = security_manager.find_user("gamma") form_data = {} @@ -168,6 +173,7 @@ def test_load_explore_json_into_cache_error( def test_soft_timeout_load_explore_json_into_cache( self, mock_update_job, mock_run_command ): + app._got_first_request = False async_query_manager.init_app(app) user = security_manager.find_user("gamma") form_data = {} diff --git a/tests/integration_tests/utils/core_tests.py b/tests/integration_tests/utils/core_tests.py index 29b94d6d37ee..1a2fa6a5218c 100644 --- a/tests/integration_tests/utils/core_tests.py +++ b/tests/integration_tests/utils/core_tests.py @@ -17,6 +17,7 @@ import pytest from superset.utils.core import form_data_to_adhoc, simple_filter_to_adhoc +from tests.integration_tests.test_app import app def test_simple_filter_to_adhoc_generates_deterministic_values(): @@ -81,4 +82,5 @@ def test_form_data_to_adhoc_incorrect_clause_type(): form_data = {"where": "1 = 1", "having": "count(*) > 1"} with pytest.raises(ValueError): - form_data_to_adhoc(form_data, "foobar") + with app.app_context(): + form_data_to_adhoc(form_data, "foobar")