aidial-client 0.17.0.dev2__tar.gz → 0.17.0.dev3__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 (72) hide show
  1. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/PKG-INFO +1 -1
  2. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/helpers/storage_resource.py +30 -10
  3. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/files.py +23 -26
  4. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/metadata.py +12 -3
  5. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/prompts.py +8 -8
  6. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/resource_permissions.py +22 -14
  7. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/pyproject.toml +1 -1
  8. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/LICENSE +0 -0
  9. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/README.md +0 -0
  10. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/__init__.py +0 -0
  11. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_auth.py +0 -0
  12. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_client.py +0 -0
  13. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_client_pool.py +0 -0
  14. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_compatibility/__init__.py +0 -0
  15. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_compatibility/openai.py +0 -0
  16. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_compatibility/pydantic.py +0 -0
  17. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_compatibility/pydantic_v1.py +0 -0
  18. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_constants.py +0 -0
  19. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_exception.py +0 -0
  20. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_http_client/__init__.py +0 -0
  21. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_http_client/_async.py +0 -0
  22. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_http_client/_base.py +0 -0
  23. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_http_client/_sse.py +0 -0
  24. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_http_client/_sync.py +0 -0
  25. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_internal_types/__init__.py +0 -0
  26. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_internal_types/_defaults.py +0 -0
  27. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_internal_types/_generic.py +0 -0
  28. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_internal_types/_http_request.py +0 -0
  29. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_internal_types/_json_rpc.py +0 -0
  30. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_internal_types/_model.py +0 -0
  31. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_log.py +0 -0
  32. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_utils/__init__.py +0 -0
  33. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_utils/_alias.py +0 -0
  34. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_utils/_dict.py +0 -0
  35. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_utils/_openai.py +0 -0
  36. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_utils/_response_processing.py +0 -0
  37. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/_utils/_type_guard.py +0 -0
  38. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/helpers/__init__.py +0 -0
  39. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/helpers/_url.py +0 -0
  40. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/py.typed +0 -0
  41. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/__init__.py +0 -0
  42. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/application.py +0 -0
  43. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/base.py +0 -0
  44. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/bucket.py +0 -0
  45. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/chat/__init__.py +0 -0
  46. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/chat/completions.py +0 -0
  47. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/client_channel.py +0 -0
  48. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/deployments.py +0 -0
  49. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/model.py +0 -0
  50. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/toolset.py +0 -0
  51. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/resources/user.py +0 -0
  52. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/__init__.py +0 -0
  53. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/application.py +0 -0
  54. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/bucket.py +0 -0
  55. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/__init__.py +0 -0
  56. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/cache.py +0 -0
  57. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/function.py +0 -0
  58. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/legacy/__init__.py +0 -0
  59. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/legacy/application_request.py +0 -0
  60. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/legacy/chat_completion.py +0 -0
  61. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/request.py +0 -0
  62. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/request_param.py +0 -0
  63. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/response.py +0 -0
  64. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/chat/tool.py +0 -0
  65. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/client_channel.py +0 -0
  66. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/deployment.py +0 -0
  67. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/file.py +0 -0
  68. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/metadata.py +0 -0
  69. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/model.py +0 -0
  70. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/prompt.py +0 -0
  71. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/toolset.py +0 -0
  72. {aidial_client-0.17.0.dev2 → aidial_client-0.17.0.dev3}/aidial_client/types/user.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aidial-client
3
- Version: 0.17.0.dev2
3
+ Version: 0.17.0.dev3
4
4
  Summary: A Python client library for the AI DIAL API
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  from pathlib import PurePosixPath
2
2
  from typing import Literal, cast, get_args
3
- from urllib.parse import urljoin, urlparse
3
+ from urllib.parse import quote, unquote, urljoin, urlparse, urlsplit
4
4
 
5
5
  from aidial_client._compatibility.pydantic_v1 import BaseModel
6
6
  from aidial_client._constants import API_PREFIX
@@ -12,6 +12,22 @@ from aidial_client.helpers._url import enforce_trailing_slash
12
12
  StorageResourceType = Literal["files", "conversations", "prompts"]
13
13
 
14
14
 
15
+ def _percent_encode_relative_url(url: str) -> str:
16
+ """
17
+ Percent-encode each path segment so reserved characters (space, ``#``,
18
+ ``?``, ``[`` …) reach DIAL Core encoded instead of making it answer 500.
19
+ Segments are decoded first, so a decoded path (``my file.txt``) and an
20
+ already-encoded one (``my%20file.txt``, as returned by the API) converge
21
+ without double-encoding. Absolute URLs come from the API already encoded and
22
+ are returned untouched.
23
+ """
24
+ if urlsplit(url).netloc:
25
+ return url
26
+
27
+ segments = url.split("/")
28
+ return "/".join(quote(unquote(seg), safe="") for seg in segments)
29
+
30
+
15
31
  def _is_directory(s: str) -> bool:
16
32
  return s[-1] == "/"
17
33
 
@@ -60,7 +76,7 @@ def safe_parse_storage_resource(
60
76
  f"API prefix as relative part is not allowed: {url}"
61
77
  )
62
78
 
63
- absolute_url = urljoin(dial_api_url, url)
79
+ absolute_url = urljoin(dial_api_url, _percent_encode_relative_url(url))
64
80
  url_parsed = urlparse(absolute_url)
65
81
  dial_api_parsed = urlparse(dial_api_url)
66
82
  if url_parsed.netloc != dial_api_parsed.netloc:
@@ -133,27 +149,30 @@ class DialStorageResourceMixin(BaseModel):
133
149
  resource_type: StorageResourceType
134
150
  dial_api_url: str
135
151
 
136
- def get_storage_resource(self, url: str) -> DialStorageResource:
152
+ def get_storage_resource(
153
+ self, url: str | PurePosixPath
154
+ ) -> DialStorageResource:
137
155
  """
138
156
  Get the storage resource object from the URL
139
157
  Args:
140
- url (str): The URL to be processed.
158
+ url (str | PurePosixPath): The URL to be processed.
141
159
  Returns:
142
160
  DialStorageResource: The storage resource object
143
161
  """
144
162
  return parse_storage_resource(
145
- url=url,
163
+ url=str(url),
146
164
  dial_api_url=self.dial_api_url,
147
165
  expected_resource_type=self.resource_type,
148
166
  )
149
167
 
150
- def get_api_path(self, url: str) -> str:
168
+ def get_api_path(self, url: str | PurePosixPath) -> str:
151
169
  """
152
- Convert URL, that could relative or absolute, to relative URL
170
+ Convert URL, that could relative or absolute, to relative,
171
+ percent-encoded API path.
153
172
  """
154
173
  return self.get_storage_resource(url).api_path
155
174
 
156
- def get_display_name(self, url: str) -> str:
175
+ def get_display_name(self, url: str | PurePosixPath) -> str:
157
176
  """
158
177
  Get the display name of the resource from the URL
159
178
  """
@@ -164,7 +183,7 @@ class DialStorageResourceMixin(BaseModel):
164
183
  url: str | PurePosixPath,
165
184
  etag_if_match: str | None,
166
185
  ) -> tuple[FinalRequestOptions, str]:
167
- storage_resource = self.get_storage_resource(str(url))
186
+ storage_resource = self.get_storage_resource(url)
168
187
 
169
188
  if storage_resource.filename is None:
170
189
  raise InvalidDialURLError("URL points to a directory, not a file")
@@ -179,4 +198,5 @@ class DialStorageResourceMixin(BaseModel):
179
198
  ),
180
199
  )
181
200
 
182
- return options, storage_resource.filename
201
+ # api_path is percent-encoded; return a human-readable filename.
202
+ return options, unquote(storage_resource.filename)
@@ -25,6 +25,19 @@ from aidial_client.types.file import FileDownloadResponse
25
25
  from aidial_client.types.metadata import FileItem, FileMetadata
26
26
 
27
27
 
28
+ def _move_copy_body(
29
+ resource: DialStorageResourceMixin,
30
+ source: str | PurePosixPath,
31
+ destination: str | PurePosixPath,
32
+ overwrite: bool,
33
+ ) -> dict[str, object]:
34
+ return {
35
+ "sourceUrl": resource.get_api_path(source),
36
+ "destinationUrl": resource.get_api_path(destination),
37
+ "overwrite": overwrite,
38
+ }
39
+
40
+
28
41
  def _files_error_processor(
29
42
  http_status_error: httpx.HTTPStatusError,
30
43
  ) -> DialException | None:
@@ -54,7 +67,7 @@ class Files(Resource, DialStorageResourceMixin):
54
67
  cast_to=FileItem,
55
68
  options=FinalRequestOptions(
56
69
  method="PUT",
57
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
70
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
58
71
  files={"file": file},
59
72
  headers=remove_none(
60
73
  {
@@ -88,7 +101,7 @@ class Files(Resource, DialStorageResourceMixin):
88
101
  cast_to=NoneType,
89
102
  options=FinalRequestOptions(
90
103
  method="DELETE",
91
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
104
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
92
105
  headers=remove_none(
93
106
  {
94
107
  "If-Match": etag_if_match,
@@ -109,11 +122,7 @@ class Files(Resource, DialStorageResourceMixin):
109
122
  options=FinalRequestOptions(
110
123
  method="POST",
111
124
  url=urljoin(API_PREFIX, "ops/resource/move"),
112
- json_data={
113
- "sourceUrl": self.get_api_path(str(source)),
114
- "destinationUrl": self.get_api_path(str(destination)),
115
- "overwrite": overwrite,
116
- },
125
+ json_data=_move_copy_body(self, source, destination, overwrite),
117
126
  ),
118
127
  on_http_error=_files_error_processor,
119
128
  )
@@ -129,11 +138,7 @@ class Files(Resource, DialStorageResourceMixin):
129
138
  options=FinalRequestOptions(
130
139
  method="POST",
131
140
  url=urljoin(API_PREFIX, "ops/resource/copy"),
132
- json_data={
133
- "sourceUrl": self.get_api_path(str(source)),
134
- "destinationUrl": self.get_api_path(str(destination)),
135
- "overwrite": overwrite,
136
- },
141
+ json_data=_move_copy_body(self, source, destination, overwrite),
137
142
  ),
138
143
  on_http_error=_files_error_processor,
139
144
  )
@@ -147,7 +152,7 @@ class Files(Resource, DialStorageResourceMixin):
147
152
  ) -> FileMetadata:
148
153
  return self.metadata.get(
149
154
  resource="files",
150
- relative_url=self.get_api_path(str(url)),
155
+ relative_url=self.get_api_path(url),
151
156
  limit=limit,
152
157
  token=token,
153
158
  )
@@ -168,7 +173,7 @@ class AsyncFiles(AsyncResource, DialStorageResourceMixin):
168
173
  cast_to=FileItem,
169
174
  options=FinalRequestOptions(
170
175
  method="PUT",
171
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
176
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
172
177
  files={"file": file},
173
178
  headers=remove_none(
174
179
  {
@@ -215,7 +220,7 @@ class AsyncFiles(AsyncResource, DialStorageResourceMixin):
215
220
  cast_to=NoneType,
216
221
  options=FinalRequestOptions(
217
222
  method="DELETE",
218
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
223
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
219
224
  headers=remove_none(
220
225
  {
221
226
  "If-Match": etag_if_match,
@@ -236,11 +241,7 @@ class AsyncFiles(AsyncResource, DialStorageResourceMixin):
236
241
  options=FinalRequestOptions(
237
242
  method="POST",
238
243
  url=urljoin(API_PREFIX, "ops/resource/move"),
239
- json_data={
240
- "sourceUrl": self.get_api_path(str(source)),
241
- "destinationUrl": self.get_api_path(str(destination)),
242
- "overwrite": overwrite,
243
- },
244
+ json_data=_move_copy_body(self, source, destination, overwrite),
244
245
  ),
245
246
  on_http_error=_files_error_processor,
246
247
  )
@@ -256,11 +257,7 @@ class AsyncFiles(AsyncResource, DialStorageResourceMixin):
256
257
  options=FinalRequestOptions(
257
258
  method="POST",
258
259
  url=urljoin(API_PREFIX, "ops/resource/copy"),
259
- json_data={
260
- "sourceUrl": self.get_api_path(str(source)),
261
- "destinationUrl": self.get_api_path(str(destination)),
262
- "overwrite": overwrite,
263
- },
260
+ json_data=_move_copy_body(self, source, destination, overwrite),
264
261
  ),
265
262
  on_http_error=_files_error_processor,
266
263
  )
@@ -274,7 +271,7 @@ class AsyncFiles(AsyncResource, DialStorageResourceMixin):
274
271
  ) -> FileMetadata:
275
272
  return await self.metadata.get(
276
273
  resource="files",
277
- relative_url=self.get_api_path(str(url)),
274
+ relative_url=self.get_api_path(url),
278
275
  limit=limit,
279
276
  token=token,
280
277
  )
@@ -6,7 +6,10 @@ from typing_extensions import assert_never
6
6
  from aidial_client._constants import METADATA_PREFIX
7
7
  from aidial_client._internal_types._http_request import FinalRequestOptions
8
8
  from aidial_client._utils._dict import remove_none
9
- from aidial_client.helpers.storage_resource import StorageResourceType
9
+ from aidial_client.helpers.storage_resource import (
10
+ StorageResourceType,
11
+ _percent_encode_relative_url,
12
+ )
10
13
  from aidial_client.resources.base import AsyncResource, Resource
11
14
  from aidial_client.types.metadata import (
12
15
  ConversationMetadata,
@@ -71,7 +74,10 @@ class Metadata(Resource):
71
74
  cast_to=_get_cast_to(resource),
72
75
  options=FinalRequestOptions(
73
76
  method="GET",
74
- url=urljoin(METADATA_PREFIX, relative_url),
77
+ url=urljoin(
78
+ METADATA_PREFIX,
79
+ _percent_encode_relative_url(relative_url),
80
+ ),
75
81
  params=remove_none({"limit": limit, "token": token}),
76
82
  ),
77
83
  )
@@ -120,7 +126,10 @@ class AsyncMetadata(AsyncResource):
120
126
  cast_to=_get_cast_to(resource),
121
127
  options=FinalRequestOptions(
122
128
  method="GET",
123
- url=urljoin(METADATA_PREFIX, relative_url),
129
+ url=urljoin(
130
+ METADATA_PREFIX,
131
+ _percent_encode_relative_url(relative_url),
132
+ ),
124
133
  params=remove_none({"limit": limit, "token": token}),
125
134
  ),
126
135
  )
@@ -56,7 +56,7 @@ class Prompts(Resource, DialStorageResourceMixin):
56
56
  cast_to=PromptItem,
57
57
  options=FinalRequestOptions(
58
58
  method="PUT",
59
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
59
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
60
60
  json_data=_prompt_to_json(prompt),
61
61
  headers=remove_none(
62
62
  {
@@ -74,7 +74,7 @@ class Prompts(Resource, DialStorageResourceMixin):
74
74
  cast_to=Prompt,
75
75
  options=FinalRequestOptions(
76
76
  method="GET",
77
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
77
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
78
78
  ),
79
79
  on_http_error=_prompts_error_processor,
80
80
  )
@@ -88,7 +88,7 @@ class Prompts(Resource, DialStorageResourceMixin):
88
88
  cast_to=NoneType,
89
89
  options=FinalRequestOptions(
90
90
  method="DELETE",
91
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
91
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
92
92
  headers=remove_none(
93
93
  {
94
94
  "If-Match": etag_if_match,
@@ -101,7 +101,7 @@ class Prompts(Resource, DialStorageResourceMixin):
101
101
  def get_metadata(self, url: str | PurePosixPath) -> PromptMetadata:
102
102
  return self.metadata.get(
103
103
  resource="prompts",
104
- relative_url=self.get_api_path(str(url)),
104
+ relative_url=self.get_api_path(url),
105
105
  )
106
106
 
107
107
 
@@ -120,7 +120,7 @@ class AsyncPrompts(AsyncResource, DialStorageResourceMixin):
120
120
  cast_to=PromptItem,
121
121
  options=FinalRequestOptions(
122
122
  method="PUT",
123
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
123
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
124
124
  json_data=_prompt_to_json(prompt),
125
125
  headers=remove_none(
126
126
  {
@@ -138,7 +138,7 @@ class AsyncPrompts(AsyncResource, DialStorageResourceMixin):
138
138
  cast_to=Prompt,
139
139
  options=FinalRequestOptions(
140
140
  method="GET",
141
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
141
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
142
142
  ),
143
143
  on_http_error=_prompts_error_processor,
144
144
  )
@@ -152,7 +152,7 @@ class AsyncPrompts(AsyncResource, DialStorageResourceMixin):
152
152
  cast_to=NoneType,
153
153
  options=FinalRequestOptions(
154
154
  method="DELETE",
155
- url=urljoin(API_PREFIX, self.get_api_path(str(url))),
155
+ url=urljoin(API_PREFIX, self.get_api_path(url)),
156
156
  headers=remove_none(
157
157
  {
158
158
  "If-Match": etag_if_match,
@@ -165,5 +165,5 @@ class AsyncPrompts(AsyncResource, DialStorageResourceMixin):
165
165
  async def get_metadata(self, url: str | PurePosixPath) -> PromptMetadata:
166
166
  return await self.metadata.get(
167
167
  resource="prompts",
168
- relative_url=self.get_api_path(str(url)),
168
+ relative_url=self.get_api_path(url),
169
169
  )
@@ -1,10 +1,30 @@
1
1
  from aidial_client._internal_types._generic import NoneType
2
2
  from aidial_client._internal_types._http_request import FinalRequestOptions
3
+ from aidial_client.helpers.storage_resource import (
4
+ _percent_encode_relative_url,
5
+ )
3
6
  from aidial_client.resources.base import AsyncResource, Resource
4
7
 
5
8
  _GRANT_URL = "v1/ops/resource/per-request-permissions/grant"
6
9
 
7
10
 
11
+ def _grant_body(
12
+ resources: list[str],
13
+ receiver: str,
14
+ permissions: list[str],
15
+ ) -> dict[str, object]:
16
+ return {
17
+ "resourcePermissions": [
18
+ {
19
+ "url": _percent_encode_relative_url(url),
20
+ "permissions": permissions,
21
+ }
22
+ for url in resources
23
+ ],
24
+ "receiver": receiver,
25
+ }
26
+
27
+
8
28
  class ResourcePermissions(Resource):
9
29
  def grant(
10
30
  self,
@@ -19,13 +39,7 @@ class ResourcePermissions(Resource):
19
39
  options=FinalRequestOptions(
20
40
  method="POST",
21
41
  url=_GRANT_URL,
22
- json_data={
23
- "resourcePermissions": [
24
- {"url": url, "permissions": permissions}
25
- for url in resources
26
- ],
27
- "receiver": receiver,
28
- },
42
+ json_data=_grant_body(resources, receiver, permissions),
29
43
  ),
30
44
  )
31
45
 
@@ -44,12 +58,6 @@ class AsyncResourcePermissions(AsyncResource):
44
58
  options=FinalRequestOptions(
45
59
  method="POST",
46
60
  url=_GRANT_URL,
47
- json_data={
48
- "resourcePermissions": [
49
- {"url": url, "permissions": permissions}
50
- for url in resources
51
- ],
52
- "receiver": receiver,
53
- },
61
+ json_data=_grant_body(resources, receiver, permissions),
54
62
  ),
55
63
  )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aidial-client"
3
- version = "0.17.0.dev2"
3
+ version = "0.17.0.dev3"
4
4
  description = "A Python client library for the AI DIAL API"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"