Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/botocore/useragent.py: 29%

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

191 statements  

1# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"). You 

4# may not use this file except in compliance with the License. A copy of 

5# the License is located at 

6# 

7# http://aws.amazon.com/apache2.0/ 

8# 

9# or in the "license" file accompanying this file. This file is 

10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 

11# ANY KIND, either express or implied. See the License for the specific 

12# language governing permissions and limitations under the License. 

13""" 

14NOTE: All classes and functions in this module are considered private and are 

15subject to abrupt breaking changes. Please do not use them directly. 

16 

17To modify the User-Agent header sent by botocore, use one of these 

18configuration options: 

19* The ``AWS_SDK_UA_APP_ID`` environment variable. 

20* The ``sdk_ua_app_id`` setting in the shared AWS config file. 

21* The ``user_agent_appid`` field in the :py:class:`botocore.config.Config`. 

22* The ``user_agent_extra`` field in the :py:class:`botocore.config.Config`. 

23 

24""" 

25 

26import logging 

27import os 

28import platform 

29from copy import copy 

30from string import ascii_letters, digits 

31from typing import NamedTuple 

32 

33from botocore import __version__ as botocore_version 

34from botocore.compat import HAS_CRT 

35from botocore.context import get_context 

36 

37logger = logging.getLogger(__name__) 

38 

39 

40_USERAGENT_ALLOWED_CHARACTERS = ascii_letters + digits + "!$%&'*+-.^_`|~," 

41_USERAGENT_ALLOWED_OS_NAMES = ( 

42 'windows', 

43 'linux', 

44 'macos', 

45 'android', 

46 'ios', 

47 'watchos', 

48 'tvos', 

49 'other', 

50) 

51_USERAGENT_PLATFORM_NAME_MAPPINGS = {'darwin': 'macos'} 

52# The name by which botocore is identified in the User-Agent header. While most 

53# AWS SDKs follow a naming pattern of "aws-sdk-*", botocore and boto3 continue 

54# using their existing values. Uses uppercase "B" with all other characters 

55# lowercase. 

56_USERAGENT_SDK_NAME = 'Botocore' 

57_USERAGENT_FEATURE_MAPPINGS = { 

58 'WAITER': 'B', 

59 'PAGINATOR': 'C', 

60 "RETRY_MODE_LEGACY": "D", 

61 "RETRY_MODE_STANDARD": "E", 

62 "RETRY_MODE_ADAPTIVE": "F", 

63 'S3_TRANSFER': 'G', 

64 'GZIP_REQUEST_COMPRESSION': 'L', 

65 'PROTOCOL_RPC_V2_CBOR': 'M', 

66 'ENDPOINT_OVERRIDE': 'N', 

67 'ACCOUNT_ID_MODE_PREFERRED': 'P', 

68 'ACCOUNT_ID_MODE_DISABLED': 'Q', 

69 'ACCOUNT_ID_MODE_REQUIRED': 'R', 

70 'SIGV4A_SIGNING': 'S', 

71 'RESOLVED_ACCOUNT_ID': 'T', 

72 'FLEXIBLE_CHECKSUMS_REQ_CRC32': 'U', 

73 'FLEXIBLE_CHECKSUMS_REQ_CRC32C': 'V', 

74 'FLEXIBLE_CHECKSUMS_REQ_CRC64': 'W', 

75 'FLEXIBLE_CHECKSUMS_REQ_SHA1': 'X', 

76 'FLEXIBLE_CHECKSUMS_REQ_SHA256': 'Y', 

77 'FLEXIBLE_CHECKSUMS_REQ_WHEN_SUPPORTED': 'Z', 

78 'FLEXIBLE_CHECKSUMS_REQ_WHEN_REQUIRED': 'a', 

79 'FLEXIBLE_CHECKSUMS_RES_WHEN_SUPPORTED': 'b', 

80 'FLEXIBLE_CHECKSUMS_RES_WHEN_REQUIRED': 'c', 

81 'CREDENTIALS_CODE': 'e', 

82 'CREDENTIALS_ENV_VARS': 'g', 

83 'CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN': 'h', 

84 'CREDENTIALS_STS_ASSUME_ROLE': 'i', 

85 'CREDENTIALS_STS_ASSUME_ROLE_WEB_ID': 'k', 

86 'CREDENTIALS_PROFILE': 'n', 

87 'CREDENTIALS_PROFILE_SOURCE_PROFILE': 'o', 

88 'CREDENTIALS_PROFILE_NAMED_PROVIDER': 'p', 

89 'CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN': 'q', 

90 'CREDENTIALS_PROFILE_SSO': 'r', 

91 'CREDENTIALS_SSO': 's', 

92 'CREDENTIALS_PROFILE_SSO_LEGACY': 't', 

93 'CREDENTIALS_SSO_LEGACY': 'u', 

94 'CREDENTIALS_PROFILE_PROCESS': 'v', 

95 'CREDENTIALS_PROCESS': 'w', 

96 'CREDENTIALS_BOTO2_CONFIG_FILE': 'x', 

97 'CREDENTIALS_HTTP': 'z', 

98 'CREDENTIALS_IMDS': '0', 

99 'BEARER_SERVICE_ENV_VARS': '3', 

100 'CLI_V1_TO_V2_MIGRATION_DEBUG_MODE': '-', 

101 'CREDENTIALS_PROFILE_LOGIN': 'AC', 

102 'CREDENTIALS_LOGIN': 'AD', 

103 'FLEXIBLE_CHECKSUMS_REQ_MD5': 'AE', 

104 'FLEXIBLE_CHECKSUMS_REQ_SHA512': 'AF', 

105 'FLEXIBLE_CHECKSUMS_REQ_XXHASH3': 'AG', 

106 'FLEXIBLE_CHECKSUMS_REQ_XXHASH64': 'AH', 

107 'FLEXIBLE_CHECKSUMS_REQ_XXHASH128': 'AI', 

108 'S3_REGION_REDIRECT': 'Ah', 

109} 

110 

111 

112def register_feature_id(feature_id): 

113 """Adds metric value to the current context object's ``features`` set. 

114 

115 :type feature_id: str 

116 :param feature_id: The name of the feature to register. Value must be a key 

117 in the ``_USERAGENT_FEATURE_MAPPINGS`` dict. 

118 """ 

119 ctx = get_context() 

120 if ctx is None: 

121 # Never register features outside the scope of a 

122 # ``botocore.context.start_as_current_context`` context manager. 

123 # Otherwise, the context variable won't be reset and features will 

124 # bleed into all subsequent requests. Return instead of raising an 

125 # exception since this function could be invoked in a public interface. 

126 return 

127 if val := _USERAGENT_FEATURE_MAPPINGS.get(feature_id): 

128 ctx.features.add(val) 

129 

130 

131def register_feature_ids(feature_ids): 

132 """Adds multiple feature IDs to the current context object's ``features`` set. 

133 

134 :type feature_ids: iterable of str 

135 :param feature_ids: An iterable of feature ID strings to register. Each 

136 value must be a key in the ``_USERAGENT_FEATURE_MAPPINGS`` dict. 

137 """ 

138 for feature_id in feature_ids: 

139 register_feature_id(feature_id) 

140 

141 

142def sanitize_user_agent_string_component(raw_str, allow_hash): 

143 """Replaces all not allowed characters in the string with a dash ("-"). 

144 

145 Allowed characters are ASCII alphanumerics and ``!$%&'*+-.^_`|~,``. If 

146 ``allow_hash`` is ``True``, "#"``" is also allowed. 

147 

148 :type raw_str: str 

149 :param raw_str: The input string to be sanitized. 

150 

151 :type allow_hash: bool 

152 :param allow_hash: Whether "#" is considered an allowed character. 

153 """ 

154 return ''.join( 

155 c 

156 if c in _USERAGENT_ALLOWED_CHARACTERS or (allow_hash and c == '#') 

157 else '-' 

158 for c in raw_str 

159 ) 

160 

161 

162class UserAgentComponentSizeConfig: 

163 """ 

164 Configures the max size of a built user agent string component and the 

165 delimiter used to truncate the string if the size is above the max. 

166 """ 

167 

168 def __init__(self, max_size_in_bytes: int, delimiter: str): 

169 self.max_size_in_bytes = max_size_in_bytes 

170 self.delimiter = delimiter 

171 self._validate_input() 

172 

173 def _validate_input(self): 

174 if self.max_size_in_bytes < 1: 

175 raise ValueError( 

176 f'Invalid `max_size_in_bytes`: {self.max_size_in_bytes}. ' 

177 'Value must be a positive integer.' 

178 ) 

179 

180 

181class UserAgentComponent(NamedTuple): 

182 """ 

183 Component of a Botocore User-Agent header string in the standard format. 

184 

185 Each component consists of a prefix, a name, a value, and a size_config. 

186 In the string representation these are combined in the format 

187 ``prefix/name#value``. 

188 

189 ``size_config`` configures the max size and truncation strategy for the 

190 built user agent string component. 

191 

192 This class is considered private and is subject to abrupt breaking changes. 

193 """ 

194 

195 prefix: str 

196 name: str 

197 value: str | None = None 

198 size_config: UserAgentComponentSizeConfig | None = None 

199 

200 def to_string(self): 

201 """Create string like 'prefix/name#value' from a UserAgentComponent.""" 

202 clean_prefix = sanitize_user_agent_string_component( 

203 self.prefix, allow_hash=True 

204 ) 

205 clean_name = sanitize_user_agent_string_component( 

206 self.name, allow_hash=False 

207 ) 

208 if self.value is None or self.value == '': 

209 clean_string = f'{clean_prefix}/{clean_name}' 

210 else: 

211 clean_value = sanitize_user_agent_string_component( 

212 self.value, allow_hash=True 

213 ) 

214 clean_string = f'{clean_prefix}/{clean_name}#{clean_value}' 

215 if self.size_config is not None: 

216 clean_string = self._truncate_string( 

217 clean_string, 

218 self.size_config.max_size_in_bytes, 

219 self.size_config.delimiter, 

220 ) 

221 return clean_string 

222 

223 def _truncate_string(self, string, max_size, delimiter): 

224 """ 

225 Pop ``delimiter``-separated values until encoded string is less than or 

226 equal to ``max_size``. 

227 """ 

228 orig = string 

229 while len(string.encode('utf-8')) > max_size: 

230 parts = string.split(delimiter) 

231 parts.pop() 

232 string = delimiter.join(parts) 

233 

234 if string == '': 

235 logger.debug( 

236 "User agent component `%s` could not be truncated to " 

237 "`%s` bytes with delimiter " 

238 "`%s` without losing all contents. " 

239 "Value will be omitted from user agent string.", 

240 orig, 

241 max_size, 

242 delimiter, 

243 ) 

244 return string 

245 

246 

247class RawStringUserAgentComponent: 

248 """ 

249 UserAgentComponent interface wrapper around ``str``. 

250 

251 Use for User-Agent header components that are not constructed from 

252 prefix+name+value but instead are provided as strings. No sanitization is 

253 performed. 

254 """ 

255 

256 def __init__(self, value): 

257 self._value = value 

258 

259 def to_string(self): 

260 return self._value 

261 

262 

263# This is not a public interface and is subject to abrupt breaking changes. 

264# Any usage is not advised or supported in external code bases. 

265try: 

266 from botocore.customizations.useragent import modify_components 

267except ImportError: 

268 # Default implementation that returns unmodified User-Agent components. 

269 def modify_components(components): 

270 return components 

271 

272 

273class UserAgentString: 

274 """ 

275 Generator for AWS SDK User-Agent header strings. 

276 

277 The User-Agent header format contains information from session, client, and 

278 request context. ``UserAgentString`` provides methods for collecting the 

279 information and ``to_string`` for assembling it into the standardized 

280 string format. 

281 

282 Example usage: 

283 

284 ua_session = UserAgentString.from_environment() 

285 ua_session.set_session_config(...) 

286 ua_client = ua_session.with_client_config(Config(...)) 

287 ua_string = ua_request.to_string() 

288 

289 For testing or when information from all sources is available at the same 

290 time, the methods can be chained: 

291 

292 ua_string = ( 

293 UserAgentString 

294 .from_environment() 

295 .set_session_config(...) 

296 .with_client_config(Config(...)) 

297 .to_string() 

298 ) 

299 

300 """ 

301 

302 def __init__( 

303 self, 

304 platform_name, 

305 platform_version, 

306 platform_machine, 

307 python_version, 

308 python_implementation, 

309 execution_env, 

310 crt_version=None, 

311 ): 

312 """ 

313 :type platform_name: str 

314 :param platform_name: Name of the operating system or equivalent 

315 platform name. Should be sourced from :py:meth:`platform.system`. 

316 :type platform_version: str 

317 :param platform_version: Version of the operating system or equivalent 

318 platform name. Should be sourced from :py:meth:`platform.version`. 

319 :type platform_machine: str 

320 :param platform_version: Processor architecture or machine type. For 

321 example "x86_64". Should be sourced from :py:meth:`platform.machine`. 

322 :type python_version: str 

323 :param python_version: Version of the python implementation as str. 

324 Should be sourced from :py:meth:`platform.python_version`. 

325 :type python_implementation: str 

326 :param python_implementation: Name of the python implementation. 

327 Should be sourced from :py:meth:`platform.python_implementation`. 

328 :type execution_env: str 

329 :param execution_env: The value of the AWS execution environment. 

330 Should be sourced from the ``AWS_EXECUTION_ENV` environment 

331 variable. 

332 :type crt_version: str 

333 :param crt_version: Version string of awscrt package, if installed. 

334 """ 

335 self._platform_name = platform_name 

336 self._platform_version = platform_version 

337 self._platform_machine = platform_machine 

338 self._python_version = python_version 

339 self._python_implementation = python_implementation 

340 self._execution_env = execution_env 

341 self._crt_version = crt_version 

342 

343 # Components that can be added with ``set_session_config()`` 

344 self._session_user_agent_name = None 

345 self._session_user_agent_version = None 

346 self._session_user_agent_extra = None 

347 

348 self._client_config = None 

349 

350 # Component that can be set with ``set_client_features()`` 

351 self._client_features = None 

352 

353 @classmethod 

354 def from_environment(cls): 

355 crt_version = None 

356 if HAS_CRT: 

357 crt_version = _get_crt_version() or 'Unknown' 

358 return cls( 

359 platform_name=platform.system(), 

360 platform_version=platform.release(), 

361 platform_machine=platform.machine(), 

362 python_version=platform.python_version(), 

363 python_implementation=platform.python_implementation(), 

364 execution_env=os.environ.get('AWS_EXECUTION_ENV'), 

365 crt_version=crt_version, 

366 ) 

367 

368 def set_session_config( 

369 self, 

370 session_user_agent_name, 

371 session_user_agent_version, 

372 session_user_agent_extra, 

373 ): 

374 """ 

375 Set the user agent configuration values that apply at session level. 

376 

377 :param user_agent_name: The user agent name configured in the 

378 :py:class:`botocore.session.Session` object. For backwards 

379 compatibility, this will always be at the beginning of the 

380 User-Agent string, together with ``user_agent_version``. 

381 :param user_agent_version: The user agent version configured in the 

382 :py:class:`botocore.session.Session` object. 

383 :param user_agent_extra: The user agent "extra" configured in the 

384 :py:class:`botocore.session.Session` object. 

385 """ 

386 self._session_user_agent_name = session_user_agent_name 

387 self._session_user_agent_version = session_user_agent_version 

388 self._session_user_agent_extra = session_user_agent_extra 

389 return self 

390 

391 def set_client_features(self, features): 

392 """ 

393 Persist client-specific features registered before or during client 

394 creation. 

395 

396 :type features: Set[str] 

397 :param features: A set of client-specific features. 

398 """ 

399 self._client_features = features 

400 

401 def with_client_config(self, client_config): 

402 """ 

403 Create a copy with all original values and client-specific values. 

404 

405 :type client_config: botocore.config.Config 

406 :param client_config: The client configuration object. 

407 """ 

408 cp = copy(self) 

409 cp._client_config = client_config 

410 return cp 

411 

412 def to_string(self): 

413 """ 

414 Build User-Agent header string from the object's properties. 

415 """ 

416 config_ua_override = None 

417 if self._client_config: 

418 if hasattr(self._client_config, '_supplied_user_agent'): 

419 config_ua_override = self._client_config._supplied_user_agent 

420 else: 

421 config_ua_override = self._client_config.user_agent 

422 

423 if config_ua_override is not None: 

424 return self._build_legacy_ua_string(config_ua_override) 

425 

426 components = [ 

427 *self._build_sdk_metadata(), 

428 RawStringUserAgentComponent('ua/2.1'), 

429 *self._build_os_metadata(), 

430 *self._build_architecture_metadata(), 

431 *self._build_language_metadata(), 

432 *self._build_execution_env_metadata(), 

433 *self._build_feature_metadata(), 

434 *self._build_config_metadata(), 

435 *self._build_app_id(), 

436 *self._build_extra(), 

437 ] 

438 

439 components = modify_components(components) 

440 

441 return ' '.join( 

442 [comp.to_string() for comp in components if comp.to_string()] 

443 ) 

444 

445 def _build_sdk_metadata(self): 

446 """ 

447 Build the SDK name and version component of the User-Agent header. 

448 

449 For backwards-compatibility both session-level and client-level config 

450 of custom tool names are honored. If this removes the Botocore 

451 information from the start of the string, Botocore's name and version 

452 are included as a separate field with "md" prefix. 

453 """ 

454 sdk_md = [] 

455 if ( 

456 self._session_user_agent_name 

457 and self._session_user_agent_version 

458 and ( 

459 self._session_user_agent_name != _USERAGENT_SDK_NAME 

460 or self._session_user_agent_version != botocore_version 

461 ) 

462 ): 

463 sdk_md.extend( 

464 [ 

465 UserAgentComponent( 

466 self._session_user_agent_name, 

467 self._session_user_agent_version, 

468 ), 

469 UserAgentComponent( 

470 'md', _USERAGENT_SDK_NAME, botocore_version 

471 ), 

472 ] 

473 ) 

474 else: 

475 sdk_md.append( 

476 UserAgentComponent(_USERAGENT_SDK_NAME, botocore_version) 

477 ) 

478 

479 if self._crt_version is not None: 

480 sdk_md.append( 

481 UserAgentComponent('md', 'awscrt', self._crt_version) 

482 ) 

483 

484 return sdk_md 

485 

486 def _build_os_metadata(self): 

487 """ 

488 Build the OS/platform components of the User-Agent header string. 

489 

490 For recognized platform names that match or map to an entry in the list 

491 of standardized OS names, a single component with prefix "os" is 

492 returned. Otherwise, one component "os/other" is returned and a second 

493 with prefix "md" and the raw platform name. 

494 

495 String representations of example return values: 

496 * ``os/macos#10.13.6`` 

497 * ``os/linux`` 

498 * ``os/other`` 

499 * ``os/other md/foobar#1.2.3`` 

500 """ 

501 if self._platform_name is None: 

502 return [UserAgentComponent('os', 'other')] 

503 

504 plt_name_lower = self._platform_name.lower() 

505 if plt_name_lower in _USERAGENT_ALLOWED_OS_NAMES: 

506 os_family = plt_name_lower 

507 elif plt_name_lower in _USERAGENT_PLATFORM_NAME_MAPPINGS: 

508 os_family = _USERAGENT_PLATFORM_NAME_MAPPINGS[plt_name_lower] 

509 else: 

510 os_family = None 

511 

512 if os_family is not None: 

513 return [ 

514 UserAgentComponent('os', os_family, self._platform_version) 

515 ] 

516 else: 

517 return [ 

518 UserAgentComponent('os', 'other'), 

519 UserAgentComponent( 

520 'md', self._platform_name, self._platform_version 

521 ), 

522 ] 

523 

524 def _build_architecture_metadata(self): 

525 """ 

526 Build architecture component of the User-Agent header string. 

527 

528 Returns the machine type with prefix "md" and name "arch", if one is 

529 available. Common values include "x86_64", "arm64", "i386". 

530 """ 

531 if self._platform_machine: 

532 return [ 

533 UserAgentComponent( 

534 'md', 'arch', self._platform_machine.lower() 

535 ) 

536 ] 

537 return [] 

538 

539 def _build_language_metadata(self): 

540 """ 

541 Build the language components of the User-Agent header string. 

542 

543 Returns the Python version in a component with prefix "lang" and name 

544 "python". The Python implementation (e.g. CPython, PyPy) is returned as 

545 separate metadata component with prefix "md" and name "pyimpl". 

546 

547 String representation of an example return value: 

548 ``lang/python#3.10.4 md/pyimpl#CPython`` 

549 """ 

550 lang_md = [ 

551 UserAgentComponent('lang', 'python', self._python_version), 

552 ] 

553 if self._python_implementation: 

554 lang_md.append( 

555 UserAgentComponent('md', 'pyimpl', self._python_implementation) 

556 ) 

557 return lang_md 

558 

559 def _build_execution_env_metadata(self): 

560 """ 

561 Build the execution environment component of the User-Agent header. 

562 

563 Returns a single component prefixed with "exec-env", usually sourced 

564 from the environment variable AWS_EXECUTION_ENV. 

565 """ 

566 if self._execution_env: 

567 return [UserAgentComponent('exec-env', self._execution_env)] 

568 else: 

569 return [] 

570 

571 def _build_feature_metadata(self): 

572 """ 

573 Build the features component of the User-Agent header string. 

574 

575 Returns a single component with prefix "m" followed by a list of 

576 comma-separated metric values. 

577 """ 

578 ctx = get_context() 

579 context_features = set() if ctx is None else ctx.features 

580 client_features = self._client_features or set() 

581 features = client_features.union(context_features) 

582 if not features: 

583 return [] 

584 size_config = UserAgentComponentSizeConfig(1024, ',') 

585 return [ 

586 UserAgentComponent( 

587 'm', ','.join(features), size_config=size_config 

588 ) 

589 ] 

590 

591 def _build_config_metadata(self): 

592 """ 

593 Build the configuration components of the User-Agent header string. 

594 

595 Returns a list of components with prefix "cfg" followed by the config 

596 setting name and its value. Tracked configuration settings may be 

597 added or removed in future versions. 

598 """ 

599 if not self._client_config or not self._client_config.retries: 

600 return [] 

601 retry_mode = self._client_config.retries.get('mode') 

602 cfg_md = [UserAgentComponent('cfg', 'retry-mode', retry_mode)] 

603 if self._client_config.endpoint_discovery_enabled: 

604 cfg_md.append(UserAgentComponent('cfg', 'endpoint-discovery')) 

605 return cfg_md 

606 

607 def _build_app_id(self): 

608 """ 

609 Build app component of the User-Agent header string. 

610 

611 Returns a single component with prefix "app" and value sourced from the 

612 ``user_agent_appid`` field in :py:class:`botocore.config.Config` or 

613 the ``sdk_ua_app_id`` setting in the shared configuration file, or the 

614 ``AWS_SDK_UA_APP_ID`` environment variable. These are the recommended 

615 ways for apps built with Botocore to insert their identifer into the 

616 User-Agent header. 

617 """ 

618 if self._client_config and self._client_config.user_agent_appid: 

619 appid = sanitize_user_agent_string_component( 

620 raw_str=self._client_config.user_agent_appid, allow_hash=True 

621 ) 

622 return [RawStringUserAgentComponent(f'app/{appid}')] 

623 else: 

624 return [] 

625 

626 def _build_extra(self): 

627 """User agent string components based on legacy "extra" settings. 

628 

629 Creates components from the session-level and client-level 

630 ``user_agent_extra`` setting, if present. Both are passed through 

631 verbatim and should be appended at the end of the string. 

632 

633 Preferred ways to inject application-specific information into 

634 botocore's User-Agent header string are the ``user_agent_appid` field 

635 in :py:class:`botocore.config.Config`. The ``AWS_SDK_UA_APP_ID`` 

636 environment variable and the ``sdk_ua_app_id`` configuration file 

637 setting are alternative ways to set the ``user_agent_appid`` config. 

638 """ 

639 extra = [] 

640 if self._session_user_agent_extra: 

641 extra.append( 

642 RawStringUserAgentComponent(self._session_user_agent_extra) 

643 ) 

644 if self._client_config and self._client_config.user_agent_extra: 

645 extra.append( 

646 RawStringUserAgentComponent( 

647 self._client_config.user_agent_extra 

648 ) 

649 ) 

650 return extra 

651 

652 def _build_legacy_ua_string(self, config_ua_override): 

653 components = [config_ua_override] 

654 if self._session_user_agent_extra: 

655 components.append(self._session_user_agent_extra) 

656 if self._client_config.user_agent_extra: 

657 components.append(self._client_config.user_agent_extra) 

658 return ' '.join(components) 

659 

660 def rebuild_and_replace_user_agent_handler( 

661 self, operation_name, request, **kwargs 

662 ): 

663 ua_string = self.to_string() 

664 if request.headers.get('User-Agent'): 

665 request.headers.replace_header('User-Agent', ua_string) 

666 

667 

668def _get_crt_version(): 

669 """ 

670 This function is considered private and is subject to abrupt breaking 

671 changes. 

672 """ 

673 try: 

674 import awscrt 

675 

676 return awscrt.__version__ 

677 except AttributeError: 

678 return None