feldera 0.149.0__tar.gz → 0.151.0__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.
Potentially problematic release.
This version of feldera might be problematic. Click here for more details.
- {feldera-0.149.0 → feldera-0.151.0}/PKG-INFO +2 -1
- {feldera-0.149.0 → feldera-0.151.0}/feldera/testutils.py +23 -0
- feldera-0.151.0/feldera/testutils_oidc.py +368 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera.egg-info/PKG-INFO +2 -1
- {feldera-0.149.0 → feldera-0.151.0}/feldera.egg-info/SOURCES.txt +1 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera.egg-info/requires.txt +1 -0
- {feldera-0.149.0 → feldera-0.151.0}/pyproject.toml +3 -2
- {feldera-0.149.0 → feldera-0.151.0}/README.md +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/__init__.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/_callback_runner.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/_helpers.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/enums.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/output_handler.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/pipeline.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/pipeline_builder.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/__init__.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/_helpers.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/_httprequests.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/config.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/errors.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/feldera_client.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/feldera_config.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/pipeline.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/sql_table.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/rest/sql_view.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/runtime_config.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/stats.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera/tests/test_datafusionize.py +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera.egg-info/dependency_links.txt +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/feldera.egg-info/top_level.txt +0 -0
- {feldera-0.149.0 → feldera-0.151.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: feldera
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.151.0
|
|
4
4
|
Summary: The feldera python client
|
|
5
5
|
Author-email: Feldera Team <dev@feldera.com>
|
|
6
6
|
License: MIT
|
|
@@ -20,6 +20,7 @@ Requires-Dist: typing-extensions
|
|
|
20
20
|
Requires-Dist: numpy>=2.2.4
|
|
21
21
|
Requires-Dist: pretty-errors
|
|
22
22
|
Requires-Dist: ruff>=0.6.9
|
|
23
|
+
Requires-Dist: PyJWT>=2.8.0
|
|
23
24
|
|
|
24
25
|
# Feldera Python SDK
|
|
25
26
|
|
|
@@ -15,6 +15,28 @@ from feldera.runtime_config import Resources, RuntimeConfig
|
|
|
15
15
|
from feldera.rest import FelderaClient
|
|
16
16
|
|
|
17
17
|
API_KEY = os.environ.get("FELDERA_API_KEY")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# OIDC authentication support
|
|
21
|
+
def _get_oidc_token():
|
|
22
|
+
"""Get OIDC token if environment is configured, otherwise return None"""
|
|
23
|
+
try:
|
|
24
|
+
from feldera.testutils_oidc import get_oidc_test_helper
|
|
25
|
+
|
|
26
|
+
oidc_helper = get_oidc_test_helper()
|
|
27
|
+
if oidc_helper is not None:
|
|
28
|
+
return oidc_helper.obtain_access_token()
|
|
29
|
+
except ImportError:
|
|
30
|
+
pass
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_effective_api_key():
|
|
35
|
+
"""Get effective API key - OIDC token takes precedence over static API key"""
|
|
36
|
+
oidc_token = _get_oidc_token()
|
|
37
|
+
return oidc_token if oidc_token else API_KEY
|
|
38
|
+
|
|
39
|
+
|
|
18
40
|
BASE_URL = (
|
|
19
41
|
os.environ.get("FELDERA_HOST")
|
|
20
42
|
or os.environ.get("FELDERA_BASE_URL")
|
|
@@ -44,6 +66,7 @@ class _LazyClient:
|
|
|
44
66
|
if self._client is None:
|
|
45
67
|
self._client = FelderaClient(
|
|
46
68
|
connection_timeout=10,
|
|
69
|
+
api_key=_get_effective_api_key(),
|
|
47
70
|
)
|
|
48
71
|
return self._client
|
|
49
72
|
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OIDC Authentication Test Helper
|
|
3
|
+
|
|
4
|
+
Utilities for testing OIDC authentication integration with remote providers.
|
|
5
|
+
Provides token generation, validation helpers, and test configuration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
import json
|
|
11
|
+
import requests
|
|
12
|
+
import jwt
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Optional, Dict, Any
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class OidcTestConfig:
|
|
20
|
+
"""Configuration for OIDC authentication tests using Resource Owner Password Flow"""
|
|
21
|
+
|
|
22
|
+
issuer: str
|
|
23
|
+
client_id: str
|
|
24
|
+
client_secret: str
|
|
25
|
+
username: str
|
|
26
|
+
password: str
|
|
27
|
+
scope: str = "openid profile email"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_environment(cls) -> Optional["OidcTestConfig"]:
|
|
31
|
+
"""Load OIDC test configuration from environment variables"""
|
|
32
|
+
issuer = os.getenv("OIDC_TEST_ISSUER")
|
|
33
|
+
client_id = os.getenv("OIDC_TEST_CLIENT_ID")
|
|
34
|
+
client_secret = os.getenv("OIDC_TEST_CLIENT_SECRET")
|
|
35
|
+
username = os.getenv("OIDC_TEST_USERNAME")
|
|
36
|
+
password = os.getenv("OIDC_TEST_PASSWORD")
|
|
37
|
+
|
|
38
|
+
# All fields are required
|
|
39
|
+
if not all([issuer, client_id, client_secret, username, password]):
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
return cls(
|
|
43
|
+
issuer=issuer,
|
|
44
|
+
client_id=client_id,
|
|
45
|
+
client_secret=client_secret,
|
|
46
|
+
username=username,
|
|
47
|
+
password=password,
|
|
48
|
+
scope=os.getenv("OIDC_TEST_SCOPE", "openid profile email"),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OidcTestHelper:
|
|
53
|
+
"""Helper class for OIDC authentication testing"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, config: OidcTestConfig):
|
|
56
|
+
self.config = config
|
|
57
|
+
self._discovery_doc = None
|
|
58
|
+
self._jwks = None
|
|
59
|
+
self._access_token = None
|
|
60
|
+
self._token_expires_at = 0
|
|
61
|
+
|
|
62
|
+
def get_discovery_document(self) -> Dict[str, Any]:
|
|
63
|
+
"""Fetch and cache the OIDC discovery document"""
|
|
64
|
+
if self._discovery_doc is None:
|
|
65
|
+
discovery_url = (
|
|
66
|
+
f"{self.config.issuer.rstrip('/')}/.well-known/openid-configuration"
|
|
67
|
+
)
|
|
68
|
+
response = requests.get(discovery_url, timeout=30)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
self._discovery_doc = response.json()
|
|
71
|
+
return self._discovery_doc
|
|
72
|
+
|
|
73
|
+
def get_jwks(self) -> Dict[str, Any]:
|
|
74
|
+
"""Fetch and cache the JSON Web Key Set"""
|
|
75
|
+
if self._jwks is None:
|
|
76
|
+
discovery_doc = self.get_discovery_document()
|
|
77
|
+
jwks_uri = discovery_doc["jwks_uri"]
|
|
78
|
+
response = requests.get(jwks_uri, timeout=30)
|
|
79
|
+
response.raise_for_status()
|
|
80
|
+
self._jwks = response.json()
|
|
81
|
+
return self._jwks
|
|
82
|
+
|
|
83
|
+
def get_token_endpoint(self) -> str:
|
|
84
|
+
"""Get the token endpoint URL from discovery document"""
|
|
85
|
+
discovery_doc = self.get_discovery_document()
|
|
86
|
+
token_endpoint = discovery_doc.get("token_endpoint")
|
|
87
|
+
if not token_endpoint:
|
|
88
|
+
raise ValueError("Token endpoint not found in OIDC discovery document")
|
|
89
|
+
return token_endpoint
|
|
90
|
+
|
|
91
|
+
def obtain_access_token(self, pytest_cache=None) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Obtain access token using environment variable set by pytest master node.
|
|
94
|
+
|
|
95
|
+
The actual token fetching is handled by pytest_configure hooks in conftest.py,
|
|
96
|
+
which guarantees only one auth request per test session across all workers.
|
|
97
|
+
|
|
98
|
+
If OIDC is configured but no token is available, this will fail fast.
|
|
99
|
+
"""
|
|
100
|
+
logger = logging.getLogger(__name__)
|
|
101
|
+
current_time = time.time()
|
|
102
|
+
|
|
103
|
+
# Check environment variable for cross-process token sharing
|
|
104
|
+
token_data = get_cached_token_from_env()
|
|
105
|
+
if token_data:
|
|
106
|
+
logger.info("Using environment variable cached access token")
|
|
107
|
+
# Cache in instance for future calls to avoid repeated parsing
|
|
108
|
+
self._access_token = token_data["access_token"]
|
|
109
|
+
self._token_expires_at = token_data["expires_at"]
|
|
110
|
+
return token_data["access_token"]
|
|
111
|
+
|
|
112
|
+
# Fallback: Check instance cache
|
|
113
|
+
if self._access_token and current_time < self._token_expires_at - 30:
|
|
114
|
+
logger.info("Using instance cached access token")
|
|
115
|
+
return self._access_token
|
|
116
|
+
|
|
117
|
+
# If OIDC is configured but no token is available, this is a critical failure
|
|
118
|
+
raise RuntimeError(
|
|
119
|
+
"OIDC authentication is configured but no valid token is available. "
|
|
120
|
+
"This indicates the oidc_token_fixture failed to retrieve a token. "
|
|
121
|
+
"Check OIDC configuration and ensure pytest hooks ran properly."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def decode_token_claims(self, token: str) -> Dict[str, Any]:
|
|
125
|
+
"""Decode JWT token claims without signature verification (for testing)"""
|
|
126
|
+
return jwt.decode(token, options={"verify_signature": False})
|
|
127
|
+
|
|
128
|
+
def is_token_expired(self, token: str) -> bool:
|
|
129
|
+
"""Check if a JWT token is expired"""
|
|
130
|
+
try:
|
|
131
|
+
claims = self.decode_token_claims(token)
|
|
132
|
+
exp = claims.get("exp")
|
|
133
|
+
if exp is None:
|
|
134
|
+
return False # No expiration claim
|
|
135
|
+
return time.time() > exp
|
|
136
|
+
except Exception:
|
|
137
|
+
return True # Invalid token is considered expired
|
|
138
|
+
|
|
139
|
+
def validate_token_structure(self, token: str) -> bool:
|
|
140
|
+
"""Validate that token has correct JWT structure"""
|
|
141
|
+
try:
|
|
142
|
+
# Just check if it can be decoded (ignoring signature)
|
|
143
|
+
self.decode_token_claims(token)
|
|
144
|
+
return True
|
|
145
|
+
except Exception:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
def create_authenticated_headers(self) -> Dict[str, str]:
|
|
149
|
+
"""Create HTTP headers with valid authentication token"""
|
|
150
|
+
token = self.obtain_access_token()
|
|
151
|
+
return {"Accept": "application/json", "Authorization": f"Bearer {token}"}
|
|
152
|
+
|
|
153
|
+
def get_malformed_test_tokens(self) -> Dict[str, str]:
|
|
154
|
+
"""Get various malformed tokens for negative testing"""
|
|
155
|
+
return {
|
|
156
|
+
"malformed_structure": "not.a.jwt",
|
|
157
|
+
"empty": "",
|
|
158
|
+
"malformed_header": "eyJhbGciOiJub25lIn0.eyJzdWIiOiJ0ZXN0In0.invalid",
|
|
159
|
+
"wrong_issuer": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3dyb25nLWlzc3Vlci5jb20iLCJhdWQiOiJ0ZXN0IiwiZXhwIjo5OTk5OTk5OTk5fQ.signature",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def skip_if_oidc_not_configured():
|
|
164
|
+
"""Decorator to skip tests if OIDC test environment is not configured"""
|
|
165
|
+
import pytest
|
|
166
|
+
|
|
167
|
+
config = OidcTestConfig.from_environment()
|
|
168
|
+
return pytest.mark.skipif(
|
|
169
|
+
config is None,
|
|
170
|
+
reason="OIDC test environment not configured. Set OIDC_TEST_ISSUER, OIDC_TEST_CLIENT_ID, OIDC_TEST_CLIENT_SECRET, OIDC_TEST_USERNAME, OIDC_TEST_PASSWORD",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Global test helper instance (lazy loaded)
|
|
175
|
+
_test_helper = None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_oidc_test_helper() -> Optional[OidcTestHelper]:
|
|
179
|
+
"""Get global OIDC test helper instance"""
|
|
180
|
+
global _test_helper
|
|
181
|
+
if _test_helper is None:
|
|
182
|
+
config = OidcTestConfig.from_environment()
|
|
183
|
+
if config:
|
|
184
|
+
_test_helper = OidcTestHelper(config)
|
|
185
|
+
return _test_helper
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def parse_cached_token(env_token: str) -> Optional[Dict[str, Any]]:
|
|
189
|
+
"""
|
|
190
|
+
Parse and validate a base64-encoded token from environment variable.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
env_token: Base64-encoded JSON token data from environment variable
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Parsed token data dict if valid, None if invalid or expired
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
import base64
|
|
200
|
+
|
|
201
|
+
token_json = base64.b64decode(env_token.encode()).decode()
|
|
202
|
+
token_data = json.loads(token_json)
|
|
203
|
+
return token_data
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logging.getLogger(__name__).warning(f"Failed to parse cached token: {e}")
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def is_token_valid(token_data: Dict[str, Any], buffer_seconds: int = 30) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Check if token data is valid and not expired.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
token_data: Dictionary containing token information
|
|
215
|
+
buffer_seconds: Safety buffer before expiration (default 30 seconds)
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if token is valid and not expired, False otherwise
|
|
219
|
+
"""
|
|
220
|
+
if not token_data:
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
access_token = token_data.get("access_token")
|
|
224
|
+
expires_at = token_data.get("expires_at", 0)
|
|
225
|
+
|
|
226
|
+
if not access_token:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
current_time = time.time()
|
|
230
|
+
return current_time < expires_at - buffer_seconds
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def encode_token_for_env(token_data: Dict[str, Any]) -> str:
|
|
234
|
+
"""
|
|
235
|
+
Encode token data as base64 for storage in environment variables.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
token_data: Dictionary containing token information
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Base64-encoded JSON string suitable for environment variable storage
|
|
242
|
+
"""
|
|
243
|
+
import base64
|
|
244
|
+
|
|
245
|
+
token_json = json.dumps(token_data)
|
|
246
|
+
return base64.b64encode(token_json.encode()).decode()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def get_cached_token_from_env(
|
|
250
|
+
env_var_name: str = "FELDERA_PYTEST_OIDC_TOKEN",
|
|
251
|
+
) -> Optional[Dict[str, Any]]:
|
|
252
|
+
"""
|
|
253
|
+
Retrieve and validate cached token from environment variable.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
env_var_name: Name of environment variable containing cached token
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Valid token data if available and not expired, None otherwise
|
|
260
|
+
"""
|
|
261
|
+
import os
|
|
262
|
+
|
|
263
|
+
env_token = os.getenv(env_var_name)
|
|
264
|
+
if not env_token:
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
token_data = parse_cached_token(env_token)
|
|
268
|
+
if token_data and is_token_valid(token_data):
|
|
269
|
+
return token_data
|
|
270
|
+
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def setup_token_cache() -> Optional[Dict[str, Any]]:
|
|
275
|
+
"""
|
|
276
|
+
Set up OIDC token cache in environment variable if not already present.
|
|
277
|
+
|
|
278
|
+
This function:
|
|
279
|
+
1. Checks if a valid token is already cached
|
|
280
|
+
2. If not, fetches a new token
|
|
281
|
+
3. Stores the token in environment variable for cross-process access
|
|
282
|
+
|
|
283
|
+
Used by both pytest hooks and demo runners to ensure consistent token caching.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Token data if successfully cached, None if OIDC not configured
|
|
287
|
+
"""
|
|
288
|
+
import os
|
|
289
|
+
|
|
290
|
+
# Check if token is already cached and still valid
|
|
291
|
+
cached_token = get_cached_token_from_env()
|
|
292
|
+
if cached_token:
|
|
293
|
+
print("🔐 AUTH: Using existing cached OIDC token")
|
|
294
|
+
return cached_token
|
|
295
|
+
|
|
296
|
+
# Fetch new token if needed
|
|
297
|
+
token_data = fetch_oidc_token()
|
|
298
|
+
if token_data:
|
|
299
|
+
# Store in environment variable for reuse by subsequent processes
|
|
300
|
+
token_b64 = encode_token_for_env(token_data)
|
|
301
|
+
os.environ["FELDERA_PYTEST_OIDC_TOKEN"] = token_b64
|
|
302
|
+
print("🔐 AUTH: Token cached in environment for cross-process access")
|
|
303
|
+
return token_data
|
|
304
|
+
else:
|
|
305
|
+
print("🔐 AUTH: No OIDC configuration found, using fallback authentication")
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def fetch_oidc_token() -> Optional[Dict[str, Any]]:
|
|
310
|
+
"""
|
|
311
|
+
Fetch OIDC token using Resource Owner Password Grant flow.
|
|
312
|
+
|
|
313
|
+
This function is used by both pytest hooks and demo runners to ensure
|
|
314
|
+
consistent token fetching behavior across the entire test infrastructure.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Dict containing access_token, expires_at, and cached_at if successful,
|
|
318
|
+
None if OIDC is not configured.
|
|
319
|
+
"""
|
|
320
|
+
oidc_helper = get_oidc_test_helper()
|
|
321
|
+
if oidc_helper is None:
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
print("🔐 AUTH: Fetching OIDC token")
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
token_endpoint = oidc_helper.get_token_endpoint()
|
|
328
|
+
data = {
|
|
329
|
+
"grant_type": "password",
|
|
330
|
+
"username": oidc_helper.config.username,
|
|
331
|
+
"password": oidc_helper.config.password,
|
|
332
|
+
"client_id": oidc_helper.config.client_id,
|
|
333
|
+
"client_secret": oidc_helper.config.client_secret,
|
|
334
|
+
"scope": oidc_helper.config.scope,
|
|
335
|
+
"audience": "feldera-api",
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
headers = {
|
|
339
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
340
|
+
"Accept": "application/json",
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
response = requests.post(token_endpoint, data=data, headers=headers, timeout=30)
|
|
344
|
+
|
|
345
|
+
if not response.ok:
|
|
346
|
+
print(f"🔐 AUTH: ❌ Token request FAILED: {response.status_code}")
|
|
347
|
+
raise Exception(
|
|
348
|
+
f"Token request failed: {response.status_code} - {response.text}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
token_response = response.json()
|
|
352
|
+
print("🔐 AUTH: ✅ Token request SUCCESS!")
|
|
353
|
+
|
|
354
|
+
access_token = token_response["access_token"]
|
|
355
|
+
expires_in = token_response.get("expires_in", 3600)
|
|
356
|
+
expires_at = time.time() + expires_in
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
"access_token": access_token,
|
|
360
|
+
"expires_at": expires_at,
|
|
361
|
+
"cached_at": time.time(),
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
except Exception as e:
|
|
365
|
+
print(f"🔐 AUTH: CRITICAL FAILURE - Failed to fetch OIDC token: {e}")
|
|
366
|
+
raise RuntimeError(
|
|
367
|
+
f"OIDC authentication is configured but token retrieval failed: {e}"
|
|
368
|
+
) from e
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: feldera
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.151.0
|
|
4
4
|
Summary: The feldera python client
|
|
5
5
|
Author-email: Feldera Team <dev@feldera.com>
|
|
6
6
|
License: MIT
|
|
@@ -20,6 +20,7 @@ Requires-Dist: typing-extensions
|
|
|
20
20
|
Requires-Dist: numpy>=2.2.4
|
|
21
21
|
Requires-Dist: pretty-errors
|
|
22
22
|
Requires-Dist: ruff>=0.6.9
|
|
23
|
+
Requires-Dist: PyJWT>=2.8.0
|
|
23
24
|
|
|
24
25
|
# Feldera Python SDK
|
|
25
26
|
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
name = "feldera"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
description = "The feldera python client"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.151.0"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
authors = [
|
|
@@ -28,6 +28,7 @@ dependencies = [
|
|
|
28
28
|
"numpy>=2.2.4",
|
|
29
29
|
"pretty-errors",
|
|
30
30
|
"ruff>=0.6.9",
|
|
31
|
+
"PyJWT>=2.8.0",
|
|
31
32
|
]
|
|
32
33
|
[project.urls]
|
|
33
34
|
Homepage = "https://www.feldera.com"
|
|
@@ -43,7 +44,7 @@ dev-dependencies = [
|
|
|
43
44
|
"pytest>=8.3.5",
|
|
44
45
|
"sphinx-rtd-theme==2.0.0",
|
|
45
46
|
"sphinx==7.3.7",
|
|
46
|
-
"simplejson==3.20.1"
|
|
47
|
+
"simplejson==3.20.1",
|
|
47
48
|
]
|
|
48
49
|
|
|
49
50
|
[tool.pytest.ini_options]
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|