Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/azure/core/settings.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

143 statements  

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 

33from typing import ( 

34 Type, 

35 Optional, 

36 Callable, 

37 Union, 

38 Dict, 

39 Any, 

40 TypeVar, 

41 Tuple, 

42 Generic, 

43 Mapping, 

44 List, 

45 TYPE_CHECKING, 

46) 

47from ._azure_clouds import AzureClouds 

48 

49if TYPE_CHECKING: 

50 from azure.core.tracing import AbstractSpan 

51 

52ValidInputType = TypeVar("ValidInputType") 

53ValueType = TypeVar("ValueType") 

54 

55 

56__all__ = ("settings", "Settings") 

57 

58 

59# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions 

60class _Unset(Enum): 

61 token = 0 

62 

63 

64_unset = _Unset.token 

65 

66 

67def convert_bool(value: Union[str, bool]) -> bool: 

68 """Convert a string to True or False 

69 

70 If a boolean is passed in, it is returned as-is. Otherwise the function 

71 maps the following strings, ignoring case: 

72 

73 * "yes", "1", "on" -> True 

74 " "no", "0", "off" -> False 

75 

76 :param value: the value to convert 

77 :type value: str or bool 

78 :returns: A boolean value matching the intent of the input 

79 :rtype: bool 

80 :raises ValueError: If conversion to bool fails 

81 

82 """ 

83 if isinstance(value, bool): 

84 return value 

85 val = value.lower() 

86 if val in ["yes", "1", "on", "true", "True"]: 

87 return True 

88 if val in ["no", "0", "off", "false", "False"]: 

89 return False 

90 raise ValueError("Cannot convert {} to boolean value".format(value)) 

91 

92 

93def convert_tracing_enabled(value: Optional[Union[str, bool]]) -> bool: 

94 """Convert tracing value to bool with regard to tracing implementation. 

95 

96 :param value: the value to convert 

97 :type value: str or bool or None 

98 :returns: A boolean value matching the intent of the input 

99 :rtype: bool 

100 :raises ValueError: If conversion to bool fails 

101 """ 

102 if value is None: 

103 # If tracing_enabled was not explicitly set to a boolean, determine tracing enablement 

104 # based on tracing_implementation being set. 

105 if settings.tracing_implementation(): 

106 return True 

107 return False 

108 return convert_bool(value) 

109 

110 

111_levels = { 

112 "CRITICAL": logging.CRITICAL, 

113 "ERROR": logging.ERROR, 

114 "WARNING": logging.WARNING, 

115 "INFO": logging.INFO, 

116 "DEBUG": logging.DEBUG, 

117} 

118 

119 

120def convert_logging(value: Union[str, int]) -> int: 

121 """Convert a string to a Python logging level 

122 

123 If a log level is passed in, it is returned as-is. Otherwise the function 

124 understands the following strings, ignoring case: 

125 

126 * "critical" 

127 * "error" 

128 * "warning" 

129 * "info" 

130 * "debug" 

131 

132 :param value: the value to convert 

133 :type value: str or int 

134 :returns: A log level as an int. See the logging module for details. 

135 :rtype: int 

136 :raises ValueError: If conversion to log level fails 

137 

138 """ 

139 if isinstance(value, int): 

140 # If it's an int, return it. We don't need to check if it's in _levels, as custom int levels are allowed. 

141 # https://docs.python.org/3/library/logging.html#levels 

142 return value 

143 val = value.upper() 

144 level = _levels.get(val) 

145 if not level: 

146 raise ValueError("Cannot convert {} to log level, valid values are: {}".format(value, ", ".join(_levels))) 

147 return level 

148 

149 

150def convert_azure_cloud(value: Union[str, AzureClouds]) -> AzureClouds: 

151 """Convert a string to an Azure Cloud 

152 

153 :param value: the value to convert 

154 :type value: string 

155 :returns: An AzureClouds enum value 

156 :rtype: AzureClouds 

157 :raises ValueError: If conversion to AzureClouds fails 

158 

159 """ 

160 if isinstance(value, AzureClouds): 

161 return value 

162 if isinstance(value, str): 

163 azure_clouds = {cloud.name: cloud for cloud in AzureClouds} 

164 if value in azure_clouds: 

165 return azure_clouds[value] 

166 raise ValueError( 

167 "Cannot convert {} to Azure Cloud, valid values are: {}".format(value, ", ".join(azure_clouds.keys())) 

168 ) 

169 raise ValueError("Cannot convert {} to Azure Cloud".format(value)) 

170 

171 

172def _get_opencensus_span() -> Optional[Type[AbstractSpan]]: 

173 """Returns the OpenCensusSpan if the opencensus tracing plugin is installed else returns None. 

174 

175 :rtype: type[AbstractSpan] or None 

176 :returns: OpenCensusSpan type or None 

177 """ 

178 try: 

179 from azure.core.tracing.ext.opencensus_span import ( 

180 OpenCensusSpan, 

181 ) 

182 

183 return OpenCensusSpan 

184 except ImportError: 

185 return None 

186 

187 

188def _get_opentelemetry_span() -> Optional[Type[AbstractSpan]]: 

189 """Returns the OpenTelemetrySpan if the opentelemetry tracing plugin is installed else returns None. 

190 

191 :rtype: type[AbstractSpan] or None 

192 :returns: OpenTelemetrySpan type or None 

193 """ 

194 try: 

195 from azure.core.tracing.ext.opentelemetry_span import ( 

196 OpenTelemetrySpan, 

197 ) 

198 

199 return OpenTelemetrySpan 

200 except ImportError: 

201 return None 

202 

203 

204_tracing_implementation_dict: Dict[str, Callable[[], Optional[Type[AbstractSpan]]]] = { 

205 "opencensus": _get_opencensus_span, 

206 "opentelemetry": _get_opentelemetry_span, 

207} 

208 

209 

210def convert_tracing_impl(value: Optional[Union[str, Type[AbstractSpan]]]) -> Optional[Type[AbstractSpan]]: 

211 """Convert a string to AbstractSpan 

212 

213 If a AbstractSpan is passed in, it is returned as-is. Otherwise the function 

214 understands the following strings, ignoring case: 

215 

216 * "opencensus" 

217 * "opentelemetry" 

218 

219 :param value: the value to convert 

220 :type value: string 

221 :returns: AbstractSpan 

222 :raises ValueError: If conversion to AbstractSpan fails 

223 

224 """ 

225 if value is None: 

226 return None 

227 

228 if not isinstance(value, str): 

229 return value 

230 

231 value = value.lower() 

232 get_wrapper_class = _tracing_implementation_dict.get(value, lambda: _unset) 

233 wrapper_class: Optional[Union[_Unset, Type[AbstractSpan]]] = get_wrapper_class() 

234 if wrapper_class is _unset: 

235 raise ValueError( 

236 "Cannot convert {} to AbstractSpan, valid values are: {}".format( 

237 value, ", ".join(_tracing_implementation_dict) 

238 ) 

239 ) 

240 return wrapper_class 

241 

242 

243class PrioritizedSetting(Generic[ValidInputType, ValueType]): 

244 """Return a value for a global setting according to configuration precedence. 

245 

246 The following methods are searched in order for the setting: 

247 

248 4. immediate values 

249 3. previously user-set value 

250 2. environment variable 

251 1. system setting 

252 0. implicit default 

253 

254 If a value cannot be determined, a RuntimeError is raised. 

255 

256 The ``env_var`` argument specifies the name of an environment to check for 

257 setting values, e.g. ``"AZURE_LOG_LEVEL"``. 

258 If a ``convert`` function is provided, the result will be converted before being used. 

259 

260 The optional ``system_hook`` can be used to specify a function that will 

261 attempt to look up a value for the setting from system-wide configurations. 

262 If a ``convert`` function is provided, the hook result will be converted before being used. 

263 

264 The optional ``default`` argument specified an implicit default value for 

265 the setting that is returned if no other methods provide a value. If a ``convert`` function is provided, 

266 ``default`` will be converted before being used. 

267 

268 A ``convert`` argument may be provided to convert values before they are 

269 returned. For instance to concert log levels in environment variables 

270 to ``logging`` module values. If a ``convert`` function is provided, it must support 

271 str as valid input type. 

272 

273 :param str name: the name of the setting 

274 :param str env_var: the name of an environment variable to check for the setting 

275 :param callable system_hook: a function that will attempt to look up a value for the setting 

276 :param default: an implicit default value for the setting 

277 :type default: any 

278 :param callable convert: a function to convert values before they are returned 

279 """ 

280 

281 def __init__( 

282 self, 

283 name: str, 

284 env_var: Optional[str] = None, 

285 system_hook: Optional[Callable[[], ValidInputType]] = None, 

286 default: Union[ValidInputType, _Unset] = _unset, 

287 convert: Optional[Callable[[Union[ValidInputType, str]], ValueType]] = None, 

288 ): 

289 

290 self._name = name 

291 self._env_var = env_var 

292 self._system_hook = system_hook 

293 self._default = default 

294 noop_convert: Callable[[Any], Any] = lambda x: x 

295 self._convert: Callable[[Union[ValidInputType, str]], ValueType] = convert if convert else noop_convert 

296 self._user_value: Union[ValidInputType, _Unset] = _unset 

297 

298 def __repr__(self) -> str: 

299 return "PrioritizedSetting(%r)" % self._name 

300 

301 def __call__(self, value: Optional[ValidInputType] = None) -> ValueType: 

302 """Return the setting value according to the standard precedence. 

303 

304 :param value: value 

305 :type value: str or int or float or None 

306 :returns: the value of the setting 

307 :rtype: str or int or float 

308 :raises RuntimeError: if no value can be determined 

309 """ 

310 

311 # 4. immediate values 

312 if value is not None: 

313 return self._convert(value) 

314 

315 # 3. previously user-set value 

316 if not isinstance(self._user_value, _Unset): 

317 return self._convert(self._user_value) 

318 

319 # 2. environment variable 

320 if self._env_var and self._env_var in os.environ: 

321 return self._convert(os.environ[self._env_var]) 

322 

323 # 1. system setting 

324 if self._system_hook: 

325 return self._convert(self._system_hook()) 

326 

327 # 0. implicit default 

328 if not isinstance(self._default, _Unset): 

329 return self._convert(self._default) 

330 

331 raise RuntimeError("No configured value found for setting %r" % self._name) 

332 

333 def __get__(self, instance: Any, owner: Optional[Any] = None) -> PrioritizedSetting[ValidInputType, ValueType]: 

334 return self 

335 

336 def __set__(self, instance: Any, value: ValidInputType) -> None: 

337 self.set_value(value) 

338 

339 def set_value(self, value: ValidInputType) -> None: 

340 """Specify a value for this setting programmatically. 

341 

342 A value set this way takes precedence over all other methods except 

343 immediate values. 

344 

345 :param value: a user-set value for this setting 

346 :type value: str or int or float 

347 """ 

348 self._user_value = value 

349 

350 def unset_value(self) -> None: 

351 """Unset the previous user value such that the priority is reset.""" 

352 self._user_value = _unset 

353 

354 @property 

355 def env_var(self) -> Optional[str]: 

356 return self._env_var 

357 

358 @property 

359 def default(self) -> Union[ValidInputType, _Unset]: 

360 return self._default 

361 

362 

363class Settings: 

364 """Settings for globally used Azure configuration values. 

365 

366 You probably don't want to create an instance of this class, but call the singleton instance: 

367 

368 .. code-block:: python 

369 

370 from azure.core.settings import settings 

371 settings.log_level = log_level = logging.DEBUG 

372 

373 The following methods are searched in order for a setting: 

374 

375 4. immediate values 

376 3. previously user-set value 

377 2. environment variable 

378 1. system setting 

379 0. implicit default 

380 

381 An implicit default is (optionally) defined by the setting attribute itself. 

382 

383 A system setting value can be obtained from registries or other OS configuration 

384 for settings that support that method. 

385 

386 An environment variable value is obtained from ``os.environ`` 

387 

388 User-set values many be specified by assigning to the attribute: 

389 

390 .. code-block:: python 

391 

392 settings.log_level = log_level = logging.DEBUG 

393 

394 Immediate values are (optionally) provided when the setting is retrieved: 

395 

396 .. code-block:: python 

397 

398 settings.log_level(logging.DEBUG()) 

399 

400 Immediate values are most often useful to provide from optional arguments 

401 to client functions. If the argument value is not None, it will be returned 

402 as-is. Otherwise, the setting searches other methods according to the 

403 precedence rules. 

404 

405 Immutable configuration snapshots can be created with the following methods: 

406 

407 * settings.defaults returns the base defaultsvalues , ignoring any environment or system 

408 or user settings 

409 

410 * settings.current returns the current computation of settings including prioritization 

411 of configuration sources, unless defaults_only is set to True (in which case the result 

412 is identical to settings.defaults) 

413 

414 * settings.config can be called with specific values to override what settings.current 

415 would provide 

416 

417 .. code-block:: python 

418 

419 # return current settings with log level overridden 

420 settings.config(log_level=logging.DEBUG) 

421 

422 :cvar log_level: a log level to use across all Azure client SDKs (AZURE_LOG_LEVEL) 

423 :type log_level: PrioritizedSetting 

424 :cvar tracing_enabled: Whether tracing should be enabled across Azure SDKs (AZURE_TRACING_ENABLED) 

425 :type tracing_enabled: PrioritizedSetting 

426 :cvar tracing_implementation: The tracing implementation to use (AZURE_SDK_TRACING_IMPLEMENTATION) 

427 :type tracing_implementation: PrioritizedSetting 

428 

429 :Example: 

430 

431 >>> import logging 

432 >>> from azure.core.settings import settings 

433 >>> settings.log_level = logging.DEBUG 

434 >>> settings.log_level() 

435 10 

436 

437 >>> settings.log_level(logging.WARN) 

438 30 

439 

440 """ 

441 

442 def __init__(self) -> None: 

443 self._defaults_only: bool = False 

444 

445 @property 

446 def defaults_only(self) -> bool: 

447 """Whether to ignore environment and system settings and return only base default values. 

448 

449 :rtype: bool 

450 :returns: Whether to ignore environment and system settings and return only base default values. 

451 """ 

452 return self._defaults_only 

453 

454 @defaults_only.setter 

455 def defaults_only(self, value: bool) -> None: 

456 self._defaults_only = value 

457 

458 @property 

459 def defaults(self) -> Tuple[Any, ...]: 

460 """Return implicit default values for all settings, ignoring environment and system. 

461 

462 :rtype: namedtuple 

463 :returns: The implicit default values for all settings 

464 """ 

465 props = {k: v.default for (k, v) in self.__class__.__dict__.items() if isinstance(v, PrioritizedSetting)} 

466 return self._config(props) 

467 

468 @property 

469 def current(self) -> Tuple[Any, ...]: 

470 """Return the current values for all settings. 

471 

472 :rtype: namedtuple 

473 :returns: The current values for all settings 

474 """ 

475 if self.defaults_only: 

476 return self.defaults 

477 return self.config() 

478 

479 def config(self, **kwargs: Any) -> Tuple[Any, ...]: 

480 """Return the currently computed settings, with values overridden by parameter values. 

481 

482 :rtype: namedtuple 

483 :returns: The current values for all settings, with values overridden by parameter values 

484 

485 Examples: 

486 

487 .. code-block:: python 

488 

489 # return current settings with log level overridden 

490 settings.config(log_level=logging.DEBUG) 

491 

492 """ 

493 props = {k: v() for (k, v) in self.__class__.__dict__.items() if isinstance(v, PrioritizedSetting)} 

494 props.update(kwargs) 

495 return self._config(props) 

496 

497 def _config(self, props: Mapping[str, Any]) -> Tuple[Any, ...]: 

498 keys: List[str] = list(props.keys()) 

499 # https://github.com/python/mypy/issues/4414 

500 Config = namedtuple("Config", keys) # type: ignore 

501 return Config(**props) 

502 

503 log_level: PrioritizedSetting[Union[str, int], int] = PrioritizedSetting( 

504 "log_level", 

505 env_var="AZURE_LOG_LEVEL", 

506 convert=convert_logging, 

507 default=logging.INFO, 

508 ) 

509 

510 tracing_enabled: PrioritizedSetting[Optional[Union[str, bool]], bool] = PrioritizedSetting( 

511 "tracing_enabled", 

512 env_var="AZURE_TRACING_ENABLED", 

513 convert=convert_tracing_enabled, 

514 default=None, 

515 ) 

516 

517 tracing_implementation: PrioritizedSetting[ 

518 Optional[Union[str, Type[AbstractSpan]]], Optional[Type[AbstractSpan]] 

519 ] = PrioritizedSetting( 

520 "tracing_implementation", 

521 env_var="AZURE_SDK_TRACING_IMPLEMENTATION", 

522 convert=convert_tracing_impl, 

523 default=None, 

524 ) 

525 

526 azure_cloud: PrioritizedSetting[Union[str, AzureClouds], AzureClouds] = PrioritizedSetting( 

527 "azure_cloud", 

528 env_var="AZURE_CLOUD", 

529 convert=convert_azure_cloud, 

530 default=AzureClouds.AZURE_PUBLIC_CLOUD, 

531 ) 

532 

533 

534settings: Settings = Settings() 

535"""The settings unique instance. 

536 

537:type settings: Settings 

538"""