aidial-client 0.11.0.dev6__tar.gz → 0.12.0.dev2__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 (71) hide show
  1. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/PKG-INFO +55 -1
  2. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/README.md +54 -0
  3. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/__init__.py +2 -0
  4. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_client.py +2 -0
  5. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_http_client/_async.py +31 -11
  6. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_http_client/_base.py +19 -0
  7. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_http_client/_sync.py +4 -11
  8. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/helpers/storage_resource.py +24 -0
  9. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/__init__.py +3 -0
  10. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/files.py +21 -31
  11. aidial_client-0.12.0.dev2/aidial_client/resources/user.py +19 -0
  12. aidial_client-0.12.0.dev2/aidial_client/types/user.py +11 -0
  13. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/pyproject.toml +1 -1
  14. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/LICENSE +0 -0
  15. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_auth.py +0 -0
  16. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_client_pool.py +0 -0
  17. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_compatibility/__init__.py +0 -0
  18. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_compatibility/openai.py +0 -0
  19. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_compatibility/pydantic.py +0 -0
  20. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_compatibility/pydantic_v1.py +0 -0
  21. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_constants.py +0 -0
  22. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_exception.py +0 -0
  23. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_http_client/__init__.py +0 -0
  24. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_http_client/_sse.py +0 -0
  25. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_internal_types/__init__.py +0 -0
  26. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_internal_types/_defaults.py +0 -0
  27. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_internal_types/_generic.py +0 -0
  28. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_internal_types/_http_request.py +0 -0
  29. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_internal_types/_json_rpc.py +0 -0
  30. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_internal_types/_model.py +0 -0
  31. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_log.py +0 -0
  32. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_utils/__init__.py +0 -0
  33. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_utils/_alias.py +0 -0
  34. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_utils/_dict.py +0 -0
  35. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_utils/_openai.py +0 -0
  36. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_utils/_response_processing.py +0 -0
  37. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/_utils/_type_guard.py +0 -0
  38. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/helpers/__init__.py +0 -0
  39. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/helpers/_url.py +0 -0
  40. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/py.typed +0 -0
  41. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/application.py +0 -0
  42. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/base.py +0 -0
  43. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/bucket.py +0 -0
  44. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/chat/__init__.py +0 -0
  45. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/chat/completions.py +0 -0
  46. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/client_channel.py +0 -0
  47. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/deployments.py +0 -0
  48. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/metadata.py +0 -0
  49. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/model.py +0 -0
  50. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/prompts.py +0 -0
  51. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/resource_permissions.py +0 -0
  52. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/resources/toolset.py +0 -0
  53. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/__init__.py +0 -0
  54. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/application.py +0 -0
  55. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/bucket.py +0 -0
  56. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/__init__.py +0 -0
  57. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/function.py +0 -0
  58. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/legacy/__init__.py +0 -0
  59. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/legacy/application_request.py +0 -0
  60. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/legacy/chat_completion.py +0 -0
  61. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/request.py +0 -0
  62. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/request_param.py +0 -0
  63. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/response.py +0 -0
  64. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/chat/tool.py +0 -0
  65. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/client_channel.py +0 -0
  66. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/deployment.py +0 -0
  67. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/file.py +0 -0
  68. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/metadata.py +0 -0
  69. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/model.py +0 -0
  70. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/prompt.py +0 -0
  71. {aidial_client-0.11.0.dev6 → aidial_client-0.12.0.dev2}/aidial_client/types/toolset.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aidial-client
3
- Version: 0.11.0.dev6
3
+ Version: 0.12.0.dev2
4
4
  Summary: A Python client library for the AI DIAL API
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -61,6 +61,8 @@ Description-Content-Type: text/markdown
61
61
  - [Get Application by Id](#get-application-by-id)
62
62
  - [Models](#models)
63
63
  - [Get Model by Name](#get-model-by-name)
64
+ - [User](#user)
65
+ - [Get Authenticated User Info](#get-authenticated-user-info)
64
66
  - [Toolsets](#toolsets)
65
67
  - [Get Toolset by Id](#get-toolset-by-id)
66
68
  - [Resource Permissions](#resource-permissions)
@@ -554,6 +556,16 @@ result = await async_client.files.download(
554
556
  )
555
557
  ```
556
558
 
559
+ For large async downloads, use `stream_download()` to process bytes as they arrive without buffering the full response in memory:
560
+
561
+ ```python
562
+ async with async_client.files.stream_download(
563
+ url=await async_client.my_files_home() / "relative_folder/my-file.txt"
564
+ ) as result:
565
+ async for bytes_chunk in result:
566
+ ...
567
+ ```
568
+
557
569
  As a result, you will receive an object of type `FileDownloadResponse`, that you can iterate by byte chunks:
558
570
 
559
571
  ```python
@@ -868,6 +880,48 @@ ModelInfo(
868
880
  )
869
881
  ```
870
882
 
883
+ ### User
884
+
885
+ #### Get Authenticated User Info
886
+
887
+ To retrieve information about the currently authenticated user:
888
+
889
+ ```python
890
+ # Sync
891
+ user_info = client.user.info()
892
+
893
+ # Async
894
+ user_info = await async_client.user.info()
895
+ ```
896
+
897
+ As a result, you will receive a `UserInfo` object. When authenticated with an
898
+ API key:
899
+
900
+ ```python
901
+ UserInfo(
902
+ roles=["default"],
903
+ project="PROJECT-NAME",
904
+ userClaims=None,
905
+ )
906
+ ```
907
+
908
+ When authenticated with an access token:
909
+
910
+ ```python
911
+ UserInfo(
912
+ roles=["BA"],
913
+ project=None,
914
+ userClaims={
915
+ "email": ["user_email"],
916
+ "sub": ["user_sub"],
917
+ },
918
+ )
919
+ ```
920
+
921
+ `userClaims` is returned as an opaque `dict` because its contents depend on the
922
+ identity provider. `UserInfo` also preserves any additional fields the DIAL
923
+ deployment may return, so forward compatibility is retained.
924
+
871
925
  ### Toolsets
872
926
 
873
927
  #### Get Toolset by Id
@@ -39,6 +39,8 @@
39
39
  - [Get Application by Id](#get-application-by-id)
40
40
  - [Models](#models)
41
41
  - [Get Model by Name](#get-model-by-name)
42
+ - [User](#user)
43
+ - [Get Authenticated User Info](#get-authenticated-user-info)
42
44
  - [Toolsets](#toolsets)
43
45
  - [Get Toolset by Id](#get-toolset-by-id)
44
46
  - [Resource Permissions](#resource-permissions)
@@ -532,6 +534,16 @@ result = await async_client.files.download(
532
534
  )
533
535
  ```
534
536
 
537
+ For large async downloads, use `stream_download()` to process bytes as they arrive without buffering the full response in memory:
538
+
539
+ ```python
540
+ async with async_client.files.stream_download(
541
+ url=await async_client.my_files_home() / "relative_folder/my-file.txt"
542
+ ) as result:
543
+ async for bytes_chunk in result:
544
+ ...
545
+ ```
546
+
535
547
  As a result, you will receive an object of type `FileDownloadResponse`, that you can iterate by byte chunks:
536
548
 
537
549
  ```python
@@ -846,6 +858,48 @@ ModelInfo(
846
858
  )
847
859
  ```
848
860
 
861
+ ### User
862
+
863
+ #### Get Authenticated User Info
864
+
865
+ To retrieve information about the currently authenticated user:
866
+
867
+ ```python
868
+ # Sync
869
+ user_info = client.user.info()
870
+
871
+ # Async
872
+ user_info = await async_client.user.info()
873
+ ```
874
+
875
+ As a result, you will receive a `UserInfo` object. When authenticated with an
876
+ API key:
877
+
878
+ ```python
879
+ UserInfo(
880
+ roles=["default"],
881
+ project="PROJECT-NAME",
882
+ userClaims=None,
883
+ )
884
+ ```
885
+
886
+ When authenticated with an access token:
887
+
888
+ ```python
889
+ UserInfo(
890
+ roles=["BA"],
891
+ project=None,
892
+ userClaims={
893
+ "email": ["user_email"],
894
+ "sub": ["user_sub"],
895
+ },
896
+ )
897
+ ```
898
+
899
+ `userClaims` is returned as an opaque `dict` because its contents depend on the
900
+ identity provider. `UserInfo` also preserves any additional fields the DIAL
901
+ deployment may return, so forward compatibility is retained.
902
+
849
903
  ### Toolsets
850
904
 
851
905
  #### Get Toolset by Id
@@ -12,6 +12,7 @@ from aidial_client._exception import (
12
12
  from aidial_client.types.client_channel import SigninResult
13
13
  from aidial_client.types.model import ModelInfo, ModelLimits, ModelPricing
14
14
  from aidial_client.types.toolset import ToolsetInfo
15
+ from aidial_client.types.user import UserInfo
15
16
 
16
17
  __all__ = [
17
18
  "Dial",
@@ -32,4 +33,5 @@ __all__ = [
32
33
  "ModelPricing",
33
34
  "ModelLimits",
34
35
  "SigninResult",
36
+ "UserInfo",
35
37
  ]
@@ -120,6 +120,7 @@ class Dial(BaseDialClient[SyncHTTPClient, SyncAuthValue]):
120
120
  self.client_channel = resources.ClientChannel(
121
121
  http_client=self._http_client
122
122
  )
123
+ self.user = resources.User(http_client=self._http_client)
123
124
 
124
125
  def _create_http_client(self) -> SyncHTTPClient:
125
126
  return SyncHTTPClient(
@@ -224,6 +225,7 @@ class AsyncDial(BaseDialClient[AsyncHTTPClient, AsyncAuthValue]):
224
225
  self.client_channel = resources.AsyncClientChannel(
225
226
  http_client=self._http_client
226
227
  )
228
+ self.user = resources.AsyncUser(http_client=self._http_client)
227
229
 
228
230
  def _create_http_client(self) -> AsyncHTTPClient:
229
231
  return AsyncHTTPClient(
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from collections.abc import AsyncIterator, Callable, Mapping
2
+ from collections.abc import AsyncIterator, Mapping
3
3
  from contextlib import asynccontextmanager, suppress
4
4
  from http import HTTPStatus
5
5
  from typing import Any
@@ -8,7 +8,7 @@ import httpx
8
8
 
9
9
  from aidial_client._auth import AsyncAuthValue, aget_combined_auth_headers
10
10
  from aidial_client._exception import DialException
11
- from aidial_client._http_client._base import BaseHTTPClient
11
+ from aidial_client._http_client._base import BaseHTTPClient, ErrorHandler
12
12
  from aidial_client._internal_types._defaults import NOT_GIVEN, NotGiven
13
13
  from aidial_client._internal_types._generic import ResponseT
14
14
  from aidial_client._internal_types._http_request import FinalRequestOptions
@@ -51,8 +51,7 @@ class AsyncHTTPClient(BaseHTTPClient[httpx.AsyncClient, AsyncAuthValue]):
51
51
  options: FinalRequestOptions,
52
52
  cast_to: type[ResponseT],
53
53
  remaining_retries: int | None = None,
54
- on_http_error: Callable[[httpx.HTTPStatusError], DialException | None]
55
- | None = None,
54
+ on_http_error: ErrorHandler | None = None,
56
55
  ) -> ResponseT:
57
56
  retries = self._remaining_retries(remaining_retries, options)
58
57
  auth_headers = await self.auth_headers()
@@ -101,16 +100,37 @@ class AsyncHTTPClient(BaseHTTPClient[httpx.AsyncClient, AsyncAuthValue]):
101
100
  cast_to=cast_to,
102
101
  remaining_retries=retries,
103
102
  )
104
- # Try to get a custom error from response status_code/code/message
105
- custom_error = on_http_error(err) if on_http_error else None
106
- # or fallback to default processing
107
- raised_error = custom_error or self._make_dial_error_from_response(
108
- err.response
109
- )
110
- raise raised_error from err
103
+ self._raise_for_status(response, on_http_error)
111
104
 
112
105
  return process_block_response(cast_to=cast_to, response=response)
113
106
 
107
+ @asynccontextmanager
108
+ async def stream(
109
+ self,
110
+ *,
111
+ options: FinalRequestOptions,
112
+ on_http_error: ErrorHandler | None = None,
113
+ ) -> AsyncIterator[httpx.Response]:
114
+ auth_headers = await self.auth_headers()
115
+ request = self._build_request(options, auth_headers)
116
+ try:
117
+ response = await self._internal_http_client.send(
118
+ request, stream=True
119
+ )
120
+ except httpx.TimeoutException as err:
121
+ raise DialException(
122
+ message="Request timed out",
123
+ status_code=HTTPStatus.REQUEST_TIMEOUT,
124
+ ) from err
125
+ except httpx.HTTPError as err:
126
+ raise DialException(message=f"Request failed: {err}") from err
127
+
128
+ try:
129
+ self._raise_for_status(response, on_http_error)
130
+ yield response
131
+ finally:
132
+ await response.aclose()
133
+
114
134
  @asynccontextmanager
115
135
  async def stream_sse(
116
136
  self,
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ from collections.abc import Callable
2
3
  from http import HTTPStatus
3
4
  from random import uniform
4
5
  from typing import Generic, TypeVar
@@ -16,6 +17,8 @@ _HttpInternalClientT = TypeVar(
16
17
  "_HttpInternalClientT", bound=httpx.Client | httpx.AsyncClient
17
18
  )
18
19
 
20
+ ErrorHandler = Callable[[httpx.HTTPStatusError], DialException | None]
21
+
19
22
 
20
23
  class BaseHTTPClient(ABC, Generic[_HttpInternalClientT, AuthValueT]):
21
24
  _internal_http_client: _HttpInternalClientT
@@ -106,6 +109,22 @@ class BaseHTTPClient(ABC, Generic[_HttpInternalClientT, AuthValueT]):
106
109
  timeout = sleep_seconds + uniform(-0.5, 0.5) # noqa: S311
107
110
  return max(0, timeout)
108
111
 
112
+ def _raise_for_status(
113
+ self,
114
+ response: httpx.Response,
115
+ on_http_error: ErrorHandler | None,
116
+ ) -> None:
117
+ try:
118
+ response.raise_for_status()
119
+ except httpx.HTTPStatusError as err:
120
+ # Try to get a custom error from response status_code/code/message
121
+ custom_error = on_http_error(err) if on_http_error else None
122
+ # or fallback to default processing
123
+ raised_error = custom_error or self._make_dial_error_from_response(
124
+ err.response
125
+ )
126
+ raise raised_error from err
127
+
109
128
  def _make_dial_error_from_response(
110
129
  self,
111
130
  response: httpx.Response,
@@ -1,5 +1,5 @@
1
1
  import time
2
- from collections.abc import Callable, Iterator, Mapping
2
+ from collections.abc import Iterator, Mapping
3
3
  from contextlib import contextmanager, suppress
4
4
  from http import HTTPStatus
5
5
  from typing import Any
@@ -8,7 +8,7 @@ import httpx
8
8
 
9
9
  from aidial_client._auth import SyncAuthValue, get_combined_auth_headers
10
10
  from aidial_client._exception import DialException
11
- from aidial_client._http_client._base import BaseHTTPClient
11
+ from aidial_client._http_client._base import BaseHTTPClient, ErrorHandler
12
12
  from aidial_client._internal_types._defaults import NOT_GIVEN, NotGiven
13
13
  from aidial_client._internal_types._generic import ResponseT
14
14
  from aidial_client._internal_types._http_request import FinalRequestOptions
@@ -50,8 +50,7 @@ class SyncHTTPClient(BaseHTTPClient[httpx.Client, SyncAuthValue]):
50
50
  cast_to: type[ResponseT],
51
51
  options: FinalRequestOptions,
52
52
  remaining_retries: int | None = None,
53
- on_http_error: Callable[[httpx.HTTPStatusError], DialException | None]
54
- | None = None,
53
+ on_http_error: ErrorHandler | None = None,
55
54
  ) -> ResponseT:
56
55
  retries = self._remaining_retries(remaining_retries, options)
57
56
  auth_headers = self.auth_headers()
@@ -101,13 +100,7 @@ class SyncHTTPClient(BaseHTTPClient[httpx.Client, SyncAuthValue]):
101
100
  cast_to=cast_to,
102
101
  remaining_retries=retries,
103
102
  )
104
- # Try to get a custom error from response status_code/code/message
105
- custom_error = on_http_error(err) if on_http_error else None
106
- # or fallback to default processing
107
- raised_error = custom_error or self._make_dial_error_from_response(
108
- err.response
109
- )
110
- raise raised_error from err
103
+ self._raise_for_status(response, on_http_error)
111
104
 
112
105
  return process_block_response(cast_to=cast_to, response=response)
113
106
 
@@ -5,6 +5,8 @@ from urllib.parse import urljoin, urlparse
5
5
  from aidial_client._compatibility.pydantic_v1 import BaseModel
6
6
  from aidial_client._constants import API_PREFIX
7
7
  from aidial_client._exception import InvalidDialURLError, NotDialURLError
8
+ from aidial_client._internal_types._http_request import FinalRequestOptions
9
+ from aidial_client._utils._dict import remove_none
8
10
  from aidial_client.helpers._url import enforce_trailing_slash
9
11
 
10
12
  StorageResourceType = Literal["files", "conversations", "prompts"]
@@ -156,3 +158,25 @@ class DialStorageResourceMixin(BaseModel):
156
158
  Get the display name of the resource from the URL
157
159
  """
158
160
  return self.get_storage_resource(url).bucket_path
161
+
162
+ def _prepare_download_request(
163
+ self,
164
+ url: str | PurePosixPath,
165
+ etag_if_match: str | None,
166
+ ) -> tuple[FinalRequestOptions, str]:
167
+ storage_resource = self.get_storage_resource(str(url))
168
+
169
+ if storage_resource.filename is None:
170
+ raise InvalidDialURLError("URL points to a directory, not a file")
171
+
172
+ options = FinalRequestOptions(
173
+ method="GET",
174
+ url=urljoin(API_PREFIX, storage_resource.api_path),
175
+ headers=remove_none(
176
+ {
177
+ "If-Match": etag_if_match,
178
+ }
179
+ ),
180
+ )
181
+
182
+ return options, storage_resource.filename
@@ -10,6 +10,7 @@ from aidial_client.resources.resource_permissions import (
10
10
  ResourcePermissions,
11
11
  )
12
12
  from aidial_client.resources.toolset import AsyncToolset, Toolset
13
+ from aidial_client.resources.user import AsyncUser, User
13
14
 
14
15
  from .application import Application, AsyncApplication
15
16
  from .bucket import AsyncBucket, Bucket
@@ -40,4 +41,6 @@ __all__ = [
40
41
  "AsyncResourcePermissions",
41
42
  "ClientChannel",
42
43
  "AsyncClientChannel",
44
+ "User",
45
+ "AsyncUser",
43
46
  ]
@@ -1,3 +1,5 @@
1
+ from collections.abc import AsyncIterator
2
+ from contextlib import asynccontextmanager
1
3
  from pathlib import PurePosixPath
2
4
  from typing import Literal
3
5
  from urllib.parse import urljoin
@@ -8,7 +10,6 @@ from aidial_client._constants import API_PREFIX
8
10
  from aidial_client._exception import (
9
11
  DialException,
10
12
  EtagMismatchError,
11
- InvalidDialURLError,
12
13
  ResourceNotFoundError,
13
14
  )
14
15
  from aidial_client._internal_types._generic import NoneType
@@ -70,25 +71,13 @@ class Files(Resource, DialStorageResourceMixin):
70
71
  url: str | PurePosixPath,
71
72
  etag_if_match: str | None = None,
72
73
  ) -> FileDownloadResponse:
73
- storage_resource = self.get_storage_resource(str(url))
74
- if storage_resource.filename is None:
75
- raise InvalidDialURLError("URL points to a directory, not a file")
74
+ options, filename = self._prepare_download_request(url, etag_if_match)
76
75
  response = self.http_client.request(
77
76
  cast_to=httpx.Response,
78
- options=FinalRequestOptions(
79
- method="GET",
80
- url=urljoin(API_PREFIX, storage_resource.api_path),
81
- headers=remove_none(
82
- {
83
- "If-Match": etag_if_match,
84
- }
85
- ),
86
- ),
77
+ options=options,
87
78
  on_http_error=_files_error_processor,
88
79
  )
89
- return FileDownloadResponse(
90
- response=response, filename=storage_resource.filename
91
- )
80
+ return FileDownloadResponse(response=response, filename=filename)
92
81
 
93
82
  def delete(
94
83
  self,
@@ -188,25 +177,26 @@ class AsyncFiles(AsyncResource, DialStorageResourceMixin):
188
177
  url: str | PurePosixPath,
189
178
  etag_if_match: str | None = None,
190
179
  ) -> FileDownloadResponse:
191
- storage_resource = self.get_storage_resource(str(url))
192
- if storage_resource.filename is None:
193
- raise InvalidDialURLError("URL points to a directory, not a file")
180
+ options, filename = self._prepare_download_request(url, etag_if_match)
194
181
  response = await self.http_client.request(
195
182
  cast_to=httpx.Response,
196
- options=FinalRequestOptions(
197
- method="GET",
198
- url=urljoin(API_PREFIX, storage_resource.api_path),
199
- headers=remove_none(
200
- {
201
- "If-Match": etag_if_match,
202
- }
203
- ),
204
- ),
183
+ options=options,
205
184
  on_http_error=_files_error_processor,
206
185
  )
207
- return FileDownloadResponse(
208
- response=response, filename=storage_resource.filename
209
- )
186
+ return FileDownloadResponse(response=response, filename=filename)
187
+
188
+ @asynccontextmanager
189
+ async def stream_download(
190
+ self,
191
+ url: str | PurePosixPath,
192
+ etag_if_match: str | None = None,
193
+ ) -> AsyncIterator[FileDownloadResponse]:
194
+ options, filename = self._prepare_download_request(url, etag_if_match)
195
+ async with self.http_client.stream(
196
+ options=options,
197
+ on_http_error=_files_error_processor,
198
+ ) as response:
199
+ yield FileDownloadResponse(response=response, filename=filename)
210
200
 
211
201
  async def delete(
212
202
  self,
@@ -0,0 +1,19 @@
1
+ from aidial_client._internal_types._http_request import FinalRequestOptions
2
+ from aidial_client.resources.base import AsyncResource, Resource
3
+ from aidial_client.types.user import UserInfo
4
+
5
+
6
+ class User(Resource):
7
+ def info(self) -> UserInfo:
8
+ return self.http_client.request(
9
+ cast_to=UserInfo,
10
+ options=FinalRequestOptions(method="GET", url="v1/user/info"),
11
+ )
12
+
13
+
14
+ class AsyncUser(AsyncResource):
15
+ async def info(self) -> UserInfo:
16
+ return await self.http_client.request(
17
+ cast_to=UserInfo,
18
+ options=FinalRequestOptions(method="GET", url="v1/user/info"),
19
+ )
@@ -0,0 +1,11 @@
1
+ from typing import Any
2
+
3
+ from aidial_client._internal_types._model import ExtraAllowModel
4
+
5
+
6
+ class UserInfo(ExtraAllowModel):
7
+ """Information about the authenticated user or API key."""
8
+
9
+ roles: list[str]
10
+ project: str | None = None
11
+ userClaims: dict[str, Any] | None = None # depends on the IdP, so opaque
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aidial-client"
3
- version = "0.11.0.dev6"
3
+ version = "0.12.0.dev2"
4
4
  description = "A Python client library for the AI DIAL API"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"