Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/botocore/useragent.py: 42%
144 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
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.
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`.
24"""
25import os
26import platform
27from copy import copy
28from string import ascii_letters, digits
29from typing import NamedTuple, Optional
31from botocore import __version__ as botocore_version
32from botocore.compat import HAS_CRT
34_USERAGENT_ALLOWED_CHARACTERS = ascii_letters + digits + "!$%&'*+-.^_`|~"
35_USERAGENT_ALLOWED_OS_NAMES = (
36 'windows',
37 'linux',
38 'macos',
39 'android',
40 'ios',
41 'watchos',
42 'tvos',
43 'other',
44)
45_USERAGENT_PLATFORM_NAME_MAPPINGS = {'darwin': 'macos'}
46# The name by which botocore is identified in the User-Agent header. While most
47# AWS SDKs follow a naming pattern of "aws-sdk-*", botocore and boto3 continue
48# using their existing values. Uses uppercase "B" with all other characters
49# lowercase.
50_USERAGENT_SDK_NAME = 'Botocore'
53def sanitize_user_agent_string_component(raw_str, allow_hash):
54 """Replaces all not allowed characters in the string with a dash ("-").
56 Allowed characters are ASCII alphanumerics and ``!$%&'*+-.^_`|~``. If
57 ``allow_hash`` is ``True``, "#"``" is also allowed.
59 :type raw_str: str
60 :param raw_str: The input string to be sanitized.
62 :type allow_hash: bool
63 :param allow_hash: Whether "#" is considered an allowed character.
64 """
65 return ''.join(
66 c
67 if c in _USERAGENT_ALLOWED_CHARACTERS or (allow_hash and c == '#')
68 else '-'
69 for c in raw_str
70 )
73class UserAgentComponent(NamedTuple):
74 """
75 Component of a Botocore User-Agent header string in the standard format.
77 Each component consists of a prefix, a name, and a value. In the string
78 representation these are combined in the format ``prefix/name#value``.
80 This class is considered private and is subject to abrupt breaking changes.
81 """
83 prefix: str
84 name: str
85 value: Optional[str] = None
87 def to_string(self):
88 """Create string like 'prefix/name#value' from a UserAgentComponent."""
89 clean_prefix = sanitize_user_agent_string_component(
90 self.prefix, allow_hash=True
91 )
92 clean_name = sanitize_user_agent_string_component(
93 self.name, allow_hash=False
94 )
95 if self.value is None or self.value == '':
96 return f'{clean_prefix}/{clean_name}'
97 clean_value = sanitize_user_agent_string_component(
98 self.value, allow_hash=True
99 )
100 return f'{clean_prefix}/{clean_name}#{clean_value}'
103class RawStringUserAgentComponent:
104 """
105 UserAgentComponent interface wrapper around ``str``.
107 Use for User-Agent header components that are not constructed from
108 prefix+name+value but instead are provided as strings. No sanitization is
109 performed.
110 """
112 def __init__(self, value):
113 self._value = value
115 def to_string(self):
116 return self._value
119# This is not a public interface and is subject to abrupt breaking changes.
120# Any usage is not advised or supported in external code bases.
121try:
122 from botocore.customizations.useragent import modify_components
123except ImportError:
124 # Default implementation that returns unmodified User-Agent components.
125 def modify_components(components):
126 return components
129class UserAgentString:
130 """
131 Generator for AWS SDK User-Agent header strings.
133 The User-Agent header format contains information from session, client, and
134 request context. ``UserAgentString`` provides methods for collecting the
135 information and ``to_string`` for assembling it into the standardized
136 string format.
138 Example usage:
140 ua_session = UserAgentString.from_environment()
141 ua_session.set_session_config(...)
142 ua_client = ua_session.with_client_config(Config(...))
143 ua_string = ua_request.to_string()
145 For testing or when information from all sources is available at the same
146 time, the methods can be chained:
148 ua_string = (
149 UserAgentString
150 .from_environment()
151 .set_session_config(...)
152 .with_client_config(Config(...))
153 .to_string()
154 )
156 """
158 def __init__(
159 self,
160 platform_name,
161 platform_version,
162 platform_machine,
163 python_version,
164 python_implementation,
165 execution_env,
166 crt_version=None,
167 ):
168 """
169 :type platform_name: str
170 :param platform_name: Name of the operating system or equivalent
171 platform name. Should be sourced from :py:meth:`platform.system`.
172 :type platform_version: str
173 :param platform_version: Version of the operating system or equivalent
174 platform name. Should be sourced from :py:meth:`platform.version`.
175 :type platform_machine: str
176 :param platform_version: Processor architecture or machine type. For
177 example "x86_64". Should be sourced from :py:meth:`platform.machine`.
178 :type python_version: str
179 :param python_version: Version of the python implementation as str.
180 Should be sourced from :py:meth:`platform.python_version`.
181 :type python_implementation: str
182 :param python_implementation: Name of the python implementation.
183 Should be sourced from :py:meth:`platform.python_implementation`.
184 :type execution_env: str
185 :param execution_env: The value of the AWS execution environment.
186 Should be sourced from the ``AWS_EXECUTION_ENV` environment
187 variable.
188 :type crt_version: str
189 :param crt_version: Version string of awscrt package, if installed.
190 """
191 self._platform_name = platform_name
192 self._platform_version = platform_version
193 self._platform_machine = platform_machine
194 self._python_version = python_version
195 self._python_implementation = python_implementation
196 self._execution_env = execution_env
197 self._crt_version = crt_version
199 # Components that can be added with ``set_session_config()``
200 self._session_user_agent_name = None
201 self._session_user_agent_version = None
202 self._session_user_agent_extra = None
204 self._client_config = None
205 self._uses_paginator = None
206 self._uses_waiter = None
207 self._uses_resource = None
209 @classmethod
210 def from_environment(cls):
211 crt_version = None
212 if HAS_CRT:
213 crt_version = _get_crt_version() or 'Unknown'
214 return cls(
215 platform_name=platform.system(),
216 platform_version=platform.release(),
217 platform_machine=platform.machine(),
218 python_version=platform.python_version(),
219 python_implementation=platform.python_implementation(),
220 execution_env=os.environ.get('AWS_EXECUTION_ENV'),
221 crt_version=crt_version,
222 )
224 def set_session_config(
225 self,
226 session_user_agent_name,
227 session_user_agent_version,
228 session_user_agent_extra,
229 ):
230 """
231 Set the user agent configuration values that apply at session level.
233 :param user_agent_name: The user agent name configured in the
234 :py:class:`botocore.session.Session` object. For backwards
235 compatibility, this will always be at the beginning of the
236 User-Agent string, together with ``user_agent_version``.
237 :param user_agent_version: The user agent version configured in the
238 :py:class:`botocore.session.Session` object.
239 :param user_agent_extra: The user agent "extra" configured in the
240 :py:class:`botocore.session.Session` object.
241 """
242 self._session_user_agent_name = session_user_agent_name
243 self._session_user_agent_version = session_user_agent_version
244 self._session_user_agent_extra = session_user_agent_extra
245 return self
247 def with_client_config(self, client_config):
248 """
249 Create a copy with all original values and client-specific values.
251 :type client_config: botocore.config.Config
252 :param client_config: The client configuration object.
253 """
254 cp = copy(self)
255 cp._client_config = client_config
256 return cp
258 def to_string(self):
259 """
260 Build User-Agent header string from the object's properties.
261 """
262 config_ua_override = None
263 if self._client_config:
264 if hasattr(self._client_config, '_supplied_user_agent'):
265 config_ua_override = self._client_config._supplied_user_agent
266 else:
267 config_ua_override = self._client_config.user_agent
269 if config_ua_override is not None:
270 return self._build_legacy_ua_string(config_ua_override)
272 components = [
273 *self._build_sdk_metadata(),
274 RawStringUserAgentComponent('ua/2.0'),
275 *self._build_os_metadata(),
276 *self._build_architecture_metadata(),
277 *self._build_language_metadata(),
278 *self._build_execution_env_metadata(),
279 *self._build_feature_metadata(),
280 *self._build_config_metadata(),
281 *self._build_app_id(),
282 *self._build_extra(),
283 ]
285 components = modify_components(components)
287 return ' '.join([comp.to_string() for comp in components])
289 def _build_sdk_metadata(self):
290 """
291 Build the SDK name and version component of the User-Agent header.
293 For backwards-compatibility both session-level and client-level config
294 of custom tool names are honored. If this removes the Botocore
295 information from the start of the string, Botocore's name and version
296 are included as a separate field with "md" prefix.
297 """
298 sdk_md = []
299 if (
300 self._session_user_agent_name
301 and self._session_user_agent_version
302 and (
303 self._session_user_agent_name != _USERAGENT_SDK_NAME
304 or self._session_user_agent_version != botocore_version
305 )
306 ):
307 sdk_md.extend(
308 [
309 UserAgentComponent(
310 self._session_user_agent_name,
311 self._session_user_agent_version,
312 ),
313 UserAgentComponent(
314 'md', _USERAGENT_SDK_NAME, botocore_version
315 ),
316 ]
317 )
318 else:
319 sdk_md.append(
320 UserAgentComponent(_USERAGENT_SDK_NAME, botocore_version)
321 )
323 if self._crt_version is not None:
324 sdk_md.append(
325 UserAgentComponent('md', 'awscrt', self._crt_version)
326 )
328 return sdk_md
330 def _build_os_metadata(self):
331 """
332 Build the OS/platform components of the User-Agent header string.
334 For recognized platform names that match or map to an entry in the list
335 of standardized OS names, a single component with prefix "os" is
336 returned. Otherwise, one component "os/other" is returned and a second
337 with prefix "md" and the raw platform name.
339 String representations of example return values:
340 * ``os/macos#10.13.6``
341 * ``os/linux``
342 * ``os/other``
343 * ``os/other md/foobar#1.2.3``
344 """
345 if self._platform_name is None:
346 return [UserAgentComponent('os', 'other')]
348 plt_name_lower = self._platform_name.lower()
349 if plt_name_lower in _USERAGENT_ALLOWED_OS_NAMES:
350 os_family = plt_name_lower
351 elif plt_name_lower in _USERAGENT_PLATFORM_NAME_MAPPINGS:
352 os_family = _USERAGENT_PLATFORM_NAME_MAPPINGS[plt_name_lower]
353 else:
354 os_family = None
356 if os_family is not None:
357 return [
358 UserAgentComponent('os', os_family, self._platform_version)
359 ]
360 else:
361 return [
362 UserAgentComponent('os', 'other'),
363 UserAgentComponent(
364 'md', self._platform_name, self._platform_version
365 ),
366 ]
368 def _build_architecture_metadata(self):
369 """
370 Build architecture component of the User-Agent header string.
372 Returns the machine type with prefix "md" and name "arch", if one is
373 available. Common values include "x86_64", "arm64", "i386".
374 """
375 if self._platform_machine:
376 return [
377 UserAgentComponent(
378 'md', 'arch', self._platform_machine.lower()
379 )
380 ]
381 return []
383 def _build_language_metadata(self):
384 """
385 Build the language components of the User-Agent header string.
387 Returns the Python version in a component with prefix "lang" and name
388 "python". The Python implementation (e.g. CPython, PyPy) is returned as
389 separate metadata component with prefix "md" and name "pyimpl".
391 String representation of an example return value:
392 ``lang/python#3.10.4 md/pyimpl#CPython``
393 """
394 lang_md = [
395 UserAgentComponent('lang', 'python', self._python_version),
396 ]
397 if self._python_implementation:
398 lang_md.append(
399 UserAgentComponent('md', 'pyimpl', self._python_implementation)
400 )
401 return lang_md
403 def _build_execution_env_metadata(self):
404 """
405 Build the execution environment component of the User-Agent header.
407 Returns a single component prefixed with "exec-env", usually sourced
408 from the environment variable AWS_EXECUTION_ENV.
409 """
410 if self._execution_env:
411 return [UserAgentComponent('exec-env', self._execution_env)]
412 else:
413 return []
415 def _build_feature_metadata(self):
416 """
417 Build the features components of the User-Agent header string.
419 Botocore currently does not report any features. This may change in a
420 future version.
421 """
422 return []
424 def _build_config_metadata(self):
425 """
426 Build the configuration components of the User-Agent header string.
428 Returns a list of components with prefix "cfg" followed by the config
429 setting name and its value. Tracked configuration settings may be
430 added or removed in future versions.
431 """
432 if not self._client_config or not self._client_config.retries:
433 return []
434 retry_mode = self._client_config.retries.get('mode')
435 cfg_md = [UserAgentComponent('cfg', 'retry-mode', retry_mode)]
436 if self._client_config.endpoint_discovery_enabled:
437 cfg_md.append(UserAgentComponent('cfg', 'endpoint-discovery'))
438 return cfg_md
440 def _build_app_id(self):
441 """
442 Build app component of the User-Agent header string.
444 Returns a single component with prefix "app" and value sourced from the
445 ``user_agent_appid`` field in :py:class:`botocore.config.Config` or
446 the ``sdk_ua_app_id`` setting in the shared configuration file, or the
447 ``AWS_SDK_UA_APP_ID`` environment variable. These are the recommended
448 ways for apps built with Botocore to insert their identifer into the
449 User-Agent header.
450 """
451 if self._client_config and self._client_config.user_agent_appid:
452 return [
453 UserAgentComponent('app', self._client_config.user_agent_appid)
454 ]
455 else:
456 return []
458 def _build_extra(self):
459 """User agent string components based on legacy "extra" settings.
461 Creates components from the session-level and client-level
462 ``user_agent_extra`` setting, if present. Both are passed through
463 verbatim and should be appended at the end of the string.
465 Preferred ways to inject application-specific information into
466 botocore's User-Agent header string are the ``user_agent_appid` field
467 in :py:class:`botocore.config.Config`. The ``AWS_SDK_UA_APP_ID``
468 environment variable and the ``sdk_ua_app_id`` configuration file
469 setting are alternative ways to set the ``user_agent_appid`` config.
470 """
471 extra = []
472 if self._session_user_agent_extra:
473 extra.append(
474 RawStringUserAgentComponent(self._session_user_agent_extra)
475 )
476 if self._client_config and self._client_config.user_agent_extra:
477 extra.append(
478 RawStringUserAgentComponent(
479 self._client_config.user_agent_extra
480 )
481 )
482 return extra
484 def _build_legacy_ua_string(self, config_ua_override):
485 components = [config_ua_override]
486 if self._session_user_agent_extra:
487 components.append(self._session_user_agent_extra)
488 if self._client_config.user_agent_extra:
489 components.append(self._client_config.user_agent_extra)
490 return ' '.join(components)
493def _get_crt_version():
494 """
495 This function is considered private and is subject to abrupt breaking
496 changes.
497 """
498 try:
499 import awscrt
501 return awscrt.__version__
502 except AttributeError:
503 return None