lkr-dev-cli 0.0.43__tar.gz → 0.0.45__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.
Files changed (63) hide show
  1. lkr_dev_cli-0.0.45/.github/workflows/website.yml +18 -0
  2. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/PKG-INFO +21 -9
  3. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/README.md +20 -8
  4. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/codemode.md +16 -5
  5. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/main.py +60 -2
  6. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/pyproject.toml +1 -1
  7. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_codemode.py +38 -1
  8. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/.github/workflows/ci.yml +0 -0
  9. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/.github/workflows/release.yml +0 -0
  10. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/.gitignore +0 -0
  11. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/.python-version +0 -0
  12. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/.vscode/launch.json +0 -0
  13. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/.vscode/settings.json +0 -0
  14. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/Dockerfile +0 -0
  15. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/LICENSE +0 -0
  16. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/Makefile +0 -0
  17. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/cloudbuild.yaml +0 -0
  18. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/__init__.py +0 -0
  19. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/auth/__init__.py +0 -0
  20. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/auth/main.py +0 -0
  21. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/auth/oauth.py +0 -0
  22. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/auth_service.py +0 -0
  23. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/classes.py +0 -0
  24. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/LOCAL.md +0 -0
  25. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/__init__.py +0 -0
  26. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/constant.py +0 -0
  27. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/download_swagger.py +0 -0
  28. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/examples.py +0 -0
  29. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/help.py +0 -0
  30. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/readme.py +0 -0
  31. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/swagger.json +0 -0
  32. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/codemode/type.py +0 -0
  33. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/constants.py +0 -0
  34. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/custom_types.py +0 -0
  35. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/exceptions.py +0 -0
  36. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/extended_sdk_methods/__init__.py +0 -0
  37. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/extended_sdk_methods/classes.py +0 -0
  38. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/extended_sdk_methods/main.py +0 -0
  39. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/logger.py +0 -0
  40. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/main.py +0 -0
  41. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/mcp/classes.py +0 -0
  42. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/mcp/main.py +0 -0
  43. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/mcp/utils.py +0 -0
  44. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/observability/classes.py +0 -0
  45. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/observability/embed_container.html +0 -0
  46. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/observability/main.py +0 -0
  47. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/observability/utils.py +0 -0
  48. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/tools/classes.py +0 -0
  49. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/tools/lookml.py +0 -0
  50. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/tools/main.py +0 -0
  51. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr/tools/permission_deprecation.py +0 -0
  52. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr.md +0 -0
  53. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/lkr_dev_cli/__init__.py +0 -0
  54. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/TESTING.md +0 -0
  55. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_dependency_resolution.py +0 -0
  56. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_deps.sh +0 -0
  57. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_extended_sdk_methods.py +0 -0
  58. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_init_sdk.py +0 -0
  59. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_lookml_tools.py +0 -0
  60. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_oauth_account.py +0 -0
  61. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/tests/test_permission_deprecation.py +0 -0
  62. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/ty.toml +0 -0
  63. {lkr_dev_cli-0.0.43 → lkr_dev_cli-0.0.45}/uv.lock +0 -0
@@ -0,0 +1,18 @@
1
+ name: Deploy Website
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - "**.md"
9
+
10
+ jobs:
11
+ deploy:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Trigger Website Deploy
15
+ run: |
16
+ curl -fSL "$DEPLOY_URL"
17
+ env:
18
+ DEPLOY_URL: ${{ secrets.DEPLOY_URL }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lkr-dev-cli
3
- Version: 0.0.43
3
+ Version: 0.0.45
4
4
  Summary: lkr: a command line interface for looker
5
5
  Author: bwebs
6
6
  License-Expression: MIT
@@ -40,7 +40,7 @@ The `lkr` cli is a tool for interacting with Looker. It combines Looker's SDK an
40
40
 
41
41
  ## Usage
42
42
 
43
- `uv` makes everyone's life easier. Go [install it](https://docs.astral.sh/uv/getting-started/installation/). You can start using `lkr` by running `uvx --from lkr-dev-cli[all] lkr --help`.
43
+ `uv` makes everyone's life easier. Go [install it](https://docs.astral.sh/uv/getting-started/installation/). You can start using `lkr` by running `uvx lkr-dev-cli --help`.
44
44
 
45
45
  Alternatively, you can install `lkr` with `pip install lkr-dev-cli[all]` and use commands directly like `lkr <command>`.
46
46
 
@@ -60,7 +60,7 @@ See the [prerequisites section](#oauth2-prerequisites)
60
60
  Login to `lkr`
61
61
 
62
62
  ```bash
63
- uvx --from lkr-dev-cli[all] lkr auth login
63
+ uvx lkr-dev-cli auth login
64
64
  ```
65
65
 
66
66
  - Select a new instance
@@ -73,7 +73,7 @@ You will be redirected to the Looker OAuth authorization page, click Allow. If y
73
73
  If everything is successful, you will see `Successfully authenticated!`. Test it with
74
74
 
75
75
  ```bash
76
- uvx --from lkr-dev-cli[all] lkr auth whoami
76
+ uvx lkr-dev-cli auth whoami
77
77
  ```
78
78
 
79
79
  ### Using API Key
@@ -81,7 +81,7 @@ uvx --from lkr-dev-cli[all] lkr auth whoami
81
81
  If you provide environment variables for `LOOKERSDK_CLIENT_ID`, `LOOKERSDK_CLIENT_SECRET`, and `LOOKERSDK_BASE_URL`, `lkr` will use the API key to authenticate and the commands. We also support command line arguments to pass in the client id, client secret, and base url.
82
82
 
83
83
  ```bash
84
- uvx --from lkr-dev-cli[all] lkr --client-id <your client id> --client-secret <your client secret> --base-url <your instance url> auth whoami
84
+ uvx lkr-dev-cli --client-id <your client id> --client-secret <your client secret> --base-url <your instance url> auth whoami
85
85
  ```
86
86
 
87
87
 
@@ -111,7 +111,19 @@ Go to the Looker API Explorer for Register OAuth App (https://your.looker.instan
111
111
 
112
112
  ## Code Mode
113
113
 
114
- Execute Python code safely with full Looker SDK coverage within a secure sandbox environment. Constructed as an MCP tool, it dynamically inspects the Looker SDK for all public methods and injects them into the Monty sandbox safely. For detailed options, safe primitives transformations, and PKCE configurations, view the full [Code Mode Docs](./codemode.md).
114
+ Execute Python code safely with full Looker SDK coverage within a secure sandbox environment (`Monty`). It dynamically inspects the Looker SDK for all public methods and injects them into the sandbox safely, supporting both Model Context Protocol (MCP) server mode and direct standalone CLI execution.
115
+
116
+ ### Direct CLI Execution (Sandbox Mode)
117
+ Run scripts or inline code directly against your active Looker instance:
118
+ ```bash
119
+ # Execute inline Python code
120
+ uvx lkr-dev-cli code-mode sandbox --code="return me()"
121
+
122
+ # Execute a Python script file
123
+ uvx lkr-dev-cli code-mode sandbox --file=./path/to/script.py
124
+ ```
125
+
126
+ For detailed options, safe primitive transformations, MCP server configurations, and PKCE recovery, view the full [Code Mode Docs](./codemode.md).
115
127
 
116
128
  ## MCP
117
129
  Built into the `lkr` is an MCP server. Right now its tools are based on helping you work within an IDE. To use it a tool like [Cursor](https://www.cursor.com/), add this to your mcp.json
@@ -120,8 +132,8 @@ Built into the `lkr` is an MCP server. Right now its tools are based on helping
120
132
  {
121
133
  "mcpServers": {
122
134
  "lkr-mcp": {
123
- "command": "uv",
124
- "args": ["run", "--with", "lkr-dev-cli[all]", "lkr", "mcp", "run"]
135
+ "command": "uvx",
136
+ "args": ["lkr-dev-cli[all]", "mcp", "run"]
125
137
  },
126
138
  "lkr-mcp-docker": {
127
139
  "command": "docker",
@@ -422,7 +434,7 @@ By running this tool, an admin can proactively identify and fix permission gaps
422
434
  This command should be run by a **Looker Admin**.
423
435
 
424
436
  ```bash
425
- uvx --from lkr-dev-cli[all] lkr tools schedule-download-deprecation
437
+ uvx lkr-dev-cli[all] tools schedule-download-deprecation
426
438
  ```
427
439
 
428
440
  Options:
@@ -4,7 +4,7 @@ The `lkr` cli is a tool for interacting with Looker. It combines Looker's SDK an
4
4
 
5
5
  ## Usage
6
6
 
7
- `uv` makes everyone's life easier. Go [install it](https://docs.astral.sh/uv/getting-started/installation/). You can start using `lkr` by running `uvx --from lkr-dev-cli[all] lkr --help`.
7
+ `uv` makes everyone's life easier. Go [install it](https://docs.astral.sh/uv/getting-started/installation/). You can start using `lkr` by running `uvx lkr-dev-cli --help`.
8
8
 
9
9
  Alternatively, you can install `lkr` with `pip install lkr-dev-cli[all]` and use commands directly like `lkr <command>`.
10
10
 
@@ -24,7 +24,7 @@ See the [prerequisites section](#oauth2-prerequisites)
24
24
  Login to `lkr`
25
25
 
26
26
  ```bash
27
- uvx --from lkr-dev-cli[all] lkr auth login
27
+ uvx lkr-dev-cli auth login
28
28
  ```
29
29
 
30
30
  - Select a new instance
@@ -37,7 +37,7 @@ You will be redirected to the Looker OAuth authorization page, click Allow. If y
37
37
  If everything is successful, you will see `Successfully authenticated!`. Test it with
38
38
 
39
39
  ```bash
40
- uvx --from lkr-dev-cli[all] lkr auth whoami
40
+ uvx lkr-dev-cli auth whoami
41
41
  ```
42
42
 
43
43
  ### Using API Key
@@ -45,7 +45,7 @@ uvx --from lkr-dev-cli[all] lkr auth whoami
45
45
  If you provide environment variables for `LOOKERSDK_CLIENT_ID`, `LOOKERSDK_CLIENT_SECRET`, and `LOOKERSDK_BASE_URL`, `lkr` will use the API key to authenticate and the commands. We also support command line arguments to pass in the client id, client secret, and base url.
46
46
 
47
47
  ```bash
48
- uvx --from lkr-dev-cli[all] lkr --client-id <your client id> --client-secret <your client secret> --base-url <your instance url> auth whoami
48
+ uvx lkr-dev-cli --client-id <your client id> --client-secret <your client secret> --base-url <your instance url> auth whoami
49
49
  ```
50
50
 
51
51
 
@@ -75,7 +75,19 @@ Go to the Looker API Explorer for Register OAuth App (https://your.looker.instan
75
75
 
76
76
  ## Code Mode
77
77
 
78
- Execute Python code safely with full Looker SDK coverage within a secure sandbox environment. Constructed as an MCP tool, it dynamically inspects the Looker SDK for all public methods and injects them into the Monty sandbox safely. For detailed options, safe primitives transformations, and PKCE configurations, view the full [Code Mode Docs](./codemode.md).
78
+ Execute Python code safely with full Looker SDK coverage within a secure sandbox environment (`Monty`). It dynamically inspects the Looker SDK for all public methods and injects them into the sandbox safely, supporting both Model Context Protocol (MCP) server mode and direct standalone CLI execution.
79
+
80
+ ### Direct CLI Execution (Sandbox Mode)
81
+ Run scripts or inline code directly against your active Looker instance:
82
+ ```bash
83
+ # Execute inline Python code
84
+ uvx lkr-dev-cli code-mode sandbox --code="return me()"
85
+
86
+ # Execute a Python script file
87
+ uvx lkr-dev-cli code-mode sandbox --file=./path/to/script.py
88
+ ```
89
+
90
+ For detailed options, safe primitive transformations, MCP server configurations, and PKCE recovery, view the full [Code Mode Docs](./codemode.md).
79
91
 
80
92
  ## MCP
81
93
  Built into the `lkr` is an MCP server. Right now its tools are based on helping you work within an IDE. To use it a tool like [Cursor](https://www.cursor.com/), add this to your mcp.json
@@ -84,8 +96,8 @@ Built into the `lkr` is an MCP server. Right now its tools are based on helping
84
96
  {
85
97
  "mcpServers": {
86
98
  "lkr-mcp": {
87
- "command": "uv",
88
- "args": ["run", "--with", "lkr-dev-cli[all]", "lkr", "mcp", "run"]
99
+ "command": "uvx",
100
+ "args": ["lkr-dev-cli[all]", "mcp", "run"]
89
101
  },
90
102
  "lkr-mcp-docker": {
91
103
  "command": "docker",
@@ -386,7 +398,7 @@ By running this tool, an admin can proactively identify and fix permission gaps
386
398
  This command should be run by a **Looker Admin**.
387
399
 
388
400
  ```bash
389
- uvx --from lkr-dev-cli[all] lkr tools schedule-download-deprecation
401
+ uvx lkr-dev-cli[all] tools schedule-download-deprecation
390
402
  ```
391
403
 
392
404
  Options:
@@ -16,10 +16,21 @@ The tool instantiates Looker SDK natively, searches all bound methods, and passe
16
16
 
17
17
  ## Continuous Usage
18
18
 
19
- ### 1. Starting the Server
20
- To immediately trigger the stdio listener, use:
19
+ ### 1. Direct CLI Execution (Sandbox Mode)
20
+ If you want to execute Python scripts or inline code directly against your Looker instance without setting up an MCP client, use the `sandbox` command:
21
+
22
+ ```bash
23
+ # Execute inline Python code
24
+ uvx lkr-dev-cli code-mode sandbox --code="return me()"
25
+
26
+ # Execute Python script file
27
+ uvx lkr-dev-cli code-mode sandbox --file=./path/to/script.py
28
+ ```
29
+
30
+ ### 2. Starting the Server
31
+ To immediately trigger the MCP stdio listener, use:
21
32
  ```bash
22
- uvx -q --from lkr-dev-cli[codemode] lkr code-mode run
33
+ uvx -q lkr-dev-cli[codemode] code-mode run
23
34
  ```
24
35
 
25
36
  ### 2. Client Configuration
@@ -30,7 +41,7 @@ To hook this server into Cursor or Claude Desktop natively over stdio, append th
30
41
  "mcpServers": {
31
42
  "lkr_codemode": {
32
43
  "command": "uvx",
33
- "args": ["-q", "--from", "lkr-dev-cli[codemode]", "lkr", "code-mode", "run"],
44
+ "args": ["-q", "lkr-dev-cli[codemode]", "code-mode", "run"],
34
45
  "env": {
35
46
  "LOOKERSDK_BASE_URL": "https://your.looker.instance",
36
47
  "LOOKERSDK_CLIENT_ID": "your-client-id",
@@ -44,7 +55,7 @@ To hook this server into Cursor or Claude Desktop natively over stdio, append th
44
55
  ### 3. Visual Inspector
45
56
  To check things out on a web panel:
46
57
  ```bash
47
- npx @modelcontextprotocol/inspector uvx -q --from lkr-dev-cli[codemode] lkr code-mode run
58
+ npx @modelcontextprotocol/inspector uvx -q lkr-dev-cli[codemode] code-mode run
48
59
  ```
49
60
 
50
61
  ## Architectural & Troubleshooting Notes
@@ -1,6 +1,8 @@
1
+ import ast
1
2
  import inspect
2
3
  import json
3
4
  import os
5
+ import re
4
6
  import sys
5
7
  import tempfile
6
8
  from contextlib import contextmanager
@@ -21,7 +23,7 @@ from lkr.logger import logger
21
23
  __all__ = ["group"]
22
24
 
23
25
  mcp = FastMCP("lkr:codemode")
24
- group = typer.Typer()
26
+ group = typer.Typer(no_args_is_help=True)
25
27
  ctx_lkr: LkrCtxObj | None = None
26
28
  class OSCapture:
27
29
  def __init__(self):
@@ -156,6 +158,24 @@ def run_python_code(code: str, dev_mode: bool = False) -> str:
156
158
  setattr(SDK, name, staticmethod(func))
157
159
  external_funcs['sdk'] = SDK
158
160
 
161
+ # Monty external_functions do not support attribute lookups on objects.
162
+ # Pre-process the code to replace `sdk.method_name` with `method_name` safely using AST.
163
+ try:
164
+ class SDKAttributeRewriter(ast.NodeTransformer):
165
+ def visit_Attribute(self, node):
166
+ self.generic_visit(node)
167
+ if isinstance(node.value, ast.Name) and node.value.id == 'sdk':
168
+ return ast.copy_location(ast.Name(id=node.attr, ctx=node.ctx), node)
169
+ return node
170
+
171
+ tree = ast.parse(code)
172
+ tree = SDKAttributeRewriter().visit(tree)
173
+ ast.fix_missing_locations(tree)
174
+ code = ast.unparse(tree)
175
+ except Exception:
176
+ # Fallback to regex if parsing fails
177
+ code = re.sub(r"\bsdk\.([a-zA-Z_][a-zA-Z0-9_]*)\b", r"\1", code)
178
+
159
179
  m = pydantic_monty.Monty(code)
160
180
 
161
181
  # Use low-level OS stdout capture to ensure any print() statements
@@ -191,7 +211,45 @@ def run_python_code(code: str, dev_mode: bool = False) -> str:
191
211
  return f"Error: {str(e)}"
192
212
 
193
213
 
214
+ @group.command(name="sandbox")
215
+ def sandbox(
216
+ ctx: typer.Context,
217
+ code: str | None = typer.Option(
218
+ None, "--code", "-c", help="Execute Python code directly in the sandbox"
219
+ ),
220
+ file: str | None = typer.Option(
221
+ None, "--file", "-f", help="Execute Python code from a file in the sandbox"
222
+ ),
223
+ dev_mode: bool = typer.Option(
224
+ False, "--dev-mode", help="Run in dev mode"
225
+ ),
226
+ ):
227
+ if not code and not file:
228
+ logger.error("Must specify either --code or --file")
229
+ raise typer.Exit(1)
230
+
231
+ if code and file:
232
+ logger.error("Cannot specify both --code and --file")
233
+ raise typer.Exit(1)
234
+
235
+ if file:
236
+ try:
237
+ with open(file, "r", encoding="utf-8") as f:
238
+ code_to_run = f.read()
239
+ except Exception as e:
240
+ logger.error(f"Failed to read file {file}: {e}")
241
+ raise typer.Exit(1)
242
+ else:
243
+ code_to_run = code
194
244
 
245
+ global ctx_lkr
246
+ ctx_lkr = (
247
+ ctx.obj.get("ctx_lkr")
248
+ if (ctx and ctx.obj and "ctx_lkr" in ctx.obj)
249
+ else LkrCtxObj(force_oauth=False)
250
+ )
251
+ result = run_python_code(code_to_run, dev_mode=dev_mode)
252
+ typer.echo(result)
195
253
 
196
254
 
197
255
  @group.command(name="run")
@@ -202,7 +260,7 @@ def run(
202
260
  global ctx_lkr
203
261
  ctx_lkr = (
204
262
  ctx.obj.get("ctx_lkr")
205
- if (ctx.obj and "ctx_lkr" in ctx.obj)
263
+ if (ctx and ctx.obj and "ctx_lkr" in ctx.obj)
206
264
  else LkrCtxObj(force_oauth=False)
207
265
  )
208
266
  mcp.run()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lkr-dev-cli"
3
- version = "0.0.43"
3
+ version = "0.0.45"
4
4
  description = "lkr: a command line interface for looker"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,11 +1,15 @@
1
1
  from typing import Any, cast
2
2
  import json
3
+ import os
4
+ import tempfile
3
5
  import pytest
4
6
  from unittest.mock import patch, MagicMock
7
+ from typer.testing import CliRunner
5
8
  from looker_sdk.rtl.auth_session import AuthSession
6
9
  from looker_sdk.rtl.transport import Transport
7
10
  from lkr.codemode.main import run_python_code
8
11
  from lkr.extended_sdk_methods import ExtendedLooker40SDK
12
+ from lkr.main import app
9
13
 
10
14
 
11
15
  class DummyAuth:
@@ -67,7 +71,7 @@ me_obj = sdk.me()
67
71
  return me_obj["first_name"]
68
72
  """
69
73
  result = run_python_code(code_sdk)
70
- assert len(result) > 0
74
+ assert result == "Test"
71
75
 
72
76
  def test_examples():
73
77
  code_examples = """
@@ -283,3 +287,36 @@ return {"status": "clean"}
283
287
 
284
288
  out, err = capfd.readouterr()
285
289
  assert out == ""
290
+
291
+ runner = CliRunner()
292
+
293
+ def test_cli_code_mode_sandbox_code():
294
+ result = runner.invoke(app, ["code-mode", "sandbox", "--code", "return me()"])
295
+ assert result.exit_code == 0
296
+ assert "Test" in result.stdout
297
+
298
+ def test_cli_code_mode_sandbox_sdk_code():
299
+ result = runner.invoke(app, ["code-mode", "sandbox", "--code", "return sdk.me()"])
300
+ assert result.exit_code == 0
301
+ assert "Test" in result.stdout
302
+
303
+ def test_cli_code_mode_sandbox_file():
304
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
305
+ f.write("return me()")
306
+ temp_path = f.name
307
+
308
+ try:
309
+ result = runner.invoke(app, ["code-mode", "sandbox", "--file", temp_path])
310
+ assert result.exit_code == 0
311
+ assert "Test" in result.stdout
312
+ finally:
313
+ if os.path.exists(temp_path):
314
+ os.remove(temp_path)
315
+
316
+ def test_cli_code_mode_sandbox_both_error():
317
+ result = runner.invoke(app, ["code-mode", "sandbox", "--code", "return me()", "--file", "dummy.py"])
318
+ assert result.exit_code == 1
319
+
320
+ def test_cli_code_mode_sandbox_neither_error():
321
+ result = runner.invoke(app, ["code-mode", "sandbox"])
322
+ assert result.exit_code == 1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes