liminal-sdk-python 2024.3.4__tar.gz → 2024.3.5__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 (25) hide show
  1. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/PKG-INFO +95 -21
  2. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/README.md +94 -20
  3. liminal_sdk_python-2024.3.5/liminal/auth/__init__.py +17 -0
  4. liminal_sdk_python-2024.3.5/liminal/auth/microsoft/__init__.py +1 -0
  5. liminal_sdk_python-2024.3.4/liminal/endpoints/auth/__init__.py → liminal_sdk_python-2024.3.5/liminal/auth/microsoft/device_code_flow.py +5 -5
  6. {liminal_sdk_python-2024.3.4/liminal/endpoints/auth → liminal_sdk_python-2024.3.5/liminal/auth/microsoft}/models.py +3 -19
  7. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/client.py +4 -17
  8. liminal_sdk_python-2024.3.5/liminal/endpoints/llm/models.py +79 -0
  9. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/models.py +28 -28
  10. liminal_sdk_python-2024.3.5/liminal/endpoints/thread/models.py +61 -0
  11. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/helpers/model.py +1 -1
  12. liminal_sdk_python-2024.3.5/pyproject.toml +115 -0
  13. liminal_sdk_python-2024.3.4/liminal/endpoints/llm/models.py +0 -46
  14. liminal_sdk_python-2024.3.4/liminal/endpoints/thread/models.py +0 -31
  15. liminal_sdk_python-2024.3.4/pyproject.toml +0 -441
  16. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/LICENSE +0 -0
  17. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/__init__.py +0 -0
  18. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/const.py +0 -0
  19. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/__init__.py +0 -0
  20. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/llm/__init__.py +0 -0
  21. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/__init__.py +0 -0
  22. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/endpoints/thread/__init__.py +0 -0
  23. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/errors.py +0 -0
  24. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/helpers/__init__.py +0 -0
  25. {liminal_sdk_python-2024.3.4 → liminal_sdk_python-2024.3.5}/liminal/helpers/typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: liminal-sdk-python
3
- Version: 2024.3.4
3
+ Version: 2024.3.5
4
4
  Summary: The Liminal SDK for Python
5
5
  Home-page: https://github.com/liminal-ai-security/liminal-sdk-python
6
6
  License: Apache-2.0
@@ -25,6 +25,7 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  # Liminal Python SDK
27
27
 
28
+ [![CI][ci-badge]][ci]
28
29
  [![PyPI][pypi-badge]][pypi]
29
30
  [![Version][version-badge]][version]
30
31
  [![License][license-badge]][license]
@@ -34,7 +35,19 @@ for interacting with the Liminal API.
34
35
 
35
36
  - [Installation](#installation)
36
37
  - [Python Versions](#python-versions)
37
- - [Usage](#usage)
38
+ - [Quickstart](#quickstart)
39
+ - [Initial Authentication](#initial-authentication)
40
+ - [Microsoft Entra ID](#microsoft-entra-id)
41
+ - [Device Code Flow](#device-code-flow)
42
+ - [Ongoing Authentication](#ongoing-authentication)
43
+ - [Manually Interacting with the Refresh Token](#manually-interacting-with-the-refresh-token)
44
+ - [Creating a Liminal Client from a Stored Refresh Token](#creating-a-liminal-client-from-a-stored-refresh-token)
45
+ - [Endpoints](#endpoints)
46
+ - [Getting Model Instances](#getting-model-instances)
47
+ - [Managing Threads](#managing-threads)
48
+ - [Submitting Prompts](#submitting-prompts)
49
+ - [Connection Pooling](#connection-pooling)
50
+ - [Running Examples](#running-examples)
38
51
  - [Contributing](#contributing)
39
52
 
40
53
  # Installation
@@ -59,16 +72,16 @@ API object is easy:
59
72
  import asyncio
60
73
 
61
74
  from liminal import Client
62
- from liminal.endpoints.auth import MicrosoftAuthProvider
75
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
63
76
 
64
77
 
65
78
  async def main() -> None:
66
79
  """Create the aiohttp session and run the example."""
67
80
  # Create an auth provider to authenticate the user:
68
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
81
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
69
82
 
70
83
  # Create the liminal SDK instance:
71
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
84
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
72
85
 
73
86
 
74
87
  asyncio.run(main())
@@ -86,13 +99,15 @@ the following auth providers are supported:
86
99
 
87
100
  ## Microsoft Entra ID
88
101
 
89
- Liminal authenticates with Microsoft Entra ID via an
102
+ ### Device Code Flow
103
+
104
+ This authentication process with Microsoft Entra ID involves an
90
105
  [OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
91
106
  to start your app, retrieve a device code from the logs produced by this SDK, and
92
107
  provide that code to Microsoft via a web browser. Once you complete the login process,
93
108
  the SDK will be authenticated for use with your Liminal instance.
94
109
 
95
- ### Finding your Entra ID Tenant and Client IDs
110
+ To authenticate with this flow, you will need an Entra ID client and tenant ID:
96
111
 
97
112
  - Log into your [Azure portal][azure-portal].
98
113
  - Navigate to `Microsoft Entra ID`.
@@ -101,8 +116,6 @@ the SDK will be authenticated for use with your Liminal instance.
101
116
  - In the `Overview` of the registration, look for the `Application (client) ID` and
102
117
  `Directory (tenant) ID` values.
103
118
 
104
- ### Authenticating Against Entra ID
105
-
106
119
  With a client ID and tenant ID, you can create a Liminal client object and authenticate
107
120
  it:
108
121
 
@@ -110,16 +123,16 @@ it:
110
123
  import asyncio
111
124
 
112
125
  from liminal import Client
113
- from liminal.endpoints.auth import MicrosoftAuthProvider
126
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
114
127
 
115
128
 
116
129
  async def main() -> None:
117
130
  """Create the aiohttp session and run the example."""
118
131
  # Create an auth provider to authenticate the user:
119
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
132
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
120
133
 
121
134
  # Create the liminal SDK instance and authenticate it:
122
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
135
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
123
136
  await liminal.authenticate_from_auth_provider()
124
137
 
125
138
 
@@ -161,13 +174,13 @@ callback at any time.
161
174
  import asyncio
162
175
 
163
176
  from liminal import Client
164
- from liminal.endpoints.auth import MicrosoftAuthProvider
177
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
165
178
 
166
179
 
167
180
  async def main() -> None:
168
181
  """Create the aiohttp session and run the example."""
169
182
  # Create an auth provider to authenticate the user:
170
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
183
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
171
184
 
172
185
  # Create the liminal SDK instance and authenticate it:
173
186
  liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
@@ -189,7 +202,32 @@ async def main() -> None:
189
202
  asyncio.run(main())
190
203
  ```
191
204
 
192
- # Getting Model Instances
205
+ ## Creating a Liminal Client from a Stored Refresh Token
206
+
207
+ Assuming you have a stored refresh token (collected from the callback process shown
208
+ above), it is simple to create a new Limina client using that token:
209
+
210
+ ```python
211
+ import asyncio
212
+
213
+ from liminal import Client
214
+
215
+
216
+ async def main() -> None:
217
+ """Create the aiohttp session and run the example."""
218
+ # Retrieve your stored refresh_token:
219
+ refresh_token = "12345"
220
+
221
+ # Create the client:
222
+ liminal = await client.authenticate_from_refresh_token(refresh_token=refresh_token)
223
+
224
+
225
+ asyncio.run(main())
226
+ ```
227
+
228
+ # Endpoints
229
+
230
+ ## Getting Model Instances
193
231
 
194
232
  Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
195
233
  instance." The SDK provides several methods to interact with model instances:
@@ -198,7 +236,7 @@ instance." The SDK provides several methods to interact with model instances:
198
236
  import asyncio
199
237
 
200
238
  from liminal import Client
201
- from liminal.endpoints.auth import MicrosoftAuthProvider
239
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
202
240
 
203
241
 
204
242
  async def main() -> None:
@@ -216,7 +254,7 @@ async def main() -> None:
216
254
  asyncio.run(main())
217
255
  ```
218
256
 
219
- # Managing Threads
257
+ ## Managing Threads
220
258
 
221
259
  Threads are conversations with an LLM instance:
222
260
 
@@ -224,7 +262,7 @@ Threads are conversations with an LLM instance:
224
262
  import asyncio
225
263
 
226
264
  from liminal import Client
227
- from liminal.endpoints.auth import MicrosoftAuthProvider
265
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
228
266
 
229
267
 
230
268
  async def main() -> None:
@@ -253,7 +291,7 @@ async def main() -> None:
253
291
  asyncio.run(main())
254
292
  ```
255
293
 
256
- # Submitting Prompts
294
+ ## Submitting Prompts
257
295
 
258
296
  Submitting prompts is easy:
259
297
 
@@ -261,7 +299,7 @@ Submitting prompts is easy:
261
299
  import asyncio
262
300
 
263
301
  from liminal import Client
264
- from liminal.endpoints.auth import MicrosoftAuthProvider
302
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
265
303
 
266
304
 
267
305
  async def main() -> None:
@@ -300,6 +338,40 @@ async def main() -> None:
300
338
  asyncio.run(main())
301
339
  ```
302
340
 
341
+ # Connection Pooling
342
+
343
+ By default, the library creates a new connection to the Liminal API server with each
344
+ coroutine. If you are calling a large number of coroutines (or merely want to squeeze
345
+ out every second of runtime savings possible), an [`httpx`][httpx] `AsyncClient` can be
346
+ used for connection pooling:
347
+
348
+ ```python
349
+ import asyncio
350
+
351
+ from liminal import Client
352
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
353
+
354
+
355
+ async def main() -> None:
356
+ # Create an auth provider to authenticate the user:
357
+ microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
358
+
359
+ # Create the liminal SDK instance with a shared HTTPX AsyncClient:
360
+ async with httpx.AsyncClient() as client:
361
+ liminal = Client(
362
+ microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>", httpx_client=client
363
+ )
364
+
365
+ # Get to work!
366
+ # ...
367
+
368
+
369
+ asyncio.run(main())
370
+ ```
371
+
372
+ Check out the examples, the tests, and the source files themselves for method
373
+ signatures and more examples.
374
+
303
375
  # Running Examples
304
376
 
305
377
  You can see examples of how to use this SDK via the [`examples`][examples] folder in
@@ -322,7 +394,7 @@ Thanks to all of [our contributors][contributors] so far!
322
394
  2. [Fork the repository][fork].
323
395
  3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
324
396
  4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
325
- 5. Install the dev environment: `script/setup`
397
+ 5. Install the dev environment: `./scripts/setup.sh`
326
398
  6. Code your new feature or bug fix on a new branch.
327
399
  7. Write tests that cover your new functionality.
328
400
  8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
@@ -330,10 +402,12 @@ Thanks to all of [our contributors][contributors] so far!
330
402
  10. Submit a pull request!
331
403
 
332
404
  [azure-portal]: https://portal.azure.com
405
+ [ci-badge]: https://img.shields.io/github/actions/workflow/status/liminal-ai-security/liminal-sdk-python/test.yml
333
406
  [ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
334
407
  [contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
335
408
  [examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
336
409
  [fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
410
+ [httpx]: https://www.python-httpx.org/
337
411
  [issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
338
412
  [license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
339
413
  [license]: https://github.com/liminal-ai-security/liminal-sdk-python/blob/main/LICENSE
@@ -1,5 +1,6 @@
1
1
  # Liminal Python SDK
2
2
 
3
+ [![CI][ci-badge]][ci]
3
4
  [![PyPI][pypi-badge]][pypi]
4
5
  [![Version][version-badge]][version]
5
6
  [![License][license-badge]][license]
@@ -9,7 +10,19 @@ for interacting with the Liminal API.
9
10
 
10
11
  - [Installation](#installation)
11
12
  - [Python Versions](#python-versions)
12
- - [Usage](#usage)
13
+ - [Quickstart](#quickstart)
14
+ - [Initial Authentication](#initial-authentication)
15
+ - [Microsoft Entra ID](#microsoft-entra-id)
16
+ - [Device Code Flow](#device-code-flow)
17
+ - [Ongoing Authentication](#ongoing-authentication)
18
+ - [Manually Interacting with the Refresh Token](#manually-interacting-with-the-refresh-token)
19
+ - [Creating a Liminal Client from a Stored Refresh Token](#creating-a-liminal-client-from-a-stored-refresh-token)
20
+ - [Endpoints](#endpoints)
21
+ - [Getting Model Instances](#getting-model-instances)
22
+ - [Managing Threads](#managing-threads)
23
+ - [Submitting Prompts](#submitting-prompts)
24
+ - [Connection Pooling](#connection-pooling)
25
+ - [Running Examples](#running-examples)
13
26
  - [Contributing](#contributing)
14
27
 
15
28
  # Installation
@@ -34,16 +47,16 @@ API object is easy:
34
47
  import asyncio
35
48
 
36
49
  from liminal import Client
37
- from liminal.endpoints.auth import MicrosoftAuthProvider
50
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
38
51
 
39
52
 
40
53
  async def main() -> None:
41
54
  """Create the aiohttp session and run the example."""
42
55
  # Create an auth provider to authenticate the user:
43
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
56
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
44
57
 
45
58
  # Create the liminal SDK instance:
46
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
59
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
47
60
 
48
61
 
49
62
  asyncio.run(main())
@@ -61,13 +74,15 @@ the following auth providers are supported:
61
74
 
62
75
  ## Microsoft Entra ID
63
76
 
64
- Liminal authenticates with Microsoft Entra ID via an
77
+ ### Device Code Flow
78
+
79
+ This authentication process with Microsoft Entra ID involves an
65
80
  [OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
66
81
  to start your app, retrieve a device code from the logs produced by this SDK, and
67
82
  provide that code to Microsoft via a web browser. Once you complete the login process,
68
83
  the SDK will be authenticated for use with your Liminal instance.
69
84
 
70
- ### Finding your Entra ID Tenant and Client IDs
85
+ To authenticate with this flow, you will need an Entra ID client and tenant ID:
71
86
 
72
87
  - Log into your [Azure portal][azure-portal].
73
88
  - Navigate to `Microsoft Entra ID`.
@@ -76,8 +91,6 @@ the SDK will be authenticated for use with your Liminal instance.
76
91
  - In the `Overview` of the registration, look for the `Application (client) ID` and
77
92
  `Directory (tenant) ID` values.
78
93
 
79
- ### Authenticating Against Entra ID
80
-
81
94
  With a client ID and tenant ID, you can create a Liminal client object and authenticate
82
95
  it:
83
96
 
@@ -85,16 +98,16 @@ it:
85
98
  import asyncio
86
99
 
87
100
  from liminal import Client
88
- from liminal.endpoints.auth import MicrosoftAuthProvider
101
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
89
102
 
90
103
 
91
104
  async def main() -> None:
92
105
  """Create the aiohttp session and run the example."""
93
106
  # Create an auth provider to authenticate the user:
94
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
107
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
95
108
 
96
109
  # Create the liminal SDK instance and authenticate it:
97
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
110
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
98
111
  await liminal.authenticate_from_auth_provider()
99
112
 
100
113
 
@@ -136,13 +149,13 @@ callback at any time.
136
149
  import asyncio
137
150
 
138
151
  from liminal import Client
139
- from liminal.endpoints.auth import MicrosoftAuthProvider
152
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
140
153
 
141
154
 
142
155
  async def main() -> None:
143
156
  """Create the aiohttp session and run the example."""
144
157
  # Create an auth provider to authenticate the user:
145
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
158
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
146
159
 
147
160
  # Create the liminal SDK instance and authenticate it:
148
161
  liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
@@ -164,7 +177,32 @@ async def main() -> None:
164
177
  asyncio.run(main())
165
178
  ```
166
179
 
167
- # Getting Model Instances
180
+ ## Creating a Liminal Client from a Stored Refresh Token
181
+
182
+ Assuming you have a stored refresh token (collected from the callback process shown
183
+ above), it is simple to create a new Limina client using that token:
184
+
185
+ ```python
186
+ import asyncio
187
+
188
+ from liminal import Client
189
+
190
+
191
+ async def main() -> None:
192
+ """Create the aiohttp session and run the example."""
193
+ # Retrieve your stored refresh_token:
194
+ refresh_token = "12345"
195
+
196
+ # Create the client:
197
+ liminal = await client.authenticate_from_refresh_token(refresh_token=refresh_token)
198
+
199
+
200
+ asyncio.run(main())
201
+ ```
202
+
203
+ # Endpoints
204
+
205
+ ## Getting Model Instances
168
206
 
169
207
  Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
170
208
  instance." The SDK provides several methods to interact with model instances:
@@ -173,7 +211,7 @@ instance." The SDK provides several methods to interact with model instances:
173
211
  import asyncio
174
212
 
175
213
  from liminal import Client
176
- from liminal.endpoints.auth import MicrosoftAuthProvider
214
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
177
215
 
178
216
 
179
217
  async def main() -> None:
@@ -191,7 +229,7 @@ async def main() -> None:
191
229
  asyncio.run(main())
192
230
  ```
193
231
 
194
- # Managing Threads
232
+ ## Managing Threads
195
233
 
196
234
  Threads are conversations with an LLM instance:
197
235
 
@@ -199,7 +237,7 @@ Threads are conversations with an LLM instance:
199
237
  import asyncio
200
238
 
201
239
  from liminal import Client
202
- from liminal.endpoints.auth import MicrosoftAuthProvider
240
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
203
241
 
204
242
 
205
243
  async def main() -> None:
@@ -228,7 +266,7 @@ async def main() -> None:
228
266
  asyncio.run(main())
229
267
  ```
230
268
 
231
- # Submitting Prompts
269
+ ## Submitting Prompts
232
270
 
233
271
  Submitting prompts is easy:
234
272
 
@@ -236,7 +274,7 @@ Submitting prompts is easy:
236
274
  import asyncio
237
275
 
238
276
  from liminal import Client
239
- from liminal.endpoints.auth import MicrosoftAuthProvider
277
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
240
278
 
241
279
 
242
280
  async def main() -> None:
@@ -275,6 +313,40 @@ async def main() -> None:
275
313
  asyncio.run(main())
276
314
  ```
277
315
 
316
+ # Connection Pooling
317
+
318
+ By default, the library creates a new connection to the Liminal API server with each
319
+ coroutine. If you are calling a large number of coroutines (or merely want to squeeze
320
+ out every second of runtime savings possible), an [`httpx`][httpx] `AsyncClient` can be
321
+ used for connection pooling:
322
+
323
+ ```python
324
+ import asyncio
325
+
326
+ from liminal import Client
327
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
328
+
329
+
330
+ async def main() -> None:
331
+ # Create an auth provider to authenticate the user:
332
+ microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
333
+
334
+ # Create the liminal SDK instance with a shared HTTPX AsyncClient:
335
+ async with httpx.AsyncClient() as client:
336
+ liminal = Client(
337
+ microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>", httpx_client=client
338
+ )
339
+
340
+ # Get to work!
341
+ # ...
342
+
343
+
344
+ asyncio.run(main())
345
+ ```
346
+
347
+ Check out the examples, the tests, and the source files themselves for method
348
+ signatures and more examples.
349
+
278
350
  # Running Examples
279
351
 
280
352
  You can see examples of how to use this SDK via the [`examples`][examples] folder in
@@ -297,7 +369,7 @@ Thanks to all of [our contributors][contributors] so far!
297
369
  2. [Fork the repository][fork].
298
370
  3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
299
371
  4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
300
- 5. Install the dev environment: `script/setup`
372
+ 5. Install the dev environment: `./scripts/setup.sh`
301
373
  6. Code your new feature or bug fix on a new branch.
302
374
  7. Write tests that cover your new functionality.
303
375
  8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
@@ -305,10 +377,12 @@ Thanks to all of [our contributors][contributors] so far!
305
377
  10. Submit a pull request!
306
378
 
307
379
  [azure-portal]: https://portal.azure.com
380
+ [ci-badge]: https://img.shields.io/github/actions/workflow/status/liminal-ai-security/liminal-sdk-python/test.yml
308
381
  [ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
309
382
  [contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
310
383
  [examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
311
384
  [fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
385
+ [httpx]: https://www.python-httpx.org/
312
386
  [issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
313
387
  [license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
314
388
  [license]: https://github.com/liminal-ai-security/liminal-sdk-python/blob/main/LICENSE
@@ -0,0 +1,17 @@
1
+ """Define auth providers."""
2
+
3
+
4
+ class AuthProvider:
5
+ """Define an auth provider abstract base class."""
6
+
7
+ async def get_access_token(self) -> str:
8
+ """Retrieve an access token from the auth provider.
9
+
10
+ The working principle here is that the auth provider will return an access
11
+ token that can be used to authenticate with the Liminal API server.
12
+
13
+ Returns:
14
+ The access token.
15
+
16
+ """
17
+ raise NotImplementedError
@@ -0,0 +1 @@
1
+ """Define Microsoft providers."""
@@ -1,4 +1,4 @@
1
- """Define the auth endpoint."""
1
+ """Define Microsoft providers."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -7,18 +7,18 @@ from typing import Final
7
7
 
8
8
  from msal import PublicClientApplication
9
9
 
10
- from liminal.const import LOGGER
11
- from liminal.endpoints.auth.models import (
12
- AuthProvider,
10
+ from liminal.auth import AuthProvider
11
+ from liminal.auth.microsoft.models import (
13
12
  MSALCacheTokenResponse,
14
13
  MSALIdentityProviderTokenResponse,
15
14
  )
15
+ from liminal.const import LOGGER
16
16
  from liminal.errors import AuthError
17
17
 
18
18
  DEFAULT_AUTH_CHALLENGE_TIMEOUT: Final[int] = 60
19
19
 
20
20
 
21
- class MicrosoftAuthProvider(AuthProvider):
21
+ class DeviceCodeFlowProvider(AuthProvider):
22
22
  """Define a Microsoft auth provider."""
23
23
 
24
24
  AUTHORITY_URL: Final[str] = "https://login.microsoftonline.com"
@@ -5,27 +5,11 @@ from __future__ import annotations
5
5
  from dataclasses import dataclass
6
6
  from typing import Literal
7
7
 
8
- from liminal.helpers.model import BaseModel
9
-
10
-
11
- class AuthProvider:
12
- """Define an auth provider abstract base class."""
13
-
14
- async def get_access_token(self) -> str:
15
- """Retrieve an access token from the auth provider.
16
-
17
- The working principle here is that the auth provider will return an access
18
- token that can be used to authenticate with the Liminal API server.
19
-
20
- Returns:
21
- The access token.
22
-
23
- """
24
- raise NotImplementedError
8
+ from liminal.helpers.model import BaseResponseModel
25
9
 
26
10
 
27
11
  @dataclass(frozen=True, kw_only=True)
28
- class MSALCacheTokenResponse(BaseModel):
12
+ class MSALCacheTokenResponse(BaseResponseModel):
29
13
  """Define an MSAL token response from Entra ID."""
30
14
 
31
15
  token_type: Literal["Bearer"]
@@ -35,7 +19,7 @@ class MSALCacheTokenResponse(BaseModel):
35
19
 
36
20
 
37
21
  @dataclass(frozen=True, kw_only=True)
38
- class MSALIdentityProviderTokenResponse(BaseModel):
22
+ class MSALIdentityProviderTokenResponse(BaseResponseModel):
39
23
  """Define an MSAL token response from the local in-memory cache."""
40
24
 
41
25
  token_type: Literal["Bearer"]
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  from collections.abc import Callable
7
7
  from datetime import UTC, datetime
8
+ from json.decoder import JSONDecodeError
8
9
  from typing import Final
9
10
 
10
11
  from httpx import AsyncClient, Cookies, HTTPStatusError, Request, Response
@@ -15,8 +16,8 @@ from mashumaro.exceptions import (
15
16
  UnserializableDataError,
16
17
  )
17
18
 
19
+ from liminal.auth import AuthProvider
18
20
  from liminal.const import LOGGER
19
- from liminal.endpoints.auth import AuthProvider
20
21
  from liminal.endpoints.llm import LLMEndpoint
21
22
  from liminal.endpoints.prompt import PromptEndpoint
22
23
  from liminal.endpoints.thread import ThreadEndpoint
@@ -55,7 +56,6 @@ class Client:
55
56
  # Token information:
56
57
  self._access_token: str | None = None
57
58
  self._access_token_expires_at: datetime | None = None
58
- self._refresh_event = asyncio.Event()
59
59
  self._refresh_lock = asyncio.Lock()
60
60
  self._refresh_token: str | None = None
61
61
  self._refresh_token_callbacks: list[Callable[[str], None]] = []
@@ -75,7 +75,6 @@ class Client:
75
75
  cookies: Cookies | None = None,
76
76
  params: dict[str, str] | None = None,
77
77
  json: dict[str, str] | None = None,
78
- refresh_request: bool = False,
79
78
  ) -> Response:
80
79
  """Make a request to the Liminal API server and return a Response.
81
80
 
@@ -86,7 +85,6 @@ class Client:
86
85
  cookies: The cookies to use.
87
86
  params: The query parameters to use.
88
87
  json: The JSON body to use.
89
- refresh_request: Whether or not this request is a refresh request.
90
88
 
91
89
  Returns:
92
90
  An HTTPX Response object.
@@ -102,14 +100,6 @@ class Client:
102
100
  self._access_token_expires_at = None
103
101
  await self.authenticate_from_refresh_token()
104
102
 
105
- # If an authenticated request arrives while we're refreshing, hold until the
106
- # refresh process is done:
107
- if not refresh_request and self._refreshing:
108
- await self._refresh_event.wait()
109
-
110
- if not endpoint.startswith("/"):
111
- endpoint = f"/{endpoint}"
112
-
113
103
  url = f"{self._api_server_url}{endpoint}"
114
104
 
115
105
  if not headers:
@@ -182,6 +172,7 @@ class Client:
182
172
  try:
183
173
  return json_decode(response.content, expected_response_type)
184
174
  except (
175
+ JSONDecodeError,
185
176
  MissingField,
186
177
  SuitableVariantNotFoundError,
187
178
  UnserializableDataError,
@@ -260,19 +251,15 @@ class Client:
260
251
  msg = "No valid refresh token provided"
261
252
  raise AuthError(msg)
262
253
 
263
- self._refreshing = True
264
-
265
254
  async with self._refresh_lock:
266
- self._refresh_event.clear()
255
+ self._refreshing = True
267
256
 
268
257
  try:
269
258
  refresh_token_response = await self._request(
270
259
  "POST",
271
260
  "/api/v1/auth/refresh-token",
272
- refresh_request=True,
273
261
  cookies=Cookies({"refreshToken": refresh_token}),
274
262
  )
275
263
  self._save_tokens_from_auth_response(refresh_token_response)
276
264
  finally:
277
265
  self._refreshing = False
278
- self._refresh_event.set()