lkr-dev-cli 0.0.31__tar.gz → 0.0.33__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 (41) hide show
  1. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/PKG-INFO +42 -28
  2. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/README.md +34 -21
  3. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/auth/main.py +17 -5
  4. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/auth_service.py +4 -3
  5. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/logger.py +33 -22
  6. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/main.py +25 -6
  7. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/observability/main.py +2 -0
  8. lkr_dev_cli-0.0.33/lkr/tools/main.py +232 -0
  9. lkr_dev_cli-0.0.33/lkr/tools/permission_deprecation.py +193 -0
  10. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr.md +21 -0
  11. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/pyproject.toml +10 -9
  12. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/tests/test_dependency_resolution.py +59 -1
  13. lkr_dev_cli-0.0.33/tests/test_permission_deprecation.py +176 -0
  14. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/uv.lock +327 -21
  15. lkr_dev_cli-0.0.31/lkr/tools/main.py +0 -84
  16. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/.github/workflows/release.yml +0 -0
  17. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/.github/workflows/test-dependencies.yml +0 -0
  18. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/.gitignore +0 -0
  19. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/.python-version +0 -0
  20. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/.vscode/launch.json +0 -0
  21. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/.vscode/settings.json +0 -0
  22. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/Dockerfile +0 -0
  23. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/LICENSE +0 -0
  24. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/Makefile +0 -0
  25. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/cloudbuild.yaml +0 -0
  26. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/__init__.py +0 -0
  27. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/auth/__init__.py +0 -0
  28. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/auth/oauth.py +0 -0
  29. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/classes.py +0 -0
  30. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/constants.py +0 -0
  31. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/custom_types.py +0 -0
  32. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/exceptions.py +0 -0
  33. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/mcp/classes.py +0 -0
  34. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/mcp/main.py +0 -0
  35. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/mcp/utils.py +0 -0
  36. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/observability/classes.py +0 -0
  37. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/observability/embed_container.html +0 -0
  38. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/observability/utils.py +0 -0
  39. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/lkr/tools/classes.py +0 -0
  40. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/tests/TESTING.md +0 -0
  41. {lkr_dev_cli-0.0.31 → lkr_dev_cli-0.0.33}/tests/test_deps.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lkr-dev-cli
3
- Version: 0.0.31
3
+ Version: 0.0.33
4
4
  Summary: lkr: a command line interface for looker
5
5
  Author: bwebs
6
6
  License-Expression: MIT
@@ -16,17 +16,18 @@ Requires-Dist: structlog>=25.3.0
16
16
  Requires-Dist: typer>=0.15.2
17
17
  Provides-Extra: all
18
18
  Requires-Dist: duckdb>=1.2.2; extra == 'all'
19
- Requires-Dist: fastapi>=0.115.12; extra == 'all'
19
+ Requires-Dist: fastapi[standard]>=0.115.12; extra == 'all'
20
20
  Requires-Dist: mcp[cli]>=1.9.2; extra == 'all'
21
21
  Requires-Dist: selenium>=4.32.0; extra == 'all'
22
- Provides-Extra: embed-observability
23
- Requires-Dist: fastapi>=0.115.12; extra == 'embed-observability'
24
- Requires-Dist: selenium>=4.32.0; extra == 'embed-observability'
25
22
  Provides-Extra: mcp
26
23
  Requires-Dist: duckdb>=1.2.2; extra == 'mcp'
24
+ Requires-Dist: fastapi[standard]>=0.115.12; extra == 'mcp'
27
25
  Requires-Dist: mcp[cli]>=1.9.2; extra == 'mcp'
28
- Provides-Extra: user-attribute-updater
29
- Requires-Dist: fastapi>=0.115.12; extra == 'user-attribute-updater'
26
+ Provides-Extra: observability
27
+ Requires-Dist: fastapi[standard]>=0.115.12; extra == 'observability'
28
+ Requires-Dist: selenium>=4.32.0; extra == 'observability'
29
+ Provides-Extra: tools
30
+ Requires-Dist: fastapi[standard]>=0.115.12; extra == 'tools'
30
31
  Description-Content-Type: text/markdown
31
32
 
32
33
  # lkr cli
@@ -35,9 +36,9 @@ The `lkr` cli is a tool for interacting with Looker. It combines Looker's SDK an
35
36
 
36
37
  ## Usage
37
38
 
38
- `uv` makes everyone's life easier. Go [install it](https://docs.astral.sh/uv/getting-started/installation/). You can start using `lkr` by running `uv run --with lkr-dev-cli lkr --help`.
39
+ `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`.
39
40
 
40
- Alternatively, you can install `lkr` with `pip install lkr-dev-cli` and use commands directly like `lkr <command>`.
41
+ Alternatively, you can install `lkr` with `pip install lkr-dev-cli[all]` and use commands directly like `lkr <command>`.
41
42
 
42
43
  We also have a public docker image that you can use to run `lkr` commands.
43
44
 
@@ -55,7 +56,7 @@ See the [prerequisites section](#oauth2-prerequisites)
55
56
  Login to `lkr`
56
57
 
57
58
  ```bash
58
- uv run --with lkr-dev-cli lkr auth login
59
+ uvx --from lkr-dev-cli[all] lkr auth login
59
60
  ```
60
61
 
61
62
  - Select a new instance
@@ -63,12 +64,12 @@ uv run --with lkr-dev-cli lkr auth login
63
64
  - Choose whether you want this login to use production or development mode
64
65
  - Give it a name
65
66
 
66
- You will be redirected to the Looker OAuth authorization page, click Allow. If you do not see an allow button, the [prerequisites](#prerequisites) were not done properly.
67
+ You will be redirected to the Looker OAuth authorization page, click Allow. If you do not see an allow button, the [prerequisites](#oauth2-prerequisites) were not done properly.
67
68
 
68
69
  If everything is successful, you will see `Successfully authenticated!`. Test it with
69
70
 
70
71
  ```bash
71
- uv run --with lkr-dev-cli lkr auth whoami
72
+ uvx --from lkr-dev-cli[all] lkr auth whoami
72
73
  ```
73
74
 
74
75
  ### Using API Key
@@ -76,7 +77,7 @@ uv run --with lkr-dev-cli lkr auth whoami
76
77
  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.
77
78
 
78
79
  ```bash
79
- uv run --with lkr-dev-cli lkr --client-id <your client id> --client-secret <your client secret> --base-url <your instance url> auth whoami
80
+ uvx --from lkr-dev-cli[all] lkr --client-id <your client id> --client-secret <your client secret> --base-url <your instance url> auth whoami
80
81
  ```
81
82
 
82
83
 
@@ -112,7 +113,7 @@ Built into the `lkr` is an MCP server. Right now its tools are based on helping
112
113
  "mcpServers": {
113
114
  "lkr-mcp": {
114
115
  "command": "uv",
115
- "args": ["run", "--with", "lkr-dev-cli", "lkr", "mcp", "run"]
116
+ "args": ["run", "--with", "lkr-dev-cli[all]", "lkr", "mcp", "run"]
116
117
  },
117
118
  "lkr-mcp-docker": {
118
119
  "command": "docker",
@@ -356,18 +357,39 @@ def delete_user_attribute(user_attribute_name: str, email: str):
356
357
  )
357
358
  updater.delete_user_attribute_value()
358
359
 
360
+ ## Permission Deprecation Tool
361
+
362
+ The `schedule-download-deprecation` tool helps Looker admins ensure that users do not lose access to models they already have when Looker moves towards more granular model-specific permissions for scheduling and downloading.
363
+
364
+ ### How it helps
365
+ Currently, some permissions in Looker can be granted instance-wide. In the future, these permissions may need to be explicitly granted at the model level (via Model Sets). This tool audits all active users and identifies those who:
366
+ - Have "target permissions" (like `download_with_limit`, `schedule_look_emails`, etc.) instance-wide.
367
+ - Do **not** have those same permissions for specific models they otherwise have access to.
368
+
369
+ By running this tool, an admin can proactively identify and fix permission gaps before any deprecation takes effect, ensuring a seamless experience for end-users.
370
+
371
+ ### Usage
372
+ This command should be run by a **Looker Admin**.
373
+
374
+ ```bash
375
+ uvx --from lkr-dev-cli[all] lkr tools schedule-download-deprecation
376
+ ```
377
+
378
+ Options:
379
+ - `--csv`: Export the results to a CSV file for easier analysis of large instances.
380
+ - `--unfiltered`: Show all users, including those who have all required permissions across all models.
381
+ - `--model-offset`: Slice the table output to show different sets of models (the table shows 5 models at a time).
382
+
383
+
359
384
  ## Optional Dependencies
360
385
 
361
386
  The `lkr` CLI supports optional dependencies that enable additional functionality. You can install these individually or all at once.
362
387
 
363
388
  ### Available Extras
364
389
 
365
- - **`mcp`**: Enables the MCP (Model Context Protocol) server functionality
366
- - Includes: `mcp[cli]>=1.9.2`, `duckdb>=1.2.2`
367
- - **`embed-observability`**: Enables the observability embed monitoring features
368
- - Includes: `fastapi>=0.115.12`, `selenium>=4.32.0`
369
- - **`user-attribute-updater`**: Enables the user attribute updater functionality
370
- - Includes: `fastapi>=0.115.12`
390
+ - **`mcp`**: Enables the MCP (Model Context Protocol) server functionality and `lkr mcp` commands
391
+ - **`observability`**: Enables the observability embed monitoring features and `lkr observability` commands
392
+ - **`tools`**: Enables the user attribute updater functionality and `lkr tools` commands
371
393
 
372
394
  ### Installing Optional Dependencies
373
395
 
@@ -399,11 +421,3 @@ pip install lkr-dev-cli[all]
399
421
  # Install specific extras
400
422
  pip install lkr-dev-cli[mcp,embed-observability,user-attribute-updater]
401
423
  ```
402
-
403
- ### What Each Extra Enables
404
-
405
- - **`mcp`**: Use the MCP server with tools like Cursor for enhanced IDE integration
406
- - **`embed-observability`**: Run the observability embed server for monitoring Looker dashboard performance
407
- - **`user-attribute-updater`**: Deploy the user attribute updater service for OIDC token management
408
-
409
- All extras are designed to work together seamlessly, and installing `all` is equivalent to installing all individual extras.
@@ -4,9 +4,9 @@ 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 `uv run --with lkr-dev-cli 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 --from lkr-dev-cli[all] lkr --help`.
8
8
 
9
- Alternatively, you can install `lkr` with `pip install lkr-dev-cli` and use commands directly like `lkr <command>`.
9
+ Alternatively, you can install `lkr` with `pip install lkr-dev-cli[all]` and use commands directly like `lkr <command>`.
10
10
 
11
11
  We also have a public docker image that you can use to run `lkr` commands.
12
12
 
@@ -24,7 +24,7 @@ See the [prerequisites section](#oauth2-prerequisites)
24
24
  Login to `lkr`
25
25
 
26
26
  ```bash
27
- uv run --with lkr-dev-cli lkr auth login
27
+ uvx --from lkr-dev-cli[all] lkr auth login
28
28
  ```
29
29
 
30
30
  - Select a new instance
@@ -32,12 +32,12 @@ uv run --with lkr-dev-cli lkr auth login
32
32
  - Choose whether you want this login to use production or development mode
33
33
  - Give it a name
34
34
 
35
- You will be redirected to the Looker OAuth authorization page, click Allow. If you do not see an allow button, the [prerequisites](#prerequisites) were not done properly.
35
+ You will be redirected to the Looker OAuth authorization page, click Allow. If you do not see an allow button, the [prerequisites](#oauth2-prerequisites) were not done properly.
36
36
 
37
37
  If everything is successful, you will see `Successfully authenticated!`. Test it with
38
38
 
39
39
  ```bash
40
- uv run --with lkr-dev-cli lkr auth whoami
40
+ uvx --from lkr-dev-cli[all] lkr auth whoami
41
41
  ```
42
42
 
43
43
  ### Using API Key
@@ -45,7 +45,7 @@ uv run --with lkr-dev-cli 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
- uv run --with lkr-dev-cli lkr --client-id <your client id> --client-secret <your client secret> --base-url <your instance url> auth whoami
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
49
49
  ```
50
50
 
51
51
 
@@ -81,7 +81,7 @@ Built into the `lkr` is an MCP server. Right now its tools are based on helping
81
81
  "mcpServers": {
82
82
  "lkr-mcp": {
83
83
  "command": "uv",
84
- "args": ["run", "--with", "lkr-dev-cli", "lkr", "mcp", "run"]
84
+ "args": ["run", "--with", "lkr-dev-cli[all]", "lkr", "mcp", "run"]
85
85
  },
86
86
  "lkr-mcp-docker": {
87
87
  "command": "docker",
@@ -325,18 +325,39 @@ def delete_user_attribute(user_attribute_name: str, email: str):
325
325
  )
326
326
  updater.delete_user_attribute_value()
327
327
 
328
+ ## Permission Deprecation Tool
329
+
330
+ The `schedule-download-deprecation` tool helps Looker admins ensure that users do not lose access to models they already have when Looker moves towards more granular model-specific permissions for scheduling and downloading.
331
+
332
+ ### How it helps
333
+ Currently, some permissions in Looker can be granted instance-wide. In the future, these permissions may need to be explicitly granted at the model level (via Model Sets). This tool audits all active users and identifies those who:
334
+ - Have "target permissions" (like `download_with_limit`, `schedule_look_emails`, etc.) instance-wide.
335
+ - Do **not** have those same permissions for specific models they otherwise have access to.
336
+
337
+ By running this tool, an admin can proactively identify and fix permission gaps before any deprecation takes effect, ensuring a seamless experience for end-users.
338
+
339
+ ### Usage
340
+ This command should be run by a **Looker Admin**.
341
+
342
+ ```bash
343
+ uvx --from lkr-dev-cli[all] lkr tools schedule-download-deprecation
344
+ ```
345
+
346
+ Options:
347
+ - `--csv`: Export the results to a CSV file for easier analysis of large instances.
348
+ - `--unfiltered`: Show all users, including those who have all required permissions across all models.
349
+ - `--model-offset`: Slice the table output to show different sets of models (the table shows 5 models at a time).
350
+
351
+
328
352
  ## Optional Dependencies
329
353
 
330
354
  The `lkr` CLI supports optional dependencies that enable additional functionality. You can install these individually or all at once.
331
355
 
332
356
  ### Available Extras
333
357
 
334
- - **`mcp`**: Enables the MCP (Model Context Protocol) server functionality
335
- - Includes: `mcp[cli]>=1.9.2`, `duckdb>=1.2.2`
336
- - **`embed-observability`**: Enables the observability embed monitoring features
337
- - Includes: `fastapi>=0.115.12`, `selenium>=4.32.0`
338
- - **`user-attribute-updater`**: Enables the user attribute updater functionality
339
- - Includes: `fastapi>=0.115.12`
358
+ - **`mcp`**: Enables the MCP (Model Context Protocol) server functionality and `lkr mcp` commands
359
+ - **`observability`**: Enables the observability embed monitoring features and `lkr observability` commands
360
+ - **`tools`**: Enables the user attribute updater functionality and `lkr tools` commands
340
361
 
341
362
  ### Installing Optional Dependencies
342
363
 
@@ -368,11 +389,3 @@ pip install lkr-dev-cli[all]
368
389
  # Install specific extras
369
390
  pip install lkr-dev-cli[mcp,embed-observability,user-attribute-updater]
370
391
  ```
371
-
372
- ### What Each Extra Enables
373
-
374
- - **`mcp`**: Use the MCP server with tools like Cursor for enhanced IDE integration
375
- - **`embed-observability`**: Run the observability embed server for monitoring Looker dashboard performance
376
- - **`user-attribute-updater`**: Deploy the user attribute updater service for OIDC token management
377
-
378
- All extras are designed to work together seamlessly, and installing `all` is equivalent to installing all individual extras.
@@ -1,16 +1,25 @@
1
1
  import urllib.parse
2
2
  from typing import Annotated, List, Union
3
3
 
4
- import questionary
5
4
  import typer
6
5
  from looker_sdk.rtl.auth_token import AccessToken, AuthToken
7
- from rich.console import Console
8
- from rich.table import Table
9
6
 
10
7
  from lkr.auth.oauth import OAuth2PKCE
11
8
  from lkr.auth_service import get_auth
12
9
  from lkr.logger import logger
13
10
 
11
+ QUESTIONARY_AVAILABLE = True
12
+ RICH_AVAILABLE = True
13
+ try:
14
+ import questionary
15
+ except ModuleNotFoundError:
16
+ QUESTIONARY_AVAILABLE = False
17
+ try:
18
+ from rich.console import Console
19
+ from rich.table import Table
20
+ except ModuleNotFoundError:
21
+ RICH_AVAILABLE = False
22
+
14
23
  __all__ = ["group"]
15
24
 
16
25
  group = typer.Typer(name="auth", help="Authentication commands for LookML Repository")
@@ -199,7 +208,7 @@ def list(ctx: typer.Context):
199
208
  """
200
209
  List all authenticated Looker instances
201
210
  """
202
- console = Console()
211
+ console = Console() if RICH_AVAILABLE else None
203
212
  auth = get_auth(ctx)
204
213
  all_instances = auth.list_auth()
205
214
  if not all_instances:
@@ -213,7 +222,10 @@ def list(ctx: typer.Context):
213
222
  instance[1],
214
223
  "Yes" if instance[3] else "No",
215
224
  )
216
- console.print(table)
225
+ if console:
226
+ console.print(table)
227
+ else:
228
+ print(table)
217
229
 
218
230
 
219
231
  if __name__ == "__main__":
@@ -3,10 +3,11 @@ import os
3
3
  import sqlite3
4
4
  import types
5
5
  from datetime import datetime, timedelta, timezone
6
- from typing import List, Self, Tuple, Union
6
+ from typing import List, Self, Tuple, Union, TYPE_CHECKING
7
7
 
8
8
  import requests
9
- import typer
9
+ if TYPE_CHECKING:
10
+ import typer
10
11
  from looker_sdk.rtl import serialize
11
12
  from looker_sdk.rtl.api_settings import ApiSettings, SettingsConfig
12
13
  from looker_sdk.rtl.auth_session import AuthSession, CryptoHash, OAuthSession
@@ -25,7 +26,7 @@ from lkr.logger import logger
25
26
  __all__ = ["get_auth", "ApiKeyAuthSession", "DbOAuthSession"]
26
27
 
27
28
 
28
- def get_auth(ctx: typer.Context | LkrCtxObj) -> Union["SqlLiteAuth", "ApiKeyAuth"]:
29
+ def get_auth(ctx: Union["typer.Context", LkrCtxObj]) -> Union["SqlLiteAuth", "ApiKeyAuth"]:
29
30
  if isinstance(ctx, LkrCtxObj):
30
31
  lkr_ctx = ctx
31
32
  else:
@@ -1,19 +1,28 @@
1
1
  import logging
2
2
  import os
3
+ from lkr.custom_types import LogLevel
3
4
 
4
- import structlog
5
- from rich.console import Console
6
- from rich.logging import RichHandler
7
- from rich.theme import Theme
5
+ STRUCT_LOG_AVAILABLE = True
6
+ RICH_AVAILABLE = True
7
+ try:
8
+ import structlog
9
+ except ModuleNotFoundError:
10
+ STRUCT_LOG_AVAILABLE = False
11
+ try:
12
+ from rich.console import Console
13
+ from rich.logging import RichHandler
14
+ from rich.theme import Theme
15
+ except ModuleNotFoundError:
16
+ RICH_AVAILABLE = False
8
17
 
9
- from lkr.custom_types import LogLevel
10
18
 
11
- structlog.configure(
12
- processors=[
13
- structlog.processors.TimeStamper(fmt="iso"),
14
- structlog.processors.JSONRenderer(),
15
- ]
16
- )
19
+ if STRUCT_LOG_AVAILABLE:
20
+ structlog.configure(
21
+ processors=[
22
+ structlog.processors.TimeStamper(fmt="iso"),
23
+ structlog.processors.JSONRenderer(),
24
+ ]
25
+ )
17
26
 
18
27
  # Define a custom theme for our logging
19
28
  theme = Theme(
@@ -24,10 +33,10 @@ theme = Theme(
24
33
  "logging.level.error": "bold red",
25
34
  "logging.level.critical": "bold white on red",
26
35
  }
27
- )
36
+ ) if RICH_AVAILABLE else None
28
37
 
29
38
  # Create a console for logging
30
- console = Console(theme=theme)
39
+ console = Console(theme=theme) if RICH_AVAILABLE else None
31
40
 
32
41
  # Configure the logging handler
33
42
  handler = RichHandler(
@@ -37,7 +46,7 @@ handler = RichHandler(
37
46
  markup=True,
38
47
  rich_tracebacks=True,
39
48
  tracebacks_show_locals=True,
40
- )
49
+ ) if RICH_AVAILABLE else None
41
50
 
42
51
  # Get log level from environment variable, defaulting to INFO
43
52
  DEFAULT_LOG_LEVEL = "INFO"
@@ -50,25 +59,27 @@ logging.basicConfig(
50
59
  ), # Fallback to INFO if invalid level
51
60
  format="%(message)s",
52
61
  datefmt="[%X]",
53
- handlers=[handler],
62
+ handlers=[handler] if handler else [],
54
63
  )
55
64
 
56
65
  # Create a logger for the application
57
66
  logger = logging.getLogger("lkr")
58
- structured_logger = structlog.get_logger("lkr.structured")
67
+ structured_logger = structlog.get_logger("lkr.structured") if STRUCT_LOG_AVAILABLE else None
59
68
 
60
69
 
61
70
  # Configure the requests_transport logger to only show debug messages when LOG_LEVEL is DEBUG
62
- requests_logger = logging.getLogger("looker_sdk.rtl.requests_transport")
63
- if log_level != "DEBUG":
71
+ requests_logger = logging.getLogger("looker_sdk.rtl.requests_transport") if RICH_AVAILABLE else None
72
+ if log_level != "DEBUG" and requests_logger:
64
73
  requests_logger.setLevel(logging.WARNING)
65
74
 
66
75
 
67
76
  def set_log_level(level: LogLevel):
68
77
  """Set the logging level for the application."""
69
78
  logger.setLevel(getattr(logging, level.value))
70
- logging.getLogger("lkr.structured").setLevel(getattr(logging, level.value))
79
+ if structured_logger:
80
+ structured_logger.setLevel(getattr(logging, level.value))
71
81
  # Update requests_transport logger level based on the new level
72
- requests_logger.setLevel(
73
- logging.DEBUG if level == LogLevel.DEBUG else logging.WARNING
74
- )
82
+ if requests_logger:
83
+ requests_logger.setLevel(
84
+ logging.DEBUG if level == LogLevel.DEBUG else logging.WARNING
85
+ )
@@ -7,9 +7,6 @@ from lkr.auth.main import group as auth_group
7
7
  from lkr.classes import LkrCtxObj
8
8
  from lkr.custom_types import LogLevel
9
9
  from lkr.logger import logger
10
- from lkr.mcp.main import group as mcp_group
11
- from lkr.observability.main import group as observability_group
12
- from lkr.tools.main import group as tools_group
13
10
 
14
11
  app = typer.Typer(
15
12
  name="lkr",
@@ -19,10 +16,32 @@ app = typer.Typer(
19
16
  )
20
17
 
21
18
  app.add_typer(auth_group, name="auth")
22
- app.add_typer(mcp_group, name="mcp")
23
- app.add_typer(observability_group, name="observability")
24
- app.add_typer(tools_group, name="tools")
25
19
 
20
+ IMPORT_ERROR = None
21
+
22
+ def add_optional_typer_group(app, import_path, group_name, extra_message=None):
23
+ try:
24
+ module_path, attr = import_path.rsplit(".", 1)
25
+ mod = __import__(module_path, fromlist=[attr])
26
+ group = getattr(mod, attr)
27
+ app.add_typer(group, name=group_name)
28
+ except ModuleNotFoundError as import_error:
29
+ @app.command(
30
+ name=group_name,
31
+ add_help_option=False,
32
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
33
+ )
34
+ def fallback(import_error=import_error):
35
+ msg = f"{group_name} tools (dependencies not available, try installing optional dependencies: lkr-dev-cli\\[{group_name}])"
36
+ if extra_message:
37
+ msg += f" {extra_message}"
38
+ logger.error(msg)
39
+ logger.error(import_error)
40
+ raise typer.Exit(1)
41
+
42
+ add_optional_typer_group(app, "lkr.mcp.main.group", "mcp")
43
+ add_optional_typer_group(app, "lkr.observability.main.group", "observability")
44
+ add_optional_typer_group(app, "lkr.tools.main.group", "tools")
26
45
 
27
46
  @app.callback()
28
47
  def callback(
@@ -39,6 +39,8 @@ DEFAULT_PERMISSIONS = set(
39
39
 
40
40
  observability_ctx = ObservabilityCtxObj()
41
41
 
42
+ if not structured_logger:
43
+ raise Exception("Structured logger is not available")
42
44
 
43
45
  def get_embed_sdk_obj(
44
46
  dashboard_id: str = Query(...),