Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions databricks/sdk/service/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ def _escape_multi_segment_path_parameter(param: str) -> str:
return urllib.parse.quote(param)


def _escape_path_parameter(param: str) -> str:
"""Percent-encode a single URL path segment.

Unlike ``_escape_multi_segment_path_parameter``, this encodes ``/`` as
``%2F`` so values that contain slashes (e.g. Unity Catalog column names)
remain a single path segment.
"""
return urllib.parse.quote(param, safe="")


def _timestamp(d: Dict[str, any], field: str) -> Optional[Timestamp]:
"""
Helper function to convert a timestamp string to a Timestamp object.
Expand Down
9 changes: 5 additions & 4 deletions databricks/sdk/service/catalog.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions tests/test_entity_tag_assignments_encoding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from types import SimpleNamespace
from unittest.mock import MagicMock

from databricks.sdk.service.catalog import EntityTagAssignmentsAPI


def _client_with_mock_api():
api = MagicMock()
api._cfg = SimpleNamespace(workspace_id=None)
api.do.return_value = {"tag_assignments": []}
return EntityTagAssignmentsAPI(api), api


def test_list_encodes_slash_in_entity_name():
client, api = _client_with_mock_api()
entity_name = "main.my_schema.my_table.Inferences/Second"
list(client.list(entity_type="columns", entity_name=entity_name))
method, path = api.do.call_args.args[:2]
assert method == "GET"
assert path == (
"/api/2.1/unity-catalog/entity-tag-assignments/columns/"
"main.my_schema.my_table.Inferences%2FSecond/tags"
)


def test_get_encodes_slash_in_entity_name_and_tag_key():
client, api = _client_with_mock_api()
api.do.return_value = {
"entity_type": "columns",
"entity_name": "main.s.t.Inferences/Second",
"tag_key": "a/b",
}
client.get(
entity_type="columns",
entity_name="main.s.t.Inferences/Second",
tag_key="a/b",
)
method, path = api.do.call_args.args[:2]
assert method == "GET"
assert path == (
"/api/2.1/unity-catalog/entity-tag-assignments/columns/"
"main.s.t.Inferences%2FSecond/tags/a%2Fb"
)


def test_delete_encodes_slash_in_entity_name():
client, api = _client_with_mock_api()
client.delete(
entity_type="columns",
entity_name="cat.sch.tbl.Inferences/Second",
tag_key="env",
)
method, path = api.do.call_args.args[:2]
assert method == "DELETE"
assert path == (
"/api/2.1/unity-catalog/entity-tag-assignments/columns/"
"cat.sch.tbl.Inferences%2FSecond/tags/env"
)
13 changes: 13 additions & 0 deletions tests/test_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
_duration,
_enum,
_escape_multi_segment_path_parameter,
_escape_path_parameter,
_fieldmask,
_from_dict,
_int64,
Expand Down Expand Up @@ -93,6 +94,18 @@ def test_escape_multi_segment_path_parameter():
assert _escape_multi_segment_path_parameter("a#b") == "a%23b"


def test_escape_path_parameter_encodes_slash():
# Single-segment encoding must turn "/" into "%2F" (issue #1493).
assert _escape_path_parameter("a/b") == "a%2Fb"
assert _escape_path_parameter("main.schema.table.Inferences/Second") == (
"main.schema.table.Inferences%2FSecond"
)
assert _escape_path_parameter("a b") == "a%20b"
assert _escape_path_parameter("a?b") == "a%3Fb"
assert _escape_path_parameter("a#b") == "a%23b"
assert _escape_path_parameter("a%b") == "a%25b"


@pytest.mark.parametrize(
"input_dict,field_name,expected_timestamp,description",
[
Expand Down
Loading