arize-phoenix 5.1.1__py3-none-any.whl → 5.1.3__py3-none-any.whl

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 arize-phoenix might be problematic. Click here for more details.

phoenix/otel/otel.py DELETED
@@ -1,451 +0,0 @@
1
- import inspect
2
- import os
3
- import sys
4
- import warnings
5
- from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast
6
- from urllib.parse import ParseResult, urlparse
7
-
8
- from openinference.semconv.resource import ResourceAttributes as _ResourceAttributes
9
- from opentelemetry import trace as trace_api
10
- from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
11
- OTLPSpanExporter as _GRPCSpanExporter,
12
- )
13
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
14
- OTLPSpanExporter as _HTTPSpanExporter,
15
- )
16
- from opentelemetry.sdk.resources import Resource
17
- from opentelemetry.sdk.trace import SpanProcessor
18
- from opentelemetry.sdk.trace import TracerProvider as _TracerProvider
19
- from opentelemetry.sdk.trace.export import BatchSpanProcessor as _BatchSpanProcessor
20
- from opentelemetry.sdk.trace.export import SimpleSpanProcessor as _SimpleSpanProcessor
21
- from opentelemetry.sdk.trace.export import SpanExporter
22
-
23
- from .settings import (
24
- get_env_client_headers,
25
- get_env_collector_endpoint,
26
- get_env_phoenix_auth_header,
27
- get_env_project_name,
28
- )
29
-
30
- PROJECT_NAME = _ResourceAttributes.PROJECT_NAME
31
-
32
- _DEFAULT_GRPC_PORT = 4317
33
-
34
-
35
- def register(
36
- *,
37
- endpoint: Optional[str] = None,
38
- project_name: Optional[str] = None,
39
- batch: bool = False,
40
- set_global_tracer_provider: bool = True,
41
- headers: Optional[Dict[str, str]] = None,
42
- verbose: bool = True,
43
- ) -> _TracerProvider:
44
- """
45
- Creates an OpenTelemetry TracerProvider for enabling OpenInference tracing.
46
-
47
- For futher configuration, the `phoenix.otel` module provides drop-in replacements for
48
- OpenTelemetry TracerProvider, SimpleSpanProcessor, BatchSpanProcessor, HTTPSpanExporter, and
49
- GRPCSpanExporter objects with Phoenix-aware defaults. Documentation on how to configure tracing
50
- can be found at https://opentelemetry.io/docs/specs/otel/trace/sdk/.
51
-
52
- Args:
53
- endpoint (str, optional): The collector endpoint to which spans will be exported. If not
54
- provided, the `PHOENIX_COLLECTOR_ENDPOINT` environment variable will be used. The
55
- export protocol will be inferred from the endpoint.
56
- project_name (str, optional): The name of the project to which spans will be associated. If
57
- not provided, the `PHOENIX_PROJECT_NAME` environment variable will be used.
58
- batch (bool): If True, spans will be processed using a BatchSpanprocessor. If False, spans
59
- will be processed one at a time using a SimpleSpanProcessor.
60
- set_global_tracer_provider (bool): If False, the TracerProvider will not be set as the
61
- global tracer provider. Defaults to True.
62
- headers (dict, optional): Optional headers to include in the request to the collector.
63
- If not provided, the `PHOENIX_CLIENT_HEADERS` environment variable will be used.
64
- verbose (bool): If True, configuration details will be printed to stdout.
65
- """
66
-
67
- project_name = project_name or get_env_project_name()
68
- resource = Resource.create({PROJECT_NAME: project_name})
69
- tracer_provider = TracerProvider(resource=resource, verbose=False)
70
- span_processor: SpanProcessor
71
- if batch:
72
- span_processor = BatchSpanProcessor(endpoint=endpoint, headers=headers)
73
- else:
74
- span_processor = SimpleSpanProcessor(endpoint=endpoint, headers=headers)
75
- tracer_provider.add_span_processor(span_processor)
76
- tracer_provider._default_processor = True
77
-
78
- if set_global_tracer_provider:
79
- trace_api.set_tracer_provider(tracer_provider)
80
- global_provider_msg = (
81
- "| \n"
82
- "| `register` has set this TracerProvider as the global OpenTelemetry default.\n"
83
- "| To disable this behavior, call `register` with "
84
- "`set_global_tracer_provider=False`.\n"
85
- )
86
- else:
87
- global_provider_msg = ""
88
-
89
- details = tracer_provider._tracing_details()
90
- if verbose:
91
- print(f"{details}" f"{global_provider_msg}")
92
- return tracer_provider
93
-
94
-
95
- class TracerProvider(_TracerProvider):
96
- """
97
- An extension of `opentelemetry.sdk.trace.TracerProvider` with Phoenix-aware defaults.
98
-
99
- Extended keyword arguments are documented in the `Args` section. For further documentation, see
100
- the OpenTelemetry documentation at https://opentelemetry.io/docs/specs/otel/trace/sdk/.
101
-
102
- Args:
103
- endpoint (str, optional): The collector endpoint to which spans will be exported. If
104
- specified, a default SpanProcessor will be created and added to this TracerProvider.
105
- If not provided, the `PHOENIX_COLLECTOR_ENDPOINT` environment variable will be
106
- used to infer which collector endpoint to use, defaults to the gRPC endpoint. When
107
- specifying the endpoint, the transport method (HTTP or gRPC) will be inferred from the
108
- URL.
109
- verbose (bool): If True, configuration details will be printed to stdout.
110
- """
111
-
112
- def __init__(
113
- self, *args: Any, endpoint: Optional[str] = None, verbose: bool = True, **kwargs: Any
114
- ):
115
- sig = _get_class_signature(_TracerProvider)
116
- bound_args = sig.bind_partial(*args, **kwargs)
117
- bound_args.apply_defaults()
118
- if bound_args.arguments.get("resource") is None:
119
- bound_args.arguments["resource"] = Resource.create(
120
- {PROJECT_NAME: get_env_project_name()}
121
- )
122
- super().__init__(*bound_args.args, **bound_args.kwargs)
123
-
124
- parsed_url, endpoint = _normalized_endpoint(endpoint)
125
- self._default_processor = False
126
-
127
- if _maybe_http_endpoint(parsed_url):
128
- http_exporter: SpanExporter = HTTPSpanExporter(endpoint=endpoint)
129
- self.add_span_processor(SimpleSpanProcessor(span_exporter=http_exporter))
130
- self._default_processor = True
131
- elif _maybe_grpc_endpoint(parsed_url):
132
- grpc_exporter: SpanExporter = GRPCSpanExporter(endpoint=endpoint)
133
- self.add_span_processor(SimpleSpanProcessor(span_exporter=grpc_exporter))
134
- self._default_processor = True
135
- if verbose:
136
- print(self._tracing_details())
137
-
138
- def add_span_processor(self, *args: Any, **kwargs: Any) -> None:
139
- """
140
- Registers a new `SpanProcessor` for this `TracerProvider`.
141
-
142
- If this `TracerProvider` has a default processor, it will be removed.
143
- """
144
-
145
- if self._default_processor:
146
- self._active_span_processor.shutdown()
147
- self._active_span_processor._span_processors = tuple() # remove default processors
148
- self._default_processor = False
149
- return super().add_span_processor(*args, **kwargs)
150
-
151
- def _tracing_details(self) -> str:
152
- project = self.resource.attributes.get(PROJECT_NAME)
153
- processor_name: Optional[str] = None
154
- endpoint: Optional[str] = None
155
- transport: Optional[str] = None
156
- headers: Optional[Union[Dict[str, str], str]] = None
157
-
158
- if self._active_span_processor:
159
- if processors := self._active_span_processor._span_processors:
160
- if len(processors) == 1:
161
- span_processor = self._active_span_processor._span_processors[0]
162
- if exporter := getattr(span_processor, "span_exporter"):
163
- processor_name = span_processor.__class__.__name__
164
- endpoint = exporter._endpoint
165
- transport = _exporter_transport(exporter)
166
- headers = _printable_headers(exporter._headers)
167
- else:
168
- processor_name = "Multiple Span Processors"
169
- endpoint = "Multiple Span Exporters"
170
- transport = "Multiple Span Exporters"
171
- headers = "Multiple Span Exporters"
172
-
173
- if os.name == "nt":
174
- details_header = "OpenTelemetry Tracing Details"
175
- else:
176
- details_header = "🔭 OpenTelemetry Tracing Details 🔭"
177
-
178
- configuration_msg = (
179
- "| Using a default SpanProcessor. `add_span_processor` will overwrite this default.\n"
180
- )
181
-
182
- details_msg = (
183
- f"{details_header}\n"
184
- f"| Phoenix Project: {project}\n"
185
- f"| Span Processor: {processor_name}\n"
186
- f"| Collector Endpoint: {endpoint}\n"
187
- f"| Transport: {transport}\n"
188
- f"| Transport Headers: {headers}\n"
189
- "| \n"
190
- f"{configuration_msg if self._default_processor else ''}"
191
- )
192
- return details_msg
193
-
194
-
195
- class SimpleSpanProcessor(_SimpleSpanProcessor):
196
- """
197
- Simple SpanProcessor implementation.
198
-
199
- SimpleSpanProcessor is an implementation of `SpanProcessor` that passes ended spans directly to
200
- the configured `SpanExporter`.
201
-
202
- Args:
203
- span_exporter (SpanExporter, optional): The `SpanExporter` to which ended spans will be
204
- passed.
205
- endpoint (str, optional): The collector endpoint to which spans will be exported. If not
206
- provided, the `PHOENIX_COLLECTOR_ENDPOINT` environment variable will be used to
207
- infer which collector endpoint to use, defaults to the gRPC endpoint. When specifying
208
- the endpoint, the transport method (HTTP or gRPC) will be inferred from the URL.
209
- headers (dict, optional): Optional headers to include in the request to the collector.
210
- If not provided, the `PHOENIX_CLIENT_HEADERS` or `OTEL_EXPORTER_OTLP_HEADERS`
211
- environment variable will be used.
212
- """
213
-
214
- def __init__(
215
- self,
216
- span_exporter: Optional[SpanExporter] = None,
217
- endpoint: Optional[str] = None,
218
- headers: Optional[Dict[str, str]] = None,
219
- ):
220
- if span_exporter is None:
221
- parsed_url, endpoint = _normalized_endpoint(endpoint)
222
- if _maybe_http_endpoint(parsed_url):
223
- span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
224
- elif _maybe_grpc_endpoint(parsed_url):
225
- span_exporter = GRPCSpanExporter(endpoint=endpoint, headers=headers)
226
- else:
227
- warnings.warn("Could not infer collector endpoint protocol, defaulting to HTTP.")
228
- span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
229
- super().__init__(span_exporter)
230
-
231
-
232
- class BatchSpanProcessor(_BatchSpanProcessor):
233
- """
234
- Batch SpanProcessor implementation.
235
-
236
- `BatchSpanProcessor` is an implementation of `SpanProcessor` that batches ended spans and
237
- pushes them to the configured `SpanExporter`.
238
-
239
- `BatchSpanProcessor` is configurable with the following environment variables which correspond
240
- to constructor parameters:
241
-
242
- - :envvar:`OTEL_BSP_SCHEDULE_DELAY`
243
- - :envvar:`OTEL_BSP_MAX_QUEUE_SIZE`
244
- - :envvar:`OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
245
- - :envvar:`OTEL_BSP_EXPORT_TIMEOUT`
246
-
247
- Args:
248
- span_exporter (SpanExporter, optional): The `SpanExporter` to which ended spans will be
249
- passed.
250
- endpoint (str, optional): The collector endpoint to which spans will be exported. If not
251
- provided, the `PHOENIX_COLLECTOR_ENDPOINT` environment variable will be used to
252
- infer which collector endpoint to use, defaults to the gRPC endpoint. When specifying
253
- the endpoint, the transport method (HTTP or gRPC) will be inferred from the URL.
254
- headers (dict, optional): Optional headers to include in the request to the collector.
255
- If not provided, the `PHOENIX_CLIENT_HEADERS` or `OTEL_EXPORTER_OTLP_HEADERS`
256
- environment variable will be used.
257
- max_queue_size (int, optional): The maximum queue size.
258
- schedule_delay_millis (float, optional): The delay between two consecutive exports in
259
- milliseconds.
260
- max_export_batch_size (int, optional): The maximum batch size.
261
- export_timeout_millis (float, optional): The batch timeout in milliseconds.
262
- """
263
-
264
- def __init__(
265
- self,
266
- span_exporter: Optional[SpanExporter] = None,
267
- endpoint: Optional[str] = None,
268
- headers: Optional[Dict[str, str]] = None,
269
- ):
270
- if span_exporter is None:
271
- parsed_url, endpoint = _normalized_endpoint(endpoint)
272
- if _maybe_http_endpoint(parsed_url):
273
- span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
274
- elif _maybe_grpc_endpoint(parsed_url):
275
- span_exporter = GRPCSpanExporter(endpoint=endpoint, headers=headers)
276
- else:
277
- warnings.warn("Could not infer collector endpoint protocol, defaulting to HTTP.")
278
- span_exporter = HTTPSpanExporter(endpoint=endpoint, headers=headers)
279
- super().__init__(span_exporter)
280
-
281
-
282
- class HTTPSpanExporter(_HTTPSpanExporter):
283
- """
284
- OTLP span exporter using HTTP.
285
-
286
- For more information, see:
287
- - `opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter`
288
-
289
- Args:
290
- endpoint (str, optional): OpenTelemetry Collector receiver endpoint. If not provided, the
291
- `PHOENIX_COLLECTOR_ENDPOINT` environment variable will be used to infer which
292
- collector endpoint to use, defaults to the HTTP endpoint.
293
- headers: Headers to send when exporting. If not provided, the `PHOENIX_CLIENT_HEADERS`
294
- or `OTEL_EXPORTER_OTLP_HEADERS` environment variables will be used.
295
- """
296
-
297
- def __init__(self, *args: Any, **kwargs: Any):
298
- sig = _get_class_signature(_HTTPSpanExporter)
299
- bound_args = sig.bind_partial(*args, **kwargs)
300
- bound_args.apply_defaults()
301
-
302
- if not bound_args.arguments.get("headers"):
303
- env_headers = get_env_client_headers()
304
- auth_header = get_env_phoenix_auth_header()
305
- headers = {
306
- **(env_headers or dict()),
307
- **(auth_header or dict()),
308
- }
309
- bound_args.arguments["headers"] = headers if headers else None
310
- else:
311
- headers = dict()
312
- for header_field, value in bound_args.arguments["headers"].items():
313
- headers[header_field.lower()] = value
314
-
315
- # If the auth header is not in the headers, add it
316
- if "authorization" not in headers:
317
- auth_header = get_env_phoenix_auth_header()
318
- bound_args.arguments["headers"] = {
319
- **headers,
320
- **(auth_header or dict()),
321
- }
322
- else:
323
- bound_args.arguments["headers"] = headers
324
-
325
- if bound_args.arguments.get("endpoint") is None:
326
- _, endpoint = _normalized_endpoint(None, use_http=True)
327
- bound_args.arguments["endpoint"] = endpoint
328
- super().__init__(*bound_args.args, **bound_args.kwargs)
329
-
330
-
331
- class GRPCSpanExporter(_GRPCSpanExporter):
332
- """
333
- OTLP span exporter using gRPC.
334
-
335
- For more information, see:
336
- - `opentelemetry.exporter.otlp.proto.grpc.trace_exporter.OTLPSpanExporter`
337
-
338
- Args:
339
- endpoint (str, optional): OpenTelemetry Collector receiver endpoint. If not provided, the
340
- `PHOENIX_COLLECTOR_ENDPOINT` environment variable will be used to infer which
341
- collector endpoint to use, defaults to the gRPC endpoint.
342
- insecure: Connection type
343
- credentials: Credentials object for server authentication
344
- headers: Headers to send when exporting. If not provided, the `PHOENIX_CLIENT_HEADERS`
345
- or `OTEL_EXPORTER_OTLP_HEADERS` environment variables will be used.
346
- timeout: Backend request timeout in seconds
347
- compression: gRPC compression method to use
348
- """
349
-
350
- def __init__(self, *args: Any, **kwargs: Any):
351
- sig = _get_class_signature(_GRPCSpanExporter)
352
- bound_args = sig.bind_partial(*args, **kwargs)
353
- bound_args.apply_defaults()
354
-
355
- if not bound_args.arguments.get("headers"):
356
- env_headers = get_env_client_headers()
357
- auth_header = get_env_phoenix_auth_header()
358
- headers = {
359
- **(env_headers or dict()),
360
- **(auth_header or dict()),
361
- }
362
- bound_args.arguments["headers"] = headers if headers else None
363
- else:
364
- headers = dict()
365
- for header_field, value in bound_args.arguments["headers"].items():
366
- headers[header_field.lower()] = value
367
-
368
- # If the auth header is not in the headers, add it
369
- if "authorization" not in headers:
370
- auth_header = get_env_phoenix_auth_header()
371
- bound_args.arguments["headers"] = {
372
- **headers,
373
- **(auth_header or dict()),
374
- }
375
- else:
376
- bound_args.arguments["headers"] = headers
377
-
378
- if bound_args.arguments.get("endpoint") is None:
379
- _, endpoint = _normalized_endpoint(None)
380
- bound_args.arguments["endpoint"] = endpoint
381
- super().__init__(*bound_args.args, **bound_args.kwargs)
382
-
383
-
384
- def _maybe_http_endpoint(parsed_endpoint: ParseResult) -> bool:
385
- if parsed_endpoint.path == "/v1/traces":
386
- return True
387
- return False
388
-
389
-
390
- def _maybe_grpc_endpoint(parsed_endpoint: ParseResult) -> bool:
391
- if not parsed_endpoint.path and parsed_endpoint.port == 4317:
392
- return True
393
- return False
394
-
395
-
396
- def _exporter_transport(exporter: SpanExporter) -> str:
397
- if isinstance(exporter, _HTTPSpanExporter):
398
- return "HTTP"
399
- if isinstance(exporter, _GRPCSpanExporter):
400
- return "gRPC"
401
- else:
402
- return exporter.__class__.__name__
403
-
404
-
405
- def _printable_headers(headers: Union[List[Tuple[str, str]], Dict[str, str]]) -> Dict[str, str]:
406
- if isinstance(headers, dict):
407
- return {key: "****" for key, _ in headers.items()}
408
- return {key: "****" for key, _ in headers}
409
-
410
-
411
- def _construct_http_endpoint(parsed_endpoint: ParseResult) -> ParseResult:
412
- return parsed_endpoint._replace(path="/v1/traces")
413
-
414
-
415
- def _construct_grpc_endpoint(parsed_endpoint: ParseResult) -> ParseResult:
416
- return parsed_endpoint._replace(netloc=f"{parsed_endpoint.hostname}:{_DEFAULT_GRPC_PORT}")
417
-
418
-
419
- _KNOWN_PROVIDERS = {
420
- "app.phoenix.arize.com": _construct_http_endpoint,
421
- }
422
-
423
-
424
- def _normalized_endpoint(
425
- endpoint: Optional[str], use_http: bool = False
426
- ) -> Tuple[ParseResult, str]:
427
- if endpoint is None:
428
- base_endpoint = get_env_collector_endpoint() or "http://localhost:6006"
429
- parsed = urlparse(base_endpoint)
430
- if parsed.hostname in _KNOWN_PROVIDERS:
431
- parsed = _KNOWN_PROVIDERS[parsed.hostname](parsed)
432
- elif use_http:
433
- parsed = _construct_http_endpoint(parsed)
434
- else:
435
- parsed = _construct_grpc_endpoint(parsed)
436
- else:
437
- parsed = urlparse(endpoint)
438
- parsed = cast(ParseResult, parsed)
439
- return parsed, parsed.geturl()
440
-
441
-
442
- def _get_class_signature(fn: Type[Any]) -> inspect.Signature:
443
- if sys.version_info >= (3, 9):
444
- return inspect.signature(fn)
445
- elif sys.version_info >= (3, 8):
446
- init_signature = inspect.signature(fn.__init__)
447
- new_params = list(init_signature.parameters.values())[1:] # Skip 'self'
448
- new_sig = init_signature.replace(parameters=new_params)
449
- return new_sig
450
- else:
451
- raise RuntimeError("Unsupported Python version")
phoenix/otel/settings.py DELETED
@@ -1,91 +0,0 @@
1
- import logging
2
- import os
3
- import urllib
4
- from re import compile
5
- from typing import Dict, List, Optional
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
- # Environment variables specific to the subpackage
10
- ENV_PHOENIX_COLLECTOR_ENDPOINT = "PHOENIX_COLLECTOR_ENDPOINT"
11
- ENV_PHOENIX_PROJECT_NAME = "PHOENIX_PROJECT_NAME"
12
- ENV_PHOENIX_CLIENT_HEADERS = "PHOENIX_CLIENT_HEADERS"
13
- ENV_PHOENIX_API_KEY = "PHOENIX_API_KEY"
14
-
15
-
16
- def get_env_collector_endpoint() -> Optional[str]:
17
- return os.getenv(ENV_PHOENIX_COLLECTOR_ENDPOINT)
18
-
19
-
20
- def get_env_project_name() -> str:
21
- return os.getenv(ENV_PHOENIX_PROJECT_NAME, "default")
22
-
23
-
24
- def get_env_client_headers() -> Optional[Dict[str, str]]:
25
- if headers_str := os.getenv(ENV_PHOENIX_CLIENT_HEADERS):
26
- return parse_env_headers(headers_str)
27
- return None
28
-
29
-
30
- def get_env_phoenix_auth_header() -> Optional[Dict[str, str]]:
31
- api_key = os.environ.get(ENV_PHOENIX_API_KEY)
32
- if api_key:
33
- return dict(authorization=f"Bearer {api_key}")
34
- else:
35
- return None
36
-
37
-
38
- # Optional whitespace
39
- _OWS = r"[ \t]*"
40
- # A key contains printable US-ASCII characters except: SP and "(),/:;<=>?@[\]{}
41
- _KEY_FORMAT = r"[\x21\x23-\x27\x2a\x2b\x2d\x2e\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+"
42
- # A value contains a URL-encoded UTF-8 string. The encoded form can contain any
43
- # printable US-ASCII characters (0x20-0x7f) other than SP, DEL, and ",;/
44
- _VALUE_FORMAT = r"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
45
- # A key-value is key=value, with optional whitespace surrounding key and value
46
- _KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"
47
-
48
- _HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
49
- _DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")
50
-
51
-
52
- def parse_env_headers(s: str) -> Dict[str, str]:
53
- """
54
- Parse ``s``, which is a ``str`` instance containing HTTP headers encoded
55
- for use in ENV variables per the W3C Baggage HTTP header format at
56
- https://www.w3.org/TR/baggage/#baggage-http-header-format, except that
57
- additional semi-colon delimited metadata is not supported.
58
-
59
- If the headers are not urlencoded, we will log a warning and attempt to urldecode them.
60
- """
61
- headers: Dict[str, str] = {}
62
- headers_list: List[str] = _DELIMITER_PATTERN.split(s)
63
-
64
- for header in headers_list:
65
- if not header: # empty string
66
- continue
67
-
68
- match = _HEADER_PATTERN.fullmatch(header.strip())
69
- if not match:
70
- parts = header.split("=", 1)
71
- name, value = parts
72
- encoded_header = f"{urllib.parse.quote(name)}={urllib.parse.quote(value)}"
73
- match = _HEADER_PATTERN.fullmatch(encoded_header.strip())
74
- if not match:
75
- logger.warning(
76
- "Header format invalid! Header values in environment variables must be "
77
- "URL encoded: %s",
78
- f"{name}: ****",
79
- )
80
- continue
81
- logger.warning(
82
- "Header values in environment variables should be URL encoded, attempting to "
83
- "URL encode header: {name}: ****"
84
- )
85
-
86
- name, value = header.split("=", 1)
87
- name = urllib.parse.unquote(name).strip().lower()
88
- value = urllib.parse.unquote(value).strip()
89
- headers[name] = value
90
-
91
- return headers