eschutho commented on code in PR #44281:
URL: https://github.com/apache/superset/pull/44281#discussion_r4289386696


##########
superset/db_engine_specs/mssql.py:
##########
@@ -49,7 +55,7 @@
 )
 
 
-class MssqlEngineSpec(BaseEngineSpec):
+class MssqlEngineSpec(BasicParametersMixin, BaseEngineSpec):

Review Comment:
   Resolved by keeping Azure Synapse on the SQLAlchemy URI box (option (b) from 
@rusackas's question), in f057a678f3:
   
   - `AzureSynapseSpec.parameters_json_schema()` returns `None`, and 
`/api/v1/database/available/` only adds `parameters` (i.e. offers the dynamic 
form) when the spec returns a schema. Synapse therefore always gets the raw URI 
box, whichever MSSQL drivers are installed, and the form can no longer produce 
a driverless `mssql+pyodbc://` URI.
   - `SybaseEngineSpec` (also a `MssqlEngineSpec` subclass, connecting through 
an ODBC DSN) opts out the same way.
   - The SQL Server form only builds `mssql+pymssql://` URIs and is only 
offered when pymssql is installed.
   
   I didn't try to make the builder emit `driver=`/`odbc_connect`: a usable 
Synapse connection also needs the ODBC driver name, 
`Encrypt`/`TrustServerCertificate`, and often an `Authentication` mode, which 
the form doesn't capture. That can come later with Synapse-specific fields.
   
   Covered by `test_mssql_subclasses_stay_on_uri_form`, 
`test_available_keeps_azure_synapse_on_uri_form` (pyodbc-only and both drivers) 
and the frontend test `Azure Synapse opens on the SQLAlchemy URI form`.
   



##########
tests/unit_tests/db_engine_specs/test_init.py:
##########
@@ -392,3 +392,107 @@ def test_get_available_engine_specs_with_denylist(mocker: 
MockerFixture) -> None
     )
     available = get_available_engine_specs()
     assert list(available.keys()) == [DatabricksNativeEngineSpec]
+
+
+class _MissingDBAPIDialect(DefaultDialect):
+    """A native dialect whose DBAPI is not installed."""
+
+    driver = "pyodbc"
+
+    @classmethod
+    def import_dbapi(cls) -> object:
+        raise ModuleNotFoundError("No module named 'pyodbc'")
+
+
+def test_get_available_engine_specs_detects_non_default_native_driver(
+    mocker: MockerFixture,
+) -> None:
+    """
+    With only pymssql installed, MSSQL is available with the pymssql driver 
even
+    though SQLAlchemy's default ``mssql`` dialect (pyodbc) is missing.
+    """
+    import sqlalchemy.dialects
+    from sqlalchemy.dialects.mssql.pymssql import MSDialect_pymssql
+
+    from superset.db_engine_specs.mssql import AzureSynapseSpec, 
MssqlEngineSpec
+
+    mocker.patch.object(sqlalchemy.dialects, "__all__", ["mssql"])
+    mocker.patch.object(
+        sqlalchemy.dialects.registry, "load", return_value=_MissingDBAPIDialect
+    )
+    mocker.patch.object(MSDialect_pymssql, "import_dbapi", 
return_value=object())
+    mocker.patch(
+        "superset.db_engine_specs.load_engine_specs",
+        return_value=iter([MssqlEngineSpec, AzureSynapseSpec]),
+    )
+    mocker.patch("superset.db_engine_specs.entry_points", return_value=[])
+
+    available = get_available_engine_specs()
+
+    assert available[MssqlEngineSpec] == {"pymssql"}
+    assert available[AzureSynapseSpec] == {"pymssql"}
+
+
+def test_get_available_engine_specs_non_default_native_driver_not_installed(
+    mocker: MockerFixture,
+) -> None:
+    """
+    MSSQL has no drivers when neither pyodbc nor pymssql is installed.
+    """
+    import sqlalchemy.dialects
+    from sqlalchemy.dialects.mssql.pymssql import MSDialect_pymssql
+
+    from superset.db_engine_specs.mssql import MssqlEngineSpec
+
+    mocker.patch.object(sqlalchemy.dialects, "__all__", ["mssql"])
+    mocker.patch.object(
+        sqlalchemy.dialects.registry, "load", return_value=_MissingDBAPIDialect
+    )
+    mocker.patch.object(
+        MSDialect_pymssql,
+        "import_dbapi",
+        side_effect=ModuleNotFoundError("No module named 'pymssql'"),
+    )
+    mocker.patch(
+        "superset.db_engine_specs.load_engine_specs",
+        return_value=iter([MssqlEngineSpec]),
+    )
+    mocker.patch("superset.db_engine_specs.entry_points", return_value=[])
+
+    assert get_available_engine_specs()[MssqlEngineSpec] == set()
+
+
+def test_get_available_engine_specs_only_probes_native_backends(
+    mocker: MockerFixture,
+) -> None:
+    """
+    The ``default_driver`` probe is limited to SQLAlchemy's bundled dialects:
+    specs whose default driver was already detected, or whose backend is not a
+    native SQLAlchemy dialect, are not probed.
+    """
+    import sqlalchemy.dialects
+
+    from superset.db_engine_specs.mysql import MySQLEngineSpec
+    from superset.db_engine_specs.snowflake import SnowflakeEngineSpec
+
+    class MySQLDialect(DefaultDialect):
+        driver = "mysqldb"
+
+        @classmethod
+        def import_dbapi(cls) -> object:
+            return object()
+
+    mocker.patch.object(sqlalchemy.dialects, "__all__", ["mysql"])
+    mocker.patch.object(sqlalchemy.dialects.registry, "load", 
return_value=MySQLDialect)
+    mocker.patch(
+        "superset.db_engine_specs.load_engine_specs",
+        return_value=iter([MySQLEngineSpec, SnowflakeEngineSpec]),
+    )
+    mocker.patch("superset.db_engine_specs.entry_points", return_value=[])
+    find_spec = mocker.patch("superset.db_engine_specs.find_spec")

Review Comment:
   Fixed in 8888033d81: the mock is now `find_spec: MagicMock = 
mocker.patch("superset.db_engine_specs.find_spec")`, with `MagicMock` imported 
from `unittest.mock`.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to