airia 0.1.9__tar.gz → 0.1.10__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 (33) hide show
  1. {airia-0.1.9 → airia-0.1.10}/PKG-INFO +99 -7
  2. {airia-0.1.9 → airia-0.1.10}/README.md +98 -6
  3. {airia-0.1.9 → airia-0.1.10}/airia/client/async_client.py +51 -20
  4. {airia-0.1.9 → airia-0.1.10}/airia/client/base_client.py +42 -16
  5. {airia-0.1.9 → airia-0.1.10}/airia/client/sync_client.py +51 -20
  6. airia-0.1.10/airia/constants.py +9 -0
  7. {airia-0.1.9 → airia-0.1.10}/airia.egg-info/PKG-INFO +99 -7
  8. {airia-0.1.9 → airia-0.1.10}/airia.egg-info/SOURCES.txt +2 -0
  9. {airia-0.1.9 → airia-0.1.10}/pyproject.toml +1 -1
  10. airia-0.1.10/tests/test_bearer_token_auth.py +90 -0
  11. {airia-0.1.9 → airia-0.1.10}/LICENSE +0 -0
  12. {airia-0.1.9 → airia-0.1.10}/airia/__init__.py +0 -0
  13. {airia-0.1.9 → airia-0.1.10}/airia/client/__init__.py +0 -0
  14. {airia-0.1.9 → airia-0.1.10}/airia/exceptions.py +0 -0
  15. {airia-0.1.9 → airia-0.1.10}/airia/logs.py +0 -0
  16. {airia-0.1.9 → airia-0.1.10}/airia/types/__init__.py +0 -0
  17. {airia-0.1.9 → airia-0.1.10}/airia/types/api/get_pipeline_config.py +0 -0
  18. {airia-0.1.9 → airia-0.1.10}/airia/types/api/get_projects.py +0 -0
  19. {airia-0.1.9 → airia-0.1.10}/airia/types/api/pipeline_execution.py +0 -0
  20. {airia-0.1.9 → airia-0.1.10}/airia/types/api_version.py +0 -0
  21. {airia-0.1.9 → airia-0.1.10}/airia/types/request_data.py +0 -0
  22. {airia-0.1.9 → airia-0.1.10}/airia/types/sse_messages.py +0 -0
  23. {airia-0.1.9 → airia-0.1.10}/airia/utils/sse_parser.py +0 -0
  24. {airia-0.1.9 → airia-0.1.10}/airia.egg-info/dependency_links.txt +0 -0
  25. {airia-0.1.9 → airia-0.1.10}/airia.egg-info/requires.txt +0 -0
  26. {airia-0.1.9 → airia-0.1.10}/airia.egg-info/top_level.txt +0 -0
  27. {airia-0.1.9 → airia-0.1.10}/setup.cfg +0 -0
  28. {airia-0.1.9 → airia-0.1.10}/tests/test_anthropic_gateway.py +0 -0
  29. {airia-0.1.9 → airia-0.1.10}/tests/test_execute_pipeline.py +0 -0
  30. {airia-0.1.9 → airia-0.1.10}/tests/test_get_active_pipelines_ids.py +0 -0
  31. {airia-0.1.9 → airia-0.1.10}/tests/test_get_pipeline_config.py +0 -0
  32. {airia-0.1.9 → airia-0.1.10}/tests/test_get_projects.py +0 -0
  33. {airia-0.1.9 → airia-0.1.10}/tests/test_openai_gateway.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airia
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: Python SDK for Airia API
5
5
  Author-email: Airia LLC <support@airia.com>
6
6
  License: MIT
@@ -43,7 +43,9 @@ Airia Python API Library that provides a clean and intuitive interface to intera
43
43
  - **Gateway Support**: Seamlessly integrate with OpenAI and Anthropic services through Airia gateways
44
44
  - **Error Handling**: Comprehensive error handling with custom exceptions
45
45
  - **Logging**: Built-in configurable logging with correlation ID support for request tracing
46
- - **API Key Management**: Flexible API key configuration via parameters or environment variables
46
+ - **Flexible Authentication**: Support for both API keys and bearer tokens with flexible configuration
47
+ - **API Key Management**: API key configuration via parameters or environment variables
48
+ - **Bearer Token Support**: Bearer token authentication for ephemeral, short-lived credentials
47
49
 
48
50
  ## Installation
49
51
 
@@ -181,17 +183,38 @@ This will create both wheel and source distribution in the `dist/` directory.
181
183
  ```python
182
184
  from airia import AiriaClient
183
185
 
186
+ # API Key Authentication
184
187
  client = AiriaClient(
185
188
  base_url="https://api.airia.ai", # Default: "https://api.airia.ai"
186
- api_key=None, # Or set AIRIA_API_KEY environment variable
187
- timeout=30.0, # Request timeout in seconds (default: 30.0)
188
- log_requests=False, # Enable request/response logging (default: False)
189
- custom_logger=None # Use custom logger (default: None - uses built-in)
189
+ api_key=None, # Or set AIRIA_API_KEY environment variable
190
+ timeout=30.0, # Request timeout in seconds (default: 30.0)
191
+ log_requests=False, # Enable request/response logging (default: False)
192
+ custom_logger=None # Use custom logger (default: None - uses built-in)
193
+ )
194
+
195
+ # Bearer Token Authentication
196
+ client = AiriaClient(
197
+ base_url="https://api.airia.ai", # Default: "https://api.airia.ai"
198
+ bearer_token="your_bearer_token", # Must be provided explicitly (no env var fallback)
199
+ timeout=30.0, # Request timeout in seconds (default: 30.0)
200
+ log_requests=False, # Enable request/response logging (default: False)
201
+ custom_logger=None # Use custom logger (default: None - uses built-in)
202
+ )
203
+
204
+ # Convenience method for bearer token
205
+ client = AiriaClient.with_bearer_token(
206
+ bearer_token="your_bearer_token",
207
+ base_url="https://api.airia.ai", # Optional, uses default if not provided
208
+ timeout=30.0, # Optional, uses default if not provided
209
+ log_requests=False, # Optional, uses default if not provided
210
+ custom_logger=None # Optional, uses default if not provided
190
211
  )
191
212
  ```
192
213
 
193
214
  ### Synchronous Usage
194
215
 
216
+ #### With API Key
217
+
195
218
  ```python
196
219
  from airia import AiriaClient
197
220
 
@@ -207,6 +230,23 @@ response = client.execute_pipeline(
207
230
  print(response.result)
208
231
  ```
209
232
 
233
+ #### With Bearer Token
234
+
235
+ ```python
236
+ from airia import AiriaClient
237
+
238
+ # Initialize client with bearer token
239
+ client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
240
+
241
+ # Execute a pipeline
242
+ response = client.execute_pipeline(
243
+ pipeline_id="your_pipeline_id",
244
+ user_input="Tell me about quantum computing"
245
+ )
246
+
247
+ print(response.result)
248
+ ```
249
+
210
250
  #### Synchronous Streaming
211
251
 
212
252
  ```python
@@ -214,6 +254,7 @@ from airia import AiriaClient
214
254
 
215
255
  # Initialize client (API key can be passed directly or via AIRIA_API_KEY environment variable)
216
256
  client = AiriaClient(api_key="your_api_key")
257
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
217
258
 
218
259
  # Execute a pipeline
219
260
  response = client.execute_pipeline(
@@ -228,6 +269,8 @@ for c in response.stream:
228
269
 
229
270
  ### Asynchronous Usage
230
271
 
272
+ #### With API Key
273
+
231
274
  ```python
232
275
  import asyncio
233
276
  from airia import AiriaAsyncClient
@@ -243,6 +286,23 @@ async def main():
243
286
  asyncio.run(main())
244
287
  ```
245
288
 
289
+ #### With Bearer Token
290
+
291
+ ```python
292
+ import asyncio
293
+ from airia import AiriaAsyncClient
294
+
295
+ async def main():
296
+ client = AiriaAsyncClient.with_bearer_token(bearer_token="your_bearer_token")
297
+ response = await client.execute_pipeline(
298
+ pipeline_id="your_pipeline_id",
299
+ user_input="Tell me about quantum computing"
300
+ )
301
+ print(response.result)
302
+
303
+ asyncio.run(main())
304
+ ```
305
+
246
306
  #### Asynchronous Streaming
247
307
 
248
308
  ```python
@@ -251,6 +311,7 @@ from airia import AiriaAsyncClient
251
311
 
252
312
  async def main():
253
313
  client = AiriaAsyncClient(api_key="your_api_key")
314
+ # Or with bearer token: client = AiriaAsyncClient.with_bearer_token(bearer_token="your_bearer_token")
254
315
  response = await client.execute_pipeline(
255
316
  pipeline_id="your_pipeline_id",
256
317
  user_input="Tell me about quantum computing",
@@ -320,6 +381,7 @@ from airia import AiriaClient
320
381
  from airia.types import AgentModelStreamFragmentMessage
321
382
 
322
383
  client = AiriaClient(api_key="your_api_key")
384
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
323
385
 
324
386
  response = client.execute_pipeline(
325
387
  pipeline_id="your_pipeline_id",
@@ -343,6 +405,7 @@ You can retrieve detailed configuration information about a pipeline using the `
343
405
  from airia import AiriaClient
344
406
 
345
407
  client = AiriaClient(api_key="your_api_key")
408
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
346
409
 
347
410
  # Get pipeline configuration
348
411
  config = client.get_pipeline_config(pipeline_id="your_pipeline_id")
@@ -351,10 +414,35 @@ config = client.get_pipeline_config(pipeline_id="your_pipeline_id")
351
414
  print(f"Pipeline Name: {config.agent.name}")
352
415
  ```
353
416
 
417
+ ## Authentication Methods
418
+
419
+ Airia supports two authentication methods:
420
+
421
+ ### API Keys
422
+ - Can be passed as a parameter or via `AIRIA_API_KEY` environment variable
423
+ - Support gateway functionality (OpenAI and Anthropic gateways)
424
+ - Suitable for long-term, persistent authentication
425
+
426
+ ### Bearer Tokens
427
+ - Must be provided explicitly (no environment variable fallback)
428
+ - **Important**: Bearer tokens cannot be used with gateway functionality
429
+ - Suitable for ephemeral, short-lived authentication scenarios
430
+ - Ideal for temporary access or programmatic token generation
431
+
432
+ ```python
433
+ # ✅ API key with gateway support
434
+ client = AiriaClient.with_openai_gateway(api_key="your_api_key")
435
+
436
+ # ❌ Bearer token with gateway - NOT SUPPORTED
437
+ # client = AiriaClient.with_openai_gateway(bearer_token="token") # This won't work
438
+ ```
439
+
354
440
  ## Gateway Usage
355
441
 
356
442
  Airia provides gateway capabilities for popular AI services like OpenAI and Anthropic, allowing you to use your Airia API key with these services.
357
443
 
444
+ > **Note**: Gateway functionality requires API key authentication. Bearer tokens are not supported for gateway usage.
445
+
358
446
  ### OpenAI Gateway
359
447
 
360
448
  ```python
@@ -482,7 +570,11 @@ console_logger = configure_logging(
482
570
  The SDK uses custom exceptions to provide clear error messages:
483
571
 
484
572
  ```python
485
- from airia import AiriaAPIError
573
+ from airia import AiriaAPIError, AiriaClient
574
+
575
+ # Works with both API keys and bearer tokens
576
+ client = AiriaClient(api_key="your_api_key")
577
+ # Or: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
486
578
 
487
579
  try:
488
580
  response = client.execute_pipeline(
@@ -13,7 +13,9 @@ Airia Python API Library that provides a clean and intuitive interface to intera
13
13
  - **Gateway Support**: Seamlessly integrate with OpenAI and Anthropic services through Airia gateways
14
14
  - **Error Handling**: Comprehensive error handling with custom exceptions
15
15
  - **Logging**: Built-in configurable logging with correlation ID support for request tracing
16
- - **API Key Management**: Flexible API key configuration via parameters or environment variables
16
+ - **Flexible Authentication**: Support for both API keys and bearer tokens with flexible configuration
17
+ - **API Key Management**: API key configuration via parameters or environment variables
18
+ - **Bearer Token Support**: Bearer token authentication for ephemeral, short-lived credentials
17
19
 
18
20
  ## Installation
19
21
 
@@ -151,17 +153,38 @@ This will create both wheel and source distribution in the `dist/` directory.
151
153
  ```python
152
154
  from airia import AiriaClient
153
155
 
156
+ # API Key Authentication
154
157
  client = AiriaClient(
155
158
  base_url="https://api.airia.ai", # Default: "https://api.airia.ai"
156
- api_key=None, # Or set AIRIA_API_KEY environment variable
157
- timeout=30.0, # Request timeout in seconds (default: 30.0)
158
- log_requests=False, # Enable request/response logging (default: False)
159
- custom_logger=None # Use custom logger (default: None - uses built-in)
159
+ api_key=None, # Or set AIRIA_API_KEY environment variable
160
+ timeout=30.0, # Request timeout in seconds (default: 30.0)
161
+ log_requests=False, # Enable request/response logging (default: False)
162
+ custom_logger=None # Use custom logger (default: None - uses built-in)
163
+ )
164
+
165
+ # Bearer Token Authentication
166
+ client = AiriaClient(
167
+ base_url="https://api.airia.ai", # Default: "https://api.airia.ai"
168
+ bearer_token="your_bearer_token", # Must be provided explicitly (no env var fallback)
169
+ timeout=30.0, # Request timeout in seconds (default: 30.0)
170
+ log_requests=False, # Enable request/response logging (default: False)
171
+ custom_logger=None # Use custom logger (default: None - uses built-in)
172
+ )
173
+
174
+ # Convenience method for bearer token
175
+ client = AiriaClient.with_bearer_token(
176
+ bearer_token="your_bearer_token",
177
+ base_url="https://api.airia.ai", # Optional, uses default if not provided
178
+ timeout=30.0, # Optional, uses default if not provided
179
+ log_requests=False, # Optional, uses default if not provided
180
+ custom_logger=None # Optional, uses default if not provided
160
181
  )
161
182
  ```
162
183
 
163
184
  ### Synchronous Usage
164
185
 
186
+ #### With API Key
187
+
165
188
  ```python
166
189
  from airia import AiriaClient
167
190
 
@@ -177,6 +200,23 @@ response = client.execute_pipeline(
177
200
  print(response.result)
178
201
  ```
179
202
 
203
+ #### With Bearer Token
204
+
205
+ ```python
206
+ from airia import AiriaClient
207
+
208
+ # Initialize client with bearer token
209
+ client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
210
+
211
+ # Execute a pipeline
212
+ response = client.execute_pipeline(
213
+ pipeline_id="your_pipeline_id",
214
+ user_input="Tell me about quantum computing"
215
+ )
216
+
217
+ print(response.result)
218
+ ```
219
+
180
220
  #### Synchronous Streaming
181
221
 
182
222
  ```python
@@ -184,6 +224,7 @@ from airia import AiriaClient
184
224
 
185
225
  # Initialize client (API key can be passed directly or via AIRIA_API_KEY environment variable)
186
226
  client = AiriaClient(api_key="your_api_key")
227
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
187
228
 
188
229
  # Execute a pipeline
189
230
  response = client.execute_pipeline(
@@ -198,6 +239,8 @@ for c in response.stream:
198
239
 
199
240
  ### Asynchronous Usage
200
241
 
242
+ #### With API Key
243
+
201
244
  ```python
202
245
  import asyncio
203
246
  from airia import AiriaAsyncClient
@@ -213,6 +256,23 @@ async def main():
213
256
  asyncio.run(main())
214
257
  ```
215
258
 
259
+ #### With Bearer Token
260
+
261
+ ```python
262
+ import asyncio
263
+ from airia import AiriaAsyncClient
264
+
265
+ async def main():
266
+ client = AiriaAsyncClient.with_bearer_token(bearer_token="your_bearer_token")
267
+ response = await client.execute_pipeline(
268
+ pipeline_id="your_pipeline_id",
269
+ user_input="Tell me about quantum computing"
270
+ )
271
+ print(response.result)
272
+
273
+ asyncio.run(main())
274
+ ```
275
+
216
276
  #### Asynchronous Streaming
217
277
 
218
278
  ```python
@@ -221,6 +281,7 @@ from airia import AiriaAsyncClient
221
281
 
222
282
  async def main():
223
283
  client = AiriaAsyncClient(api_key="your_api_key")
284
+ # Or with bearer token: client = AiriaAsyncClient.with_bearer_token(bearer_token="your_bearer_token")
224
285
  response = await client.execute_pipeline(
225
286
  pipeline_id="your_pipeline_id",
226
287
  user_input="Tell me about quantum computing",
@@ -290,6 +351,7 @@ from airia import AiriaClient
290
351
  from airia.types import AgentModelStreamFragmentMessage
291
352
 
292
353
  client = AiriaClient(api_key="your_api_key")
354
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
293
355
 
294
356
  response = client.execute_pipeline(
295
357
  pipeline_id="your_pipeline_id",
@@ -313,6 +375,7 @@ You can retrieve detailed configuration information about a pipeline using the `
313
375
  from airia import AiriaClient
314
376
 
315
377
  client = AiriaClient(api_key="your_api_key")
378
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
316
379
 
317
380
  # Get pipeline configuration
318
381
  config = client.get_pipeline_config(pipeline_id="your_pipeline_id")
@@ -321,10 +384,35 @@ config = client.get_pipeline_config(pipeline_id="your_pipeline_id")
321
384
  print(f"Pipeline Name: {config.agent.name}")
322
385
  ```
323
386
 
387
+ ## Authentication Methods
388
+
389
+ Airia supports two authentication methods:
390
+
391
+ ### API Keys
392
+ - Can be passed as a parameter or via `AIRIA_API_KEY` environment variable
393
+ - Support gateway functionality (OpenAI and Anthropic gateways)
394
+ - Suitable for long-term, persistent authentication
395
+
396
+ ### Bearer Tokens
397
+ - Must be provided explicitly (no environment variable fallback)
398
+ - **Important**: Bearer tokens cannot be used with gateway functionality
399
+ - Suitable for ephemeral, short-lived authentication scenarios
400
+ - Ideal for temporary access or programmatic token generation
401
+
402
+ ```python
403
+ # ✅ API key with gateway support
404
+ client = AiriaClient.with_openai_gateway(api_key="your_api_key")
405
+
406
+ # ❌ Bearer token with gateway - NOT SUPPORTED
407
+ # client = AiriaClient.with_openai_gateway(bearer_token="token") # This won't work
408
+ ```
409
+
324
410
  ## Gateway Usage
325
411
 
326
412
  Airia provides gateway capabilities for popular AI services like OpenAI and Anthropic, allowing you to use your Airia API key with these services.
327
413
 
414
+ > **Note**: Gateway functionality requires API key authentication. Bearer tokens are not supported for gateway usage.
415
+
328
416
  ### OpenAI Gateway
329
417
 
330
418
  ```python
@@ -452,7 +540,11 @@ console_logger = configure_logging(
452
540
  The SDK uses custom exceptions to provide clear error messages:
453
541
 
454
542
  ```python
455
- from airia import AiriaAPIError
543
+ from airia import AiriaAPIError, AiriaClient
544
+
545
+ # Works with both API keys and bearer tokens
546
+ client = AiriaClient(api_key="your_api_key")
547
+ # Or: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
456
548
 
457
549
  try:
458
550
  response = client.execute_pipeline(
@@ -6,6 +6,7 @@ from urllib.parse import urljoin
6
6
  import aiohttp
7
7
  import loguru
8
8
 
9
+ from ..constants import DEFAULT_ANTHROPIC_GATEWAY_URL, DEFAULT_BASE_URL, DEFAULT_OPENAI_GATEWAY_URL, DEFAULT_TIMEOUT
9
10
  from ..exceptions import AiriaAPIError
10
11
  from ..types import (
11
12
  ApiVersion,
@@ -26,9 +27,10 @@ class AiriaAsyncClient(AiriaBaseClient):
26
27
 
27
28
  def __init__(
28
29
  self,
29
- base_url: str = "https://api.airia.ai/",
30
+ base_url: str = DEFAULT_BASE_URL,
30
31
  api_key: Optional[str] = None,
31
- timeout: float = 30.0,
32
+ bearer_token: Optional[str] = None,
33
+ timeout: float = DEFAULT_TIMEOUT,
32
34
  log_requests: bool = False,
33
35
  custom_logger: Optional["loguru.Logger"] = None,
34
36
  ):
@@ -38,6 +40,7 @@ class AiriaAsyncClient(AiriaBaseClient):
38
40
  Args:
39
41
  base_url: Base URL of the Airia API.
40
42
  api_key: API key for authentication. If not provided, will attempt to use AIRIA_API_KEY environment variable.
43
+ bearer_token: Bearer token for authentication. Must be provided explicitly (no environment variable fallback).
41
44
  timeout: Request timeout in seconds.
42
45
  log_requests: Whether to log API requests and responses. Default is False.
43
46
  custom_logger: Optional custom logger object to use for logging. If not provided, will use a default logger when `log_requests` is True.
@@ -45,6 +48,7 @@ class AiriaAsyncClient(AiriaBaseClient):
45
48
  super().__init__(
46
49
  base_url=base_url,
47
50
  api_key=api_key,
51
+ bearer_token=bearer_token,
48
52
  timeout=timeout,
49
53
  log_requests=log_requests,
50
54
  custom_logger=custom_logger,
@@ -80,10 +84,10 @@ class AiriaAsyncClient(AiriaBaseClient):
80
84
  @classmethod
81
85
  def with_openai_gateway(
82
86
  cls,
83
- base_url: str = "https://api.airia.ai/",
84
- gateway_url: str = "https://gateway.airia.ai/openai/v1",
87
+ base_url: str = DEFAULT_BASE_URL,
88
+ gateway_url: str = DEFAULT_OPENAI_GATEWAY_URL,
85
89
  api_key: Optional[str] = None,
86
- timeout: float = 30.0,
90
+ timeout: float = DEFAULT_TIMEOUT,
87
91
  log_requests: bool = False,
88
92
  custom_logger: Optional["loguru.Logger"] = None,
89
93
  **kwargs,
@@ -102,22 +106,22 @@ class AiriaAsyncClient(AiriaBaseClient):
102
106
  """
103
107
  from openai import AsyncOpenAI
104
108
 
105
- api_key = cls._get_api_key(api_key)
109
+ client = cls(base_url=base_url, api_key=api_key, timeout=timeout, log_requests=log_requests, custom_logger=custom_logger)
106
110
  cls.openai = AsyncOpenAI(
107
- api_key=api_key,
111
+ api_key=client.api_key,
108
112
  base_url=gateway_url,
109
113
  **kwargs,
110
114
  )
111
115
 
112
- return cls(base_url, api_key, timeout, log_requests, custom_logger)
116
+ return client
113
117
 
114
118
  @classmethod
115
119
  def with_anthropic_gateway(
116
120
  cls,
117
- base_url: str = "https://api.airia.ai/",
118
- gateway_url: str = "https://gateway.airia.ai/anthropic",
121
+ base_url: str = DEFAULT_BASE_URL,
122
+ gateway_url: str = DEFAULT_ANTHROPIC_GATEWAY_URL,
119
123
  api_key: Optional[str] = None,
120
- timeout: float = 30.0,
124
+ timeout: float = DEFAULT_TIMEOUT,
121
125
  log_requests: bool = False,
122
126
  custom_logger: Optional["loguru.Logger"] = None,
123
127
  **kwargs,
@@ -136,14 +140,41 @@ class AiriaAsyncClient(AiriaBaseClient):
136
140
  """
137
141
  from anthropic import AsyncAnthropic
138
142
 
139
- api_key = cls._get_api_key(api_key)
143
+ client = cls(base_url=base_url, api_key=api_key, timeout=timeout, log_requests=log_requests, custom_logger=custom_logger)
140
144
  cls.anthropic = AsyncAnthropic(
141
- api_key=api_key,
145
+ api_key=client.api_key,
142
146
  base_url=gateway_url,
143
147
  **kwargs,
144
148
  )
145
149
 
146
- return cls(base_url, api_key, timeout, log_requests, custom_logger)
150
+ return client
151
+
152
+ @classmethod
153
+ def with_bearer_token(
154
+ cls,
155
+ bearer_token: str,
156
+ base_url: str = DEFAULT_BASE_URL,
157
+ timeout: float = DEFAULT_TIMEOUT,
158
+ log_requests: bool = False,
159
+ custom_logger: Optional["loguru.Logger"] = None,
160
+ ):
161
+ """
162
+ Initialize the asynchronous Airia API client with bearer token authentication.
163
+
164
+ Args:
165
+ bearer_token: Bearer token for authentication.
166
+ base_url: Base URL of the Airia API.
167
+ timeout: Request timeout in seconds.
168
+ log_requests: Whether to log API requests and responses. Default is False.
169
+ custom_logger: Optional custom logger object to use for logging. If not provided, will use a default logger when `log_requests` is True.
170
+ """
171
+ return cls(
172
+ base_url=base_url,
173
+ bearer_token=bearer_token,
174
+ timeout=timeout,
175
+ log_requests=log_requests,
176
+ custom_logger=custom_logger,
177
+ )
147
178
 
148
179
  def _handle_exception(
149
180
  self, e: aiohttp.ClientResponseError, url: str, correlation_id: str
@@ -159,12 +190,12 @@ class AiriaAsyncClient(AiriaBaseClient):
159
190
  # Extract error details from response
160
191
  error_message = e.message
161
192
 
162
- # Make sure API key is not included in error messages
163
- sanitized_message = (
164
- error_message.replace(self.api_key, "[REDACTED]")
165
- if self.api_key in error_message
166
- else error_message
167
- )
193
+ # Make sure sensitive auth information is not included in error messages
194
+ sanitized_message = error_message
195
+ if self.api_key and self.api_key in sanitized_message:
196
+ sanitized_message = sanitized_message.replace(self.api_key, "[REDACTED]")
197
+ if self.bearer_token and self.bearer_token in sanitized_message:
198
+ sanitized_message = sanitized_message.replace(self.bearer_token, "[REDACTED]")
168
199
 
169
200
  # Raise custom exception with status code and sanitized message
170
201
  raise AiriaAPIError(status_code=e.status, message=sanitized_message) from e
@@ -5,6 +5,7 @@ from urllib.parse import urljoin
5
5
 
6
6
  import loguru
7
7
 
8
+ from ..constants import DEFAULT_BASE_URL, DEFAULT_TIMEOUT
8
9
  from ..logs import configure_logging, set_correlation_id
9
10
  from ..types import ApiVersion, RequestData
10
11
 
@@ -17,9 +18,10 @@ class AiriaBaseClient:
17
18
 
18
19
  def __init__(
19
20
  self,
20
- base_url: str = "https://api.airia.ai/",
21
+ base_url: str = DEFAULT_BASE_URL,
21
22
  api_key: Optional[str] = None,
22
- timeout: float = 30.0,
23
+ bearer_token: Optional[str] = None,
24
+ timeout: float = DEFAULT_TIMEOUT,
23
25
  log_requests: bool = False,
24
26
  custom_logger: Optional["loguru.Logger"] = None,
25
27
  ):
@@ -28,12 +30,13 @@ class AiriaBaseClient:
28
30
 
29
31
  Args:
30
32
  api_key: API key for authentication. If not provided, will attempt to use AIRIA_API_KEY environment variable.
33
+ bearer_token: Bearer token for authentication. Must be provided explicitly (no environment variable fallback).
31
34
  timeout: Request timeout in seconds.
32
35
  log_requests: Whether to log API requests and responses. Default is False.
33
36
  custom_logger: Optional custom logger object to use for logging. If not provided, will use a default logger when `log_requests` is True.
34
37
  """
35
- # Resolve API key: parameter takes precedence over environment variable
36
- self.api_key = self.__class__._get_api_key(api_key)
38
+ # Resolve authentication credentials
39
+ self.api_key, self.bearer_token = self.__class__._resolve_auth_credentials(api_key, bearer_token)
37
40
 
38
41
  # Store configuration
39
42
  self.base_url = base_url
@@ -44,27 +47,43 @@ class AiriaBaseClient:
44
47
  self.logger = configure_logging() if custom_logger is None else custom_logger
45
48
 
46
49
  @staticmethod
47
- def _get_api_key(api_key: Optional[str] = None):
50
+ def _resolve_auth_credentials(api_key: Optional[str] = None, bearer_token: Optional[str] = None):
48
51
  """
49
- Get the API key from either the provided parameter or environment variable.
52
+ Resolve authentication credentials from parameters and environment variables.
50
53
 
51
54
  Args:
52
55
  api_key (Optional[str]): The API key provided as a parameter. Defaults to None.
56
+ bearer_token (Optional[str]): The bearer token provided as a parameter. Defaults to None.
53
57
 
54
58
  Returns:
55
- str: The resolved API key.
59
+ tuple: (api_key, bearer_token) - exactly one will be non-None
56
60
 
57
61
  Raises:
58
- ValueError: If no API key is provided through either method.
62
+ ValueError: If no authentication method is provided or if both are provided.
59
63
  """
60
- api_key = api_key or os.environ.get("AIRIA_API_KEY")
61
-
62
- if not api_key:
64
+ # Check for explicit conflict first
65
+ if api_key and bearer_token:
63
66
  raise ValueError(
64
- "API key must be provided either as a parameter or through the AIRIA_API_KEY environment variable."
67
+ "Cannot provide both api_key and bearer_token. Please use only one authentication method."
65
68
  )
66
-
67
- return api_key
69
+
70
+ # If bearer token is explicitly provided, use it exclusively
71
+ if bearer_token:
72
+ return None, bearer_token
73
+
74
+ # If API key is explicitly provided, use it exclusively
75
+ if api_key:
76
+ return api_key, None
77
+
78
+ # If neither is provided explicitly, fall back to environment variable
79
+ resolved_api_key = os.environ.get("AIRIA_API_KEY")
80
+ if resolved_api_key:
81
+ return resolved_api_key, None
82
+
83
+ # No authentication method found
84
+ raise ValueError(
85
+ "Authentication required. Provide either api_key (or set AIRIA_API_KEY environment variable) or bearer_token."
86
+ )
68
87
 
69
88
  def _prepare_request(
70
89
  self,
@@ -76,13 +95,18 @@ class AiriaBaseClient:
76
95
  # Set correlation ID if provided or generate a new one
77
96
  correlation_id = set_correlation_id(correlation_id)
78
97
 
79
- # Add the X-API-KEY header and correlation ID
98
+ # Set up base headers
80
99
  headers = {
81
- "X-API-KEY": self.api_key,
82
100
  "X-Correlation-ID": correlation_id,
83
101
  "Content-Type": "application/json",
84
102
  }
85
103
 
104
+ # Add authentication header based on the method used
105
+ if self.api_key:
106
+ headers["X-API-KEY"] = self.api_key
107
+ elif self.bearer_token:
108
+ headers["Authorization"] = f"Bearer {self.bearer_token}"
109
+
86
110
  # Log the request if enabled
87
111
  if self.log_requests:
88
112
  # Create a sanitized copy of headers and params for logging
@@ -92,6 +116,8 @@ class AiriaBaseClient:
92
116
  # Filter out sensitive headers
93
117
  if "X-API-KEY" in log_headers:
94
118
  log_headers["X-API-KEY"] = "[REDACTED]"
119
+ if "Authorization" in log_headers:
120
+ log_headers["Authorization"] = "[REDACTED]"
95
121
 
96
122
  # Process payload for logging
97
123
  log_payload = payload.copy() if payload is not None else {}
@@ -4,6 +4,7 @@ from urllib.parse import urljoin
4
4
  import loguru
5
5
  import requests
6
6
 
7
+ from ..constants import DEFAULT_ANTHROPIC_GATEWAY_URL, DEFAULT_BASE_URL, DEFAULT_OPENAI_GATEWAY_URL, DEFAULT_TIMEOUT
7
8
  from ..exceptions import AiriaAPIError
8
9
  from ..types import (
9
10
  ApiVersion,
@@ -24,9 +25,10 @@ class AiriaClient(AiriaBaseClient):
24
25
 
25
26
  def __init__(
26
27
  self,
27
- base_url: str = "https://api.airia.ai/",
28
+ base_url: str = DEFAULT_BASE_URL,
28
29
  api_key: Optional[str] = None,
29
- timeout: float = 30.0,
30
+ bearer_token: Optional[str] = None,
31
+ timeout: float = DEFAULT_TIMEOUT,
30
32
  log_requests: bool = False,
31
33
  custom_logger: Optional["loguru.Logger"] = None,
32
34
  ):
@@ -36,6 +38,7 @@ class AiriaClient(AiriaBaseClient):
36
38
  Args:
37
39
  base_url: Base URL of the Airia API.
38
40
  api_key: API key for authentication. If not provided, will attempt to use AIRIA_API_KEY environment variable.
41
+ bearer_token: Bearer token for authentication. Must be provided explicitly (no environment variable fallback).
39
42
  timeout: Request timeout in seconds.
40
43
  log_requests: Whether to log API requests and responses. Default is False.
41
44
  custom_logger: Optional custom logger object to use for logging. If not provided, will use a default logger when `log_requests` is True.
@@ -43,6 +46,7 @@ class AiriaClient(AiriaBaseClient):
43
46
  super().__init__(
44
47
  base_url=base_url,
45
48
  api_key=api_key,
49
+ bearer_token=bearer_token,
46
50
  timeout=timeout,
47
51
  log_requests=log_requests,
48
52
  custom_logger=custom_logger,
@@ -55,10 +59,10 @@ class AiriaClient(AiriaBaseClient):
55
59
  @classmethod
56
60
  def with_openai_gateway(
57
61
  cls,
58
- base_url: str = "https://api.airia.ai/",
59
- gateway_url: str = "https://gateway.airia.ai/openai/v1",
62
+ base_url: str = DEFAULT_BASE_URL,
63
+ gateway_url: str = DEFAULT_OPENAI_GATEWAY_URL,
60
64
  api_key: Optional[str] = None,
61
- timeout: float = 30.0,
65
+ timeout: float = DEFAULT_TIMEOUT,
62
66
  log_requests: bool = False,
63
67
  custom_logger: Optional["loguru.Logger"] = None,
64
68
  **kwargs,
@@ -77,22 +81,22 @@ class AiriaClient(AiriaBaseClient):
77
81
  """
78
82
  from openai import OpenAI
79
83
 
80
- api_key = cls._get_api_key(api_key)
84
+ client = cls(base_url=base_url, api_key=api_key, timeout=timeout, log_requests=log_requests, custom_logger=custom_logger)
81
85
  cls.openai = OpenAI(
82
- api_key=api_key,
86
+ api_key=client.api_key,
83
87
  base_url=gateway_url,
84
88
  **kwargs,
85
89
  )
86
90
 
87
- return cls(base_url, api_key, timeout, log_requests, custom_logger)
91
+ return client
88
92
 
89
93
  @classmethod
90
94
  def with_anthropic_gateway(
91
95
  cls,
92
- base_url: str = "https://api.airia.ai/",
93
- gateway_url: str = "https://gateway.airia.ai/anthropic",
96
+ base_url: str = DEFAULT_BASE_URL,
97
+ gateway_url: str = DEFAULT_ANTHROPIC_GATEWAY_URL,
94
98
  api_key: Optional[str] = None,
95
- timeout: float = 30.0,
99
+ timeout: float = DEFAULT_TIMEOUT,
96
100
  log_requests: bool = False,
97
101
  custom_logger: Optional["loguru.Logger"] = None,
98
102
  **kwargs,
@@ -111,14 +115,41 @@ class AiriaClient(AiriaBaseClient):
111
115
  """
112
116
  from anthropic import Anthropic
113
117
 
114
- api_key = cls._get_api_key(api_key)
118
+ client = cls(base_url=base_url, api_key=api_key, timeout=timeout, log_requests=log_requests, custom_logger=custom_logger)
115
119
  cls.anthropic = Anthropic(
116
- api_key=api_key,
120
+ api_key=client.api_key,
117
121
  base_url=gateway_url,
118
122
  **kwargs,
119
123
  )
120
124
 
121
- return cls(base_url, api_key, timeout, log_requests, custom_logger)
125
+ return client
126
+
127
+ @classmethod
128
+ def with_bearer_token(
129
+ cls,
130
+ bearer_token: str,
131
+ base_url: str = DEFAULT_BASE_URL,
132
+ timeout: float = DEFAULT_TIMEOUT,
133
+ log_requests: bool = False,
134
+ custom_logger: Optional["loguru.Logger"] = None,
135
+ ):
136
+ """
137
+ Initialize the synchronous Airia API client with bearer token authentication.
138
+
139
+ Args:
140
+ bearer_token: Bearer token for authentication.
141
+ base_url: Base URL of the Airia API.
142
+ timeout: Request timeout in seconds.
143
+ log_requests: Whether to log API requests and responses. Default is False.
144
+ custom_logger: Optional custom logger object to use for logging. If not provided, will use a default logger when `log_requests` is True.
145
+ """
146
+ return cls(
147
+ base_url=base_url,
148
+ bearer_token=bearer_token,
149
+ timeout=timeout,
150
+ log_requests=log_requests,
151
+ custom_logger=custom_logger,
152
+ )
122
153
 
123
154
  def _handle_exception(self, e: requests.HTTPError, url: str, correlation_id: str):
124
155
  # Log the error response if enabled
@@ -141,12 +172,12 @@ class AiriaClient(AiriaBaseClient):
141
172
  # If JSON parsing fails or expected keys are missing
142
173
  error_message = f"API request failed: {str(e)}"
143
174
 
144
- # Make sure API key is not included in error messages
145
- sanitized_message = (
146
- error_message.replace(self.api_key, "[REDACTED]")
147
- if self.api_key in error_message
148
- else error_message
149
- )
175
+ # Make sure sensitive auth information is not included in error messages
176
+ sanitized_message = error_message
177
+ if self.api_key and self.api_key in sanitized_message:
178
+ sanitized_message = sanitized_message.replace(self.api_key, "[REDACTED]")
179
+ if self.bearer_token and self.bearer_token in sanitized_message:
180
+ sanitized_message = sanitized_message.replace(self.bearer_token, "[REDACTED]")
150
181
 
151
182
  # Raise custom exception with status code and sanitized message
152
183
  raise AiriaAPIError(
@@ -0,0 +1,9 @@
1
+ """Constants used throughout the Airia SDK."""
2
+
3
+ # Default API endpoints
4
+ DEFAULT_BASE_URL = "https://api.airia.ai/"
5
+ DEFAULT_OPENAI_GATEWAY_URL = "https://gateway.airia.ai/openai/v1"
6
+ DEFAULT_ANTHROPIC_GATEWAY_URL = "https://gateway.airia.ai/anthropic"
7
+
8
+ # Default timeouts
9
+ DEFAULT_TIMEOUT = 30.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airia
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: Python SDK for Airia API
5
5
  Author-email: Airia LLC <support@airia.com>
6
6
  License: MIT
@@ -43,7 +43,9 @@ Airia Python API Library that provides a clean and intuitive interface to intera
43
43
  - **Gateway Support**: Seamlessly integrate with OpenAI and Anthropic services through Airia gateways
44
44
  - **Error Handling**: Comprehensive error handling with custom exceptions
45
45
  - **Logging**: Built-in configurable logging with correlation ID support for request tracing
46
- - **API Key Management**: Flexible API key configuration via parameters or environment variables
46
+ - **Flexible Authentication**: Support for both API keys and bearer tokens with flexible configuration
47
+ - **API Key Management**: API key configuration via parameters or environment variables
48
+ - **Bearer Token Support**: Bearer token authentication for ephemeral, short-lived credentials
47
49
 
48
50
  ## Installation
49
51
 
@@ -181,17 +183,38 @@ This will create both wheel and source distribution in the `dist/` directory.
181
183
  ```python
182
184
  from airia import AiriaClient
183
185
 
186
+ # API Key Authentication
184
187
  client = AiriaClient(
185
188
  base_url="https://api.airia.ai", # Default: "https://api.airia.ai"
186
- api_key=None, # Or set AIRIA_API_KEY environment variable
187
- timeout=30.0, # Request timeout in seconds (default: 30.0)
188
- log_requests=False, # Enable request/response logging (default: False)
189
- custom_logger=None # Use custom logger (default: None - uses built-in)
189
+ api_key=None, # Or set AIRIA_API_KEY environment variable
190
+ timeout=30.0, # Request timeout in seconds (default: 30.0)
191
+ log_requests=False, # Enable request/response logging (default: False)
192
+ custom_logger=None # Use custom logger (default: None - uses built-in)
193
+ )
194
+
195
+ # Bearer Token Authentication
196
+ client = AiriaClient(
197
+ base_url="https://api.airia.ai", # Default: "https://api.airia.ai"
198
+ bearer_token="your_bearer_token", # Must be provided explicitly (no env var fallback)
199
+ timeout=30.0, # Request timeout in seconds (default: 30.0)
200
+ log_requests=False, # Enable request/response logging (default: False)
201
+ custom_logger=None # Use custom logger (default: None - uses built-in)
202
+ )
203
+
204
+ # Convenience method for bearer token
205
+ client = AiriaClient.with_bearer_token(
206
+ bearer_token="your_bearer_token",
207
+ base_url="https://api.airia.ai", # Optional, uses default if not provided
208
+ timeout=30.0, # Optional, uses default if not provided
209
+ log_requests=False, # Optional, uses default if not provided
210
+ custom_logger=None # Optional, uses default if not provided
190
211
  )
191
212
  ```
192
213
 
193
214
  ### Synchronous Usage
194
215
 
216
+ #### With API Key
217
+
195
218
  ```python
196
219
  from airia import AiriaClient
197
220
 
@@ -207,6 +230,23 @@ response = client.execute_pipeline(
207
230
  print(response.result)
208
231
  ```
209
232
 
233
+ #### With Bearer Token
234
+
235
+ ```python
236
+ from airia import AiriaClient
237
+
238
+ # Initialize client with bearer token
239
+ client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
240
+
241
+ # Execute a pipeline
242
+ response = client.execute_pipeline(
243
+ pipeline_id="your_pipeline_id",
244
+ user_input="Tell me about quantum computing"
245
+ )
246
+
247
+ print(response.result)
248
+ ```
249
+
210
250
  #### Synchronous Streaming
211
251
 
212
252
  ```python
@@ -214,6 +254,7 @@ from airia import AiriaClient
214
254
 
215
255
  # Initialize client (API key can be passed directly or via AIRIA_API_KEY environment variable)
216
256
  client = AiriaClient(api_key="your_api_key")
257
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
217
258
 
218
259
  # Execute a pipeline
219
260
  response = client.execute_pipeline(
@@ -228,6 +269,8 @@ for c in response.stream:
228
269
 
229
270
  ### Asynchronous Usage
230
271
 
272
+ #### With API Key
273
+
231
274
  ```python
232
275
  import asyncio
233
276
  from airia import AiriaAsyncClient
@@ -243,6 +286,23 @@ async def main():
243
286
  asyncio.run(main())
244
287
  ```
245
288
 
289
+ #### With Bearer Token
290
+
291
+ ```python
292
+ import asyncio
293
+ from airia import AiriaAsyncClient
294
+
295
+ async def main():
296
+ client = AiriaAsyncClient.with_bearer_token(bearer_token="your_bearer_token")
297
+ response = await client.execute_pipeline(
298
+ pipeline_id="your_pipeline_id",
299
+ user_input="Tell me about quantum computing"
300
+ )
301
+ print(response.result)
302
+
303
+ asyncio.run(main())
304
+ ```
305
+
246
306
  #### Asynchronous Streaming
247
307
 
248
308
  ```python
@@ -251,6 +311,7 @@ from airia import AiriaAsyncClient
251
311
 
252
312
  async def main():
253
313
  client = AiriaAsyncClient(api_key="your_api_key")
314
+ # Or with bearer token: client = AiriaAsyncClient.with_bearer_token(bearer_token="your_bearer_token")
254
315
  response = await client.execute_pipeline(
255
316
  pipeline_id="your_pipeline_id",
256
317
  user_input="Tell me about quantum computing",
@@ -320,6 +381,7 @@ from airia import AiriaClient
320
381
  from airia.types import AgentModelStreamFragmentMessage
321
382
 
322
383
  client = AiriaClient(api_key="your_api_key")
384
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
323
385
 
324
386
  response = client.execute_pipeline(
325
387
  pipeline_id="your_pipeline_id",
@@ -343,6 +405,7 @@ You can retrieve detailed configuration information about a pipeline using the `
343
405
  from airia import AiriaClient
344
406
 
345
407
  client = AiriaClient(api_key="your_api_key")
408
+ # Or with bearer token: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
346
409
 
347
410
  # Get pipeline configuration
348
411
  config = client.get_pipeline_config(pipeline_id="your_pipeline_id")
@@ -351,10 +414,35 @@ config = client.get_pipeline_config(pipeline_id="your_pipeline_id")
351
414
  print(f"Pipeline Name: {config.agent.name}")
352
415
  ```
353
416
 
417
+ ## Authentication Methods
418
+
419
+ Airia supports two authentication methods:
420
+
421
+ ### API Keys
422
+ - Can be passed as a parameter or via `AIRIA_API_KEY` environment variable
423
+ - Support gateway functionality (OpenAI and Anthropic gateways)
424
+ - Suitable for long-term, persistent authentication
425
+
426
+ ### Bearer Tokens
427
+ - Must be provided explicitly (no environment variable fallback)
428
+ - **Important**: Bearer tokens cannot be used with gateway functionality
429
+ - Suitable for ephemeral, short-lived authentication scenarios
430
+ - Ideal for temporary access or programmatic token generation
431
+
432
+ ```python
433
+ # ✅ API key with gateway support
434
+ client = AiriaClient.with_openai_gateway(api_key="your_api_key")
435
+
436
+ # ❌ Bearer token with gateway - NOT SUPPORTED
437
+ # client = AiriaClient.with_openai_gateway(bearer_token="token") # This won't work
438
+ ```
439
+
354
440
  ## Gateway Usage
355
441
 
356
442
  Airia provides gateway capabilities for popular AI services like OpenAI and Anthropic, allowing you to use your Airia API key with these services.
357
443
 
444
+ > **Note**: Gateway functionality requires API key authentication. Bearer tokens are not supported for gateway usage.
445
+
358
446
  ### OpenAI Gateway
359
447
 
360
448
  ```python
@@ -482,7 +570,11 @@ console_logger = configure_logging(
482
570
  The SDK uses custom exceptions to provide clear error messages:
483
571
 
484
572
  ```python
485
- from airia import AiriaAPIError
573
+ from airia import AiriaAPIError, AiriaClient
574
+
575
+ # Works with both API keys and bearer tokens
576
+ client = AiriaClient(api_key="your_api_key")
577
+ # Or: client = AiriaClient.with_bearer_token(bearer_token="your_bearer_token")
486
578
 
487
579
  try:
488
580
  response = client.execute_pipeline(
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  airia/__init__.py
5
+ airia/constants.py
5
6
  airia/exceptions.py
6
7
  airia/logs.py
7
8
  airia.egg-info/PKG-INFO
@@ -22,6 +23,7 @@ airia/types/api/get_projects.py
22
23
  airia/types/api/pipeline_execution.py
23
24
  airia/utils/sse_parser.py
24
25
  tests/test_anthropic_gateway.py
26
+ tests/test_bearer_token_auth.py
25
27
  tests/test_execute_pipeline.py
26
28
  tests/test_get_active_pipelines_ids.py
27
29
  tests/test_get_pipeline_config.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "airia"
7
- version = "0.1.9"
7
+ version = "0.1.10"
8
8
  description = "Python SDK for Airia API"
9
9
  license = { text = "MIT" }
10
10
  authors = [{ name = "Airia LLC", email = "support@airia.com" }]
@@ -0,0 +1,90 @@
1
+ import os
2
+ import pytest
3
+ import pytest_asyncio
4
+ from unittest.mock import patch, MagicMock
5
+
6
+ from airia import AiriaAsyncClient, AiriaClient
7
+
8
+
9
+ class TestBearerTokenAuthentication:
10
+ """Tests for bearer token authentication functionality."""
11
+
12
+ def test_sync_client_with_bearer_token(self):
13
+ """Test synchronous client initialization with bearer token."""
14
+ bearer_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test.token"
15
+ client = AiriaClient(bearer_token=bearer_token)
16
+
17
+ assert client.bearer_token == bearer_token
18
+ assert client.api_key is None
19
+
20
+ def test_sync_client_with_bearer_token_class_method(self):
21
+ """Test synchronous client initialization using with_bearer_token class method."""
22
+ bearer_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test.token"
23
+ client = AiriaClient.with_bearer_token(bearer_token)
24
+
25
+ assert client.bearer_token == bearer_token
26
+ assert client.api_key is None
27
+
28
+ def test_explicit_api_key_and_bearer_token_conflict(self):
29
+ """Test that providing both api_key and bearer_token explicitly raises an error."""
30
+ with pytest.raises(ValueError, match="Cannot provide both api_key and bearer_token"):
31
+ AiriaClient(api_key="test_key", bearer_token="test_token")
32
+
33
+ def test_no_auth_provided_raises_error(self):
34
+ """Test that providing no authentication raises an error."""
35
+ with patch.dict(os.environ, {}, clear=True):
36
+ with pytest.raises(ValueError, match="Authentication required"):
37
+ AiriaClient()
38
+
39
+ def test_bearer_token_auth_header_generation(self):
40
+ """Test that bearer token generates correct Authorization header."""
41
+ bearer_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test.token"
42
+ client = AiriaClient(bearer_token=bearer_token)
43
+
44
+ request_data = client._prepare_request("https://test.com")
45
+
46
+ assert "Authorization" in request_data.headers
47
+ assert request_data.headers["Authorization"] == f"Bearer {bearer_token}"
48
+ assert "X-API-KEY" not in request_data.headers
49
+
50
+ def test_api_key_auth_header_generation(self):
51
+ """Test that API key generates correct X-API-KEY header."""
52
+ api_key = "test_api_key"
53
+ client = AiriaClient(api_key=api_key)
54
+
55
+ request_data = client._prepare_request("https://test.com")
56
+
57
+ assert "X-API-KEY" in request_data.headers
58
+ assert request_data.headers["X-API-KEY"] == api_key
59
+ assert "Authorization" not in request_data.headers
60
+
61
+ def test_bearer_token_logging_redaction(self):
62
+ """Test that bearer token is redacted in logs."""
63
+ bearer_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test.token"
64
+ client = AiriaClient(bearer_token=bearer_token, log_requests=True)
65
+
66
+ # Mock the logger to capture log output
67
+ with patch.object(client, 'logger') as mock_logger:
68
+ request_data = client._prepare_request("https://test.com")
69
+
70
+ # Check that the log call was made and bearer token was redacted
71
+ mock_logger.info.assert_called_once()
72
+ log_call_args = mock_logger.info.call_args[0][0]
73
+ assert "[REDACTED]" in log_call_args
74
+ assert bearer_token not in log_call_args
75
+
76
+ def test_bearer_token_error_sanitization(self):
77
+ """Test that bearer token is sanitized in error messages."""
78
+ bearer_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.test.token"
79
+ client = AiriaClient(bearer_token=bearer_token)
80
+
81
+ # Test the sanitization logic directly
82
+ error_message = f"Invalid token: {bearer_token}"
83
+ sanitized_message = error_message
84
+ if client.bearer_token and client.bearer_token in sanitized_message:
85
+ sanitized_message = sanitized_message.replace(client.bearer_token, "[REDACTED]")
86
+
87
+ assert bearer_token not in sanitized_message
88
+ assert "[REDACTED]" in sanitized_message
89
+
90
+
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes