From 99117045b3349f951eb9c6ab49cbe13bbb0611a8 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:48:30 -0300 Subject: [PATCH 01/30] Fix sqlglot dependency --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d6af7ec4..0fc906af 100644 --- a/setup.cfg +++ b/setup.cfg @@ -71,7 +71,7 @@ install_requires = requests>=2.26.0 rich>=12.3.0 sqlalchemy>=1.4,<2 - sqlglot>=19,<23 + sqlglot>=23.0.2,<24 tabulate>=0.8.9 typing-extensions>=4.0.1 yarl>=1.7.2 From c4811c0408c4cd534ad123f2ba2d5b412df942b9 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:17:38 -0300 Subject: [PATCH 02/30] test --- src/preset_cli/auth/main.py | 5 ++++- src/preset_cli/auth/superset.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/preset_cli/auth/main.py b/src/preset_cli/auth/main.py index 04488033..7e746af0 100644 --- a/src/preset_cli/auth/main.py +++ b/src/preset_cli/auth/main.py @@ -1,13 +1,14 @@ """ Mechanisms for authentication and authorization. """ - +import logging from typing import Any, Dict from requests import Response, Session from requests.adapters import HTTPAdapter from urllib3.util import Retry +_logger = logging.getLogger(__name__) class Auth: # pylint: disable=too-few-public-methods """ @@ -46,6 +47,8 @@ def reauth(self, r: Response, *args: Any, **kwargs: Any) -> Response: if r.status_code != 401: return r + _logger.info('Token expired. Re-authenticating...') + try: self.auth() except NotImplementedError: diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index 957c9f64..f5f4011e 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -44,7 +44,8 @@ def auth(self) -> None: self.csrf_token = csrf_token # set cookies - self.session.post(self.baseurl / "login/", data=data) + response = self.session.post(self.baseurl / "login/", data=data) + response.raise_for_status() class SupersetJWTAuth(TokenAuth): # pylint: disable=abstract-method From 03e9604c18b5ca48f8f4dc254ed1acfe1b94e40f Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:24:39 -0300 Subject: [PATCH 03/30] fix ldap auth --- src/preset_cli/auth/superset.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index f5f4011e..a68fdef5 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -32,21 +32,14 @@ def auth(self) -> None: """ Login to get CSRF token and cookies. """ - data = {"username": self.username, "password": self.password} - - response = self.session.get(self.baseurl / "login/") - soup = BeautifulSoup(response.text, "html.parser") - input_ = soup.find("input", {"id": "csrf_token"}) - csrf_token = input_["value"] if input_ else None + body = {"username": self.username, "password": self.password, "provider": "ldap"} + response = self.session.post(self.baseurl / "api/v1/security/login", json=body) + response.raise_for_status() + csrf_token = response.json().get("access_token") if csrf_token: self.session.headers["X-CSRFToken"] = csrf_token - data["csrf_token"] = csrf_token self.csrf_token = csrf_token - # set cookies - response = self.session.post(self.baseurl / "login/", data=data) - response.raise_for_status() - class SupersetJWTAuth(TokenAuth): # pylint: disable=abstract-method """ From aae24e8c12ffdc80c79976ae5f920cd895f30fb3 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:31:23 -0300 Subject: [PATCH 04/30] fix auth --- src/preset_cli/auth/superset.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index a68fdef5..728ab723 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -4,7 +4,6 @@ from typing import Dict, Optional -from bs4 import BeautifulSoup from yarl import URL from preset_cli.auth.main import Auth @@ -28,16 +27,27 @@ def __init__(self, baseurl: URL, username: str, password: Optional[str] = None): def get_headers(self) -> Dict[str, str]: return {"X-CSRFToken": self.csrf_token} if self.csrf_token else {} + def get_access_token(self) + body = {"username": self.username, "password": self.password, "provider": "ldap"} + response = self.session.post(self.baseurl / "api/v1/security/login", json=body) + response.raise_for_status() + return response.json()["access_token"] + + def get_csrf_token(self): + response = self.session.get(self.baseurl / "/api/v1/security/csrf_token/") + response.raise_for_status() + return response.json()["result"] + def auth(self) -> None: """ Login to get CSRF token and cookies. """ - body = {"username": self.username, "password": self.password, "provider": "ldap"} - response = self.session.post(self.baseurl / "api/v1/security/login", json=body) - response.raise_for_status() - csrf_token = response.json().get("access_token") + self.session.headers = {"Authorization": f"Bearer {self.get_access_token()}"} + csrf_token = self.get_csrf_token() + if csrf_token: self.session.headers["X-CSRFToken"] = csrf_token + self.session.headers["Referer"] = self.baseurl / "/api/v1/security/csrf_token/" self.csrf_token = csrf_token From 54b07526eaa851d6ab3840a1a9e97a39025ff039 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:37:07 -0300 Subject: [PATCH 05/30] fix auth --- src/preset_cli/auth/superset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index 728ab723..1f5a9d13 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -27,7 +27,7 @@ def __init__(self, baseurl: URL, username: str, password: Optional[str] = None): def get_headers(self) -> Dict[str, str]: return {"X-CSRFToken": self.csrf_token} if self.csrf_token else {} - def get_access_token(self) + def get_access_token(self): body = {"username": self.username, "password": self.password, "provider": "ldap"} response = self.session.post(self.baseurl / "api/v1/security/login", json=body) response.raise_for_status() From c1b60ae5b721dfe930072be14aa6a4b9620232bc Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:52:15 -0300 Subject: [PATCH 06/30] fix --- src/preset_cli/auth/superset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index 1f5a9d13..79c34fc2 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -34,7 +34,7 @@ def get_access_token(self): return response.json()["access_token"] def get_csrf_token(self): - response = self.session.get(self.baseurl / "/api/v1/security/csrf_token/") + response = self.session.get(self.baseurl / "api/v1/security/csrf_token/") response.raise_for_status() return response.json()["result"] From 430a04d985d6e4a16ef9286139a85fb0415da2b2 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:23:35 -0300 Subject: [PATCH 07/30] fix --- src/preset_cli/auth/superset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index 79c34fc2..6a9f9c4c 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -47,7 +47,7 @@ def auth(self) -> None: if csrf_token: self.session.headers["X-CSRFToken"] = csrf_token - self.session.headers["Referer"] = self.baseurl / "/api/v1/security/csrf_token/" + self.session.headers["Referer"] = self.baseurl / "api/v1/security/csrf_token/" self.csrf_token = csrf_token From 3117c828fb421f46c99569250835a64a36621faa Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:28:21 -0300 Subject: [PATCH 08/30] fix --- src/preset_cli/auth/superset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index 6a9f9c4c..9a0e12d6 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -47,7 +47,7 @@ def auth(self) -> None: if csrf_token: self.session.headers["X-CSRFToken"] = csrf_token - self.session.headers["Referer"] = self.baseurl / "api/v1/security/csrf_token/" + self.session.headers["Referer"] = str(self.baseurl / "api/v1/security/csrf_token/") self.csrf_token = csrf_token From 24a1790a0e308103d33f84ad47f746c08d162b0e Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:35:15 -0300 Subject: [PATCH 09/30] fix --- src/preset_cli/auth/superset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index 9a0e12d6..a61a9e6c 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -29,6 +29,7 @@ def get_headers(self) -> Dict[str, str]: def get_access_token(self): body = {"username": self.username, "password": self.password, "provider": "ldap"} + self.session.headers = {} response = self.session.post(self.baseurl / "api/v1/security/login", json=body) response.raise_for_status() return response.json()["access_token"] From 26aa7020044c940bbdb58e8989fd0ec816c41648 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:21:33 -0300 Subject: [PATCH 10/30] removing Referer from header during get token --- src/preset_cli/auth/superset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index a61a9e6c..23e0d450 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -29,7 +29,8 @@ def get_headers(self) -> Dict[str, str]: def get_access_token(self): body = {"username": self.username, "password": self.password, "provider": "ldap"} - self.session.headers = {} + if "Referer" in self.session.headers: + del self.session.headers["Referer"] response = self.session.post(self.baseurl / "api/v1/security/login", json=body) response.raise_for_status() return response.json()["access_token"] From 4765c0dd7309f7bc88b96e294988bb120852912c Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:44:11 -0300 Subject: [PATCH 11/30] add debug --- src/preset_cli/api/clients/superset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index af5936c7..4661c3c3 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -767,6 +767,8 @@ def import_zip( ) validate_response(response) + _logger.debug(response.text) + payload = response.json() return payload["message"] == "OK" From 4ccc812b0d47d1b16129fe3a6240927becb0d04d Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:17:55 -0300 Subject: [PATCH 12/30] fix header updage --- src/preset_cli/auth/superset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/auth/superset.py b/src/preset_cli/auth/superset.py index 23e0d450..897f37c0 100644 --- a/src/preset_cli/auth/superset.py +++ b/src/preset_cli/auth/superset.py @@ -44,7 +44,7 @@ def auth(self) -> None: """ Login to get CSRF token and cookies. """ - self.session.headers = {"Authorization": f"Bearer {self.get_access_token()}"} + self.session.headers["Authorization"] = f"Bearer {self.get_access_token()}" csrf_token = self.get_csrf_token() if csrf_token: From 7685ef5ccffd1ed007be0839e11aa24fa11c7f28 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:44:43 -0300 Subject: [PATCH 13/30] fix header update --- src/preset_cli/api/clients/superset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index 4661c3c3..5456a09f 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -399,6 +399,7 @@ def get_data( # pylint: disable=too-many-locals, too-many-arguments headers = { "Accept": "application/json", + "Content-Type": "application/json", } self.session.headers.update(headers) From 2b4c6a5d7e53f8eec1d175533ecdf67d856bc70e Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:00:29 -0300 Subject: [PATCH 14/30] revert --- src/preset_cli/api/clients/superset.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index 5456a09f..3468cda4 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -399,7 +399,6 @@ def get_data( # pylint: disable=too-many-locals, too-many-arguments headers = { "Accept": "application/json", - "Content-Type": "application/json", } self.session.headers.update(headers) @@ -758,7 +757,10 @@ def import_zip( files = {key: form_data} url = self.baseurl / "api/v1" / resource_name / "import/" - self.session.headers.update({"Accept": "application/json"}) + self.session.headers.update({ + "Accept": "application/json", + "Content-Type": "application/json", + }) data = {"overwrite": json.dumps(overwrite)} _logger.debug("POST %s\n%s", url, json.dumps(data, indent=4)) response = self.session.post( From c92b449c660b22c322896306de9cf4d8096b7eca Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:14:21 -0300 Subject: [PATCH 15/30] use resources to limit. --- .../cli/superset/sync/native/command.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index 122c14ed..d1b6e817 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -155,6 +155,13 @@ def render_yaml(path: Path, env: Dict[str, Any]) -> Dict[str, Any]: default=False, help="Split imports into individual assets", ) +@click.option( + "--resources", + "-r", + multiple=True, + default=None, + help="Resources to be imported (if no one is specified, all resources will be imported)", +) @click.pass_context def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-branches ctx: click.core.Context, @@ -166,6 +173,7 @@ def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-bra external_url_prefix: str = "", load_env: bool = False, split: bool = False, + resources: Tuple[str, ...] = (), ) -> None: """ Sync exported DBs/datasets/charts/dashboards to Superset. @@ -175,6 +183,9 @@ def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-bra client = SupersetClient(url, auth) root = Path(directory) + if resources and not split: + raise click.UsageError('Resources must be specified if splitting is enabled') + base_url = URL(external_url_prefix) if external_url_prefix else None # collecting existing database UUIDs so we know if we're creating or updating @@ -242,7 +253,7 @@ def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-bra configs["bundle" / relative_path] = config if split: - import_resources_individually(configs, client, overwrite) + import_resources_individually(configs, client, overwrite, resources) else: contents = {str(k): yaml.dump(v) for k, v in configs.items()} import_resources(contents, client, overwrite) @@ -252,6 +263,7 @@ def import_resources_individually( configs: Dict[Path, AssetConfig], client: SupersetClient, overwrite: bool, + resources: Tuple[str, ...] = () ) -> None: """ Import contents individually. @@ -274,6 +286,8 @@ def import_resources_individually( ("charts", lambda config: [config["dataset_uuid"]]), ("dashboards", get_dashboard_related_uuids), ] + if resources: + imports = [(name, func) for name, func in imports if name in resources] related_configs: Dict[str, Dict[Path, AssetConfig]] = {} for resource_name, get_related_uuids in imports: for path, config in configs.items(): From d7780ab40a678cd9271dcb5700ed33b5bcd1cae4 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:16:56 -0300 Subject: [PATCH 16/30] validate resources --- src/preset_cli/cli/superset/sync/native/command.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index d1b6e817..2bea96c3 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -160,7 +160,7 @@ def render_yaml(path: Path, env: Dict[str, Any]) -> Dict[str, Any]: "-r", multiple=True, default=None, - help="Resources to be imported (if no one is specified, all resources will be imported)", + help="Resources to be imported e.g. (if no one is specified, all resources will be imported)", ) @click.pass_context def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-branches @@ -185,6 +185,7 @@ def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-bra if resources and not split: raise click.UsageError('Resources must be specified if splitting is enabled') + assert set(resources).issubset({"databases", "datasets", "charts", "dashboards"}), "Invalid resources specified" base_url = URL(external_url_prefix) if external_url_prefix else None From 34656d45cdaf3dd3946d7a8f4417803c53276187 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:22:48 -0300 Subject: [PATCH 17/30] allow import only a single resource --- src/preset_cli/cli/superset/sync/native/command.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index 2bea96c3..28335ce4 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -302,7 +302,7 @@ def import_resources_individually( _logger.info("Importing %s", path.relative_to("bundle")) contents = {str(k): yaml.dump(v) for k, v in asset_configs.items()} if path not in imported: - import_resources(contents, client, overwrite) + import_resources(contents, client, overwrite, resource_name) imported.add(path) log.write(str(path) + "\n") log.flush() @@ -390,6 +390,7 @@ def import_resources( contents: Dict[str, str], client: SupersetClient, overwrite: bool, + resource_name = "assets" ) -> None: """ Import a bundle of assets. @@ -409,7 +410,7 @@ def import_resources( output.write(file_content.encode()) buf.seek(0) try: - client.import_zip("assets", buf, overwrite=overwrite) + client.import_zip(resource_name, buf, overwrite=overwrite) except SupersetError as ex: click.echo( click.style( From ed762a8f93033547a88214ddba5785f7db49ad0b Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:30:11 -0300 Subject: [PATCH 18/30] fix resource url --- src/preset_cli/cli/superset/sync/native/command.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index 28335ce4..81d107d9 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -410,7 +410,8 @@ def import_resources( output.write(file_content.encode()) buf.seek(0) try: - client.import_zip(resource_name, buf, overwrite=overwrite) + client.import_zip(resource_name if resource_name == "assets" else resource_name.rstrip('s'), + buf, overwrite=overwrite) except SupersetError as ex: click.echo( click.style( From 72bd4cd29507488d8717721b5904a843cbad265d Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:50:25 -0300 Subject: [PATCH 19/30] fix --- src/preset_cli/api/clients/superset.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index 3468cda4..4661c3c3 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -757,10 +757,7 @@ def import_zip( files = {key: form_data} url = self.baseurl / "api/v1" / resource_name / "import/" - self.session.headers.update({ - "Accept": "application/json", - "Content-Type": "application/json", - }) + self.session.headers.update({"Accept": "application/json"}) data = {"overwrite": json.dumps(overwrite)} _logger.debug("POST %s\n%s", url, json.dumps(data, indent=4)) response = self.session.post( From eebf94431d8c1c33fc501058a29d0ab31ce2cdc2 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:57:18 -0300 Subject: [PATCH 20/30] update --- src/preset_cli/cli/superset/sync/native/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index 81d107d9..de141ade 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -398,7 +398,7 @@ def import_resources( contents["bundle/metadata.yaml"] = yaml.dump( dict( version="1.0.0", - type="assets", + type=resource_name, timestamp=datetime.now(tz=timezone.utc).isoformat(), ), ) From cec3e5299f44f4d023d376912cc94ad1f5f46d94 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:05:55 -0300 Subject: [PATCH 21/30] update --- src/preset_cli/cli/superset/sync/native/command.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index de141ade..18238993 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -395,10 +395,11 @@ def import_resources( """ Import a bundle of assets. """ + resource = resource_name if resource_name == "assets" else resource_name.rstrip('s') contents["bundle/metadata.yaml"] = yaml.dump( dict( version="1.0.0", - type=resource_name, + type=resource, timestamp=datetime.now(tz=timezone.utc).isoformat(), ), ) @@ -410,8 +411,7 @@ def import_resources( output.write(file_content.encode()) buf.seek(0) try: - client.import_zip(resource_name if resource_name == "assets" else resource_name.rstrip('s'), - buf, overwrite=overwrite) + client.import_zip(resource, buf, overwrite=overwrite) except SupersetError as ex: click.echo( click.style( From c7ce2405b5fe988dc1d2178cca221ae8580d082e Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:22:30 -0300 Subject: [PATCH 22/30] update --- src/preset_cli/cli/superset/sync/native/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index 18238993..d1974ace 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -395,7 +395,7 @@ def import_resources( """ Import a bundle of assets. """ - resource = resource_name if resource_name == "assets" else resource_name.rstrip('s') + resource = resource_name if resource_name == "assets" else resource_name.rstrip('s').title() contents["bundle/metadata.yaml"] = yaml.dump( dict( version="1.0.0", From 2d4852190e4fe2499124808fd4126657d65c904f Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:28:54 -0300 Subject: [PATCH 23/30] update --- src/preset_cli/cli/superset/sync/native/command.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index d1974ace..7cbfd3a8 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -395,15 +395,16 @@ def import_resources( """ Import a bundle of assets. """ - resource = resource_name if resource_name == "assets" else resource_name.rstrip('s').title() contents["bundle/metadata.yaml"] = yaml.dump( dict( version="1.0.0", - type=resource, + type=resource_name if resource_name == "assets" else resource_name.rstrip('s').title(), timestamp=datetime.now(tz=timezone.utc).isoformat(), ), ) + _logger.info("Importing %s \n Content %s", resource_name, contents) + buf = BytesIO() with ZipFile(buf, "w") as bundle: for file_path, file_content in contents.items(): @@ -411,7 +412,7 @@ def import_resources( output.write(file_content.encode()) buf.seek(0) try: - client.import_zip(resource, buf, overwrite=overwrite) + client.import_zip(resource_name if resource_name == "assets" else resource_name.rstrip('s'), buf, overwrite=overwrite) except SupersetError as ex: click.echo( click.style( From bd7c58d8f3606048e48c0963944adabab8b64992 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:20:16 -0300 Subject: [PATCH 24/30] update --- src/preset_cli/api/clients/superset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index 4661c3c3..689e1b53 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -758,6 +758,8 @@ def import_zip( url = self.baseurl / "api/v1" / resource_name / "import/" self.session.headers.update({"Accept": "application/json"}) + if key == "formData": + self.session.headers.update({"Content-Type": "multipart/form-data"}) data = {"overwrite": json.dumps(overwrite)} _logger.debug("POST %s\n%s", url, json.dumps(data, indent=4)) response = self.session.post( From 1ac73276ea74de6c97a715b8e7e845b3432568f1 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:39:45 -0300 Subject: [PATCH 25/30] test --- src/preset_cli/api/clients/superset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index 689e1b53..59751f77 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -761,7 +761,7 @@ def import_zip( if key == "formData": self.session.headers.update({"Content-Type": "multipart/form-data"}) data = {"overwrite": json.dumps(overwrite)} - _logger.debug("POST %s\n%s", url, json.dumps(data, indent=4)) + _logger.debug("POST %s\n%s\n%s", url, json.dumps(data, indent=4), files) response = self.session.post( url, files=files, From 73e628abfb1903d1a5f3eca8400594224a3f0df4 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:31:29 -0300 Subject: [PATCH 26/30] Revert "update" This reverts commit bd7c58d8f3606048e48c0963944adabab8b64992. --- src/preset_cli/api/clients/superset.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index 59751f77..d8e202f7 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -758,8 +758,6 @@ def import_zip( url = self.baseurl / "api/v1" / resource_name / "import/" self.session.headers.update({"Accept": "application/json"}) - if key == "formData": - self.session.headers.update({"Content-Type": "multipart/form-data"}) data = {"overwrite": json.dumps(overwrite)} _logger.debug("POST %s\n%s\n%s", url, json.dumps(data, indent=4), files) response = self.session.post( From ed0b1a51f9004a7be9100dc1392b451a2f24ca30 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:09:04 -0300 Subject: [PATCH 27/30] set debug log --- src/preset_cli/cli/superset/sync/native/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index 7cbfd3a8..73689692 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -403,7 +403,7 @@ def import_resources( ), ) - _logger.info("Importing %s \n Content %s", resource_name, contents) + _logger.debug("Importing %s \n Content %s", resource_name, contents) buf = BytesIO() with ZipFile(buf, "w") as bundle: From d2ba276228e3cdd27f01145b99eefbd5e48cd0f3 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:58:15 -0300 Subject: [PATCH 28/30] Fix tests --- tests/auth/superset_test.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/tests/auth/superset_test.py b/tests/auth/superset_test.py index 8ebdcba9..dc2b487a 100644 --- a/tests/auth/superset_test.py +++ b/tests/auth/superset_test.py @@ -14,11 +14,13 @@ def test_username_password_auth(requests_mock: Mocker) -> None: Tests for the username/password authentication mechanism. """ csrf_token = "CSFR_TOKEN" + access_token = "ACCESS_TOKEN" requests_mock.get( - "https://superset.example.org/login/", - text=f'', + "https://superset.example.org/api/v1/security/csrf_token/", + json={'result': csrf_token}, ) - requests_mock.post("https://superset.example.org/login/") + requests_mock.post("https://superset.example.org/api/v1/security/login", + json={'access_token': access_token}) auth = UsernamePasswordAuth( URL("https://superset.example.org/"), @@ -29,21 +31,18 @@ def test_username_password_auth(requests_mock: Mocker) -> None: "X-CSRFToken": csrf_token, } - assert ( - requests_mock.last_request.text - == "username=admin&password=password123&csrf_token=CSFR_TOKEN" - ) - def test_username_password_auth_no_csrf(requests_mock: Mocker) -> None: """ Tests for the username/password authentication mechanism. """ + access_token = "ACCESS_TOKEN" requests_mock.get( - "https://superset.example.org/login/", - text="WTF_CSRF_ENABLED = False", + "https://superset.example.org/api/v1/security/csrf_token/", + json={'result': None}, ) - requests_mock.post("https://superset.example.org/login/") + requests_mock.post("https://superset.example.org/api/v1/security/login", + json={'access_token': access_token}) auth = UsernamePasswordAuth( URL("https://superset.example.org/"), @@ -53,9 +52,6 @@ def test_username_password_auth_no_csrf(requests_mock: Mocker) -> None: # pylint: disable=use-implicit-booleaness-not-comparison assert auth.get_headers() == {} - assert requests_mock.last_request.text == "username=admin&password=password123" - - def test_jwt_auth_superset(mocker: MockerFixture) -> None: """ Test the ``JWTAuth`` authentication mechanism for Superset tenant. From c892352117c9e83f27437ca95dde7c6f036711d1 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:04:13 -0300 Subject: [PATCH 29/30] revert resource --- .gitignore | 2 ++ Makefile | 9 +++---- src/preset_cli/api/clients/superset.py | 2 +- .../cli/superset/sync/native/command.py | 26 +++---------------- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index afb79e97..445c4f08 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ MANIFEST .pyre/ .pyre_configuration .watchmanconfig + +backend-sdk \ No newline at end of file diff --git a/Makefile b/Makefile index 4e909dad..8396e279 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,11 @@ pyenv: .python-version .python-version: setup.cfg - if [ -z "`pyenv virtualenvs | grep backend-sdk`" ]; then\ - pyenv virtualenv backend-sdk;\ - fi - if [ ! -f .python-version ]; then\ - pyenv local backend-sdk;\ + if [ ! -d "backend-sdk" ]; then \ + pyenv local 3.11; \ + python -m venv backend-sdk; \ fi + backend-sdk/bin/activate; \ pip install -e '.[testing]' touch .python-version diff --git a/src/preset_cli/api/clients/superset.py b/src/preset_cli/api/clients/superset.py index d8e202f7..4661c3c3 100644 --- a/src/preset_cli/api/clients/superset.py +++ b/src/preset_cli/api/clients/superset.py @@ -759,7 +759,7 @@ def import_zip( self.session.headers.update({"Accept": "application/json"}) data = {"overwrite": json.dumps(overwrite)} - _logger.debug("POST %s\n%s\n%s", url, json.dumps(data, indent=4), files) + _logger.debug("POST %s\n%s", url, json.dumps(data, indent=4)) response = self.session.post( url, files=files, diff --git a/src/preset_cli/cli/superset/sync/native/command.py b/src/preset_cli/cli/superset/sync/native/command.py index 73689692..122c14ed 100644 --- a/src/preset_cli/cli/superset/sync/native/command.py +++ b/src/preset_cli/cli/superset/sync/native/command.py @@ -155,13 +155,6 @@ def render_yaml(path: Path, env: Dict[str, Any]) -> Dict[str, Any]: default=False, help="Split imports into individual assets", ) -@click.option( - "--resources", - "-r", - multiple=True, - default=None, - help="Resources to be imported e.g. (if no one is specified, all resources will be imported)", -) @click.pass_context def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-branches ctx: click.core.Context, @@ -173,7 +166,6 @@ def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-bra external_url_prefix: str = "", load_env: bool = False, split: bool = False, - resources: Tuple[str, ...] = (), ) -> None: """ Sync exported DBs/datasets/charts/dashboards to Superset. @@ -183,10 +175,6 @@ def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-bra client = SupersetClient(url, auth) root = Path(directory) - if resources and not split: - raise click.UsageError('Resources must be specified if splitting is enabled') - assert set(resources).issubset({"databases", "datasets", "charts", "dashboards"}), "Invalid resources specified" - base_url = URL(external_url_prefix) if external_url_prefix else None # collecting existing database UUIDs so we know if we're creating or updating @@ -254,7 +242,7 @@ def native( # pylint: disable=too-many-locals, too-many-arguments, too-many-bra configs["bundle" / relative_path] = config if split: - import_resources_individually(configs, client, overwrite, resources) + import_resources_individually(configs, client, overwrite) else: contents = {str(k): yaml.dump(v) for k, v in configs.items()} import_resources(contents, client, overwrite) @@ -264,7 +252,6 @@ def import_resources_individually( configs: Dict[Path, AssetConfig], client: SupersetClient, overwrite: bool, - resources: Tuple[str, ...] = () ) -> None: """ Import contents individually. @@ -287,8 +274,6 @@ def import_resources_individually( ("charts", lambda config: [config["dataset_uuid"]]), ("dashboards", get_dashboard_related_uuids), ] - if resources: - imports = [(name, func) for name, func in imports if name in resources] related_configs: Dict[str, Dict[Path, AssetConfig]] = {} for resource_name, get_related_uuids in imports: for path, config in configs.items(): @@ -302,7 +287,7 @@ def import_resources_individually( _logger.info("Importing %s", path.relative_to("bundle")) contents = {str(k): yaml.dump(v) for k, v in asset_configs.items()} if path not in imported: - import_resources(contents, client, overwrite, resource_name) + import_resources(contents, client, overwrite) imported.add(path) log.write(str(path) + "\n") log.flush() @@ -390,7 +375,6 @@ def import_resources( contents: Dict[str, str], client: SupersetClient, overwrite: bool, - resource_name = "assets" ) -> None: """ Import a bundle of assets. @@ -398,13 +382,11 @@ def import_resources( contents["bundle/metadata.yaml"] = yaml.dump( dict( version="1.0.0", - type=resource_name if resource_name == "assets" else resource_name.rstrip('s').title(), + type="assets", timestamp=datetime.now(tz=timezone.utc).isoformat(), ), ) - _logger.debug("Importing %s \n Content %s", resource_name, contents) - buf = BytesIO() with ZipFile(buf, "w") as bundle: for file_path, file_content in contents.items(): @@ -412,7 +394,7 @@ def import_resources( output.write(file_content.encode()) buf.seek(0) try: - client.import_zip(resource_name if resource_name == "assets" else resource_name.rstrip('s'), buf, overwrite=overwrite) + client.import_zip("assets", buf, overwrite=overwrite) except SupersetError as ex: click.echo( click.style( From 20d89911d5b483159ecdd6a7693bad6728f269e7 Mon Sep 17 00:00:00 2001 From: Joao Amaral <7281460+joaopamaral@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:01:44 -0300 Subject: [PATCH 30/30] Update .gitignore nit --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 445c4f08..16b667e3 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,4 @@ MANIFEST .pyre_configuration .watchmanconfig -backend-sdk \ No newline at end of file +backend-sdk