Skip to content

Commit 85ba774

Browse files
authored
Add get_workspace_id to WorkspaceClient (#537)
## Changes There are times when it is especially useful to get the workspace ID for the current workspace client. Currently, the workspace ID for the current workspace is exposed as a header in the SCIM Me API call. We'll expose this through a get_workspace_id() method, caching the workspace ID for the lifetime of the client. In the future, we may add a meta service for exposing information about the current account/workspace. At that point, we can migrate off of this somewhat hacky approach. Ports databricks/databricks-sdk-go#808 to the Python SDK. ## Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> - [ ] `make test` run locally - [ ] `make fmt` applied - [ ] relevant integration tests applied
1 parent 09aa3e9 commit 85ba774

File tree

6 files changed

+87
-14
lines changed

6 files changed

+87
-14
lines changed

.codegen/__init__.py.tmpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ class WorkspaceClient:
8181
return self._{{.SnakeName}}
8282
{{end -}}{{end}}
8383

84+
def get_workspace_id(self) -> int:
85+
"""Get the workspace ID of the workspace that this client is connected to."""
86+
response = self._api_client.do("GET",
87+
"/api/2.0/preview/scim/v2/Me",
88+
response_headers=['X-Databricks-Org-Id'])
89+
return int(response["X-Databricks-Org-Id"])
90+
8491
def __repr__(self):
8592
return f"WorkspaceClient(host='{self._config.host}', auth_type='{self._config.auth_type}', ...)"
8693

databricks/sdk/__init__.py

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

databricks/sdk/casing.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class _Name(object):
2+
"""Parses a name in camelCase, PascalCase, snake_case, or kebab-case into its segments."""
3+
4+
def __init__(self, raw_name: str):
5+
#
6+
self._segments = []
7+
segment = []
8+
for ch in raw_name:
9+
if ch.isupper():
10+
if segment:
11+
self._segments.append(''.join(segment))
12+
segment = [ch.lower()]
13+
elif ch.islower():
14+
segment.append(ch)
15+
else:
16+
if segment:
17+
self._segments.append(''.join(segment))
18+
segment = []
19+
if segment:
20+
self._segments.append(''.join(segment))
21+
22+
def to_snake_case(self) -> str:
23+
return '_'.join(self._segments)
24+
25+
def to_header_case(self) -> str:
26+
return '-'.join([s.capitalize() for s in self._segments])
27+
28+
29+
class Casing(object):
30+
31+
@staticmethod
32+
def to_header_case(name: str) -> str:
33+
"""
34+
Convert a name from camelCase, PascalCase, snake_case, or kebab-case to header-case.
35+
:param name:
36+
:return:
37+
"""
38+
return _Name(name).to_header_case()

databricks/sdk/core.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from requests.adapters import HTTPAdapter
99

10+
from .casing import Casing
1011
from .config import *
1112
# To preserve backwards compatibility (as these definitions were previously in this module)
1213
from .credentials_provider import *
@@ -115,7 +116,8 @@ def do(self,
115116
body: dict = None,
116117
raw: bool = False,
117118
files=None,
118-
data=None) -> Union[dict, BinaryIO]:
119+
data=None,
120+
response_headers: List[str] = None) -> Union[dict, BinaryIO]:
119121
# Remove extra `/` from path for Files API
120122
# Once we've fixed the OpenAPI spec, we can remove this
121123
path = re.sub('^/api/2.0/fs/files//', '/api/2.0/fs/files/', path)
@@ -125,14 +127,22 @@ def do(self,
125127
retryable = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
126128
is_retryable=self._is_retryable,
127129
clock=self._cfg.clock)
128-
return retryable(self._perform)(method,
129-
path,
130-
query=query,
131-
headers=headers,
132-
body=body,
133-
raw=raw,
134-
files=files,
135-
data=data)
130+
response = retryable(self._perform)(method,
131+
path,
132+
query=query,
133+
headers=headers,
134+
body=body,
135+
raw=raw,
136+
files=files,
137+
data=data)
138+
if raw:
139+
return StreamingResponse(response)
140+
resp = dict()
141+
for header in response_headers if response_headers else []:
142+
resp[header] = response.headers.get(Casing.to_header_case(header))
143+
if not len(response.content):
144+
return resp
145+
return {**resp, **response.json()}
136146

137147
@staticmethod
138148
def _is_retryable(err: BaseException) -> Optional[str]:
@@ -219,11 +229,7 @@ def _perform(self,
219229
# See https://stackoverflow.com/a/58821552/277035
220230
payload = response.json()
221231
raise self._make_nicer_error(response=response, **payload) from None
222-
if raw:
223-
return StreamingResponse(response)
224-
if not len(response.content):
225-
return {}
226-
return response.json()
232+
return response
227233
except requests.exceptions.JSONDecodeError:
228234
message = self._make_sense_from_html(response.text)
229235
if not message:

tests/integration/test_client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ def test_get_workspace_client(a):
99
pytest.skip("no workspaces")
1010
w = a.get_workspace_client(wss[0])
1111
assert w.current_user.me().active
12+
13+
14+
def test_get_workspace_id(ucws, env_or_skip):
15+
ws_id = int(env_or_skip('THIS_WORKSPACE_ID'))
16+
assert ucws.get_workspace_id() == ws_id

tests/testdata/test_casing.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import pytest
2+
3+
from databricks.sdk.casing import Casing
4+
5+
6+
@pytest.mark.parametrize('name, expected', [('', ''), ('a', 'A'), ('abc', 'Abc'), ('Abc', 'Abc'),
7+
('abc_def', 'Abc-Def'), ('abc-def', 'Abc-Def'),
8+
('abcDef', 'Abc-Def'), ('AbcDef', 'Abc-Def'), ])
9+
def test_to_header_case(name, expected):
10+
assert Casing.to_header_case(name) == expected

0 commit comments

Comments
 (0)