lkr-dev-cli 0.0.40__tar.gz → 0.0.42__tar.gz
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_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/PKG-INFO +45 -3
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/README.md +44 -2
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/codemode.md +10 -1
- lkr_dev_cli-0.0.42/lkr/__init__.py +5 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/auth_service.py +62 -1
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/classes.py +4 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/main.py +44 -10
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/extended_sdk_methods/__init__.py +2 -0
- lkr_dev_cli-0.0.42/lkr/tools/lookml.py +386 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/tools/main.py +3 -0
- lkr_dev_cli-0.0.42/lkr_dev_cli/__init__.py +5 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/pyproject.toml +2 -2
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/tests/test_codemode.py +35 -0
- lkr_dev_cli-0.0.42/tests/test_init_sdk.py +167 -0
- lkr_dev_cli-0.0.42/tests/test_lookml_tools.py +268 -0
- lkr_dev_cli-0.0.40/lkr/__init__.py +0 -3
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/.github/workflows/ci.yml +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/.github/workflows/release.yml +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/.gitignore +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/.python-version +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/.vscode/launch.json +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/.vscode/settings.json +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/Dockerfile +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/LICENSE +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/Makefile +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/cloudbuild.yaml +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/auth/__init__.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/auth/main.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/auth/oauth.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/LOCAL.md +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/__init__.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/constant.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/download_swagger.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/examples.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/help.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/readme.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/swagger.json +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/codemode/type.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/constants.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/custom_types.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/exceptions.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/extended_sdk_methods/classes.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/extended_sdk_methods/main.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/logger.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/main.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/mcp/classes.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/mcp/main.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/mcp/utils.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/observability/classes.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/observability/embed_container.html +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/observability/main.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/observability/utils.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/tools/classes.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr/tools/permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/lkr.md +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/tests/TESTING.md +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/tests/test_dependency_resolution.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/tests/test_deps.sh +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/tests/test_extended_sdk_methods.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/tests/test_oauth_account.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/tests/test_permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/ty.toml +0 -0
- {lkr_dev_cli-0.0.40 → lkr_dev_cli-0.0.42}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lkr-dev-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.42
|
|
4
4
|
Summary: lkr: a command line interface for looker
|
|
5
5
|
Author: bwebs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -288,9 +288,51 @@ gcloud run deploy lkr-access-token-updater \
|
|
|
288
288
|
--set-env-vars LOOKERSDK_CLIENT_ID=<your client id>,LOOKERSDK_CLIENT_SECRET=<your client secret>,LOOKERSDK_BASE_URL=<your instance url>,LOOKER_WHITELISTED_BASE_URLS=<your instance url>
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
-
##
|
|
291
|
+
## Python SDK & Library Usage
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
When installing `lkr-dev-cli` in your Python environment, you can import tools and pre-authenticated Looker SDK clients directly into your scripts, apps, or Jupyter notebooks.
|
|
294
|
+
|
|
295
|
+
### Package Import Aliases (`lkr` vs `lkr_dev_cli`)
|
|
296
|
+
To maximize convenience and match user intuition, both `lkr` and `lkr_dev_cli` are available as identical top-level Python packages. You can use them completely interchangeably:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from lkr import init_sdk, UserAttributeUpdater
|
|
300
|
+
# works exactly identical to:
|
|
301
|
+
from lkr_dev_cli import init_sdk, UserAttributeUpdater
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Initializing the Looker SDK (`init_sdk`)
|
|
305
|
+
The `init_sdk()` function initializes and returns an authenticated `ExtendedLooker40SDK` instance. It automatically handles credential discovery and authentication:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from lkr_dev_cli import init_sdk
|
|
309
|
+
|
|
310
|
+
# Option 1: Explicit API Authentication
|
|
311
|
+
sdk = init_sdk(
|
|
312
|
+
base_url="https://acme.cloud.looker.com",
|
|
313
|
+
client_id="your-client-id",
|
|
314
|
+
client_secret="your-client-secret",
|
|
315
|
+
verify_ssl=True, # optional, defaults to True
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# Option 2: Environment Variable API Authentication
|
|
319
|
+
# Automatically uses LOOKERSDK_BASE_URL, LOOKERSDK_CLIENT_ID, LOOKERSDK_CLIENT_SECRET
|
|
320
|
+
sdk = init_sdk()
|
|
321
|
+
|
|
322
|
+
# Option 3: Database (SQLite) OAuth Login Profile
|
|
323
|
+
# Uses your saved active session from running `lkr auth login`
|
|
324
|
+
sdk = init_sdk() # Picks the active current_instance = 1
|
|
325
|
+
# Or pick a specific named login profile:
|
|
326
|
+
sdk = init_sdk(instance_name="my_dev_instance")
|
|
327
|
+
|
|
328
|
+
# Make an API call
|
|
329
|
+
me = sdk.me()
|
|
330
|
+
print(f"Hello, {me.display_name}")
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### UserAttributeUpdater
|
|
334
|
+
|
|
335
|
+
Exported from the package is the `UserAttributeUpdater` pydantic class. This class has all the necessary logic to update a user attribute value.
|
|
294
336
|
|
|
295
337
|
It supports the following operations:
|
|
296
338
|
- Updating a default value
|
|
@@ -252,9 +252,51 @@ gcloud run deploy lkr-access-token-updater \
|
|
|
252
252
|
--set-env-vars LOOKERSDK_CLIENT_ID=<your client id>,LOOKERSDK_CLIENT_SECRET=<your client secret>,LOOKERSDK_BASE_URL=<your instance url>,LOOKER_WHITELISTED_BASE_URLS=<your instance url>
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
##
|
|
255
|
+
## Python SDK & Library Usage
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
When installing `lkr-dev-cli` in your Python environment, you can import tools and pre-authenticated Looker SDK clients directly into your scripts, apps, or Jupyter notebooks.
|
|
258
|
+
|
|
259
|
+
### Package Import Aliases (`lkr` vs `lkr_dev_cli`)
|
|
260
|
+
To maximize convenience and match user intuition, both `lkr` and `lkr_dev_cli` are available as identical top-level Python packages. You can use them completely interchangeably:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from lkr import init_sdk, UserAttributeUpdater
|
|
264
|
+
# works exactly identical to:
|
|
265
|
+
from lkr_dev_cli import init_sdk, UserAttributeUpdater
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Initializing the Looker SDK (`init_sdk`)
|
|
269
|
+
The `init_sdk()` function initializes and returns an authenticated `ExtendedLooker40SDK` instance. It automatically handles credential discovery and authentication:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from lkr_dev_cli import init_sdk
|
|
273
|
+
|
|
274
|
+
# Option 1: Explicit API Authentication
|
|
275
|
+
sdk = init_sdk(
|
|
276
|
+
base_url="https://acme.cloud.looker.com",
|
|
277
|
+
client_id="your-client-id",
|
|
278
|
+
client_secret="your-client-secret",
|
|
279
|
+
verify_ssl=True, # optional, defaults to True
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Option 2: Environment Variable API Authentication
|
|
283
|
+
# Automatically uses LOOKERSDK_BASE_URL, LOOKERSDK_CLIENT_ID, LOOKERSDK_CLIENT_SECRET
|
|
284
|
+
sdk = init_sdk()
|
|
285
|
+
|
|
286
|
+
# Option 3: Database (SQLite) OAuth Login Profile
|
|
287
|
+
# Uses your saved active session from running `lkr auth login`
|
|
288
|
+
sdk = init_sdk() # Picks the active current_instance = 1
|
|
289
|
+
# Or pick a specific named login profile:
|
|
290
|
+
sdk = init_sdk(instance_name="my_dev_instance")
|
|
291
|
+
|
|
292
|
+
# Make an API call
|
|
293
|
+
me = sdk.me()
|
|
294
|
+
print(f"Hello, {me.display_name}")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### UserAttributeUpdater
|
|
298
|
+
|
|
299
|
+
Exported from the package is the `UserAttributeUpdater` pydantic class. This class has all the necessary logic to update a user attribute value.
|
|
258
300
|
|
|
259
301
|
It supports the following operations:
|
|
260
302
|
- Updating a default value
|
|
@@ -47,5 +47,14 @@ To check things out on a web panel:
|
|
|
47
47
|
npx @modelcontextprotocol/inspector uvx -q --from lkr-dev-cli[codemode] lkr code-mode run
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
## Architectural & Troubleshooting Notes
|
|
51
|
+
|
|
52
|
+
### The stdio Stream Corruption Trap
|
|
53
|
+
When running an MCP server over standard input/output (`stdio`) transport, JSON-RPC packets are transmitted directly through the process's `stdout`.
|
|
54
|
+
|
|
55
|
+
If Python code executed within the server (such as tool execution or third-party libraries) writes raw text directly to `sys.stdout` via standard `print()` calls, that raw text corrupts the JSON-RPC response stream. This causes fatal JSON parsing errors on the client (e.g., `invalid character 'C' looking for beginning of value`) and immediately closes the connection.
|
|
56
|
+
|
|
57
|
+
To safeguard against this:
|
|
58
|
+
1. `run_python_code` automatically wraps all Monty execution in a `redirect_stdout` block.
|
|
59
|
+
2. If any `print()` output is captured, it is safely packaged and returned as part of a valid JSON structure rather than polluting raw `stdout`.
|
|
51
60
|
|
|
@@ -22,7 +22,13 @@ from lkr.constants import LOOKER_API_VERSION, OAUTH_CLIENT_ID
|
|
|
22
22
|
from lkr.custom_types import NewTokenCallback
|
|
23
23
|
from lkr.logger import logger
|
|
24
24
|
|
|
25
|
-
__all__ = [
|
|
25
|
+
__all__ = [
|
|
26
|
+
"get_auth",
|
|
27
|
+
"ApiKeyAuthSession",
|
|
28
|
+
"DbOAuthSession",
|
|
29
|
+
"is_auth_expired",
|
|
30
|
+
"init_sdk",
|
|
31
|
+
]
|
|
26
32
|
|
|
27
33
|
|
|
28
34
|
def is_auth_expired(e: Exception) -> bool:
|
|
@@ -57,6 +63,7 @@ class ApiKeyApiSettings(ApiSettings):
|
|
|
57
63
|
base_url=self.api_key.base_url,
|
|
58
64
|
client_id=self.api_key.client_id,
|
|
59
65
|
client_secret=self.api_key.client_secret,
|
|
66
|
+
verify_ssl=str(self.api_key.verify_ssl).lower(),
|
|
60
67
|
)
|
|
61
68
|
|
|
62
69
|
|
|
@@ -624,3 +631,57 @@ class ApiKeyAuth:
|
|
|
624
631
|
raise NotImplementedError(
|
|
625
632
|
"ApiKeyAuth does not support getting current instance"
|
|
626
633
|
)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def init_sdk(
|
|
637
|
+
base_url: str | None = None,
|
|
638
|
+
client_id: str | None = None,
|
|
639
|
+
client_secret: str | None = None,
|
|
640
|
+
verify_ssl: bool | None = None,
|
|
641
|
+
use_production: bool = True,
|
|
642
|
+
instance_name: str | None = None,
|
|
643
|
+
) -> Looker40SDK:
|
|
644
|
+
resolved_base_url = base_url or os.environ.get("LOOKERSDK_BASE_URL")
|
|
645
|
+
resolved_client_id = client_id or os.environ.get("LOOKERSDK_CLIENT_ID")
|
|
646
|
+
resolved_client_secret = (
|
|
647
|
+
client_secret or os.environ.get("LOOKERSDK_CLIENT_SECRET")
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
resolved_verify_ssl: bool
|
|
651
|
+
if verify_ssl is not None:
|
|
652
|
+
resolved_verify_ssl = verify_ssl
|
|
653
|
+
else:
|
|
654
|
+
if "LOOKERSDK_VERIFY_SSL" in os.environ:
|
|
655
|
+
verify_ssl_str = os.environ.get("LOOKERSDK_VERIFY_SSL", "true").lower()
|
|
656
|
+
resolved_verify_ssl = verify_ssl_str not in ("false", "0", "f")
|
|
657
|
+
else:
|
|
658
|
+
resolved_verify_ssl = True
|
|
659
|
+
|
|
660
|
+
if resolved_base_url or resolved_client_id or resolved_client_secret:
|
|
661
|
+
if not (
|
|
662
|
+
resolved_base_url and resolved_client_id and resolved_client_secret
|
|
663
|
+
):
|
|
664
|
+
missing = []
|
|
665
|
+
if not resolved_base_url:
|
|
666
|
+
missing.append("base_url")
|
|
667
|
+
if not resolved_client_id:
|
|
668
|
+
missing.append("client_id")
|
|
669
|
+
if not resolved_client_secret:
|
|
670
|
+
missing.append("client_secret")
|
|
671
|
+
raise ValueError(
|
|
672
|
+
f"Incomplete API key configuration. Missing: {', '.join(missing)}"
|
|
673
|
+
)
|
|
674
|
+
api_key = LookerApiKey(
|
|
675
|
+
client_id=resolved_client_id,
|
|
676
|
+
client_secret=resolved_client_secret,
|
|
677
|
+
base_url=resolved_base_url,
|
|
678
|
+
verify_ssl=resolved_verify_ssl,
|
|
679
|
+
)
|
|
680
|
+
return init_api_key_sdk(api_key, use_production=use_production)
|
|
681
|
+
|
|
682
|
+
ctx = LkrCtxObj(
|
|
683
|
+
force_oauth=True, oauth_account=instance_name, use_production=use_production
|
|
684
|
+
)
|
|
685
|
+
sql_auth = SqlLiteAuth(ctx)
|
|
686
|
+
return sql_auth.get_current_sdk()
|
|
687
|
+
|
|
@@ -8,14 +8,18 @@ class LookerApiKey(BaseModel):
|
|
|
8
8
|
client_id: str = Field(..., min_length=1)
|
|
9
9
|
client_secret: str = Field(..., min_length=1)
|
|
10
10
|
base_url: str = Field(..., min_length=1)
|
|
11
|
+
verify_ssl: bool = True
|
|
11
12
|
|
|
12
13
|
@classmethod
|
|
13
14
|
def from_env(cls):
|
|
14
15
|
try:
|
|
16
|
+
verify_ssl_str = os.environ.get("LOOKERSDK_VERIFY_SSL", "true").lower()
|
|
17
|
+
verify_ssl = verify_ssl_str not in ("false", "0", "f")
|
|
15
18
|
return cls(
|
|
16
19
|
client_id=os.environ.get("LOOKERSDK_CLIENT_ID"), # type: ignore
|
|
17
20
|
client_secret=os.environ.get("LOOKERSDK_CLIENT_SECRET"), # type: ignore
|
|
18
21
|
base_url=os.environ.get("LOOKERSDK_BASE_URL"), # type: ignore
|
|
22
|
+
verify_ssl=verify_ssl,
|
|
19
23
|
)
|
|
20
24
|
except Exception:
|
|
21
25
|
return None
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
import io
|
|
3
2
|
import json
|
|
4
|
-
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
from contextlib import contextmanager
|
|
5
7
|
|
|
6
8
|
import typer
|
|
7
9
|
import pydantic_monty
|
|
@@ -21,8 +23,38 @@ __all__ = ["group"]
|
|
|
21
23
|
mcp = FastMCP("lkr:codemode")
|
|
22
24
|
group = typer.Typer()
|
|
23
25
|
ctx_lkr: LkrCtxObj | None = None
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
class OSCapture:
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.output = ""
|
|
29
|
+
|
|
30
|
+
@contextmanager
|
|
31
|
+
def capture_os_stdout():
|
|
32
|
+
cap = OSCapture()
|
|
33
|
+
fd, temp_path = tempfile.mkstemp()
|
|
34
|
+
|
|
35
|
+
if sys.stdout and hasattr(sys.stdout, "flush"):
|
|
36
|
+
sys.stdout.flush()
|
|
37
|
+
|
|
38
|
+
original_stdout = sys.stdout
|
|
39
|
+
original_stdout_fd = os.dup(1)
|
|
40
|
+
try:
|
|
41
|
+
os.dup2(fd, 1)
|
|
42
|
+
with os.fdopen(os.dup(1), "w") as f:
|
|
43
|
+
sys.stdout = f
|
|
44
|
+
yield cap
|
|
45
|
+
f.flush()
|
|
46
|
+
finally:
|
|
47
|
+
sys.stdout = original_stdout
|
|
48
|
+
os.dup2(original_stdout_fd, 1)
|
|
49
|
+
os.close(original_stdout_fd)
|
|
50
|
+
os.close(fd)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with open(temp_path, "r") as f:
|
|
54
|
+
cap.output = f.read()
|
|
55
|
+
finally:
|
|
56
|
+
if os.path.exists(temp_path):
|
|
57
|
+
os.remove(temp_path)
|
|
26
58
|
|
|
27
59
|
def get_mcp_sdk(ctx: LkrCtxObj):
|
|
28
60
|
sdk = get_auth(ctx).get_current_sdk(prompt_refresh_invalid_token=True)
|
|
@@ -126,13 +158,12 @@ def run_python_code(code: str, dev_mode: bool = False) -> str:
|
|
|
126
158
|
|
|
127
159
|
m = pydantic_monty.Monty(code)
|
|
128
160
|
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
with redirect_stdout(f):
|
|
161
|
+
# Use low-level OS stdout capture to ensure any print() statements
|
|
162
|
+
# in Rust or sub-interpreters don't corrupt the JSON-RPC stream
|
|
163
|
+
with capture_os_stdout() as cap:
|
|
133
164
|
result = m.run(external_functions=external_funcs)
|
|
134
165
|
|
|
135
|
-
printed_output =
|
|
166
|
+
printed_output = cap.output
|
|
136
167
|
|
|
137
168
|
# m.run() returns the evaluated result of the last expression (which is already a primitive)
|
|
138
169
|
try:
|
|
@@ -148,7 +179,10 @@ def run_python_code(code: str, dev_mode: bool = False) -> str:
|
|
|
148
179
|
output = repr(result)
|
|
149
180
|
|
|
150
181
|
if printed_output:
|
|
151
|
-
return
|
|
182
|
+
return json.dumps({
|
|
183
|
+
"stdout": printed_output,
|
|
184
|
+
"result": result
|
|
185
|
+
}, indent=2, default=str)
|
|
152
186
|
return output
|
|
153
187
|
except Exception as e:
|
|
154
188
|
logger.error(f"Error executing Monty: {e}")
|
|
@@ -8,6 +8,7 @@ from lkr.extended_sdk_methods.classes import (
|
|
|
8
8
|
ProjectGeneratorColumn,
|
|
9
9
|
ProjectGeneratorTable,
|
|
10
10
|
SelectedTable,
|
|
11
|
+
ProjectCommitRequest,
|
|
11
12
|
)
|
|
12
13
|
from lkr.extended_sdk_methods.main import ExtendedLooker40SDK
|
|
13
14
|
|
|
@@ -22,4 +23,5 @@ __all__ = [
|
|
|
22
23
|
"GenerateLookMLParameters",
|
|
23
24
|
"SelectedTable",
|
|
24
25
|
"GenerateLookMLWithNewFilesResponse",
|
|
26
|
+
"ProjectCommitRequest",
|
|
25
27
|
]
|