Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/azure/core/settings.py: 46%
133 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-07 06:33 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-07 06:33 +0000
1# --------------------------------------------------------------------------
2#
3# Copyright (c) Microsoft Corporation. All rights reserved.
4#
5# The MIT License (MIT)
6#
7# Permission is hereby granted, free of charge, to any person obtaining a copy
8# of this software and associated documentation files (the ""Software""), to deal
9# in the Software without restriction, including without limitation the rights
10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# copies of the Software, and to permit persons to whom the Software is
12# furnished to do so, subject to the following conditions:
13#
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# THE SOFTWARE.
24#
25# --------------------------------------------------------------------------
26"""Provide access to settings for globally used Azure configuration values.
27"""
28from __future__ import annotations
29from collections import namedtuple
30from enum import Enum
31import logging
32import os
33import sys
34from typing import Type, Optional, Callable, Union, Dict, Any, TypeVar, Tuple, Generic, Mapping, List
35from azure.core.tracing import AbstractSpan
37ValidInputType = TypeVar("ValidInputType")
38ValueType = TypeVar("ValueType")
41__all__ = ("settings", "Settings")
44# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions
45class _Unset(Enum):
46 token = 0
49_unset = _Unset.token
52def convert_bool(value: Union[str, bool]) -> bool:
53 """Convert a string to True or False
55 If a boolean is passed in, it is returned as-is. Otherwise the function
56 maps the following strings, ignoring case:
58 * "yes", "1", "on" -> True
59 " "no", "0", "off" -> False
61 :param value: the value to convert
62 :type value: str or bool
63 :returns: A boolean value matching the intent of the input
64 :rtype: bool
65 :raises ValueError: If conversion to bool fails
67 """
68 if isinstance(value, bool):
69 return value
70 val = value.lower()
71 if val in ["yes", "1", "on", "true", "True"]:
72 return True
73 if val in ["no", "0", "off", "false", "False"]:
74 return False
75 raise ValueError("Cannot convert {} to boolean value".format(value))
78_levels = {
79 "CRITICAL": logging.CRITICAL,
80 "ERROR": logging.ERROR,
81 "WARNING": logging.WARNING,
82 "INFO": logging.INFO,
83 "DEBUG": logging.DEBUG,
84}
87def convert_logging(value: Union[str, int]) -> int:
88 """Convert a string to a Python logging level
90 If a log level is passed in, it is returned as-is. Otherwise the function
91 understands the following strings, ignoring case:
93 * "critical"
94 * "error"
95 * "warning"
96 * "info"
97 * "debug"
99 :param value: the value to convert
100 :type value: str or int
101 :returns: A log level as an int. See the logging module for details.
102 :rtype: int
103 :raises ValueError: If conversion to log level fails
105 """
106 if isinstance(value, int):
107 # If it's an int, return it. We don't need to check if it's in _levels, as custom int levels are allowed.
108 # https://docs.python.org/3/library/logging.html#levels
109 return value
110 val = value.upper()
111 level = _levels.get(val)
112 if not level:
113 raise ValueError("Cannot convert {} to log level, valid values are: {}".format(value, ", ".join(_levels)))
114 return level
117def _get_opencensus_span() -> Optional[Type[AbstractSpan]]:
118 """Returns the OpenCensusSpan if the opencensus tracing plugin is installed else returns None.
120 :rtype: type[AbstractSpan] or None
121 :returns: OpenCensusSpan type or None
122 """
123 try:
124 from azure.core.tracing.ext.opencensus_span import ( # pylint:disable=redefined-outer-name
125 OpenCensusSpan,
126 )
128 return OpenCensusSpan
129 except ImportError:
130 return None
133def _get_opentelemetry_span() -> Optional[Type[AbstractSpan]]:
134 """Returns the OpenTelemetrySpan if the opentelemetry tracing plugin is installed else returns None.
136 :rtype: type[AbstractSpan] or None
137 :returns: OpenTelemetrySpan type or None
138 """
139 try:
140 from azure.core.tracing.ext.opentelemetry_span import ( # pylint:disable=redefined-outer-name
141 OpenTelemetrySpan,
142 )
144 return OpenTelemetrySpan
145 except ImportError:
146 return None
149def _get_opencensus_span_if_opencensus_is_imported() -> Optional[Type[AbstractSpan]]:
150 if "opencensus" not in sys.modules:
151 return None
152 return _get_opencensus_span()
155def _get_opentelemetry_span_if_opentelemetry_is_imported() -> Optional[Type[AbstractSpan]]:
156 if "opentelemetry" not in sys.modules:
157 return None
158 return _get_opentelemetry_span()
161_tracing_implementation_dict: Dict[str, Callable[[], Optional[Type[AbstractSpan]]]] = {
162 "opencensus": _get_opencensus_span,
163 "opentelemetry": _get_opentelemetry_span,
164}
167def convert_tracing_impl(value: Optional[Union[str, Type[AbstractSpan]]]) -> Optional[Type[AbstractSpan]]:
168 """Convert a string to AbstractSpan
170 If a AbstractSpan is passed in, it is returned as-is. Otherwise the function
171 understands the following strings, ignoring case:
173 * "opencensus"
174 * "opentelemetry"
176 :param value: the value to convert
177 :type value: string
178 :returns: AbstractSpan
179 :raises ValueError: If conversion to AbstractSpan fails
181 """
182 if value is None:
183 return (
184 _get_opencensus_span_if_opencensus_is_imported() or _get_opentelemetry_span_if_opentelemetry_is_imported()
185 )
187 if not isinstance(value, str):
188 return value
190 value = value.lower()
191 get_wrapper_class = _tracing_implementation_dict.get(value, lambda: _unset)
192 wrapper_class: Optional[Union[_Unset, Type[AbstractSpan]]] = get_wrapper_class()
193 if wrapper_class is _unset:
194 raise ValueError(
195 "Cannot convert {} to AbstractSpan, valid values are: {}".format(
196 value, ", ".join(_tracing_implementation_dict)
197 )
198 )
199 return wrapper_class
202class PrioritizedSetting(Generic[ValidInputType, ValueType]):
203 """Return a value for a global setting according to configuration precedence.
205 The following methods are searched in order for the setting:
207 4. immediate values
208 3. previously user-set value
209 2. environment variable
210 1. system setting
211 0. implicit default
213 If a value cannot be determined, a RuntimeError is raised.
215 The ``env_var`` argument specifies the name of an environment to check for
216 setting values, e.g. ``"AZURE_LOG_LEVEL"``.
217 If a ``convert`` function is provided, the result will be converted before being used.
219 The optional ``system_hook`` can be used to specify a function that will
220 attempt to look up a value for the setting from system-wide configurations.
221 If a ``convert`` function is provided, the hook result will be converted before being used.
223 The optional ``default`` argument specified an implicit default value for
224 the setting that is returned if no other methods provide a value. If a ``convert`` function is provided,
225 ``default`` will be converted before being used.
227 A ``convert`` argument may be provided to convert values before they are
228 returned. For instance to concert log levels in environment variables
229 to ``logging`` module values. If a ``convert`` function is provided, it must support
230 str as valid input type.
232 :param str name: the name of the setting
233 :param str env_var: the name of an environment variable to check for the setting
234 :param callable system_hook: a function that will attempt to look up a value for the setting
235 :param default: an implicit default value for the setting
236 :type default: any
237 :param callable convert: a function to convert values before they are returned
238 """
240 def __init__(
241 self,
242 name: str,
243 env_var: Optional[str] = None,
244 system_hook: Optional[Callable[[], ValidInputType]] = None,
245 default: Union[ValidInputType, _Unset] = _unset,
246 convert: Optional[Callable[[Union[ValidInputType, str]], ValueType]] = None,
247 ):
249 self._name = name
250 self._env_var = env_var
251 self._system_hook = system_hook
252 self._default = default
253 noop_convert: Callable[[Any], Any] = lambda x: x
254 self._convert: Callable[[Union[ValidInputType, str]], ValueType] = convert if convert else noop_convert
255 self._user_value: Union[ValidInputType, _Unset] = _unset
257 def __repr__(self) -> str:
258 return "PrioritizedSetting(%r)" % self._name
260 def __call__(self, value: Optional[ValidInputType] = None) -> ValueType:
261 """Return the setting value according to the standard precedence.
263 :param value: value
264 :type value: str or int or float or None
265 :returns: the value of the setting
266 :rtype: str or int or float
267 :raises: RuntimeError if no value can be determined
268 """
270 # 4. immediate values
271 if value is not None:
272 return self._convert(value)
274 # 3. previously user-set value
275 if not isinstance(self._user_value, _Unset):
276 return self._convert(self._user_value)
278 # 2. environment variable
279 if self._env_var and self._env_var in os.environ:
280 return self._convert(os.environ[self._env_var])
282 # 1. system setting
283 if self._system_hook:
284 return self._convert(self._system_hook())
286 # 0. implicit default
287 if not isinstance(self._default, _Unset):
288 return self._convert(self._default)
290 raise RuntimeError("No configured value found for setting %r" % self._name)
292 def __get__(self, instance: Any, owner: Optional[Any] = None) -> PrioritizedSetting[ValidInputType, ValueType]:
293 return self
295 def __set__(self, instance: Any, value: ValidInputType) -> None:
296 self.set_value(value)
298 def set_value(self, value: ValidInputType) -> None:
299 """Specify a value for this setting programmatically.
301 A value set this way takes precedence over all other methods except
302 immediate values.
304 :param value: a user-set value for this setting
305 :type value: str or int or float
306 """
307 self._user_value = value
309 def unset_value(self) -> None:
310 """Unset the previous user value such that the priority is reset."""
311 self._user_value = _unset
313 @property
314 def env_var(self) -> Optional[str]:
315 return self._env_var
317 @property
318 def default(self) -> Union[ValidInputType, _Unset]:
319 return self._default
322class Settings:
323 """Settings for globally used Azure configuration values.
325 You probably don't want to create an instance of this class, but call the singleton instance:
327 .. code-block:: python
329 from azure.core.settings import settings
330 settings.log_level = log_level = logging.DEBUG
332 The following methods are searched in order for a setting:
334 4. immediate values
335 3. previously user-set value
336 2. environment variable
337 1. system setting
338 0. implicit default
340 An implicit default is (optionally) defined by the setting attribute itself.
342 A system setting value can be obtained from registries or other OS configuration
343 for settings that support that method.
345 An environment variable value is obtained from ``os.environ``
347 User-set values many be specified by assigning to the attribute:
349 .. code-block:: python
351 settings.log_level = log_level = logging.DEBUG
353 Immediate values are (optionally) provided when the setting is retrieved:
355 .. code-block:: python
357 settings.log_level(logging.DEBUG())
359 Immediate values are most often useful to provide from optional arguments
360 to client functions. If the argument value is not None, it will be returned
361 as-is. Otherwise, the setting searches other methods according to the
362 precedence rules.
364 Immutable configuration snapshots can be created with the following methods:
366 * settings.defaults returns the base defaultsvalues , ignoring any environment or system
367 or user settings
369 * settings.current returns the current computation of settings including prioritization
370 of configuration sources, unless defaults_only is set to True (in which case the result
371 is identical to settings.defaults)
373 * settings.config can be called with specific values to override what settings.current
374 would provide
376 .. code-block:: python
378 # return current settings with log level overridden
379 settings.config(log_level=logging.DEBUG)
381 :cvar log_level: a log level to use across all Azure client SDKs (AZURE_LOG_LEVEL)
382 :type log_level: PrioritizedSetting
383 :cvar tracing_enabled: Whether tracing should be enabled across Azure SDKs (AZURE_TRACING_ENABLED)
384 :type tracing_enabled: PrioritizedSetting
385 :cvar tracing_implementation: The tracing implementation to use (AZURE_SDK_TRACING_IMPLEMENTATION)
386 :type tracing_implementation: PrioritizedSetting
388 :Example:
390 >>> import logging
391 >>> from azure.core.settings import settings
392 >>> settings.log_level = logging.DEBUG
393 >>> settings.log_level()
394 10
396 >>> settings.log_level(logging.WARN)
397 30
399 """
401 def __init__(self) -> None:
402 self._defaults_only: bool = False
404 @property
405 def defaults_only(self) -> bool:
406 """Whether to ignore environment and system settings and return only base default values.
408 :rtype: bool
409 :returns: Whether to ignore environment and system settings and return only base default values.
410 """
411 return self._defaults_only
413 @defaults_only.setter
414 def defaults_only(self, value: bool) -> None:
415 self._defaults_only = value
417 @property
418 def defaults(self) -> Tuple[Any, ...]:
419 """Return implicit default values for all settings, ignoring environment and system.
421 :rtype: namedtuple
422 :returns: The implicit default values for all settings
423 """
424 props = {k: v.default for (k, v) in self.__class__.__dict__.items() if isinstance(v, PrioritizedSetting)}
425 return self._config(props)
427 @property
428 def current(self) -> Tuple[Any, ...]:
429 """Return the current values for all settings.
431 :rtype: namedtuple
432 :returns: The current values for all settings
433 """
434 if self.defaults_only:
435 return self.defaults
436 return self.config()
438 def config(self, **kwargs: Any) -> Tuple[Any, ...]:
439 """Return the currently computed settings, with values overridden by parameter values.
441 :rtype: namedtuple
442 :returns: The current values for all settings, with values overridden by parameter values
444 Examples:
446 .. code-block:: python
448 # return current settings with log level overridden
449 settings.config(log_level=logging.DEBUG)
451 """
452 props = {k: v() for (k, v) in self.__class__.__dict__.items() if isinstance(v, PrioritizedSetting)}
453 props.update(kwargs)
454 return self._config(props)
456 def _config(self, props: Mapping[str, Any]) -> Tuple[Any, ...]:
457 keys: List[str] = list(props.keys())
458 # https://github.com/python/mypy/issues/4414
459 Config = namedtuple("Config", keys) # type: ignore
460 return Config(**props)
462 log_level: PrioritizedSetting[Union[str, int], int] = PrioritizedSetting(
463 "log_level",
464 env_var="AZURE_LOG_LEVEL",
465 convert=convert_logging,
466 default=logging.INFO,
467 )
469 tracing_enabled: PrioritizedSetting[Union[str, bool], bool] = PrioritizedSetting(
470 "tracing_enabled",
471 env_var="AZURE_TRACING_ENABLED",
472 convert=convert_bool,
473 default=False,
474 )
476 tracing_implementation: PrioritizedSetting[
477 Optional[Union[str, Type[AbstractSpan]]], Optional[Type[AbstractSpan]]
478 ] = PrioritizedSetting(
479 "tracing_implementation",
480 env_var="AZURE_SDK_TRACING_IMPLEMENTATION",
481 convert=convert_tracing_impl,
482 default=None,
483 )
486settings: Settings = Settings()
487"""The settings unique instance.
489:type settings: Settings
490"""