Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix sqlglot dependency conflict with superset 4 and fix superset auth/reauth to use only the API #319

Open
wants to merge 53 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
9911704
Fix sqlglot dependency
joaopamaral Sep 11, 2024
c4811c0
test
joaopamaral Sep 12, 2024
03e9604
fix ldap auth
joaopamaral Sep 12, 2024
aae24e8
fix auth
joaopamaral Sep 12, 2024
54b0752
fix auth
joaopamaral Sep 12, 2024
c1b60ae
fix
joaopamaral Sep 12, 2024
430a04d
fix
joaopamaral Sep 12, 2024
3117c82
fix
joaopamaral Sep 12, 2024
24a1790
fix
joaopamaral Sep 12, 2024
26aa702
removing Referer from header during get token
joaopamaral Sep 12, 2024
4765c0d
add debug
joaopamaral Sep 12, 2024
4ccc812
fix header updage
joaopamaral Sep 12, 2024
7685ef5
fix header update
joaopamaral Sep 12, 2024
2b4c6a5
revert
joaopamaral Sep 12, 2024
c92b449
use resources to limit.
joaopamaral Sep 12, 2024
d7780ab
validate resources
joaopamaral Sep 12, 2024
34656d4
allow import only a single resource
joaopamaral Sep 12, 2024
ed762a8
fix resource url
joaopamaral Sep 12, 2024
72bd4cd
fix
joaopamaral Sep 12, 2024
eebf944
update
joaopamaral Sep 12, 2024
cec3e52
update
joaopamaral Sep 12, 2024
c7ce240
update
joaopamaral Sep 12, 2024
2d48521
update
joaopamaral Sep 12, 2024
bd7c58d
update
joaopamaral Sep 12, 2024
1ac7327
test
joaopamaral Sep 12, 2024
73e628a
Revert "update"
joaopamaral Sep 12, 2024
ed0b1a5
set debug log
joaopamaral Sep 16, 2024
d2ba276
Fix tests
joaopamaral Sep 16, 2024
c892352
revert resource
joaopamaral Sep 16, 2024
20d8991
Update .gitignore
joaopamaral Sep 16, 2024
3753207
Merge pull request #1 from Automattic/fix-dependencies
joaopamaral Sep 17, 2024
493e3bf
extended superset client
jcellary Nov 15, 2024
c28ff9b
added methods for report handling
jcellary Nov 18, 2024
31bd2ac
Merge pull request #2 from Automattic/superset-client-ext
jcellary Nov 18, 2024
6684f0c
rebranded, minor fixes
jcellary Nov 21, 2024
7352afc
Merge pull request #3 from Automattic/rebranded
jcellary Nov 21, 2024
d189d97
Superset delete methods
jcellary Nov 25, 2024
90955e7
Merge pull request #4 from Automattic/superset-delete
jcellary Nov 26, 2024
01932bb
fix sqlglot newer version
joaopamaral Nov 27, 2024
86f5ad6
Include replace_jinja_tokens method
joaopamaral Nov 28, 2024
26dfd2f
remove unused import
joaopamaral Nov 28, 2024
08ec18e
Merge branch 'main' into fix-dependencies
joaopamaral Nov 28, 2024
6cd4c90
dry code
joaopamaral Nov 28, 2024
e699e8b
Merge remote-tracking branch 'origin/fix-dependencies' into fix-depen…
joaopamaral Nov 28, 2024
077c785
nit
joaopamaral Nov 28, 2024
5f1c297
nit
joaopamaral Nov 28, 2024
494113c
add test
joaopamaral Nov 28, 2024
9da9b4e
fix pylint errors
joaopamaral Nov 29, 2024
f525667
fix pylint errors
joaopamaral Nov 29, 2024
0376793
fix pylint errors
joaopamaral Nov 29, 2024
8bda015
fix pylint errors
joaopamaral Nov 29, 2024
e45888b
change log to debug level and remove -a8c from project name
joaopamaral Nov 29, 2024
07fedd9
Making provider configurable
joaopamaral Nov 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ MANIFEST
.pyre/
.pyre_configuration
.watchmanconfig

backend-sdk
9 changes: 4 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ rich==12.5.1
six==1.16.0
soupsieve==2.3.2.post1
sqlalchemy==1.4.40
sqlglot==20.7.1
sqlglot==25.24.5
tabulate==0.8.10
toml==0.10.2
tomli==2.0.1
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ rich==12.3.0
six==1.16.0
soupsieve==2.3.2.post1
sqlalchemy==1.4.35
sqlglot==20.7.1
sqlglot==25.24.5
tabulate==0.8.9
typing-extensions==4.2.0
urllib3==1.26.9
Expand Down
4 changes: 3 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ package_dir =
# Version specifiers like >=2.2,<3.0 avoid problems due to API changes in
# new major versions. This works if the required packages follow Semantic Versioning.
# For more information, check out https://semver.org/.

# Setting sqlglot to the same version used in the latest superset
install_requires =
importlib-metadata; python_version<"3.8"
Cython>=0.29.26
Expand All @@ -71,7 +73,7 @@ install_requires =
requests>=2.26.0
rich>=12.3.0
sqlalchemy>=1.4,<2
sqlglot>=19,<23
sqlglot==25.24.5
tabulate>=0.8.9
typing-extensions>=4.0.1
yarl>=1.7.2
Expand Down
68 changes: 66 additions & 2 deletions src/preset_cli/api/clients/superset.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,12 @@ def get_resource(self, resource_name: str, resource_id: int) -> Any:

return resource

def get_resources(self, resource_name: str, **kwargs: Any) -> List[Any]:
def get_resources(
self,
resource_name: str,
order_column: str = "changed_on_delta_humanized",
**kwargs: Any,
) -> List[Any]:
"""
Return one or more of a resource, possibly filtered.
"""
Expand All @@ -442,7 +447,7 @@ def get_resources(self, resource_name: str, **kwargs: Any) -> List[Any]:
dict(col=col, opr=value.operator, value=value.value)
for col, value in operations.items()
],
"order_column": "changed_on_delta_humanized",
"order_column": order_column,
"order_direction": "desc",
"page": page,
"page_size": MAX_PAGE_SIZE,
Expand Down Expand Up @@ -499,6 +504,15 @@ def update_resource(

return resource

def delete_resource(self, resource_name: str, resource_id: int):
"""
Delete a resource.
"""
url = self.baseurl / "api/v1" / resource_name / str(resource_id)

response = self.session.delete(url)
validate_response(response)

def get_resource_endpoint_info(self, resource_name: str, **kwargs: Any) -> Any:
"""
Get resource endpoint info (such as available columns) possibly filtered.
Expand Down Expand Up @@ -671,6 +685,18 @@ def get_charts(self, **kwargs: str) -> List[Any]:
"""
return self.get_resources("chart", **kwargs)

def update_chart(self, chart_id: int, **kwargs: Any) -> Any:
"""
Update a chart.
"""
return self.update_resource("chart", chart_id, **kwargs)

def delete_chart(self, chart_id):
"""
delete a chart.
"""
self.delete_resource("chart", chart_id)

def get_dashboard(self, dashboard_id: int) -> Any:
"""
Return a single dashboard.
Expand All @@ -695,6 +721,42 @@ def update_dashboard(self, dashboard_id: int, **kwargs: Any) -> Any:
"""
return self.update_resource("dashboard", dashboard_id, **kwargs)

def delete_dashboard(self, dashboard_id: int) -> Any:
"""
Delete a dashboard.
"""
self.delete_resource("dashboard", dashboard_id)

def get_users(self, **kwargs: str) -> List[Any]:
"""
Return users, possibly filtered.
"""
return self.get_resources("security/users", "id", **kwargs)

def get_report(self, report_id: int) -> Any:
"""
Return a single report.
"""
return self.get_resource("report", report_id)

def get_reports(self, **kwargs: str) -> List[Any]:
"""
Return reports, possibly filtered.
"""
return self.get_resources("report", **kwargs)

def create_report(self, **kwargs: Any) -> Any:
"""
Create a report.
"""
return self.create_resource("report", **kwargs)

def update_report(self, report_id: int, **kwargs: Any) -> Any:
"""
Update a report.
"""
return self.update_resource("report", report_id, **kwargs)

def export_zip(self, resource_name: str, ids: List[int]) -> BytesIO:
"""
Export one or more of a resource.
Expand Down Expand Up @@ -767,6 +829,8 @@ def import_zip(
)
validate_response(response)

_logger.debug(response.text)

payload = response.json()

return payload["message"] == "OK"
Expand Down
6 changes: 5 additions & 1 deletion src/preset_cli/auth/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""
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
"""
Expand Down Expand Up @@ -46,6 +48,8 @@ def reauth(self, r: Response, *args: Any, **kwargs: Any) -> Response:
if r.status_code != 401:
return r

_logger.debug("Token expired. Re-authenticating...")

try:
self.auth()
except NotImplementedError:
Expand Down
41 changes: 30 additions & 11 deletions src/preset_cli/auth/superset.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from typing import Dict, Optional

from bs4 import BeautifulSoup
from yarl import URL

from preset_cli.auth.main import Auth
Expand All @@ -16,36 +15,56 @@ class UsernamePasswordAuth(Auth): # pylint: disable=too-few-public-methods
Auth to Superset via username/password.
"""

def __init__(self, baseurl: URL, username: str, password: Optional[str] = None):
def __init__(self, baseurl: URL, username: str, password: Optional[str] = None, provider: Optional[str] = None):
super().__init__()

self.csrf_token: Optional[str] = None
self.baseurl = baseurl
self.username = username
self.password = password
self.provider = provider or "ldap"
self.auth()

def get_headers(self) -> Dict[str, str]:
return {"X-CSRFToken": self.csrf_token} if self.csrf_token else {}

def get_access_token(self):
"""
Get an access token from superset API: api/v1/security/login.
"""
body = {
"username": self.username,
"password": self.password,
"provider": self.provider,
}
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"]

def get_csrf_token(self):
"""
Get a CSRF token from superset API: api/v1/security/csrf_token .
"""
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.
"""
data = {"username": self.username, "password": self.password}
self.session.headers["Authorization"] = f"Bearer {self.get_access_token()}"
csrf_token = self.get_csrf_token()

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
if csrf_token:
self.session.headers["X-CSRFToken"] = csrf_token
data["csrf_token"] = csrf_token
self.session.headers["Referer"] = str(
self.baseurl / "api/v1/security/csrf_token/",
)
self.csrf_token = csrf_token

# set cookies
self.session.post(self.baseurl / "login/", data=data)


class SupersetJWTAuth(TokenAuth): # pylint: disable=abstract-method
"""
Expand Down
6 changes: 5 additions & 1 deletion src/preset_cli/cli/superset/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
help="Password (leave empty for prompt)",
)
@click.option("--loglevel", default="INFO")
@click.option("--provider",
default="ldap",
help="Provider that store the superset credentials (e.g. ldap, db, etc.)")
@click.version_option()
@click.pass_context
def superset_cli( # pylint: disable=too-many-arguments
Expand All @@ -44,6 +47,7 @@ def superset_cli( # pylint: disable=too-many-arguments
username: str,
password: str,
loglevel: str,
provider: str
):
"""
An Apache Superset CLI.
Expand All @@ -59,7 +63,7 @@ def superset_cli( # pylint: disable=too-many-arguments
if jwt_token:
ctx.obj["AUTH"] = SupersetJWTAuth(jwt_token, URL(instance))
else:
ctx.obj["AUTH"] = UsernamePasswordAuth(URL(instance), username, password)
ctx.obj["AUTH"] = UsernamePasswordAuth(URL(instance), username, password, provider)


superset_cli.add_command(sql)
Expand Down
Loading
Loading