Spaces:
Paused
Paused
Download tests/unit_tests/extensions/test_sqlalchemy.py from Leon4gr45/powerbi: direct link, hf CLI and curl.
- Browser
- Download file 13.3 kB
-
https://huggingface.co/spaces/Leon4gr45/powerbi/resolve/main/tests/unit_tests/extensions/test_sqlalchemy.py
- Command line
-
hf download hf://spaces/Leon4gr45/powerbi/tests/unit_tests/extensions/test_sqlalchemy.py
-
curl -L -o test_sqlalchemy.py https://huggingface.co/spaces/Leon4gr45/powerbi/resolve/main/tests/unit_tests/extensions/test_sqlalchemy.py
13.3 kB
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| # pylint: disable=redefined-outer-name, import-outside-toplevel, unused-argument | |
| import os | |
| import re | |
| from collections.abc import Iterator | |
| from typing import TYPE_CHECKING | |
| import pytest | |
| from pytest_mock import MockerFixture | |
| from sqlalchemy import text | |
| from sqlalchemy.engine import create_engine | |
| from sqlalchemy.exc import ProgrammingError | |
| from sqlalchemy.orm.session import Session | |
| from superset import db | |
| from superset.errors import ErrorLevel, SupersetError, SupersetErrorType | |
| from superset.exceptions import SupersetSecurityException | |
| from tests.conftest import with_config | |
| from tests.unit_tests.conftest import with_feature_flags | |
| if TYPE_CHECKING: | |
| from superset.models.core import Database | |
| def _normalize_sqla_doc_link(message: str) -> str: | |
| """ | |
| Replace the SQLAlchemy error-doc URL's version segment (e.g. ``/e/20/``) | |
| with a placeholder so assertions don't need updating every time | |
| SQLAlchemy bumps its minor version. | |
| """ | |
| return re.sub(r"/e/\d+/", "/e/XX/", message) | |
| def database1(session: Session) -> Iterator["Database"]: | |
| from superset.models.core import Database | |
| engine = db.session.connection().engine | |
| Database.metadata.create_all(engine) # pylint: disable=no-member | |
| database = Database( | |
| database_name="database1", | |
| sqlalchemy_uri="sqlite:///database1.db", | |
| allow_dml=True, | |
| ) | |
| db.session.add(database) | |
| db.session.commit() | |
| yield database | |
| db.session.delete(database) | |
| db.session.commit() | |
| if os.path.exists("database1.db"): | |
| os.unlink("database1.db") | |
| def table1(session: Session, database1: "Database") -> Iterator[None]: | |
| with database1.get_sqla_engine() as engine: | |
| with engine.begin() as conn: | |
| conn.execute( | |
| text("CREATE TABLE table1 (a INTEGER NOT NULL PRIMARY KEY, b INTEGER)") | |
| ) | |
| conn.execute(text("INSERT INTO table1 (a, b) VALUES (1, 10), (2, 20)")) | |
| db.session.commit() | |
| yield | |
| with engine.begin() as conn: | |
| conn.execute(text("DROP TABLE table1")) | |
| db.session.commit() | |
| def database2(session: Session) -> Iterator["Database"]: | |
| from superset.models.core import Database | |
| database = Database( | |
| database_name="database2", | |
| sqlalchemy_uri="sqlite:///database2.db", | |
| allow_dml=False, | |
| ) | |
| db.session.add(database) | |
| db.session.commit() | |
| yield database | |
| db.session.delete(database) | |
| db.session.commit() | |
| if os.path.exists("database2.db"): | |
| os.unlink("database2.db") | |
| def table2(session: Session, database2: "Database") -> Iterator[None]: | |
| with database2.get_sqla_engine() as engine: | |
| with engine.begin() as conn: | |
| conn.execute( | |
| text("CREATE TABLE table2 (a INTEGER NOT NULL PRIMARY KEY, b TEXT)") | |
| ) | |
| conn.execute( | |
| text("INSERT INTO table2 (a, b) VALUES (1, 'ten'), (2, 'twenty')") | |
| ) | |
| db.session.commit() | |
| yield | |
| with engine.begin() as conn: | |
| conn.execute(text("DROP TABLE table2")) | |
| db.session.commit() | |
| def test_superset(mocker: MockerFixture, app_context: None, table1: None) -> None: | |
| """ | |
| Simple test querying a table. | |
| """ | |
| # Mock the security_manager.raise_for_access to allow access | |
| mocker.patch( | |
| "superset.extensions.metadb.security_manager.raise_for_access", | |
| return_value=None, | |
| ) | |
| # Mock Flask g.user for security checks | |
| # In Python 3.8+, we can't directly patch flask.g | |
| # Instead, we need to ensure g.user exists in the context | |
| from flask import g | |
| g.user = mocker.MagicMock() | |
| g.user.is_anonymous = False | |
| try: | |
| engine = create_engine("superset://") | |
| except Exception as e: | |
| # Skip test if superset:// dialect can't be loaded (common in Docker) | |
| pytest.skip(f"Superset dialect not available: {e}") | |
| with engine.connect() as conn: | |
| results = conn.execute(text('SELECT * FROM "database1.table1"')) | |
| assert list(results) == [(1, 10), (2, 20)] | |
| def test_superset_limit(mocker: MockerFixture, app_context: None, table1: None) -> None: | |
| """ | |
| Simple that limit is applied when querying a table. | |
| """ | |
| # Note: We don't patch flask.current_app.config directly anymore | |
| # The @with_config decorator handles the config patching | |
| # Mock the security_manager.raise_for_access to allow access | |
| mocker.patch( | |
| "superset.extensions.metadb.security_manager.raise_for_access", | |
| return_value=None, | |
| ) | |
| # Mock Flask g.user for security checks | |
| # In Python 3.8+, we can't directly patch flask.g | |
| # Instead, we need to ensure g.user exists in the context | |
| from flask import g | |
| g.user = mocker.MagicMock() | |
| g.user.is_anonymous = False | |
| try: | |
| engine = create_engine("superset://") | |
| except Exception as e: | |
| # Skip test if superset:// dialect can't be loaded (common in Docker) | |
| pytest.skip(f"Superset dialect not available: {e}") | |
| with engine.connect() as conn: | |
| results = conn.execute(text('SELECT * FROM "database1.table1"')) | |
| assert list(results) == [(1, 10)] | |
| def test_superset_joins( | |
| mocker: MockerFixture, | |
| app_context: None, | |
| table1: None, | |
| table2: None, | |
| ) -> None: | |
| """ | |
| A test joining across databases. | |
| """ | |
| # Mock the security_manager.raise_for_access to allow access | |
| mocker.patch( | |
| "superset.extensions.metadb.security_manager.raise_for_access", | |
| return_value=None, | |
| ) | |
| # Mock Flask g.user for security checks | |
| # In Python 3.8+, we can't directly patch flask.g | |
| # Instead, we need to ensure g.user exists in the context | |
| from flask import g | |
| g.user = mocker.MagicMock() | |
| g.user.is_anonymous = False | |
| try: | |
| engine = create_engine("superset://") | |
| except Exception as e: | |
| # Skip test if superset:// dialect can't be loaded (common in Docker) | |
| pytest.skip(f"Superset dialect not available: {e}") | |
| with engine.connect() as conn: | |
| results = conn.execute( | |
| text(""" | |
| SELECT t1.b, t2.b | |
| FROM "database1.table1" AS t1 | |
| JOIN "database2.table2" AS t2 | |
| ON t1.a = t2.a | |
| """) | |
| ) | |
| assert list(results) == [(10, "ten"), (20, "twenty")] | |
| def test_dml( | |
| mocker: MockerFixture, | |
| app_context: None, | |
| table1: None, | |
| table2: None, | |
| ) -> None: | |
| """ | |
| DML tests. | |
| Test that we can update/delete data, only if DML is enabled. | |
| """ | |
| # Mock the security_manager.raise_for_access to allow access | |
| mocker.patch( | |
| "superset.extensions.metadb.security_manager.raise_for_access", | |
| return_value=None, | |
| ) | |
| # Mock Flask g.user for security checks | |
| # In Python 3.8+, we can't directly patch flask.g | |
| # Instead, we need to ensure g.user exists in the context | |
| from flask import g | |
| g.user = mocker.MagicMock() | |
| g.user.is_anonymous = False | |
| try: | |
| engine = create_engine("superset://") | |
| except Exception as e: | |
| # Skip test if superset:// dialect can't be loaded (common in Docker) | |
| pytest.skip(f"Superset dialect not available: {e}") | |
| with engine.begin() as conn: | |
| conn.execute(text('INSERT INTO "database1.table1" (a, b) VALUES (3, 30)')) | |
| with engine.connect() as conn: | |
| results = conn.execute(text('SELECT * FROM "database1.table1"')) | |
| assert list(results) == [(1, 10), (2, 20), (3, 30)] | |
| with engine.begin() as conn: | |
| conn.execute(text('UPDATE "database1.table1" SET b=35 WHERE a=3')) | |
| with engine.connect() as conn: | |
| results = conn.execute(text('SELECT * FROM "database1.table1"')) | |
| assert list(results) == [(1, 10), (2, 20), (3, 35)] | |
| with engine.begin() as conn: | |
| conn.execute(text('DELETE FROM "database1.table1" WHERE b>20')) | |
| with engine.connect() as conn: | |
| results = conn.execute(text('SELECT * FROM "database1.table1"')) | |
| assert list(results) == [(1, 10), (2, 20)] | |
| with engine.begin() as conn: | |
| with pytest.raises(ProgrammingError) as excinfo: | |
| conn.execute( | |
| text("""INSERT INTO "database2.table2" (a, b) VALUES (3, 'thirty')""") | |
| ) | |
| assert _normalize_sqla_doc_link(str(excinfo.value).strip()) == ( | |
| "(shillelagh.exceptions.ProgrammingError) DML not enabled in database " | |
| '"database2"\n[SQL: INSERT INTO "database2.table2" (a, b) ' | |
| "VALUES (3, 'thirty')]\n(Background on this error at: " | |
| "https://sqlalche.me/e/XX/f405)" | |
| ) | |
| def test_security_manager( | |
| mocker: MockerFixture, app_context: None, table1: None | |
| ) -> None: | |
| """ | |
| Test that we use the security manager to check for permissions. | |
| """ | |
| # Skip this test if metadb dependencies are not available | |
| try: | |
| import superset.extensions.metadb # noqa: F401 | |
| except ImportError: | |
| pytest.skip("metadb dependencies not available") | |
| # Mock Flask g.user first to avoid AttributeError | |
| # We need to mock the actual g object that's imported by security.manager | |
| mock_user = mocker.MagicMock() | |
| mock_user.is_anonymous = False | |
| mocker.patch("superset.security.manager.g", mocker.MagicMock(user=mock_user)) | |
| # Then patch the security_manager to raise an exception | |
| security_manager = mocker.MagicMock() | |
| # Patch it in the metadb module where it's actually used | |
| mocker.patch( | |
| "superset.extensions.metadb.security_manager", | |
| new=security_manager, | |
| ) | |
| security_manager.raise_for_access.side_effect = SupersetSecurityException( | |
| SupersetError( | |
| error_type=SupersetErrorType.TABLE_SECURITY_ACCESS_ERROR, | |
| message=( | |
| "You need access to the following tables: `table1`,\n " | |
| "`all_database_access` or `all_datasource_access` permission" | |
| ), | |
| level=ErrorLevel.ERROR, | |
| ) | |
| ) | |
| try: | |
| engine = create_engine("superset://") | |
| except Exception as e: | |
| # Skip test if superset:// dialect can't be loaded (common in Docker) | |
| pytest.skip(f"Superset dialect not available: {e}") | |
| with engine.connect() as conn: | |
| with pytest.raises(SupersetSecurityException) as excinfo: | |
| conn.execute(text('SELECT * FROM "database1.table1"')) | |
| assert str(excinfo.value) == ( | |
| "You need access to the following tables: `table1`,\n " | |
| "`all_database_access` or `all_datasource_access` permission" | |
| ) | |
| def test_allowed_dbs(mocker: MockerFixture, app_context: None, table1: None) -> None: | |
| """ | |
| Test that DBs can be restricted. | |
| """ | |
| # Mock the security_manager.raise_for_access to allow access | |
| mocker.patch( | |
| "superset.extensions.metadb.security_manager.raise_for_access", | |
| return_value=None, | |
| ) | |
| # Mock Flask g.user for security checks | |
| # In Python 3.8+, we can't directly patch flask.g | |
| # Instead, we need to ensure g.user exists in the context | |
| from flask import g | |
| g.user = mocker.MagicMock() | |
| g.user.is_anonymous = False | |
| try: | |
| engine = create_engine("superset://", allowed_dbs=["database1"]) | |
| except Exception as e: | |
| # Skip test if superset:// dialect can't be loaded (common in Docker) | |
| pytest.skip(f"Superset dialect not available: {e}") | |
| with engine.connect() as conn: | |
| results = conn.execute(text('SELECT * FROM "database1.table1"')) | |
| assert list(results) == [(1, 10), (2, 20)] | |
| with engine.connect() as conn: | |
| with pytest.raises(ProgrammingError) as excinfo: | |
| conn.execute(text('SELECT * FROM "database2.table2"')) | |
| assert _normalize_sqla_doc_link(str(excinfo.value)) == ( | |
| """ | |
| (shillelagh.exceptions.ProgrammingError) Unsupported table: database2.table2 | |
| [SQL: SELECT * FROM "database2.table2"] | |
| (Background on this error at: https://sqlalche.me/e/XX/f405) | |
| """.strip() | |
| ) | |