semblance-databricks 0.1.0__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.
Files changed (42) hide show
  1. semblance_databricks/__init__.py +19 -0
  2. semblance_databricks/app.py +104 -0
  3. semblance_databricks/auth.py +145 -0
  4. semblance_databricks/cli.py +112 -0
  5. semblance_databricks/compatibility.py +30 -0
  6. semblance_databricks/compatibility.yaml +269 -0
  7. semblance_databricks/config.py +52 -0
  8. semblance_databricks/errors.py +72 -0
  9. semblance_databricks/fixtures/__init__.py +5 -0
  10. semblance_databricks/fixtures/defaults/__init__.py +0 -0
  11. semblance_databricks/fixtures/defaults/acme.yaml +47 -0
  12. semblance_databricks/fixtures/loaders.py +196 -0
  13. semblance_databricks/http.py +34 -0
  14. semblance_databricks/ids.py +88 -0
  15. semblance_databricks/models/__init__.py +1 -0
  16. semblance_databricks/pagination.py +48 -0
  17. semblance_databricks/py.typed +0 -0
  18. semblance_databricks/registry.py +29 -0
  19. semblance_databricks/services/__init__.py +1 -0
  20. semblance_databricks/services/clusters/__init__.py +3 -0
  21. semblance_databricks/services/clusters/routes.py +191 -0
  22. semblance_databricks/services/dbfs/__init__.py +3 -0
  23. semblance_databricks/services/dbfs/routes.py +126 -0
  24. semblance_databricks/services/dbsql/__init__.py +3 -0
  25. semblance_databricks/services/dbsql/routes.py +163 -0
  26. semblance_databricks/services/deps.py +37 -0
  27. semblance_databricks/services/jobs/__init__.py +3 -0
  28. semblance_databricks/services/jobs/routes.py +168 -0
  29. semblance_databricks/services/permissions/__init__.py +5 -0
  30. semblance_databricks/services/permissions/routes.py +50 -0
  31. semblance_databricks/services/secrets/__init__.py +3 -0
  32. semblance_databricks/services/secrets/routes.py +53 -0
  33. semblance_databricks/services/workspace/__init__.py +3 -0
  34. semblance_databricks/services/workspace/routes.py +39 -0
  35. semblance_databricks/state.py +253 -0
  36. semblance_databricks/testing.py +66 -0
  37. semblance_databricks-0.1.0.dist-info/METADATA +126 -0
  38. semblance_databricks-0.1.0.dist-info/RECORD +42 -0
  39. semblance_databricks-0.1.0.dist-info/WHEEL +5 -0
  40. semblance_databricks-0.1.0.dist-info/entry_points.txt +2 -0
  41. semblance_databricks-0.1.0.dist-info/licenses/LICENSE.md +21 -0
  42. semblance_databricks-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,191 @@
1
+ """Clusters list/get/create/edit/delete/restart/events/libraries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from fastapi import APIRouter, Request
8
+
9
+ from semblance_databricks.errors import DatabricksError
10
+ from semblance_databricks.http import json_object, require_int
11
+ from semblance_databricks.pagination import paginate
12
+ from semblance_databricks.services.deps import (
13
+ mock_from,
14
+ require_fail_stage,
15
+ stub_unimplemented,
16
+ )
17
+
18
+
19
+ def create_clusters_router() -> APIRouter:
20
+ router = APIRouter()
21
+
22
+ def _list(
23
+ request: Request, page_size: int | None, page_token: str | None
24
+ ) -> dict[str, Any]:
25
+ mock = mock_from(request)
26
+ mock.state.maybe_tick_real(mock.config.clock)
27
+ items = [
28
+ mock.state.cluster_json(c)
29
+ for c in sorted(mock.state.clusters.values(), key=lambda r: r.cluster_id)
30
+ if not c.deleted
31
+ ]
32
+ page, nxt = paginate(
33
+ items,
34
+ page_size=page_size,
35
+ page_token=page_token,
36
+ resource="clusters",
37
+ codec=mock.page_token_codec,
38
+ revision=mock.state.revision,
39
+ default_page_size=mock.config.default_page_size,
40
+ max_page_size=mock.config.max_page_size,
41
+ )
42
+ body: dict[str, Any] = {"clusters": page}
43
+ if nxt:
44
+ body["next_page_token"] = nxt
45
+ return body
46
+
47
+ def _get(request: Request, cluster_id: str | None) -> dict[str, Any]:
48
+ mock = mock_from(request)
49
+ mock.state.maybe_tick_real(mock.config.clock)
50
+ if not cluster_id:
51
+ raise DatabricksError(400, "INVALID_PARAMETER_VALUE", "cluster_id required")
52
+ rec = mock.state.clusters.get(cluster_id)
53
+ if rec is None or rec.deleted:
54
+ raise DatabricksError(
55
+ 404, "RESOURCE_DOES_NOT_EXIST", f"Cluster {cluster_id} not found"
56
+ )
57
+ return mock.state.cluster_json(rec)
58
+
59
+ @router.get("/api/2.1/clusters/list")
60
+ def list_21(
61
+ request: Request,
62
+ page_size: int | None = None,
63
+ page_token: str | None = None,
64
+ ) -> dict[str, Any]:
65
+ return _list(request, page_size, page_token)
66
+
67
+ @router.get("/api/2.1/clusters/get")
68
+ def get_21(request: Request, cluster_id: str | None = None) -> dict[str, Any]:
69
+ return _get(request, cluster_id)
70
+
71
+ @router.get("/api/2.0/clusters/get")
72
+ def get_20(request: Request, cluster_id: str | None = None) -> dict[str, Any]:
73
+ return _get(request, cluster_id)
74
+
75
+ @router.get("/api/2.0/clusters/list")
76
+ def list_20(
77
+ request: Request,
78
+ page_size: int | None = None,
79
+ page_token: str | None = None,
80
+ ) -> dict[str, Any]:
81
+ return _list(request, page_size, page_token)
82
+
83
+ @router.post("/api/2.1/clusters/create")
84
+ @router.post("/api/2.0/clusters/create")
85
+ async def create(request: Request) -> dict[str, Any]:
86
+ require_fail_stage(request, "before_validate")
87
+ mock = mock_from(request)
88
+ body = await json_object(request)
89
+ require_fail_stage(request, "before_write")
90
+ rec = mock.state.add_cluster(
91
+ str(body.get("cluster_name", "cluster")),
92
+ spark_version=str(body.get("spark_version", "13.3.x-scala2.12")),
93
+ node_type_id=str(body.get("node_type_id", "i3.xlarge")),
94
+ state="PENDING",
95
+ ticks_remaining=require_int(
96
+ body.get("startup_delay_ticks"), 1, "startup_delay_ticks"
97
+ ),
98
+ fail_after=bool(body.get("startup_failure", False)),
99
+ num_workers=require_int(body.get("num_workers"), 1, "num_workers"),
100
+ )
101
+ require_fail_stage(request, "after_write")
102
+ return {"cluster_id": rec.cluster_id}
103
+
104
+ @router.post("/api/2.1/clusters/edit")
105
+ @router.post("/api/2.0/clusters/edit")
106
+ async def edit(request: Request) -> dict[str, Any]:
107
+ mock = mock_from(request)
108
+ body = await json_object(request)
109
+ cid = str(body.get("cluster_id", ""))
110
+ rec = mock.state.clusters.get(cid)
111
+ if rec is None or rec.deleted:
112
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Cluster not found")
113
+ if "cluster_name" in body:
114
+ rec.cluster_name = str(body["cluster_name"])
115
+ if "num_workers" in body:
116
+ rec.num_workers = require_int(body["num_workers"], 1, "num_workers")
117
+ mock.state.bump()
118
+ return {}
119
+
120
+ @router.post("/api/2.1/clusters/delete")
121
+ @router.post("/api/2.0/clusters/delete")
122
+ async def delete(request: Request) -> dict[str, Any]:
123
+ mock = mock_from(request)
124
+ body = await json_object(request)
125
+ cid = str(body.get("cluster_id", ""))
126
+ rec = mock.state.clusters.get(cid)
127
+ if rec is None or rec.deleted:
128
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Cluster not found")
129
+ rec.state = "TERMINATING"
130
+ mock.state.bump()
131
+ return {}
132
+
133
+ @router.post("/api/2.1/clusters/restart")
134
+ @router.post("/api/2.0/clusters/restart")
135
+ async def restart(request: Request) -> dict[str, Any]:
136
+ mock = mock_from(request)
137
+ body = await json_object(request)
138
+ cid = str(body.get("cluster_id", ""))
139
+ rec = mock.state.clusters.get(cid)
140
+ if rec is None or rec.deleted:
141
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Cluster not found")
142
+ rec.state = "RESTARTING"
143
+ rec.ticks_remaining = 1
144
+ rec.fail_after = False
145
+ mock.state.bump()
146
+ return {}
147
+
148
+ @router.post("/api/2.1/clusters/permanent-delete")
149
+ @router.post("/api/2.0/clusters/permanent-delete")
150
+ async def permanent_delete(request: Request) -> dict[str, Any]:
151
+ stub_unimplemented(request, "clusters/permanent-delete")
152
+ return {}
153
+
154
+ @router.post("/api/2.1/clusters/events")
155
+ @router.post("/api/2.0/clusters/events")
156
+ async def events(request: Request) -> dict[str, Any]:
157
+ mock = mock_from(request)
158
+ body = await json_object(request)
159
+ cid = str(body.get("cluster_id", ""))
160
+ rec = mock.state.clusters.get(cid)
161
+ if rec is None or rec.deleted:
162
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Cluster not found")
163
+ return {"events": rec.events, "total_count": len(rec.events)}
164
+
165
+ @router.post("/api/2.0/libraries/install")
166
+ async def install(request: Request) -> dict[str, Any]:
167
+ mock = mock_from(request)
168
+ body = await json_object(request)
169
+ cid = str(body.get("cluster_id", ""))
170
+ rec = mock.state.clusters.get(cid)
171
+ if rec is None or rec.deleted:
172
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Cluster not found")
173
+ rec.libraries.extend(list(body.get("libraries") or []))
174
+ return {}
175
+
176
+ @router.post("/api/2.0/libraries/uninstall")
177
+ async def uninstall(request: Request) -> dict[str, Any]:
178
+ mock = mock_from(request)
179
+ body = await json_object(request)
180
+ cid = str(body.get("cluster_id", ""))
181
+ rec = mock.state.clusters.get(cid)
182
+ if rec is None or rec.deleted:
183
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Cluster not found")
184
+ requested = list(body.get("libraries") or [])
185
+ if requested:
186
+ rec.libraries = [lib for lib in rec.libraries if lib not in requested]
187
+ else:
188
+ rec.libraries = []
189
+ return {}
190
+
191
+ return router
@@ -0,0 +1,3 @@
1
+ from semblance_databricks.services.dbfs.routes import create_dbfs_router
2
+
3
+ __all__ = ["create_dbfs_router"]
@@ -0,0 +1,126 @@
1
+ """DBFS list/read, put, and add-block stub."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from fastapi import APIRouter, Request
10
+
11
+ from semblance_databricks.errors import DatabricksError
12
+ from semblance_databricks.http import json_object
13
+ from semblance_databricks.services.deps import mock_from, stub_unimplemented
14
+ from semblance_databricks.state import DbfsNode
15
+
16
+
17
+ def _normalize(path: str) -> str:
18
+ raw = path.replace("\\", "/")
19
+ if not raw.startswith("/"):
20
+ raw = "/" + raw
21
+ parts: list[str] = []
22
+ for seg in raw.split("/"):
23
+ if seg in {"", "."}:
24
+ continue
25
+ if seg == ".." or ":" in seg:
26
+ raise DatabricksError(400, "INVALID_PARAMETER_VALUE", "Invalid path")
27
+ parts.append(seg)
28
+ return "/" + "/".join(parts) if parts else "/"
29
+
30
+
31
+ def _parent(path: str) -> str:
32
+ if path in {"", "/"}:
33
+ return "/"
34
+ stripped = path.rstrip("/")
35
+ idx = stripped.rfind("/")
36
+ return stripped[:idx] or "/"
37
+
38
+
39
+ def _write_temp(root: str, dbfs_path: str, data: bytes) -> None:
40
+ base = Path(root).resolve()
41
+ rel = dbfs_path.lstrip("/")
42
+ dest = base.joinpath(*rel.split("/")).resolve()
43
+ try:
44
+ dest.relative_to(base)
45
+ except ValueError as exc:
46
+ raise DatabricksError(400, "INVALID_PARAMETER_VALUE", "Invalid path") from exc
47
+ dest.parent.mkdir(parents=True, exist_ok=True)
48
+ dest.write_bytes(data)
49
+
50
+
51
+ def create_dbfs_router() -> APIRouter:
52
+ router = APIRouter()
53
+
54
+ @router.get("/api/2.0/dbfs/list")
55
+ def list_dbfs(request: Request, path: str | None = None) -> dict[str, Any]:
56
+ mock = mock_from(request)
57
+ if not path:
58
+ raise DatabricksError(400, "INVALID_PARAMETER_VALUE", "path required")
59
+ prefix = _normalize(path)
60
+ files: list[dict[str, Any]] = []
61
+ for node in mock.state.dbfs.values():
62
+ parent = _parent(node.path)
63
+ if parent == prefix:
64
+ if node.path == prefix:
65
+ continue
66
+ files.append(
67
+ {
68
+ "path": node.path,
69
+ "is_dir": node.is_dir,
70
+ "file_size": 0 if node.is_dir else len(node.data),
71
+ }
72
+ )
73
+ if prefix not in mock.state.dbfs and not files:
74
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Path not found")
75
+ return {"files": files}
76
+
77
+ @router.get("/api/2.0/dbfs/read")
78
+ def read_dbfs(
79
+ request: Request,
80
+ path: str | None = None,
81
+ offset: int = 0,
82
+ length: int = 1048576,
83
+ ) -> dict[str, Any]:
84
+ mock = mock_from(request)
85
+ if not path:
86
+ raise DatabricksError(400, "INVALID_PARAMETER_VALUE", "path required")
87
+ node = mock.state.dbfs.get(_normalize(path))
88
+ if node is None or node.is_dir:
89
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "File not found")
90
+ data = node.data[offset : offset + length]
91
+ return {
92
+ "bytes_read": len(data),
93
+ "data": base64.b64encode(data).decode(),
94
+ }
95
+
96
+ @router.post("/api/2.0/dbfs/add-block")
97
+ async def add_block(request: Request) -> dict[str, Any]:
98
+ stub_unimplemented(request, "dbfs/add-block")
99
+ return {}
100
+
101
+ @router.post("/api/2.0/dbfs/put")
102
+ async def put_file(request: Request) -> dict[str, Any]:
103
+ mock = mock_from(request)
104
+ body = await json_object(request)
105
+ path = _normalize(str(body.get("path", "")))
106
+ if not path or path == "/":
107
+ raise DatabricksError(400, "INVALID_PARAMETER_VALUE", "path required")
108
+ if len(mock.state.dbfs) >= mock.config.dbfs_max_files:
109
+ raise DatabricksError(400, "MAX_BLOCK_SIZE_EXCEEDED", "DBFS file cap")
110
+ raw = str(body.get("contents", ""))
111
+ try:
112
+ content = base64.b64decode(raw, validate=True)
113
+ except Exception as exc:
114
+ raise DatabricksError(
115
+ 400, "INVALID_PARAMETER_VALUE", "contents must be base64"
116
+ ) from exc
117
+ total = sum(len(n.data) for n in mock.state.dbfs.values()) + len(content)
118
+ if total > mock.config.dbfs_max_bytes:
119
+ raise DatabricksError(400, "MAX_BLOCK_SIZE_EXCEEDED", "DBFS size cap")
120
+ mock.state.dbfs[path] = DbfsNode(path=path, is_dir=False, data=content)
121
+ if mock.config.dbfs_temp_dir:
122
+ _write_temp(mock.config.dbfs_temp_dir, path, content)
123
+ mock.state.bump()
124
+ return {}
125
+
126
+ return router
@@ -0,0 +1,3 @@
1
+ from semblance_databricks.services.dbsql.routes import create_dbsql_router
2
+
3
+ __all__ = ["create_dbsql_router"]
@@ -0,0 +1,163 @@
1
+ """SQL warehouses and statement execution (no Spark)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from fastapi import APIRouter, Request
8
+
9
+ from semblance_databricks.errors import DatabricksError
10
+ from semblance_databricks.http import json_object
11
+ from semblance_databricks.ids import make_id
12
+ from semblance_databricks.pagination import paginate
13
+ from semblance_databricks.services.deps import mock_from
14
+ from semblance_databricks.state import WarehouseRecord
15
+
16
+
17
+ def _statement_payload(statement_id: str, rec: dict[str, Any]) -> dict[str, Any]:
18
+ chunks: list[Any] = rec.get("chunks") or []
19
+ index = int(rec.get("chunk_index", 0))
20
+ chunk = chunks[index] if index < len(chunks) else []
21
+ next_chunk = None
22
+ if index + 1 < len(chunks):
23
+ next_chunk = index + 1
24
+ result: dict[str, Any] = {
25
+ "chunk_index": index,
26
+ "row_count": len(chunk) if isinstance(chunk, list) else 0,
27
+ "data_array": chunk,
28
+ }
29
+ if next_chunk is not None:
30
+ result["next_chunk_internal"] = next_chunk
31
+ return {
32
+ "statement_id": statement_id,
33
+ "status": {"state": rec.get("state", "SUCCEEDED")},
34
+ "manifest": {
35
+ "format": "JSON_ARRAY",
36
+ "total_chunk_count": len(chunks),
37
+ "total_row_count": sum(
38
+ len(c) if isinstance(c, list) else 0 for c in chunks
39
+ ),
40
+ },
41
+ "result": result,
42
+ }
43
+
44
+
45
+ def create_dbsql_router() -> APIRouter:
46
+ router = APIRouter()
47
+
48
+ @router.get("/api/2.0/sql/warehouses")
49
+ def list_warehouses(
50
+ request: Request,
51
+ page_size: int | None = None,
52
+ page_token: str | None = None,
53
+ ) -> dict[str, Any]:
54
+ mock = mock_from(request)
55
+ items = [
56
+ {
57
+ "id": w.id,
58
+ "name": w.name,
59
+ "state": w.state,
60
+ "cluster_size": w.cluster_size,
61
+ }
62
+ for w in mock.state.warehouses.values()
63
+ ]
64
+ page, nxt = paginate(
65
+ items,
66
+ page_size=page_size,
67
+ page_token=page_token,
68
+ resource="warehouses",
69
+ codec=mock.page_token_codec,
70
+ revision=mock.state.revision,
71
+ default_page_size=mock.config.default_page_size,
72
+ max_page_size=mock.config.max_page_size,
73
+ )
74
+ body: dict[str, Any] = {"warehouses": page}
75
+ if nxt:
76
+ body["next_page_token"] = nxt
77
+ return body
78
+
79
+ @router.post("/api/2.0/sql/warehouses")
80
+ async def create_warehouse(request: Request) -> dict[str, Any]:
81
+ mock = mock_from(request)
82
+ body = await json_object(request)
83
+ name = str(body.get("name", "warehouse"))
84
+ wid = make_id("warehouse", f"{name}:{mock.state.next_seq()}", mock.config.seed)
85
+ rec = WarehouseRecord(
86
+ id=wid,
87
+ name=name,
88
+ cluster_size=str(body.get("cluster_size", "X-Small")),
89
+ )
90
+ mock.state.warehouses[wid] = rec
91
+ mock.state.permissions[("warehouses", wid)] = {
92
+ "object_id": wid,
93
+ "object_type": "warehouses",
94
+ "access_control_list": [],
95
+ }
96
+ mock.state.bump()
97
+ return {"id": wid}
98
+
99
+ @router.get("/api/2.0/sql/warehouses/{warehouse_id}")
100
+ def get_warehouse(request: Request, warehouse_id: str) -> dict[str, Any]:
101
+ mock = mock_from(request)
102
+ rec = mock.state.warehouses.get(warehouse_id)
103
+ if rec is None:
104
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Warehouse not found")
105
+ return {
106
+ "id": rec.id,
107
+ "name": rec.name,
108
+ "state": rec.state,
109
+ "cluster_size": rec.cluster_size,
110
+ }
111
+
112
+ @router.delete("/api/2.0/sql/warehouses/{warehouse_id}")
113
+ def delete_warehouse(request: Request, warehouse_id: str) -> dict[str, Any]:
114
+ mock = mock_from(request)
115
+ if warehouse_id not in mock.state.warehouses:
116
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Warehouse not found")
117
+ del mock.state.warehouses[warehouse_id]
118
+ mock.state.bump()
119
+ return {}
120
+
121
+ @router.post("/api/2.0/sql/statements")
122
+ async def execute_statement(request: Request) -> dict[str, Any]:
123
+ mock = mock_from(request)
124
+ body = await json_object(request)
125
+ warehouse_id = str(body.get("warehouse_id") or "")
126
+ sql = str(body.get("statement", ""))
127
+ if not warehouse_id:
128
+ raise DatabricksError(
129
+ 400, "INVALID_PARAMETER_VALUE", "warehouse_id required"
130
+ )
131
+ if warehouse_id not in mock.state.warehouses:
132
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Warehouse not found")
133
+ sid = make_id("statement", f"{sql}:{mock.state.next_seq()}", mock.config.seed)
134
+ callback = mock.state.statement_callbacks.get(
135
+ sql
136
+ ) or mock.state.statement_callbacks.get(warehouse_id)
137
+ if callback is not None:
138
+ payload = callback(body, mock.state)
139
+ chunks = payload.get("chunks", payload.get("data_array", [["1"]]))
140
+ if chunks and not isinstance(chunks[0], list):
141
+ chunks = [chunks]
142
+ else:
143
+ chunks = [[["ok"]]]
144
+ rec = {"state": "SUCCEEDED", "chunks": chunks, "chunk_index": 0}
145
+ mock.state.statements[sid] = rec
146
+ mock.state.bump()
147
+ return _statement_payload(sid, rec)
148
+
149
+ @router.get("/api/2.0/sql/statements/{statement_id}")
150
+ def get_statement(
151
+ request: Request,
152
+ statement_id: str,
153
+ chunk_index: int | None = None,
154
+ ) -> dict[str, Any]:
155
+ mock = mock_from(request)
156
+ rec = mock.state.statements.get(statement_id)
157
+ if rec is None:
158
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", "Statement not found")
159
+ if chunk_index is not None:
160
+ rec = {**rec, "chunk_index": chunk_index}
161
+ return _statement_payload(statement_id, rec)
162
+
163
+ return router
@@ -0,0 +1,37 @@
1
+ """Request helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Protocol, cast
6
+
7
+ from fastapi import Request
8
+
9
+ from semblance_databricks.config import DatabricksMockConfig
10
+ from semblance_databricks.errors import DatabricksError
11
+ from semblance_databricks.ids import PageTokenCodec
12
+ from semblance_databricks.state import DatabricksState
13
+
14
+
15
+ class DatabricksMockLike(Protocol):
16
+ config: DatabricksMockConfig
17
+ state: DatabricksState
18
+ page_token_codec: PageTokenCodec
19
+
20
+ def tick(self) -> None: ...
21
+
22
+
23
+ def mock_from(request: Request) -> DatabricksMockLike:
24
+ return cast(DatabricksMockLike, request.app.state.databricks_mock)
25
+
26
+
27
+ def require_fail_stage(request: Request, stage: str) -> None:
28
+ mock = mock_from(request)
29
+ if mock.config.fail_stage == stage:
30
+ raise DatabricksError(500, "INTERNAL_ERROR", f"fail_stage={stage}")
31
+
32
+
33
+ def stub_unimplemented(request: Request, name: str) -> None:
34
+ mock = mock_from(request)
35
+ if mock.config.auth == "strict":
36
+ raise DatabricksError(501, "FEATURE_DISABLED", f"{name} is not implemented")
37
+ raise DatabricksError(404, "RESOURCE_DOES_NOT_EXIST", f"{name} not found")
@@ -0,0 +1,3 @@
1
+ from semblance_databricks.services.jobs.routes import create_jobs_router
2
+
3
+ __all__ = ["create_jobs_router"]