liminal-sdk-python 2024.3.3__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 (26) hide show
  1. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/PKG-INFO +95 -27
  2. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/README.md +94 -26
  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.3/liminal/endpoints/auth/__init__.py → liminal_sdk_python-2024.3.5/liminal/auth/microsoft/device_code_flow.py +13 -9
  6. {liminal_sdk_python-2024.3.3/liminal/endpoints/auth → liminal_sdk_python-2024.3.5/liminal/auth/microsoft}/models.py +3 -20
  7. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/client.py +20 -29
  8. liminal_sdk_python-2024.3.5/liminal/const.py +6 -0
  9. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/llm/__init__.py +6 -6
  10. liminal_sdk_python-2024.3.5/liminal/endpoints/llm/models.py +79 -0
  11. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/__init__.py +2 -0
  12. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/prompt/models.py +28 -28
  13. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/thread/__init__.py +2 -0
  14. liminal_sdk_python-2024.3.5/liminal/endpoints/thread/models.py +61 -0
  15. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/helpers/model.py +2 -2
  16. liminal_sdk_python-2024.3.5/pyproject.toml +115 -0
  17. liminal_sdk_python-2024.3.3/liminal/const.py +0 -5
  18. liminal_sdk_python-2024.3.3/liminal/endpoints/llm/models.py +0 -46
  19. liminal_sdk_python-2024.3.3/liminal/endpoints/thread/models.py +0 -31
  20. liminal_sdk_python-2024.3.3/pyproject.toml +0 -119
  21. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/LICENSE +0 -0
  22. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/__init__.py +0 -0
  23. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/endpoints/__init__.py +0 -0
  24. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/errors.py +0 -0
  25. {liminal_sdk_python-2024.3.3 → liminal_sdk_python-2024.3.5}/liminal/helpers/__init__.py +0 -0
  26. {liminal_sdk_python-2024.3.3 → 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.3
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,18 +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
- logging.basicConfig(level=logging.DEBUG)
68
-
69
80
  # Create an auth provider to authenticate the user:
70
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
81
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
71
82
 
72
83
  # Create the liminal SDK instance:
73
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
84
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
74
85
 
75
86
 
76
87
  asyncio.run(main())
@@ -88,13 +99,15 @@ the following auth providers are supported:
88
99
 
89
100
  ## Microsoft Entra ID
90
101
 
91
- Liminal authenticates with Microsoft Entra ID via an
102
+ ### Device Code Flow
103
+
104
+ This authentication process with Microsoft Entra ID involves an
92
105
  [OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
93
106
  to start your app, retrieve a device code from the logs produced by this SDK, and
94
107
  provide that code to Microsoft via a web browser. Once you complete the login process,
95
108
  the SDK will be authenticated for use with your Liminal instance.
96
109
 
97
- ### Finding your Entra ID Tenant and Client IDs
110
+ To authenticate with this flow, you will need an Entra ID client and tenant ID:
98
111
 
99
112
  - Log into your [Azure portal][azure-portal].
100
113
  - Navigate to `Microsoft Entra ID`.
@@ -103,8 +116,6 @@ the SDK will be authenticated for use with your Liminal instance.
103
116
  - In the `Overview` of the registration, look for the `Application (client) ID` and
104
117
  `Directory (tenant) ID` values.
105
118
 
106
- ### Authenticating Against Entra ID
107
-
108
119
  With a client ID and tenant ID, you can create a Liminal client object and authenticate
109
120
  it:
110
121
 
@@ -112,18 +123,16 @@ it:
112
123
  import asyncio
113
124
 
114
125
  from liminal import Client
115
- from liminal.endpoints.auth import MicrosoftAuthProvider
126
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
116
127
 
117
128
 
118
129
  async def main() -> None:
119
130
  """Create the aiohttp session and run the example."""
120
- logging.basicConfig(level=logging.DEBUG)
121
-
122
131
  # Create an auth provider to authenticate the user:
123
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
132
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
124
133
 
125
134
  # Create the liminal SDK instance and authenticate it:
126
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
135
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
127
136
  await liminal.authenticate_from_auth_provider()
128
137
 
129
138
 
@@ -165,15 +174,13 @@ callback at any time.
165
174
  import asyncio
166
175
 
167
176
  from liminal import Client
168
- from liminal.endpoints.auth import MicrosoftAuthProvider
177
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
169
178
 
170
179
 
171
180
  async def main() -> None:
172
181
  """Create the aiohttp session and run the example."""
173
- logging.basicConfig(level=logging.DEBUG)
174
-
175
182
  # Create an auth provider to authenticate the user:
176
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
183
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
177
184
 
178
185
  # Create the liminal SDK instance and authenticate it:
179
186
  liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
@@ -195,7 +202,32 @@ async def main() -> None:
195
202
  asyncio.run(main())
196
203
  ```
197
204
 
198
- # 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
199
231
 
200
232
  Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
201
233
  instance." The SDK provides several methods to interact with model instances:
@@ -204,7 +236,7 @@ instance." The SDK provides several methods to interact with model instances:
204
236
  import asyncio
205
237
 
206
238
  from liminal import Client
207
- from liminal.endpoints.auth import MicrosoftAuthProvider
239
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
208
240
 
209
241
 
210
242
  async def main() -> None:
@@ -222,7 +254,7 @@ async def main() -> None:
222
254
  asyncio.run(main())
223
255
  ```
224
256
 
225
- # Managing Threads
257
+ ## Managing Threads
226
258
 
227
259
  Threads are conversations with an LLM instance:
228
260
 
@@ -230,7 +262,7 @@ Threads are conversations with an LLM instance:
230
262
  import asyncio
231
263
 
232
264
  from liminal import Client
233
- from liminal.endpoints.auth import MicrosoftAuthProvider
265
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
234
266
 
235
267
 
236
268
  async def main() -> None:
@@ -259,7 +291,7 @@ async def main() -> None:
259
291
  asyncio.run(main())
260
292
  ```
261
293
 
262
- # Submitting Prompts
294
+ ## Submitting Prompts
263
295
 
264
296
  Submitting prompts is easy:
265
297
 
@@ -267,7 +299,7 @@ Submitting prompts is easy:
267
299
  import asyncio
268
300
 
269
301
  from liminal import Client
270
- from liminal.endpoints.auth import MicrosoftAuthProvider
302
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
271
303
 
272
304
 
273
305
  async def main() -> None:
@@ -306,6 +338,40 @@ async def main() -> None:
306
338
  asyncio.run(main())
307
339
  ```
308
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
+
309
375
  # Running Examples
310
376
 
311
377
  You can see examples of how to use this SDK via the [`examples`][examples] folder in
@@ -328,7 +394,7 @@ Thanks to all of [our contributors][contributors] so far!
328
394
  2. [Fork the repository][fork].
329
395
  3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
330
396
  4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
331
- 5. Install the dev environment: `script/setup`
397
+ 5. Install the dev environment: `./scripts/setup.sh`
332
398
  6. Code your new feature or bug fix on a new branch.
333
399
  7. Write tests that cover your new functionality.
334
400
  8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
@@ -336,10 +402,12 @@ Thanks to all of [our contributors][contributors] so far!
336
402
  10. Submit a pull request!
337
403
 
338
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
339
406
  [ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
340
407
  [contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
341
408
  [examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
342
409
  [fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
410
+ [httpx]: https://www.python-httpx.org/
343
411
  [issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
344
412
  [license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
345
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,18 +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
- logging.basicConfig(level=logging.DEBUG)
43
-
44
55
  # Create an auth provider to authenticate the user:
45
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
56
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
46
57
 
47
58
  # Create the liminal SDK instance:
48
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
59
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
49
60
 
50
61
 
51
62
  asyncio.run(main())
@@ -63,13 +74,15 @@ the following auth providers are supported:
63
74
 
64
75
  ## Microsoft Entra ID
65
76
 
66
- Liminal authenticates with Microsoft Entra ID via an
77
+ ### Device Code Flow
78
+
79
+ This authentication process with Microsoft Entra ID involves an
67
80
  [OAuth 2.0 Device Authorization Grant][oauth-device-auth-grant]. This flow requires you
68
81
  to start your app, retrieve a device code from the logs produced by this SDK, and
69
82
  provide that code to Microsoft via a web browser. Once you complete the login process,
70
83
  the SDK will be authenticated for use with your Liminal instance.
71
84
 
72
- ### Finding your Entra ID Tenant and Client IDs
85
+ To authenticate with this flow, you will need an Entra ID client and tenant ID:
73
86
 
74
87
  - Log into your [Azure portal][azure-portal].
75
88
  - Navigate to `Microsoft Entra ID`.
@@ -78,8 +91,6 @@ the SDK will be authenticated for use with your Liminal instance.
78
91
  - In the `Overview` of the registration, look for the `Application (client) ID` and
79
92
  `Directory (tenant) ID` values.
80
93
 
81
- ### Authenticating Against Entra ID
82
-
83
94
  With a client ID and tenant ID, you can create a Liminal client object and authenticate
84
95
  it:
85
96
 
@@ -87,18 +98,16 @@ it:
87
98
  import asyncio
88
99
 
89
100
  from liminal import Client
90
- from liminal.endpoints.auth import MicrosoftAuthProvider
101
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
91
102
 
92
103
 
93
104
  async def main() -> None:
94
105
  """Create the aiohttp session and run the example."""
95
- logging.basicConfig(level=logging.DEBUG)
96
-
97
106
  # Create an auth provider to authenticate the user:
98
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
107
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
99
108
 
100
109
  # Create the liminal SDK instance and authenticate it:
101
- liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
110
+ liminal = Client(auth_provider, "<LIMINAL_API_SERVER_URL>")
102
111
  await liminal.authenticate_from_auth_provider()
103
112
 
104
113
 
@@ -140,15 +149,13 @@ callback at any time.
140
149
  import asyncio
141
150
 
142
151
  from liminal import Client
143
- from liminal.endpoints.auth import MicrosoftAuthProvider
152
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
144
153
 
145
154
 
146
155
  async def main() -> None:
147
156
  """Create the aiohttp session and run the example."""
148
- logging.basicConfig(level=logging.DEBUG)
149
-
150
157
  # Create an auth provider to authenticate the user:
151
- microsoft_auth_provider = MicrosoftAuthProvider("<TENANT_ID>", "<CLIENT_ID>")
158
+ auth_provider = DeviceCodeFlowProvider("<TENANT_ID>", "<CLIENT_ID>")
152
159
 
153
160
  # Create the liminal SDK instance and authenticate it:
154
161
  liminal = Client(microsoft_auth_provider, "<LIMINAL_API_SERVER_URL>")
@@ -170,7 +177,32 @@ async def main() -> None:
170
177
  asyncio.run(main())
171
178
  ```
172
179
 
173
- # 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
174
206
 
175
207
  Every LLM instance connected in the Liminal admin dashboard is referred to as a "model
176
208
  instance." The SDK provides several methods to interact with model instances:
@@ -179,7 +211,7 @@ instance." The SDK provides several methods to interact with model instances:
179
211
  import asyncio
180
212
 
181
213
  from liminal import Client
182
- from liminal.endpoints.auth import MicrosoftAuthProvider
214
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
183
215
 
184
216
 
185
217
  async def main() -> None:
@@ -197,7 +229,7 @@ async def main() -> None:
197
229
  asyncio.run(main())
198
230
  ```
199
231
 
200
- # Managing Threads
232
+ ## Managing Threads
201
233
 
202
234
  Threads are conversations with an LLM instance:
203
235
 
@@ -205,7 +237,7 @@ Threads are conversations with an LLM instance:
205
237
  import asyncio
206
238
 
207
239
  from liminal import Client
208
- from liminal.endpoints.auth import MicrosoftAuthProvider
240
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
209
241
 
210
242
 
211
243
  async def main() -> None:
@@ -234,7 +266,7 @@ async def main() -> None:
234
266
  asyncio.run(main())
235
267
  ```
236
268
 
237
- # Submitting Prompts
269
+ ## Submitting Prompts
238
270
 
239
271
  Submitting prompts is easy:
240
272
 
@@ -242,7 +274,7 @@ Submitting prompts is easy:
242
274
  import asyncio
243
275
 
244
276
  from liminal import Client
245
- from liminal.endpoints.auth import MicrosoftAuthProvider
277
+ from liminal.auth.microsoft.device_code_flow import DeviceCodeFlowProvider
246
278
 
247
279
 
248
280
  async def main() -> None:
@@ -281,6 +313,40 @@ async def main() -> None:
281
313
  asyncio.run(main())
282
314
  ```
283
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
+
284
350
  # Running Examples
285
351
 
286
352
  You can see examples of how to use this SDK via the [`examples`][examples] folder in
@@ -303,7 +369,7 @@ Thanks to all of [our contributors][contributors] so far!
303
369
  2. [Fork the repository][fork].
304
370
  3. (_optional, but highly recommended_) Create a virtual environment: `python3 -m venv .venv`
305
371
  4. (_optional, but highly recommended_) Enter the virtual environment: `source ./.venv/bin/activate`
306
- 5. Install the dev environment: `script/setup`
372
+ 5. Install the dev environment: `./scripts/setup.sh`
307
373
  6. Code your new feature or bug fix on a new branch.
308
374
  7. Write tests that cover your new functionality.
309
375
  8. Run tests and ensure 100% code coverage: `poetry run pytest --cov liminal tests`
@@ -311,10 +377,12 @@ Thanks to all of [our contributors][contributors] so far!
311
377
  10. Submit a pull request!
312
378
 
313
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
314
381
  [ci]: https://github.com/liminal-ai-security/liminal-sdk-python/actions
315
382
  [contributors]: https://github.com/liminal-ai-security/liminal-sdk-python/graphs/contributors
316
383
  [examples]: https://github.com/liminal-ai-security/liminal-sdk-python/tree/development/examples
317
384
  [fork]: https://github.com/liminal-ai-security/liminal-sdk-python/fork
385
+ [httpx]: https://www.python-httpx.org/
318
386
  [issues]: https://github.com/liminal-ai-security/liminal-sdk-python/issues
319
387
  [license-badge]: https://img.shields.io/pypi/l/liminal-sdk-python.svg
320
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,25 +1,28 @@
1
- """Define the auth endpoint."""
1
+ """Define Microsoft providers."""
2
+
3
+ from __future__ import annotations
2
4
 
3
5
  import asyncio
6
+ from typing import Final
4
7
 
5
8
  from msal import PublicClientApplication
6
9
 
7
- from liminal.const import LOGGER
8
- from liminal.endpoints.auth.models import (
9
- AuthProvider,
10
+ from liminal.auth import AuthProvider
11
+ from liminal.auth.microsoft.models import (
10
12
  MSALCacheTokenResponse,
11
13
  MSALIdentityProviderTokenResponse,
12
14
  )
15
+ from liminal.const import LOGGER
13
16
  from liminal.errors import AuthError
14
17
 
15
- DEFAULT_AUTH_CHALLENGE_TIMEOUT = 60
18
+ DEFAULT_AUTH_CHALLENGE_TIMEOUT: Final[int] = 60
16
19
 
17
20
 
18
- class MicrosoftAuthProvider(AuthProvider):
21
+ class DeviceCodeFlowProvider(AuthProvider):
19
22
  """Define a Microsoft auth provider."""
20
23
 
21
- AUTHORITY_URL = "https://login.microsoftonline.com"
22
- DEFAULT_SCOPES = ["User.Read"]
24
+ AUTHORITY_URL: Final[str] = "https://login.microsoftonline.com"
25
+ DEFAULT_SCOPES: Final[list[str]] = ["User.Read"]
23
26
 
24
27
  def __init__(
25
28
  self,
@@ -76,7 +79,8 @@ class MicrosoftAuthProvider(AuthProvider):
76
79
  # Setting the flow to expire immediately will effectively kill the future
77
80
  # that we're awaiting:
78
81
  flow["expires_at"] = 0
79
- raise AuthError("Timed out waiting for authentication challenge") from err
82
+ msg = "Timed out waiting for authentication challenge"
83
+ raise AuthError(msg) from err
80
84
 
81
85
  identity_provider_response = MSALIdentityProviderTokenResponse.from_dict(result)
82
86
  return identity_provider_response.access_token
@@ -2,31 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from abc import ABC
6
5
  from dataclasses import dataclass
7
6
  from typing import Literal
8
7
 
9
- from liminal.helpers.model import BaseModel
10
-
11
-
12
- class AuthProvider(ABC):
13
- """Define an auth provider abstract base class."""
14
-
15
- async def get_access_token(self) -> str:
16
- """Retrieve an access token from the auth provider.
17
-
18
- The working principle here is that the auth provider will return an access
19
- token that can be used to authenticate with the Liminal API server.
20
-
21
- Returns:
22
- The access token.
23
-
24
- """
25
- raise NotImplementedError
8
+ from liminal.helpers.model import BaseResponseModel
26
9
 
27
10
 
28
11
  @dataclass(frozen=True, kw_only=True)
29
- class MSALCacheTokenResponse(BaseModel):
12
+ class MSALCacheTokenResponse(BaseResponseModel):
30
13
  """Define an MSAL token response from Entra ID."""
31
14
 
32
15
  token_type: Literal["Bearer"]
@@ -36,7 +19,7 @@ class MSALCacheTokenResponse(BaseModel):
36
19
 
37
20
 
38
21
  @dataclass(frozen=True, kw_only=True)
39
- class MSALIdentityProviderTokenResponse(BaseModel):
22
+ class MSALIdentityProviderTokenResponse(BaseResponseModel):
40
23
  """Define an MSAL token response from the local in-memory cache."""
41
24
 
42
25
  token_type: Literal["Bearer"]