def build_sqlalchemy_uri( self, data: Dict[str, Any], **kwargs: Any ) -> Dict[str, Any]: """ Build SQLAlchemy URI from separate parameters. This is used for databases that support being configured by individual parameters (eg, username, password, host, etc.), instead of requiring the constructed SQLAlchemy URI to be passed. """ parameters = data.pop("parameters", {}) # TODO (betodealmeida): remove second expression after making sure # frontend is not passing engine inside parameters engine = data.pop("engine", None) or parameters.pop("engine", None) configuration_method = data.get("configuration_method") if configuration_method == ConfigurationMethod.DYNAMIC_FORM: if not engine: raise ValidationError( [ _( "An engine must be specified when passing " "individual parameters to a database." ) ] ) engine_specs = get_engine_specs() if engine not in engine_specs: raise ValidationError( [_('Engine "%(engine)s" is not a valid engine.', engine=engine,)] ) engine_spec = engine_specs[engine] if not hasattr(engine_spec, "build_sqlalchemy_uri") or not hasattr( engine_spec, "parameters_schema" ): raise ValidationError( [ _( 'Engine spec "InvalidEngine" does not support ' "being configured via individual parameters." ) ] ) # validate parameters parameters = engine_spec.parameters_schema.load(parameters) # type: ignore serialized_encrypted_extra = data.get("encrypted_extra", "{}") try: encrypted_extra = json.loads(serialized_encrypted_extra) except json.decoder.JSONDecodeError: encrypted_extra = {} data["sqlalchemy_uri"] = engine_spec.build_sqlalchemy_uri( # type: ignore parameters, encrypted_extra ) return data
def build_sqlalchemy_uri(self, data: Dict[str, Any], **kwargs: Any) -> Dict[str, Any]: """ Build SQLAlchemy URI from separate parameters. This is used for databases that support being configured by individual parameters (eg, username, password, host, etc.), instead of requiring the constructed SQLAlchemy URI to be passed. """ parameters = data.pop("parameters", None) if parameters: if "engine" not in parameters: raise ValidationError([ _("An engine must be specified when passing " "individual parameters to a database.") ]) engine = parameters["engine"] engine_specs = get_engine_specs() if engine not in engine_specs: raise ValidationError([ _( 'Engine "%(engine)s" is not a valid engine.', engine=engine, ) ]) engine_spec = engine_specs[engine] if hasattr(engine_spec, "build_sqlalchemy_uri"): data[ "sqlalchemy_uri"] = engine_spec.build_sqlalchemy_uri( # type: ignore parameters) return data
def get_engine_spec(engine: Optional[str]) -> Type[BaseEngineSpec]: if not engine: raise ValidationError([ _("An engine must be specified when passing " "individual parameters to a database.") ]) engine_specs = get_engine_specs() if engine not in engine_specs: raise ValidationError( [_( 'Engine "%(engine)s" is not a valid engine.', engine=engine, )]) return engine_specs[engine]
def test_engine_time_grain_validity(self): time_grains = set(builtin_time_grains.keys()) # loop over all subclasses of BaseEngineSpec for engine in get_engine_specs().values(): if engine is not BaseEngineSpec: # make sure time grain functions have been defined self.assertGreater(len(engine.get_time_grain_expressions()), 0) # make sure all defined time grains are supported defined_grains = { grain.duration for grain in engine.get_time_grains() } intersection = time_grains.intersection(defined_grains) self.assertSetEqual(defined_grains, intersection, engine)
def get_db_engine_spec_for_backend( cls, backend: str ) -> Type[db_engine_specs.BaseEngineSpec]: engines = db_engine_specs.get_engine_specs() return engines.get(backend, db_engine_specs.BaseEngineSpec)
def test_engine_alias_name(self): """ DB Eng Specs (postgres): Test "postgres" in engine spec """ self.assertIn("postgres", get_engine_specs())
def run(self) -> None: engine = self._properties["engine"] engine_specs = get_engine_specs() if engine in BYPASS_VALIDATION_ENGINES: # Skip engines that are only validated onCreate return if engine not in engine_specs: raise InvalidEngineError( SupersetError( message=__( 'Engine "%(engine)s" is not a valid engine.', engine=engine, ), error_type=SupersetErrorType.GENERIC_DB_ENGINE_ERROR, level=ErrorLevel.ERROR, extra={ "allowed": list(engine_specs), "provided": engine }, ), ) engine_spec = engine_specs[engine] if not issubclass(engine_spec, BasicParametersMixin): raise InvalidEngineError( SupersetError( message=__( 'Engine "%(engine)s" cannot be configured through parameters.', engine=engine, ), error_type=SupersetErrorType.GENERIC_DB_ENGINE_ERROR, level=ErrorLevel.ERROR, extra={ "allowed": [ name for name, engine_spec in engine_specs.items() if issubclass(engine_spec, BasicParametersMixin) ], "provided": engine, }, ), ) # perform initial validation errors = engine_spec.validate_parameters( self._properties.get("parameters", {})) if errors: raise InvalidParametersError(errors) serialized_encrypted_extra = self._properties.get( "encrypted_extra", "{}") try: encrypted_extra = json.loads(serialized_encrypted_extra) except json.decoder.JSONDecodeError: encrypted_extra = {} # try to connect sqlalchemy_uri = engine_spec.build_sqlalchemy_uri( self._properties.get("parameters", None), # type: ignore encrypted_extra, ) if self._model and sqlalchemy_uri == self._model.safe_sqlalchemy_uri(): sqlalchemy_uri = self._model.sqlalchemy_uri_decrypted database = DatabaseDAO.build_db_for_connection_test( server_cert=self._properties.get("server_cert", ""), extra=self._properties.get("extra", "{}"), impersonate_user=self._properties.get("impersonate_user", False), encrypted_extra=serialized_encrypted_extra, ) database.set_sqlalchemy_uri(sqlalchemy_uri) database.db_engine_spec.mutate_db_for_connection_test(database) username = self._actor.username if self._actor is not None else None engine = database.get_sqla_engine(user_name=username) try: with closing(engine.raw_connection()) as conn: alive = engine.dialect.do_ping(conn) except Exception as ex: # pylint: disable=broad-except url = make_url(sqlalchemy_uri) context = { "hostname": url.host, "password": url.password, "port": url.port, "username": url.username, "database": url.database, } errors = database.db_engine_spec.extract_errors(ex, context) raise DatabaseTestConnectionFailedError(errors) if not alive: raise DatabaseOfflineError( SupersetError( message=__("Database is offline."), error_type=SupersetErrorType.GENERIC_DB_ENGINE_ERROR, level=ErrorLevel.ERROR, ), )
def db_engine_spec(self) -> Type[db_engine_specs.BaseEngineSpec]: engines = db_engine_specs.get_engine_specs() return engines.get(self.backend, db_engine_specs.BaseEngineSpec)