lkr-dev-cli 0.0.24__tar.gz → 0.0.26__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.26/.vscode/launch.json +41 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/PKG-INFO +13 -6
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/README.md +11 -4
- lkr_dev_cli-0.0.26/lkr/__init__.py +3 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/logger.py +8 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/main.py +2 -0
- lkr_dev_cli-0.0.26/lkr/mcp/classes.py +99 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/mcp/main.py +132 -193
- lkr_dev_cli-0.0.26/lkr/mcp/utils.py +55 -0
- lkr_dev_cli-0.0.26/lkr/tools/classes.py +206 -0
- lkr_dev_cli-0.0.26/lkr/tools/main.py +55 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/pyproject.toml +2 -2
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/uv.lock +17 -86
- lkr_dev_cli-0.0.24/.vscode/launch.json +0 -19
- lkr_dev_cli-0.0.24/lkr/auth/__init__.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/.github/workflows/release.yml +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/.gitignore +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/.python-version +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/.vscode/settings.json +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/Dockerfile +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/LICENSE +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/Makefile +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/cloudbuild.yaml +0 -0
- {lkr_dev_cli-0.0.24/lkr → lkr_dev_cli-0.0.26/lkr/auth}/__init__.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/auth/main.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/auth/oauth.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/auth_service.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/classes.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/constants.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/custom_types.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/exceptions.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/observability/classes.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/observability/embed_container.html +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/observability/main.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr/observability/utils.py +0 -0
- {lkr_dev_cli-0.0.24 → lkr_dev_cli-0.0.26}/lkr.md +0 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
3
|
+
// Hover to view descriptions of existing attributes.
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
5
|
+
"version": "0.2.0",
|
6
|
+
"configurations": [
|
7
|
+
{
|
8
|
+
"name": "lkr auth login",
|
9
|
+
"type": "debugpy",
|
10
|
+
"request": "launch",
|
11
|
+
"program": "${workspaceFolder}/.venv/bin/lkr",
|
12
|
+
"args": ["auth", "login"],
|
13
|
+
"justMyCode": false,
|
14
|
+
"env": {
|
15
|
+
"PYTHONPATH": "${workspaceFolder}"
|
16
|
+
}
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"name": "lkr mcp run",
|
20
|
+
"type": "debugpy",
|
21
|
+
"request": "launch",
|
22
|
+
"program": "${workspaceFolder}/.venv/bin/lkr",
|
23
|
+
"args": ["mcp", "run", "--debug"],
|
24
|
+
"justMyCode": false,
|
25
|
+
"env": {
|
26
|
+
"PYTHONPATH": "${workspaceFolder}"
|
27
|
+
}
|
28
|
+
},
|
29
|
+
{
|
30
|
+
"name": "lkr tools access token updater",
|
31
|
+
"type": "debugpy",
|
32
|
+
"request": "launch",
|
33
|
+
"program": "${workspaceFolder}/.venv/bin/lkr",
|
34
|
+
"args": ["tools", "access-token-updater"],
|
35
|
+
"justMyCode": false,
|
36
|
+
"env": {
|
37
|
+
"PYTHONPATH": "${workspaceFolder}"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
]
|
41
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lkr-dev-cli
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.26
|
4
4
|
Summary: lkr: a command line interface for looker
|
5
5
|
Author: bwebs
|
6
6
|
License-Expression: MIT
|
@@ -9,8 +9,8 @@ Requires-Python: >=3.12
|
|
9
9
|
Requires-Dist: cryptography>=42.0.0
|
10
10
|
Requires-Dist: duckdb>=1.2.2
|
11
11
|
Requires-Dist: fastapi>=0.115.12
|
12
|
-
Requires-Dist: fastmcp>=2.3.5
|
13
12
|
Requires-Dist: looker-sdk>=25.4.0
|
13
|
+
Requires-Dist: mcp[cli]>=1.9.2
|
14
14
|
Requires-Dist: pydantic>=2.11.4
|
15
15
|
Requires-Dist: pydash>=8.0.5
|
16
16
|
Requires-Dist: questionary>=2.1.0
|
@@ -178,26 +178,33 @@ For example:
|
|
178
178
|
|
179
179
|
### Cloud Run + GCP Health Check example
|
180
180
|
|
181
|
-
One of the simplest ways to launch the health check is the `lkr-cli` public docker image, Cloud Run, and the GCP health check service. Here's an example; make sure to change your region
|
181
|
+
One of the simplest ways to launch the health check is the `lkr-cli` public docker image, Cloud Run, and the GCP health check service. Here's an example; make sure to change your region and project. HEALTH_URL is an example of how to structure the query parameters for the health check.
|
182
182
|
|
183
183
|
```bash
|
184
184
|
export REGION=<your region>
|
185
185
|
export PROJECT=<your project id>
|
186
186
|
|
187
|
-
export HEALTH_URL="/health?dashboard_id=1&external_user_id=embed-user
|
187
|
+
export HEALTH_URL="/health?dashboard_id=1&external_user_id=observability-embed-user&models=thelook&user_attributes={\"store_id\":\"1\"}"
|
188
188
|
|
189
189
|
gcloud run deploy lkr-observability \
|
190
190
|
--image us-central1-docker.pkg.dev/lkr-dev-production/lkr-cli/cli:latest \
|
191
191
|
--command lkr \
|
192
192
|
--args observability,embed \
|
193
193
|
--platform managed \
|
194
|
-
--region $REGION \
|
194
|
+
--region $REGION \
|
195
195
|
--project $PROJECT \
|
196
196
|
--cpu 2 \
|
197
197
|
--memory 4Gi \
|
198
|
-
--liveness-probe httpGet.path=$HEALTH_URL,timeoutSeconds=20,periodSeconds=20 \
|
199
198
|
--set-env-vars LOOKERSDK_CLIENT_ID=<your client id>,LOOKERSDK_CLIENT_SECRET=<your client secret>,LOOKERSDK_BASE_URL=<your instance url>
|
200
199
|
|
200
|
+
gcloud monitoring uptime create lkr-observability-health-check \
|
201
|
+
--protocol https \
|
202
|
+
--project $PROJECT \
|
203
|
+
--resource-type="cloud-run-revision" \
|
204
|
+
--resource-labels="project_id=${PROJECT},service_name=lkr-observability,location=${REGION}" \
|
205
|
+
--path="${HEALTH_URL}" \
|
206
|
+
--period="15" \
|
207
|
+
--timeout="60"
|
201
208
|
|
202
209
|
```
|
203
210
|
|
@@ -156,26 +156,33 @@ For example:
|
|
156
156
|
|
157
157
|
### Cloud Run + GCP Health Check example
|
158
158
|
|
159
|
-
One of the simplest ways to launch the health check is the `lkr-cli` public docker image, Cloud Run, and the GCP health check service. Here's an example; make sure to change your region
|
159
|
+
One of the simplest ways to launch the health check is the `lkr-cli` public docker image, Cloud Run, and the GCP health check service. Here's an example; make sure to change your region and project. HEALTH_URL is an example of how to structure the query parameters for the health check.
|
160
160
|
|
161
161
|
```bash
|
162
162
|
export REGION=<your region>
|
163
163
|
export PROJECT=<your project id>
|
164
164
|
|
165
|
-
export HEALTH_URL="/health?dashboard_id=1&external_user_id=embed-user
|
165
|
+
export HEALTH_URL="/health?dashboard_id=1&external_user_id=observability-embed-user&models=thelook&user_attributes={\"store_id\":\"1\"}"
|
166
166
|
|
167
167
|
gcloud run deploy lkr-observability \
|
168
168
|
--image us-central1-docker.pkg.dev/lkr-dev-production/lkr-cli/cli:latest \
|
169
169
|
--command lkr \
|
170
170
|
--args observability,embed \
|
171
171
|
--platform managed \
|
172
|
-
--region $REGION \
|
172
|
+
--region $REGION \
|
173
173
|
--project $PROJECT \
|
174
174
|
--cpu 2 \
|
175
175
|
--memory 4Gi \
|
176
|
-
--liveness-probe httpGet.path=$HEALTH_URL,timeoutSeconds=20,periodSeconds=20 \
|
177
176
|
--set-env-vars LOOKERSDK_CLIENT_ID=<your client id>,LOOKERSDK_CLIENT_SECRET=<your client secret>,LOOKERSDK_BASE_URL=<your instance url>
|
178
177
|
|
178
|
+
gcloud monitoring uptime create lkr-observability-health-check \
|
179
|
+
--protocol https \
|
180
|
+
--project $PROJECT \
|
181
|
+
--resource-type="cloud-run-revision" \
|
182
|
+
--resource-labels="project_id=${PROJECT},service_name=lkr-observability,location=${REGION}" \
|
183
|
+
--path="${HEALTH_URL}" \
|
184
|
+
--period="15" \
|
185
|
+
--timeout="60"
|
179
186
|
|
180
187
|
```
|
181
188
|
|
@@ -8,6 +8,13 @@ from rich.theme import Theme
|
|
8
8
|
|
9
9
|
from lkr.custom_types import LogLevel
|
10
10
|
|
11
|
+
structlog.configure(
|
12
|
+
processors=[
|
13
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
14
|
+
structlog.processors.JSONRenderer(),
|
15
|
+
]
|
16
|
+
)
|
17
|
+
|
11
18
|
# Define a custom theme for our logging
|
12
19
|
theme = Theme(
|
13
20
|
{
|
@@ -50,6 +57,7 @@ logging.basicConfig(
|
|
50
57
|
logger = logging.getLogger("lkr")
|
51
58
|
structured_logger = structlog.get_logger("lkr.structured")
|
52
59
|
|
60
|
+
|
53
61
|
# Configure the requests_transport logger to only show debug messages when LOG_LEVEL is DEBUG
|
54
62
|
requests_logger = logging.getLogger("looker_sdk.rtl.requests_transport")
|
55
63
|
if log_level != "DEBUG":
|
@@ -9,6 +9,7 @@ from lkr.custom_types import LogLevel
|
|
9
9
|
from lkr.logger import logger
|
10
10
|
from lkr.mcp.main import group as mcp_group
|
11
11
|
from lkr.observability.main import group as observability_group
|
12
|
+
from lkr.tools.main import group as tools_group
|
12
13
|
|
13
14
|
app = typer.Typer(
|
14
15
|
name="lkr", help="LookML Repository CLI", add_completion=True, no_args_is_help=True
|
@@ -17,6 +18,7 @@ app = typer.Typer(
|
|
17
18
|
app.add_typer(auth_group, name="auth")
|
18
19
|
app.add_typer(mcp_group, name="mcp")
|
19
20
|
app.add_typer(observability_group, name="observability")
|
21
|
+
app.add_typer(tools_group, name="tools")
|
20
22
|
|
21
23
|
|
22
24
|
@app.callback()
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# server.py
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import Annotated, Any, List
|
4
|
+
|
5
|
+
import duckdb
|
6
|
+
from pydantic import BaseModel, Field, computed_field
|
7
|
+
|
8
|
+
from lkr.mcp.utils import get_database_search_file
|
9
|
+
from lkr.observability.classes import now
|
10
|
+
|
11
|
+
|
12
|
+
class SpectaclesResponse(BaseModel):
|
13
|
+
success: bool
|
14
|
+
result: Any | None = None
|
15
|
+
error: str | None = None
|
16
|
+
sql: str | None = None
|
17
|
+
share_url: str | None = None
|
18
|
+
|
19
|
+
|
20
|
+
class SpectaclesRequest(BaseModel):
|
21
|
+
model: Annotated[
|
22
|
+
str,
|
23
|
+
Field(
|
24
|
+
description="the model to run a test query against, you can find this by the filenames in the repository, they will end with .model.lkml. You should not pass in the .model.lkml extension.",
|
25
|
+
default="",
|
26
|
+
),
|
27
|
+
]
|
28
|
+
explore: Annotated[
|
29
|
+
str,
|
30
|
+
Field(
|
31
|
+
description="the explore to run a test query against, you can find this by finding explore: <name> {} in any file in the repository",
|
32
|
+
default="",
|
33
|
+
),
|
34
|
+
]
|
35
|
+
fields: Annotated[
|
36
|
+
List[str],
|
37
|
+
Field(
|
38
|
+
description="this should be the list of fields you want to return from the test query. If the user does not provide them, use all that have changed in your current context",
|
39
|
+
default=[],
|
40
|
+
),
|
41
|
+
]
|
42
|
+
|
43
|
+
|
44
|
+
class Connection(BaseModel):
|
45
|
+
connection: str
|
46
|
+
updated_at: datetime = Field(default_factory=now)
|
47
|
+
|
48
|
+
@computed_field(return_type=str)
|
49
|
+
@property
|
50
|
+
def fully_qualified_name(self) -> str:
|
51
|
+
return self.connection
|
52
|
+
|
53
|
+
|
54
|
+
class Database(Connection):
|
55
|
+
database: str
|
56
|
+
|
57
|
+
@computed_field(return_type=str)
|
58
|
+
@property
|
59
|
+
def fully_qualified_name(self) -> str:
|
60
|
+
return f"{self.connection}.{self.database}"
|
61
|
+
|
62
|
+
|
63
|
+
class Schema(Database):
|
64
|
+
database_schema_name: str
|
65
|
+
|
66
|
+
@computed_field(return_type=str)
|
67
|
+
@property
|
68
|
+
def fully_qualified_name(self) -> str:
|
69
|
+
return f"{self.connection}.{self.database}.{self.database_schema_name}"
|
70
|
+
|
71
|
+
|
72
|
+
class Table(Schema):
|
73
|
+
database_table_name: str
|
74
|
+
|
75
|
+
@computed_field(return_type=str)
|
76
|
+
@property
|
77
|
+
def fully_qualified_name(self) -> str:
|
78
|
+
return f"{self.connection}.{self.database}.{self.database_schema_name}.{self.database_table_name}"
|
79
|
+
|
80
|
+
|
81
|
+
class Row(Table):
|
82
|
+
database_column_name: str
|
83
|
+
data_type_database: str
|
84
|
+
data_type_looker: str
|
85
|
+
|
86
|
+
@computed_field(return_type=str)
|
87
|
+
@property
|
88
|
+
def fully_qualified_name(self) -> str:
|
89
|
+
return f"{self.connection}.{self.database}.{self.database_schema_name}.{self.database_table_name}.{self.database_column_name}"
|
90
|
+
|
91
|
+
def append(self, base_url: str) -> None:
|
92
|
+
with open(get_database_search_file(base_url), "a") as f:
|
93
|
+
f.write(self.model_dump_json() + "\n")
|
94
|
+
|
95
|
+
def exists(self, conn: duckdb.DuckDBPyConnection, *, base_url: str) -> bool:
|
96
|
+
columns = conn.execute(
|
97
|
+
f"SELECT * FROM read_json_auto('{get_database_search_file(base_url)}') WHERE fully_qualified_name = '{self.fully_qualified_name}'"
|
98
|
+
).fetchall()
|
99
|
+
return len(columns) > 0
|