lkr-dev-cli 0.0.21__py3-none-any.whl → 0.0.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lkr/auth/oauth.py +52 -31
- lkr/auth_service.py +39 -9
- lkr/classes.py +7 -6
- lkr/main.py +12 -2
- lkr/mcp/main.py +1 -1
- lkr/observability/embed_container.html +8 -3
- lkr/observability/main.py +49 -8
- {lkr_dev_cli-0.0.21.dist-info → lkr_dev_cli-0.0.23.dist-info}/METADATA +1 -1
- lkr_dev_cli-0.0.23.dist-info/RECORD +21 -0
- lkr/utils.py +0 -0
- lkr_dev_cli-0.0.21.dist-info/RECORD +0 -22
- /lkr/{embed/observability → observability}/utils.py +0 -0
- {lkr_dev_cli-0.0.21.dist-info → lkr_dev_cli-0.0.23.dist-info}/WHEEL +0 -0
- {lkr_dev_cli-0.0.21.dist-info → lkr_dev_cli-0.0.23.dist-info}/entry_points.txt +0 -0
- {lkr_dev_cli-0.0.21.dist-info → lkr_dev_cli-0.0.23.dist-info}/licenses/LICENSE +0 -0
lkr/auth/oauth.py
CHANGED
@@ -19,21 +19,23 @@ def kill_process_on_port(port: int, retries: int = 5, delay: float = 1) -> None:
|
|
19
19
|
# Try to create a socket binding to check if port is in use
|
20
20
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
21
21
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
22
|
-
sock.bind((
|
22
|
+
sock.bind(("localhost", port))
|
23
23
|
sock.close()
|
24
24
|
return # Port is free, no need to kill anything
|
25
25
|
except socket.error:
|
26
26
|
# Port is in use, try to kill the process
|
27
|
-
if os.name ==
|
28
|
-
os.system(f
|
29
|
-
elif os.name ==
|
30
|
-
os.system(
|
27
|
+
if os.name == "posix": # macOS/Linux
|
28
|
+
os.system(f"lsof -ti tcp:{port} | xargs kill -9 2>/dev/null")
|
29
|
+
elif os.name == "nt": # Windows
|
30
|
+
os.system(
|
31
|
+
f'for /f "tokens=5" %a in (\'netstat -aon ^| find ":{port}"\') do taskkill /F /PID %a 2>nul'
|
32
|
+
)
|
31
33
|
# After killing, wait for the port to be free
|
32
34
|
for _ in range(retries):
|
33
35
|
try:
|
34
36
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
35
37
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
36
|
-
sock.bind((
|
38
|
+
sock.bind(("localhost", port))
|
37
39
|
sock.close()
|
38
40
|
return
|
39
41
|
except socket.error:
|
@@ -45,19 +47,21 @@ class OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
45
47
|
def do_GET(self):
|
46
48
|
"""Handle the callback from OAuth authorization"""
|
47
49
|
self.send_response(200)
|
48
|
-
self.send_header(
|
50
|
+
self.send_header("Content-type", "text/html")
|
49
51
|
self.end_headers()
|
50
|
-
|
52
|
+
|
51
53
|
# Parse the authorization code from query parameters
|
52
54
|
query_components = parse_qs(urllib.parse.urlparse(self.path).query)
|
53
|
-
|
55
|
+
|
54
56
|
# Store the code in the server instance
|
55
|
-
if
|
56
|
-
self.server.auth_code = query_components[
|
57
|
-
|
57
|
+
if "code" in query_components:
|
58
|
+
self.server.auth_code = query_components["code"][0] # type: ignore
|
59
|
+
|
58
60
|
# Display a success message to the user
|
59
|
-
self.wfile.write(
|
60
|
-
|
61
|
+
self.wfile.write(
|
62
|
+
b"Successfully authenticated to Looker OAuth! You can close this window."
|
63
|
+
)
|
64
|
+
|
61
65
|
# Shutdown the server
|
62
66
|
threading.Thread(target=self.server.shutdown).start()
|
63
67
|
|
@@ -65,18 +69,22 @@ class OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
65
69
|
"""Suppress logging of requests"""
|
66
70
|
pass
|
67
71
|
|
72
|
+
|
68
73
|
class OAuthCallbackServer(socketserver.TCPServer):
|
69
74
|
def __init__(self, server_address):
|
70
75
|
super().__init__(server_address, OAuthCallbackHandler)
|
71
76
|
self.auth_code: str | None = None
|
72
77
|
|
78
|
+
|
73
79
|
class LoginResponse(TypedDict):
|
74
80
|
auth_code: Optional[str]
|
75
81
|
code_verifier: Optional[str]
|
76
82
|
|
83
|
+
|
77
84
|
class OAuth2PKCE:
|
78
85
|
def __init__(self, new_token_callback: NewTokenCallback, use_production: bool):
|
79
86
|
from lkr.auth_service import DbOAuthSession
|
87
|
+
|
80
88
|
self.auth_code: Optional[str] = None
|
81
89
|
self.state = secrets.token_urlsafe(16)
|
82
90
|
self.new_token_callback: NewTokenCallback = new_token_callback
|
@@ -85,7 +93,7 @@ class OAuth2PKCE:
|
|
85
93
|
self.server: OAuthCallbackServer | None = None
|
86
94
|
self.port: int = 8000
|
87
95
|
self.use_production: bool = use_production
|
88
|
-
|
96
|
+
|
89
97
|
def cleanup(self):
|
90
98
|
"""Clean up the server and its thread."""
|
91
99
|
if self.server:
|
@@ -100,51 +108,64 @@ class OAuth2PKCE:
|
|
100
108
|
"""
|
101
109
|
Initiates the OAuth2 PKCE login flow by opening the browser with the authorization URL
|
102
110
|
and starting a local server to catch the callback.
|
103
|
-
|
111
|
+
|
104
112
|
Returns:
|
105
113
|
Optional[str]: The authorization code if successful, None otherwise
|
106
114
|
"""
|
107
115
|
from lkr.auth_service import get_auth_session
|
108
|
-
|
116
|
+
|
109
117
|
# Kill any process using port 8000
|
110
118
|
kill_process_on_port(self.port)
|
111
|
-
|
119
|
+
|
112
120
|
# Start the local server
|
113
|
-
self.server = OAuthCallbackServer((
|
114
|
-
|
121
|
+
self.server = OAuthCallbackServer(("localhost", self.port))
|
122
|
+
|
115
123
|
# Start the server in a separate thread
|
116
124
|
self.server_thread = threading.Thread(target=self.server.serve_forever)
|
117
125
|
self.server_thread.daemon = True
|
118
126
|
self.server_thread.start()
|
119
127
|
|
120
128
|
# Construct and open the OAuth URL
|
121
|
-
self.auth_session = get_auth_session(
|
122
|
-
|
123
|
-
|
129
|
+
self.auth_session = get_auth_session(
|
130
|
+
base_url, self.new_token_callback, use_production=self.use_production
|
131
|
+
)
|
132
|
+
oauth_url = self.auth_session.create_auth_code_request_url(
|
133
|
+
"cors_api", self.state
|
134
|
+
)
|
135
|
+
|
124
136
|
webbrowser.open(oauth_url)
|
125
|
-
|
137
|
+
|
126
138
|
# Wait for the callback
|
127
139
|
self.server_thread.join()
|
128
|
-
|
140
|
+
|
129
141
|
# Get the authorization code
|
130
|
-
return LoginResponse(
|
142
|
+
return LoginResponse(
|
143
|
+
auth_code=self.server.auth_code,
|
144
|
+
code_verifier=self.auth_session.code_verifier,
|
145
|
+
)
|
131
146
|
|
132
147
|
def exchange_code_for_token(self):
|
133
148
|
"""
|
134
149
|
Exchange the authorization code for access and refresh tokens.
|
135
|
-
|
150
|
+
|
136
151
|
Args:
|
137
152
|
base_url: The base URL of the Looker instance
|
138
153
|
client_id: The OAuth client ID
|
139
|
-
|
154
|
+
|
140
155
|
Returns:
|
141
156
|
Dict containing access_token, refresh_token, token_type, and expires_in
|
142
157
|
"""
|
143
158
|
if not self.auth_code:
|
144
|
-
raise ValueError(
|
159
|
+
raise ValueError(
|
160
|
+
"No authorization code available. Must call initiate_login first."
|
161
|
+
)
|
145
162
|
if not self.auth_session:
|
146
|
-
raise ValueError(
|
147
|
-
|
163
|
+
raise ValueError(
|
164
|
+
"No auth session available. Must call initiate_login first."
|
165
|
+
)
|
166
|
+
self.auth_session.redeem_auth_code(
|
167
|
+
self.auth_code, self.auth_session.code_verifier
|
168
|
+
)
|
148
169
|
self.cleanup()
|
149
170
|
return self.auth_session.token
|
150
171
|
|
lkr/auth_service.py
CHANGED
@@ -20,7 +20,7 @@ from lkr.constants import LOOKER_API_VERSION, OAUTH_CLIENT_ID, OAUTH_REDIRECT_UR
|
|
20
20
|
from lkr.logging import logger
|
21
21
|
from lkr.types import NewTokenCallback
|
22
22
|
|
23
|
-
__all__ = ["get_auth"]
|
23
|
+
__all__ = ["get_auth", "ApiKeyAuthSession", "DbOAuthSession"]
|
24
24
|
|
25
25
|
|
26
26
|
def get_auth(ctx: typer.Context | LkrCtxObj) -> Union["SqlLiteAuth", "ApiKeyAuth"]:
|
@@ -33,7 +33,7 @@ def get_auth(ctx: typer.Context | LkrCtxObj) -> Union["SqlLiteAuth", "ApiKeyAuth
|
|
33
33
|
raise typer.Exit(1)
|
34
34
|
if lkr_ctx.use_sdk == "api_key" and lkr_ctx.api_key:
|
35
35
|
logger.info("Using API key authentication")
|
36
|
-
return ApiKeyAuth(lkr_ctx.api_key)
|
36
|
+
return ApiKeyAuth(lkr_ctx.api_key, use_production=lkr_ctx.use_production)
|
37
37
|
else:
|
38
38
|
return SqlLiteAuth(lkr_ctx)
|
39
39
|
|
@@ -70,6 +70,36 @@ class OAuthApiSettings(ApiSettings):
|
|
70
70
|
)
|
71
71
|
|
72
72
|
|
73
|
+
class ApiKeyAuthSession(AuthSession):
|
74
|
+
def __init__(self, *args, use_production: bool, **kwargs):
|
75
|
+
super().__init__(*args, **kwargs)
|
76
|
+
self.use_production = use_production
|
77
|
+
|
78
|
+
def _login(self, *args, **kwargs):
|
79
|
+
super()._login(*args, **kwargs)
|
80
|
+
if not self.use_production:
|
81
|
+
self._switch_to_dev_mode()
|
82
|
+
|
83
|
+
def _switch_to_dev_mode(self):
|
84
|
+
logger.debug("Switching to dev mode")
|
85
|
+
config = self.settings.read_config()
|
86
|
+
if "base_url" in config:
|
87
|
+
url = f"{config['base_url']}/api/{LOOKER_API_VERSION}/session"
|
88
|
+
return self.transport.request(
|
89
|
+
method=HttpMethod.PATCH,
|
90
|
+
path=url,
|
91
|
+
body=json.dumps({"workspace_id": "dev"}).encode("utf-8"),
|
92
|
+
transport_options={
|
93
|
+
"headers": {
|
94
|
+
"Content-Type": "application/json",
|
95
|
+
"Authorization": f"Bearer {self.token.access_token}",
|
96
|
+
}
|
97
|
+
},
|
98
|
+
)
|
99
|
+
else:
|
100
|
+
raise ValueError("Base URL not found in settings")
|
101
|
+
|
102
|
+
|
73
103
|
class DbOAuthSession(OAuthSession):
|
74
104
|
def __init__(
|
75
105
|
self,
|
@@ -137,18 +167,19 @@ def get_auth_session(
|
|
137
167
|
return auth
|
138
168
|
|
139
169
|
|
140
|
-
def init_api_key_sdk(api_key: LookerApiKey) -> Looker40SDK:
|
170
|
+
def init_api_key_sdk(api_key: LookerApiKey, use_production: bool) -> Looker40SDK:
|
141
171
|
from looker_sdk.rtl import serialize
|
142
172
|
|
143
173
|
settings = ApiKeyApiSettings(api_key)
|
144
174
|
settings.is_configured()
|
145
175
|
transport = RequestsTransport.configure(settings)
|
146
176
|
return Looker40SDK(
|
147
|
-
auth=
|
177
|
+
auth=ApiKeyAuthSession(
|
148
178
|
settings,
|
149
179
|
transport,
|
150
180
|
serialize.deserialize40, # type: ignore
|
151
181
|
LOOKER_API_VERSION,
|
182
|
+
use_production=use_production,
|
152
183
|
),
|
153
184
|
deserialize=serialize.deserialize40, # type: ignore
|
154
185
|
serialize=serialize.serialize40, # type: ignore
|
@@ -395,9 +426,7 @@ class SqlLiteAuth:
|
|
395
426
|
return current_auth.instance_name
|
396
427
|
return None
|
397
428
|
|
398
|
-
def get_current_sdk(
|
399
|
-
self, prompt_refresh_invalid_token: bool = False
|
400
|
-
) -> Looker40SDK:
|
429
|
+
def get_current_sdk(self, prompt_refresh_invalid_token: bool = True) -> Looker40SDK:
|
401
430
|
current_auth = self._get_current_auth()
|
402
431
|
if current_auth:
|
403
432
|
if not current_auth.valid_refresh_token:
|
@@ -480,8 +509,9 @@ class SqlLiteAuth:
|
|
480
509
|
|
481
510
|
|
482
511
|
class ApiKeyAuth:
|
483
|
-
def __init__(self, api_key: LookerApiKey):
|
512
|
+
def __init__(self, api_key: LookerApiKey, use_production: bool):
|
484
513
|
self.api_key = api_key
|
514
|
+
self.use_production = use_production
|
485
515
|
|
486
516
|
def __enter__(self):
|
487
517
|
return self
|
@@ -518,7 +548,7 @@ class ApiKeyAuth:
|
|
518
548
|
)
|
519
549
|
|
520
550
|
def get_current_sdk(self, **kwargs) -> Looker40SDK:
|
521
|
-
return init_api_key_sdk(self.api_key)
|
551
|
+
return init_api_key_sdk(self.api_key, self.use_production)
|
522
552
|
|
523
553
|
def get_current_instance(self) -> str | None:
|
524
554
|
raise NotImplementedError(
|
lkr/classes.py
CHANGED
@@ -13,24 +13,25 @@ class LookerApiKey(BaseModel):
|
|
13
13
|
def from_env(cls):
|
14
14
|
try:
|
15
15
|
return cls(
|
16
|
-
client_id=os.environ.get("LOOKERSDK_CLIENT_ID"),
|
17
|
-
client_secret=os.environ.get("LOOKERSDK_CLIENT_SECRET"),
|
18
|
-
base_url=os.environ.get("LOOKERSDK_BASE_URL"),
|
16
|
+
client_id=os.environ.get("LOOKERSDK_CLIENT_ID"), # type: ignore
|
17
|
+
client_secret=os.environ.get("LOOKERSDK_CLIENT_SECRET"), # type: ignore
|
18
|
+
base_url=os.environ.get("LOOKERSDK_BASE_URL"), # type: ignore
|
19
19
|
)
|
20
20
|
except Exception:
|
21
21
|
return None
|
22
22
|
|
23
|
-
|
23
|
+
|
24
24
|
class LkrCtxObj(BaseModel):
|
25
25
|
api_key: LookerApiKey | None
|
26
26
|
force_oauth: bool = False
|
27
|
-
|
27
|
+
use_production: bool = True
|
28
|
+
|
28
29
|
@property
|
29
30
|
def use_sdk(self) -> Literal["oauth", "api_key"]:
|
30
31
|
if self.force_oauth:
|
31
32
|
return "oauth"
|
32
33
|
return "api_key" if self.api_key else "oauth"
|
33
|
-
|
34
|
+
|
34
35
|
def __init__(self, api_key: LookerApiKey | None = None, *args, **kwargs):
|
35
36
|
super().__init__(api_key=api_key, *args, **kwargs)
|
36
37
|
if not self.api_key:
|
lkr/main.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import os
|
2
|
-
from typing import Annotated
|
2
|
+
from typing import Annotated, Optional
|
3
3
|
|
4
4
|
import typer
|
5
5
|
|
@@ -30,6 +30,7 @@ def callback(
|
|
30
30
|
log_level: Annotated[LogLevel | None, typer.Option(envvar="LOG_LEVEL")] = None,
|
31
31
|
quiet: Annotated[bool, typer.Option("--quiet")] = False,
|
32
32
|
force_oauth: Annotated[bool, typer.Option("--force-oauth")] = False,
|
33
|
+
dev: Annotated[Optional[bool], typer.Option("--dev")] = None,
|
33
34
|
):
|
34
35
|
if client_id:
|
35
36
|
os.environ["LOOKERSDK_CLIENT_ID"] = client_id
|
@@ -43,7 +44,16 @@ def callback(
|
|
43
44
|
# Initialize ctx.obj as a dictionary if it's None
|
44
45
|
if ctx.obj is None:
|
45
46
|
ctx.obj = {}
|
46
|
-
|
47
|
+
|
48
|
+
ctx_obj = LkrCtxObj(
|
49
|
+
force_oauth=force_oauth,
|
50
|
+
use_production=not dev if dev is not None else True,
|
51
|
+
)
|
52
|
+
ctx.obj["ctx_lkr"] = ctx_obj
|
53
|
+
# if the user passes --dev, but lkrCtxObj.use_sdk is oauth, then we need to log a warning saying we're ignoring the --dev flag
|
54
|
+
if dev and ctx_obj.use_sdk == "oauth":
|
55
|
+
logger.warning("Ignoring --dev flag because OAuth token tracks dev/prod mode.")
|
56
|
+
|
47
57
|
if log_level:
|
48
58
|
from lkr.logging import set_log_level
|
49
59
|
|
lkr/mcp/main.py
CHANGED
@@ -101,7 +101,7 @@ class SpectaclesRequest(BaseModel):
|
|
101
101
|
|
102
102
|
|
103
103
|
def get_mcp_sdk(ctx: LkrCtxObj | typer.Context):
|
104
|
-
sdk = get_auth(ctx).get_current_sdk()
|
104
|
+
sdk = get_auth(ctx).get_current_sdk(prompt_refresh_invalid_token=False)
|
105
105
|
sdk.auth.settings.agent_tag += "-mcp"
|
106
106
|
return sdk
|
107
107
|
|
@@ -32,10 +32,12 @@
|
|
32
32
|
const urlParams = new URLSearchParams(window.location.search);
|
33
33
|
const iframeUrl = urlParams.get('iframe_url');
|
34
34
|
const origin = new URL(iframeUrl).origin;
|
35
|
-
console.log(origin)
|
36
|
-
console.log(iframeUrl)
|
37
35
|
const sessionId = urlParams.get('session_id');
|
36
|
+
const debug = urlParams.get('debug');
|
38
37
|
// Set the iframe source
|
38
|
+
if (debug) {
|
39
|
+
console.log({debug, iframeUrl, origin, sessionId})
|
40
|
+
}
|
39
41
|
document.getElementById('looker-iframe').src = iframeUrl;
|
40
42
|
|
41
43
|
// Track which events we've received
|
@@ -55,7 +57,7 @@
|
|
55
57
|
return;
|
56
58
|
}
|
57
59
|
const {type, ...data} = JSON.parse(event.data)
|
58
|
-
if (!trackedEvents.has(type)) {
|
60
|
+
if (!trackedEvents.has(type) && !debug) {
|
59
61
|
return;
|
60
62
|
}
|
61
63
|
if (type) {
|
@@ -65,6 +67,9 @@
|
|
65
67
|
event_data: data,
|
66
68
|
timestamp: now.toISOString(),
|
67
69
|
};
|
70
|
+
if (debug) {
|
71
|
+
console.log(eventData)
|
72
|
+
}
|
68
73
|
// Send event data to the server
|
69
74
|
fetch(`/log_event?session_id=${sessionId}`, {
|
70
75
|
method: 'POST',
|
lkr/observability/main.py
CHANGED
@@ -6,7 +6,7 @@ from uuid import uuid4
|
|
6
6
|
|
7
7
|
import typer
|
8
8
|
import uvicorn
|
9
|
-
from fastapi import Depends, FastAPI, HTTPException, Query
|
9
|
+
from fastapi import Depends, FastAPI, HTTPException, Query, Request
|
10
10
|
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
11
11
|
from pydash import get
|
12
12
|
from selenium import webdriver
|
@@ -17,6 +17,7 @@ from selenium.webdriver.remote.webdriver import WebDriver
|
|
17
17
|
from selenium.webdriver.support import expected_conditions as EC
|
18
18
|
from selenium.webdriver.support.ui import WebDriverWait
|
19
19
|
|
20
|
+
from lkr.logging import structured_logger
|
20
21
|
from lkr.observability.classes import (
|
21
22
|
EmbedSDKObj,
|
22
23
|
IframeRequestEvent,
|
@@ -52,7 +53,17 @@ def get_embed_sdk_obj(
|
|
52
53
|
user_attributes: str = Query(default="{}"),
|
53
54
|
secret_id: str = Query(default=None),
|
54
55
|
):
|
55
|
-
|
56
|
+
try:
|
57
|
+
user_attributes_dict = json.loads(user_attributes)
|
58
|
+
except json.JSONDecodeError as e:
|
59
|
+
structured_logger.error(
|
60
|
+
"JSONDecodeError: Invalid user attributes",
|
61
|
+
error=str(e),
|
62
|
+
user_attributes=user_attributes,
|
63
|
+
)
|
64
|
+
return None
|
65
|
+
except Exception:
|
66
|
+
return None
|
56
67
|
return EmbedSDKObj(
|
57
68
|
dashboard_id=dashboard_id,
|
58
69
|
external_user_id=external_user_id,
|
@@ -122,13 +133,19 @@ def settings():
|
|
122
133
|
|
123
134
|
@app.get("/health")
|
124
135
|
def health_check(
|
125
|
-
|
136
|
+
request: Request,
|
137
|
+
params: EmbedSDKObj | None = Depends(get_embed_sdk_obj),
|
126
138
|
open: bool = Query(default=False),
|
127
139
|
):
|
128
140
|
"""
|
129
141
|
Launch a headless browser, open the embed container, and wait for the completion indicator.
|
130
142
|
Returns health status and timing info.
|
131
143
|
"""
|
144
|
+
if not params:
|
145
|
+
raise HTTPException(
|
146
|
+
status_code=400,
|
147
|
+
detail=f"Invalid parameters: {str(request.query_params)}",
|
148
|
+
)
|
132
149
|
session_id = str(uuid4())
|
133
150
|
redirect = False
|
134
151
|
observability_ctx.external_user_id = params.external_user_id
|
@@ -141,14 +158,30 @@ def health_check(
|
|
141
158
|
params.model_dump(mode="json"), "health_check_start", session_id
|
142
159
|
)
|
143
160
|
chrome_options = Options()
|
161
|
+
chrome_options.set_capability("goog:loggingPrefs", {"browser": "ALL"})
|
144
162
|
chrome_options.add_argument("--headless=new")
|
145
163
|
chrome_options.add_argument("--no-sandbox")
|
164
|
+
chrome_options.add_argument("--disable-dev-shm-usage")
|
165
|
+
chrome_options.add_argument("--disable-gpu")
|
166
|
+
chrome_options.add_argument("--enable-logging")
|
167
|
+
chrome_options.add_argument("--v=1")
|
168
|
+
|
169
|
+
chrome_options.add_experimental_option(
|
170
|
+
"prefs",
|
171
|
+
{
|
172
|
+
"profile.default_content_settings.cookies": 1,
|
173
|
+
"profile.cookie_controls_mode": 0,
|
174
|
+
},
|
175
|
+
)
|
146
176
|
driver = webdriver.Chrome(options=chrome_options)
|
147
177
|
url = observability_ctx.sdk.create_sso_embed_url(
|
148
178
|
body=params.to_embed_sso_params(
|
149
179
|
observability_ctx.origin, observability_ctx.base_url or ""
|
150
180
|
)
|
151
181
|
)
|
182
|
+
observability_ctx.log_event(
|
183
|
+
{"sso_url": url.url}, "create_sso_embed_url", session_id
|
184
|
+
)
|
152
185
|
|
153
186
|
if not (url and url.url):
|
154
187
|
raise HTTPException(status_code=500, detail="No URL found")
|
@@ -158,11 +191,16 @@ def health_check(
|
|
158
191
|
else:
|
159
192
|
quoted_url = quote(url.url, safe="")
|
160
193
|
embed_url = f"{observability_ctx.origin}/?iframe_url={quoted_url}&session_id={session_id}"
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
194
|
+
driver.get(embed_url)
|
195
|
+
observability_ctx.log_event(
|
196
|
+
{"url": embed_url}, "chromium_driver_get", session_id
|
197
|
+
)
|
198
|
+
WebDriverWait(driver, observability_ctx.timeout).until(
|
199
|
+
EC.presence_of_element_located((By.ID, "completion-indicator"))
|
200
|
+
)
|
201
|
+
observability_ctx.log_event(
|
202
|
+
{"session_id": session_id}, "chromium_driver_get_complete", session_id
|
203
|
+
)
|
166
204
|
|
167
205
|
except TimeoutException:
|
168
206
|
observability_ctx.log_event(
|
@@ -192,6 +230,9 @@ def health_check(
|
|
192
230
|
session_id,
|
193
231
|
)
|
194
232
|
finally:
|
233
|
+
observability_ctx.log_event(
|
234
|
+
{"session_id": session_id}, "health_check_complete", session_id
|
235
|
+
)
|
195
236
|
if driver:
|
196
237
|
driver.quit()
|
197
238
|
if not redirect:
|
@@ -0,0 +1,21 @@
|
|
1
|
+
lkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
lkr/auth_service.py,sha256=gwKbNXa2v6oSc01h7Vlhrld12uQgrFd5NkbxYnxjGS0,19140
|
3
|
+
lkr/classes.py,sha256=f2TJOXFta0s8LJLEXOqPdWPLg-EIIntUSDS6gDOon7M,1163
|
4
|
+
lkr/constants.py,sha256=DdCfsV6q8wgs2iHpIQeb6oDP_2XejusEHyPvCbaM3yY,108
|
5
|
+
lkr/exceptions.py,sha256=M_aR4YaCZtY4wyxhcoqJCVkxVu9z3Wwo5KgSDyOoEnI,210
|
6
|
+
lkr/logging.py,sha256=bBGdxkVrUidRTJAvqGHyQ5KS6IJGYDCoool5tTB_JxM,1822
|
7
|
+
lkr/main.py,sha256=R3WSyj1AklinWE7wntuV9JSO_a6SsPhE3iOJE1nUn30,2371
|
8
|
+
lkr/types.py,sha256=feJ-W2U61PJTiotMLuZJqxrotA53er95kO1O30mooy4,323
|
9
|
+
lkr/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
lkr/auth/main.py,sha256=ebGp-NdgULodHaDrLKspnGSFf_sOkV2Vanil1NbOJKY,7382
|
11
|
+
lkr/auth/oauth.py,sha256=THynLg8ejAPl4RJ7q4PugEsLq4FFB0wKCHhDeNO9NDo,6106
|
12
|
+
lkr/mcp/main.py,sha256=91LQ8Ny6VTOBRRcDI58Z1c8na9TyfgJfZ8nPjmt1n6w,22887
|
13
|
+
lkr/observability/classes.py,sha256=5kQnHxa3pgUartrbccuG0yMClfoFqYCXY8iCTJTZvgk,6025
|
14
|
+
lkr/observability/embed_container.html,sha256=IcDG-QVsYYNGQGrkDrx9OMZ2Pmo4C8oAjRHddFQ7Tlw,2939
|
15
|
+
lkr/observability/main.py,sha256=1KCmJQLb6ht2-l2TXlN8Y8CCfpIP_E5TcMTy2VMaboA,9545
|
16
|
+
lkr/observability/utils.py,sha256=Hv3g60cI03cQwyEe8QV7bUMssE6pyrZKnDyl9rxm5b8,2915
|
17
|
+
lkr_dev_cli-0.0.23.dist-info/METADATA,sha256=M-UniLnNT-vHWfPc5xC4h09X5WzmSmjkwh5BQZrfr2o,10663
|
18
|
+
lkr_dev_cli-0.0.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
19
|
+
lkr_dev_cli-0.0.23.dist-info/entry_points.txt,sha256=nn2sFMGDpwUVE61ZUpbDPnQZkW7Gc08nV-tyLGo8q34,37
|
20
|
+
lkr_dev_cli-0.0.23.dist-info/licenses/LICENSE,sha256=hKnCOORW1JRE_M2vStz8dblS5u1iR-2VpqS9xagKNa0,1063
|
21
|
+
lkr_dev_cli-0.0.23.dist-info/RECORD,,
|
lkr/utils.py
DELETED
File without changes
|
@@ -1,22 +0,0 @@
|
|
1
|
-
lkr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
lkr/auth_service.py,sha256=_enIsYxpL1IILtBfNG4YAA_H9cqwnwcvrGMyfmzgH7U,17815
|
3
|
-
lkr/classes.py,sha256=0NKttc5kAhW_omuxwnN9Jy6oQnRKgI7b2c7HZUfI0lc,1144
|
4
|
-
lkr/constants.py,sha256=DdCfsV6q8wgs2iHpIQeb6oDP_2XejusEHyPvCbaM3yY,108
|
5
|
-
lkr/exceptions.py,sha256=M_aR4YaCZtY4wyxhcoqJCVkxVu9z3Wwo5KgSDyOoEnI,210
|
6
|
-
lkr/logging.py,sha256=bBGdxkVrUidRTJAvqGHyQ5KS6IJGYDCoool5tTB_JxM,1822
|
7
|
-
lkr/main.py,sha256=5m036LAS4PTLIzpWW5ohxIs2FGYtVf-OwYnTmtrpnxU,1933
|
8
|
-
lkr/types.py,sha256=feJ-W2U61PJTiotMLuZJqxrotA53er95kO1O30mooy4,323
|
9
|
-
lkr/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
lkr/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
lkr/auth/main.py,sha256=ebGp-NdgULodHaDrLKspnGSFf_sOkV2Vanil1NbOJKY,7382
|
12
|
-
lkr/auth/oauth.py,sha256=-QCb7YGIqmuTiy6SZcUTbVZrDo7itOpUUOK1VuvfMqM,5987
|
13
|
-
lkr/embed/observability/utils.py,sha256=Hv3g60cI03cQwyEe8QV7bUMssE6pyrZKnDyl9rxm5b8,2915
|
14
|
-
lkr/mcp/main.py,sha256=39VO0_bdscrCW7BC3kdlgjfIWaNigpT38HZ74_Drm-A,22853
|
15
|
-
lkr/observability/classes.py,sha256=5kQnHxa3pgUartrbccuG0yMClfoFqYCXY8iCTJTZvgk,6025
|
16
|
-
lkr/observability/embed_container.html,sha256=BMzV_jfBhcussUExf0KccOmOTAO3UEV7SuZ4lznQ0no,2758
|
17
|
-
lkr/observability/main.py,sha256=Oo9ro6jCm_qSG4Fv377pS2BWW-SIjtbhGLGGFMrlRCA,8036
|
18
|
-
lkr_dev_cli-0.0.21.dist-info/METADATA,sha256=7lD040SjtJdLv8JGASaFxtBbu4NcA8maWEo2XZGv3TM,10663
|
19
|
-
lkr_dev_cli-0.0.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
20
|
-
lkr_dev_cli-0.0.21.dist-info/entry_points.txt,sha256=nn2sFMGDpwUVE61ZUpbDPnQZkW7Gc08nV-tyLGo8q34,37
|
21
|
-
lkr_dev_cli-0.0.21.dist-info/licenses/LICENSE,sha256=hKnCOORW1JRE_M2vStz8dblS5u1iR-2VpqS9xagKNa0,1063
|
22
|
-
lkr_dev_cli-0.0.21.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|