boto3-refresh-session 1.3.22__tar.gz → 2.0.1__tar.gz

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.
Files changed (18) hide show
  1. {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/PKG-INFO +13 -2
  2. {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/README.md +11 -1
  3. {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/boto3_refresh_session/__init__.py +4 -4
  4. {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/boto3_refresh_session/exceptions.py +2 -2
  5. boto3_refresh_session-2.0.1/boto3_refresh_session/methods/__init__.py +0 -0
  6. {boto3_refresh_session-1.3.22/boto3_refresh_session → boto3_refresh_session-2.0.1/boto3_refresh_session/methods}/custom.py +5 -4
  7. {boto3_refresh_session-1.3.22/boto3_refresh_session → boto3_refresh_session-2.0.1/boto3_refresh_session/methods}/ecs.py +5 -4
  8. boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/__init__.typed +4 -0
  9. boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/certificate.typed +54 -0
  10. boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/cognito.typed +16 -0
  11. boto3_refresh_session-2.0.1/boto3_refresh_session/methods/iot/core.typed +50 -0
  12. {boto3_refresh_session-1.3.22/boto3_refresh_session → boto3_refresh_session-2.0.1/boto3_refresh_session/methods}/sts.py +10 -10
  13. boto3_refresh_session-2.0.1/boto3_refresh_session/session.py +94 -0
  14. boto3_refresh_session-2.0.1/boto3_refresh_session/utils.py +212 -0
  15. {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/pyproject.toml +3 -2
  16. boto3_refresh_session-1.3.22/boto3_refresh_session/session.py +0 -175
  17. {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/LICENSE +0 -0
  18. {boto3_refresh_session-1.3.22 → boto3_refresh_session-2.0.1}/NOTICE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: boto3-refresh-session
3
- Version: 1.3.22
3
+ Version: 2.0.1
4
4
  Summary: A simple Python package for refreshing the temporary security credentials in a boto3.session.Session object automatically.
5
5
  License: MIT
6
6
  Keywords: boto3,botocore,aws,sts,ecs,credentials,token,refresh
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
18
18
  Requires-Dist: boto3
19
19
  Requires-Dist: botocore
20
20
  Requires-Dist: requests
21
+ Requires-Dist: typing-extensions
21
22
  Project-URL: Documentation, https://michaelthomasletts.github.io/boto3-refresh-session/index.html
22
23
  Project-URL: Repository, https://github.com/michaelthomasletts/boto3-refresh-session
23
24
  Description-Content-Type: text/markdown
@@ -57,7 +58,7 @@ Description-Content-Type: text/markdown
57
58
  </a>
58
59
 
59
60
  <a href="https://pepy.tech/projects/boto3-refresh-session">
60
- <img src="https://img.shields.io/badge/downloads-76.9K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
61
+ <img src="https://img.shields.io/badge/downloads-85.0K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
61
62
  </a>
62
63
 
63
64
  <a href="https://michaelthomasletts.github.io/boto3-refresh-session/index.html">
@@ -83,6 +84,16 @@ Description-Content-Type: text/markdown
83
84
 
84
85
  </div>
85
86
 
87
+ ---
88
+
89
+ ## ⚠️ Important Update
90
+
91
+ I am currently grappling with a serious medical condition that negatively impacts my vision. Accordingly, development of the `iot` and `ec2` modules has been delayed. Expect delayed responses to issues and pull requests until my health stabilizes.
92
+
93
+ Thank you for supporting this project.
94
+
95
+ ---
96
+
86
97
  ## Features
87
98
 
88
99
  - Drop-in replacement for `boto3.session.Session`
@@ -33,7 +33,7 @@
33
33
  </a>
34
34
 
35
35
  <a href="https://pepy.tech/projects/boto3-refresh-session">
36
- <img src="https://img.shields.io/badge/downloads-76.9K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
36
+ <img src="https://img.shields.io/badge/downloads-85.0K-red?logo=python&color=%23FF0000&label=Downloads" alt="Downloads"/>
37
37
  </a>
38
38
 
39
39
  <a href="https://michaelthomasletts.github.io/boto3-refresh-session/index.html">
@@ -59,6 +59,16 @@
59
59
 
60
60
  </div>
61
61
 
62
+ ---
63
+
64
+ ## ⚠️ Important Update
65
+
66
+ I am currently grappling with a serious medical condition that negatively impacts my vision. Accordingly, development of the `iot` and `ec2` modules has been delayed. Expect delayed responses to issues and pull requests until my health stabilizes.
67
+
68
+ Thank you for supporting this project.
69
+
70
+ ---
71
+
62
72
  ## Features
63
73
 
64
74
  - Drop-in replacement for `boto3.session.Session`
@@ -1,10 +1,10 @@
1
- from .custom import CustomRefreshableSession
2
- from .ecs import ECSRefreshableSession
1
+ from .methods.custom import CustomRefreshableSession
2
+ from .methods.ecs import ECSRefreshableSession
3
+ from .methods.sts import STSRefreshableSession
3
4
  from .session import RefreshableSession
4
- from .sts import STSRefreshableSession
5
5
 
6
6
  __all__ = ["RefreshableSession"]
7
- __version__ = "1.3.22"
7
+ __version__ = "2.0.1"
8
8
  __title__ = "boto3-refresh-session"
9
9
  __author__ = "Mike Letts"
10
10
  __maintainer__ = "Mike Letts"
@@ -15,7 +15,7 @@ class BRSError(Exception):
15
15
  return self.message
16
16
 
17
17
  def __repr__(self) -> str:
18
- return f"{self.__class__.__name__}({repr(self.message)})"
18
+ return f"{self.__class__.__name__}({self.message!r})"
19
19
 
20
20
 
21
21
  class BRSWarning(UserWarning):
@@ -35,4 +35,4 @@ class BRSWarning(UserWarning):
35
35
  return self.message
36
36
 
37
37
  def __repr__(self) -> str:
38
- return f"{self.__class__.__name__}({repr(self.message)})"
38
+ return f"{self.__class__.__name__}({self.message!r})"
@@ -4,11 +4,12 @@ __all__ = ["CustomRefreshableSession"]
4
4
 
5
5
  from typing import Any, Callable
6
6
 
7
- from .exceptions import BRSError
8
- from .session import BaseRefreshableSession, TemporaryCredentials
7
+ from ..exceptions import BRSError
8
+ from ..session import BaseRefreshableSession
9
+ from ..utils import TemporaryCredentials
9
10
 
10
11
 
11
- class CustomRefreshableSession(BaseRefreshableSession, method="custom"):
12
+ class CustomRefreshableSession(BaseRefreshableSession, registry_key="custom"):
12
13
  """A :class:`boto3.session.Session` object that automatically refreshes
13
14
  temporary credentials returned by a custom credential getter provided
14
15
  by the user. Useful for users with highly sophisticated or idiosyncratic
@@ -74,7 +75,7 @@ class CustomRefreshableSession(BaseRefreshableSession, method="custom"):
74
75
  else {}
75
76
  )
76
77
 
77
- self._refresh_using(
78
+ self.initialize(
78
79
  credentials_method=self._get_credentials,
79
80
  defer_refresh=defer_refresh is not False,
80
81
  refresh_method="custom",
@@ -6,8 +6,9 @@ import os
6
6
 
7
7
  import requests
8
8
 
9
- from .exceptions import BRSError
10
- from .session import BaseRefreshableSession, TemporaryCredentials
9
+ from ..exceptions import BRSError
10
+ from ..session import BaseRefreshableSession
11
+ from ..utils import TemporaryCredentials
11
12
 
12
13
  _ECS_CREDENTIALS_RELATIVE_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
13
14
  _ECS_CREDENTIALS_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
@@ -15,7 +16,7 @@ _ECS_AUTHORIZATION_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
15
16
  _DEFAULT_ENDPOINT_BASE = "http://169.254.170.2"
16
17
 
17
18
 
18
- class ECSRefreshableSession(BaseRefreshableSession, method="ecs"):
19
+ class ECSRefreshableSession(BaseRefreshableSession, registry_key="ecs"):
19
20
  """A boto3 session that automatically refreshes temporary AWS credentials
20
21
  from the ECS container credentials metadata endpoint.
21
22
 
@@ -40,7 +41,7 @@ class ECSRefreshableSession(BaseRefreshableSession, method="ecs"):
40
41
  self._headers = self._build_headers()
41
42
  self._http = self._init_http_session()
42
43
 
43
- self._refresh_using(
44
+ self.initialize(
44
45
  credentials_method=self._get_credentials,
45
46
  defer_refresh=defer_refresh is not False,
46
47
  refresh_method="ecs-container-metadata",
@@ -0,0 +1,4 @@
1
+ from .certificate import IoTCertificateRefreshableSession
2
+ from .core import IoTRefreshableSession
3
+
4
+ __all__ = ["IoTRefreshableSession"]
@@ -0,0 +1,54 @@
1
+ __all__ = ["IoTCertificateRefreshableSession"]
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from ...exceptions import BRSError
7
+ from ...utils import PKCS11, TemporaryCredentials
8
+ from .core import BaseIoTRefreshableSession
9
+
10
+
11
+ class IoTCertificateRefreshableSession(
12
+ BaseIoTRefreshableSession, registry_key="certificate"
13
+ ):
14
+ def __init__(
15
+ self,
16
+ endpoint: str,
17
+ role_alias: str,
18
+ thing_name: str,
19
+ certificate: str | bytes,
20
+ private_key: str | bytes | None = None,
21
+ pkcs11: PKCS11 | None = None,
22
+ ca: bytes | None = None,
23
+ verify_peer: bool = True,
24
+ ):
25
+ self.endpoint = endpoint
26
+ self.role_alias = role_alias
27
+ self.thing_name = thing_name
28
+ self.certificate = certificate
29
+ self.private_key = private_key
30
+ self.pkcs11 = pkcs11
31
+ self.ca = ca
32
+ self.verify_peer = verify_peer
33
+
34
+ if self.certificate and isinstance(self.certificate, str):
35
+ with open(Path(self.certificate), "rb") as cert_pem_file:
36
+ self.certificate = cert_pem_file.read()
37
+
38
+ if self.private_key is None and self.pkcs11 is None:
39
+ raise BRSError(
40
+ "Either 'private_key' or 'pkcs11' must be provided."
41
+ )
42
+
43
+ if self.private_key is not None and self.pkcs11 is not None:
44
+ raise BRSError(
45
+ "Only one of 'private_key' or 'pkcs11' can be provided."
46
+ )
47
+
48
+ if self.private_key and isinstance(self.private_key, str):
49
+ with open(Path(self.private_key), "rb") as private_key_pem_file:
50
+ self.private_key = private_key_pem_file.read()
51
+
52
+ def _get_credentials(self) -> TemporaryCredentials: ...
53
+
54
+ def get_identity(self) -> dict[str, Any]: ...
@@ -0,0 +1,16 @@
1
+ __all__ = ["IoTCognitoRefreshableSession"]
2
+
3
+ from typing import Any
4
+
5
+ from ...utils import TemporaryCredentials
6
+ from .core import BaseIoTRefreshableSession
7
+
8
+
9
+ class IoTCognitoRefreshableSession(
10
+ BaseIoTRefreshableSession, registry_key="cognito"
11
+ ):
12
+ def __init__(self): ...
13
+
14
+ def _get_credentials(self) -> TemporaryCredentials: ...
15
+
16
+ def get_identity(self) -> dict[str, Any]: ...
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = ["IoTRefreshableSession"]
4
+
5
+ from typing import get_args
6
+
7
+ from ...exceptions import BRSError
8
+ from ...session import BaseRefreshableSession
9
+ from ...utils import (
10
+ IoTAuthenticationMethod,
11
+ BRSSession,
12
+ CredentialProvider,
13
+ Registry,
14
+ )
15
+
16
+
17
+ class BaseIoTRefreshableSession(
18
+ Registry[IoTAuthenticationMethod],
19
+ CredentialProvider,
20
+ BRSSession,
21
+ registry_key="__iot_sentinel__",
22
+ ):
23
+ def __init__(self, **kwargs):
24
+ super().__init__(**kwargs)
25
+
26
+
27
+ class IoTRefreshableSession(BaseRefreshableSession, registry_key="iot"):
28
+ def __new__(
29
+ cls,
30
+ authentication_method: IoTAuthenticationMethod = "certificate",
31
+ **kwargs,
32
+ ) -> BaseIoTRefreshableSession:
33
+ if authentication_method not in (
34
+ methods := cls.get_available_authentication_methods()
35
+ ):
36
+ raise BRSError(
37
+ f"{authentication_method!r} is an invalid authentication "
38
+ "method parameter. Available authentication methods are "
39
+ f"{', '.join(repr(meth) for meth in methods)}."
40
+ )
41
+
42
+ return BaseIoTRefreshableSession.registry[authentication_method](
43
+ **kwargs
44
+ )
45
+
46
+ @classmethod
47
+ def get_available_authentication_methods(cls) -> list[str]:
48
+ args = list(get_args(IoTAuthenticationMethod))
49
+ args.remove("__iot_sentinel__")
50
+ return args
@@ -4,17 +4,18 @@ __all__ = ["STSRefreshableSession"]
4
4
 
5
5
  from typing import Any
6
6
 
7
- from .exceptions import BRSWarning
8
- from .session import BaseRefreshableSession, TemporaryCredentials
7
+ from ..exceptions import BRSWarning
8
+ from ..session import BaseRefreshableSession
9
+ from ..utils import AssumeRoleParams, STSClientParams, TemporaryCredentials
9
10
 
10
11
 
11
- class STSRefreshableSession(BaseRefreshableSession, method="sts"):
12
+ class STSRefreshableSession(BaseRefreshableSession, registry_key="sts"):
12
13
  """A :class:`boto3.session.Session` object that automatically refreshes
13
14
  temporary AWS credentials using an IAM role that is assumed via STS.
14
15
 
15
16
  Parameters
16
17
  ----------
17
- assume_role_kwargs : dict
18
+ assume_role_kwargs : AssumeRoleParams
18
19
  Required keyword arguments for :meth:`STS.Client.assume_role` (i.e.
19
20
  boto3 STS client).
20
21
  defer_refresh : bool, optional
@@ -22,7 +23,7 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
22
23
  until they are explicitly needed. If ``False`` then temporary
23
24
  credentials refresh immediately upon expiration. It is highly
24
25
  recommended that you use ``True``. Default is ``True``.
25
- sts_client_kwargs : dict, optional
26
+ sts_client_kwargs : STSClientParams, optional
26
27
  Optional keyword arguments for the :class:`STS.Client` object. Do not
27
28
  provide values for ``service_name`` as they are unnecessary. Default
28
29
  is None.
@@ -36,13 +37,12 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
36
37
 
37
38
  def __init__(
38
39
  self,
39
- assume_role_kwargs: dict,
40
+ assume_role_kwargs: AssumeRoleParams,
40
41
  defer_refresh: bool | None = None,
41
- sts_client_kwargs: dict | None = None,
42
+ sts_client_kwargs: STSClientParams | None = None,
42
43
  **kwargs,
43
44
  ):
44
45
  super().__init__(**kwargs)
45
- defer_refresh = defer_refresh is not False
46
46
  self.assume_role_kwargs = assume_role_kwargs
47
47
 
48
48
  if sts_client_kwargs is not None:
@@ -60,9 +60,9 @@ class STSRefreshableSession(BaseRefreshableSession, method="sts"):
60
60
  self._sts_client = self.client(service_name="sts")
61
61
 
62
62
  # mounting refreshable credentials
63
- self._refresh_using(
63
+ self.initialize(
64
64
  credentials_method=self._get_credentials,
65
- defer_refresh=defer_refresh,
65
+ defer_refresh=defer_refresh is not False,
66
66
  refresh_method="sts-assume-role",
67
67
  )
68
68
 
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = ["RefreshableSession"]
4
+
5
+ from typing import get_args
6
+
7
+ from .exceptions import BRSError
8
+ from .utils import BRSSession, CredentialProvider, Method, Registry
9
+
10
+
11
+ class BaseRefreshableSession(
12
+ Registry[Method],
13
+ CredentialProvider,
14
+ BRSSession,
15
+ registry_key="__sentinel__",
16
+ ):
17
+ """Abstract base class for implementing refreshable AWS sessions.
18
+
19
+ Provides a common interface and factory registration mechanism
20
+ for subclasses that generate temporary credentials using various
21
+ AWS authentication methods (e.g., STS).
22
+
23
+ Subclasses must implement ``_get_credentials()`` and ``get_identity()``.
24
+ They should also register themselves using the ``method=...`` argument
25
+ to ``__init_subclass__``.
26
+
27
+ Parameters
28
+ ----------
29
+ registry : dict[str, type[BaseRefreshableSession]]
30
+ Class-level registry mapping method names to registered session types.
31
+ """
32
+
33
+ def __init__(self, **kwargs):
34
+ super().__init__(**kwargs)
35
+
36
+
37
+ class RefreshableSession:
38
+ """Factory class for constructing refreshable boto3 sessions using various
39
+ authentication methods, e.g. STS.
40
+
41
+ This class provides a unified interface for creating boto3 sessions whose
42
+ credentials are automatically refreshed in the background.
43
+
44
+ Use ``RefreshableSession(method="...")`` to construct an instance using
45
+ the desired method.
46
+
47
+ For additional information on required parameters, refer to the See Also
48
+ section below.
49
+
50
+ Parameters
51
+ ----------
52
+ method : Method
53
+ The authentication and refresh method to use for the session. Must
54
+ match a registered method name. Default is "sts".
55
+
56
+ Other Parameters
57
+ ----------------
58
+ **kwargs : dict
59
+ Additional keyword arguments forwarded to the constructor of the
60
+ selected session class.
61
+
62
+ See Also
63
+ --------
64
+ boto3_refresh_session.methods.custom.CustomRefreshableSession
65
+ boto3_refresh_session.methods.sts.STSRefreshableSession
66
+ boto3_refresh_session.methods.ecs.ECSRefreshableSession
67
+ """
68
+
69
+ def __new__(
70
+ cls, method: Method = "sts", **kwargs
71
+ ) -> BaseRefreshableSession:
72
+ if method not in (methods := cls.get_available_methods()):
73
+ raise BRSError(
74
+ f"{method!r} is an invalid method parameter. "
75
+ "Available methods are "
76
+ f"{', '.join(repr(meth) for meth in methods)}."
77
+ )
78
+
79
+ return BaseRefreshableSession.registry[method](**kwargs)
80
+
81
+ @classmethod
82
+ def get_available_methods(cls) -> list[str]:
83
+ """Lists all currently available credential refresh methods.
84
+
85
+ Returns
86
+ -------
87
+ list[str]
88
+ A list of all currently available credential refresh methods,
89
+ e.g. 'sts', 'ecs', 'custom'.
90
+ """
91
+
92
+ args = list(get_args(Method))
93
+ args.remove("__sentinel__")
94
+ return args
@@ -0,0 +1,212 @@
1
+ from abc import ABC, abstractmethod
2
+ from datetime import datetime
3
+ from typing import (
4
+ Any,
5
+ Callable,
6
+ ClassVar,
7
+ Generic,
8
+ List,
9
+ Literal,
10
+ TypedDict,
11
+ TypeVar,
12
+ )
13
+
14
+ from boto3.session import Session
15
+ from botocore.credentials import (
16
+ DeferredRefreshableCredentials,
17
+ RefreshableCredentials,
18
+ )
19
+
20
+ from .exceptions import BRSWarning
21
+
22
+ try:
23
+ from typing import NotRequired # type: ignore[import]
24
+ except ImportError:
25
+ from typing_extensions import NotRequired
26
+
27
+ #: Type alias for all currently available IoT authentication methods.
28
+ IoTAuthenticationMethod = Literal["certificate", "cognito", "__iot_sentinel__"]
29
+
30
+ #: Type alias for all currently available credential refresh methods.
31
+ Method = Literal[
32
+ "sts",
33
+ "ecs",
34
+ "custom",
35
+ "__sentinel__",
36
+ ] # TODO: Add iot when implemented
37
+
38
+ #: Type alias for all refresh method names.
39
+ RefreshMethod = Literal[
40
+ "sts-assume-role",
41
+ "ecs-container-metadata",
42
+ "custom",
43
+ ] # Add iot-certificate and iot-cognito when iot implemented
44
+
45
+ #: Type alias for all currently registered credential refresh methods.
46
+ RegistryKey = TypeVar("RegistryKey", bound=str)
47
+
48
+
49
+ class Registry(Generic[RegistryKey]):
50
+ """Gives any hierarchy a class-level registry."""
51
+
52
+ registry: ClassVar[dict[str, type]] = {}
53
+
54
+ def __init_subclass__(cls, *, registry_key: RegistryKey, **kwargs: Any):
55
+ super().__init_subclass__(**kwargs)
56
+
57
+ if registry_key in cls.registry:
58
+ BRSWarning(f"{registry_key!r} already registered. Overwriting.")
59
+
60
+ if "sentinel" not in registry_key:
61
+ cls.registry[registry_key] = cls
62
+
63
+ @classmethod
64
+ def items(cls) -> dict[str, type]:
65
+ """Typed accessor for introspection / debugging."""
66
+
67
+ return dict(cls.registry)
68
+
69
+
70
+ class TemporaryCredentials(TypedDict):
71
+ """Temporary IAM credentials."""
72
+
73
+ access_key: str
74
+ secret_key: str
75
+ token: str
76
+ expiry_time: datetime | str
77
+
78
+
79
+ class RefreshableTemporaryCredentials(TypedDict):
80
+ """Refreshable IAM credentials.
81
+
82
+ Parameters
83
+ ----------
84
+ AWS_ACCESS_KEY_ID : str
85
+ AWS access key identifier.
86
+ AWS_SECRET_ACCESS_KEY : str
87
+ AWS secret access key.
88
+ AWS_SESSION_TOKEN : str
89
+ AWS session token.
90
+ """
91
+
92
+ AWS_ACCESS_KEY_ID: str
93
+ AWS_SECRET_ACCESS_KEY: str
94
+ AWS_SESSION_TOKEN: str
95
+
96
+
97
+ class CredentialProvider(ABC):
98
+ """Defines the abstract surface every refreshable session must expose."""
99
+
100
+ @abstractmethod
101
+ def _get_credentials(self) -> TemporaryCredentials: ...
102
+
103
+ @abstractmethod
104
+ def get_identity(self) -> dict[str, Any]: ...
105
+
106
+
107
+ class BRSSession(Session):
108
+ """Wrapper for boto3.session.Session.
109
+
110
+ Other Parameters
111
+ ----------------
112
+ kwargs : Any
113
+ Optional keyword arguments for initializing boto3.session.Session."""
114
+
115
+ def __init__(self, **kwargs):
116
+ super().__init__(**kwargs)
117
+
118
+ def initialize(
119
+ self,
120
+ credentials_method: Callable,
121
+ defer_refresh: bool,
122
+ refresh_method: RefreshMethod,
123
+ ):
124
+ # determining how exactly to refresh expired temporary credentials
125
+ if not defer_refresh:
126
+ self._credentials = RefreshableCredentials.create_from_metadata(
127
+ metadata=credentials_method(),
128
+ refresh_using=credentials_method,
129
+ method=refresh_method,
130
+ )
131
+ else:
132
+ self._credentials = DeferredRefreshableCredentials(
133
+ refresh_using=credentials_method, method=refresh_method
134
+ )
135
+
136
+ def refreshable_credentials(self) -> RefreshableTemporaryCredentials:
137
+ """The current temporary AWS security credentials.
138
+
139
+ Returns
140
+ -------
141
+ RefreshableTemporaryCredentials
142
+ Temporary AWS security credentials containing:
143
+ AWS_ACCESS_KEY_ID : str
144
+ AWS access key identifier.
145
+ AWS_SECRET_ACCESS_KEY : str
146
+ AWS secret access key.
147
+ AWS_SESSION_TOKEN : str
148
+ AWS session token.
149
+ """
150
+
151
+ creds = self.get_credentials().get_frozen_credentials()
152
+ return {
153
+ "AWS_ACCESS_KEY_ID": creds.access_key,
154
+ "AWS_SECRET_ACCESS_KEY": creds.secret_key,
155
+ "AWS_SESSION_TOKEN": creds.token,
156
+ }
157
+
158
+ @property
159
+ def credentials(self) -> RefreshableTemporaryCredentials:
160
+ """The current temporary AWS security credentials."""
161
+
162
+ return self.refreshable_credentials()
163
+
164
+
165
+ class Tag(TypedDict):
166
+ Key: str
167
+ Value: str
168
+
169
+
170
+ class PolicyDescriptorType(TypedDict):
171
+ arn: str
172
+
173
+
174
+ class ProvidedContext(TypedDict):
175
+ ProviderArn: str
176
+ ContextAssertion: str
177
+
178
+
179
+ class AssumeRoleParams(TypedDict):
180
+ RoleArn: str
181
+ RoleSessionName: str
182
+ PolicyArns: NotRequired[List[PolicyDescriptorType]]
183
+ Policy: NotRequired[str]
184
+ DurationSeconds: NotRequired[int]
185
+ ExternalId: NotRequired[str]
186
+ SerialNumber: NotRequired[str]
187
+ TokenCode: NotRequired[str]
188
+ Tags: NotRequired[List[Tag]]
189
+ TransitiveTagKeys: NotRequired[List[str]]
190
+ SourceIdentity: NotRequired[str]
191
+ ProvidedContexts: NotRequired[List[ProvidedContext]]
192
+
193
+
194
+ class STSClientParams(TypedDict):
195
+ region_name: NotRequired[str]
196
+ api_version: NotRequired[str]
197
+ use_ssl: NotRequired[bool]
198
+ verify: NotRequired[bool | str]
199
+ endpoint_url: NotRequired[str]
200
+ aws_access_key_id: NotRequired[str]
201
+ aws_secret_access_key: NotRequired[str]
202
+ aws_session_token: NotRequired[str]
203
+ config: NotRequired[Any]
204
+ aws_account_id: NotRequired[str]
205
+
206
+
207
+ class PKCS11(TypedDict):
208
+ pkcs11_loc: str
209
+ user_pin: NotRequired[str]
210
+ slot_id: NotRequired[int]
211
+ token_label: NotRequired[str | None]
212
+ private_key_label: NotRequired[str | None]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "boto3-refresh-session"
3
- version = "1.3.22"
3
+ version = "2.0.1"
4
4
  description = "A simple Python package for refreshing the temporary security credentials in a boto3.session.Session object automatically."
5
5
  authors = [
6
6
  {name = "Mike Letts",email = "lettsmt@gmail.com"}
@@ -8,7 +8,7 @@ authors = [
8
8
  license = {text = "MIT"}
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
- dependencies = ["boto3", "botocore", "requests"]
11
+ dependencies = ["boto3", "botocore", "requests", "typing-extensions"]
12
12
  keywords = ["boto3", "botocore", "aws", "sts", "ecs", "credentials", "token", "refresh"]
13
13
  maintainers = [
14
14
  {name="Michael Letts", email="lettsmt@gmail.com"},
@@ -37,6 +37,7 @@ numpydoc = "^1.8.0"
37
37
  tomlkit = "^0.13.2"
38
38
  jinja2 = "^3.1.6"
39
39
  flask = "^3.1.1"
40
+ pepy-chart = {git = "https://github.com/michaelthomasletts/pepy-chart.git"}
40
41
 
41
42
  [tool.black]
42
43
  line-length = 79
@@ -1,175 +0,0 @@
1
- from __future__ import annotations
2
-
3
- __all__ = ["RefreshableSession"]
4
-
5
- from abc import ABC, abstractmethod
6
- from datetime import datetime
7
- from typing import Any, Callable, ClassVar, Literal, TypedDict, get_args
8
-
9
- from boto3.session import Session
10
- from botocore.credentials import (
11
- DeferredRefreshableCredentials,
12
- RefreshableCredentials,
13
- )
14
-
15
- from .exceptions import BRSError, BRSWarning
16
-
17
- #: Type alias for all currently available credential refresh methods.
18
- Method = Literal["sts", "ecs", "custom"]
19
- RefreshMethod = Literal["sts-assume-role", "ecs-container-metadata", "custom"]
20
-
21
-
22
- class TemporaryCredentials(TypedDict):
23
- """Temporary IAM credentials."""
24
-
25
- access_key: str
26
- secret_key: str
27
- token: str
28
- expiry_time: datetime | str
29
-
30
-
31
- class BaseRefreshableSession(ABC, Session):
32
- """Abstract base class for implementing refreshable AWS sessions.
33
-
34
- Provides a common interface and factory registration mechanism
35
- for subclasses that generate temporary credentials using various
36
- AWS authentication methods (e.g., STS).
37
-
38
- Subclasses must implement ``_get_credentials()`` and ``get_identity()``.
39
- They should also register themselves using the ``method=...`` argument
40
- to ``__init_subclass__``.
41
-
42
- Parameters
43
- ----------
44
- registry : dict[str, type[BaseRefreshableSession]]
45
- Class-level registry mapping method names to registered session types.
46
- """
47
-
48
- # adding this and __init_subclass__ to avoid circular imports
49
- # as well as simplify future addition of new methods
50
- registry: ClassVar[dict[Method, type[BaseRefreshableSession]]] = {}
51
-
52
- def __init_subclass__(cls, method: Method):
53
- super().__init_subclass__()
54
-
55
- # guarantees that methods are unique
56
- if method in BaseRefreshableSession.registry:
57
- BRSWarning(
58
- f"Method {repr(method)} is already registered. Overwriting."
59
- )
60
-
61
- BaseRefreshableSession.registry[method] = cls
62
-
63
- def __init__(self, **kwargs):
64
- super().__init__(**kwargs)
65
-
66
- @abstractmethod
67
- def _get_credentials(self) -> TemporaryCredentials: ...
68
-
69
- @abstractmethod
70
- def get_identity(self) -> dict[str, Any]: ...
71
-
72
- def _refresh_using(
73
- self,
74
- credentials_method: Callable,
75
- defer_refresh: bool,
76
- refresh_method: RefreshMethod,
77
- ):
78
- # determining how exactly to refresh expired temporary credentials
79
- if not defer_refresh:
80
- self._credentials = RefreshableCredentials.create_from_metadata(
81
- metadata=credentials_method(),
82
- refresh_using=credentials_method,
83
- method=refresh_method,
84
- )
85
- else:
86
- self._credentials = DeferredRefreshableCredentials(
87
- refresh_using=credentials_method, method=refresh_method
88
- )
89
-
90
- def refreshable_credentials(self) -> dict[str, str]:
91
- """The current temporary AWS security credentials.
92
-
93
- Returns
94
- -------
95
- dict[str, str]
96
- Temporary AWS security credentials containing:
97
- AWS_ACCESS_KEY_ID : str
98
- AWS access key identifier.
99
- AWS_SECRET_ACCESS_KEY : str
100
- AWS secret access key.
101
- AWS_SESSION_TOKEN : str
102
- AWS session token.
103
- """
104
-
105
- creds = self.get_credentials().get_frozen_credentials()
106
- return {
107
- "AWS_ACCESS_KEY_ID": creds.access_key,
108
- "AWS_SECRET_ACCESS_KEY": creds.secret_key,
109
- "AWS_SESSION_TOKEN": creds.token,
110
- }
111
-
112
- @property
113
- def credentials(self) -> dict[str, str]:
114
- """The current temporary AWS security credentials."""
115
-
116
- return self.refreshable_credentials()
117
-
118
-
119
- class RefreshableSession:
120
- """Factory class for constructing refreshable boto3 sessions using various
121
- authentication methods, e.g. STS.
122
-
123
- This class provides a unified interface for creating boto3 sessions whose
124
- credentials are automatically refreshed in the background.
125
-
126
- Use ``RefreshableSession(method="...")`` to construct an instance using
127
- the desired method.
128
-
129
- For additional information on required parameters, refer to the See Also
130
- section below.
131
-
132
- Parameters
133
- ----------
134
- method : Method
135
- The authentication and refresh method to use for the session. Must
136
- match a registered method name. Default is "sts".
137
-
138
- Other Parameters
139
- ----------------
140
- **kwargs : dict
141
- Additional keyword arguments forwarded to the constructor of the
142
- selected session class.
143
-
144
- See Also
145
- --------
146
- boto3_refresh_session.custom.CustomRefreshableSession
147
- boto3_refresh_session.sts.STSRefreshableSession
148
- boto3_refresh_session.ecs.ECSRefreshableSession
149
- """
150
-
151
- def __new__(
152
- cls, method: Method = "sts", **kwargs
153
- ) -> BaseRefreshableSession:
154
- if method not in (methods := cls.get_available_methods()):
155
- raise BRSError(
156
- f"{repr(method)} is an invalid method parameter. "
157
- "Available methods are "
158
- f"{', '.join(repr(meth) for meth in methods)}."
159
- )
160
-
161
- obj = BaseRefreshableSession.registry[method]
162
- return obj(**kwargs)
163
-
164
- @classmethod
165
- def get_available_methods(cls) -> list[str]:
166
- """Lists all currently available credential refresh methods.
167
-
168
- Returns
169
- -------
170
- list[str]
171
- A list of all currently available credential refresh methods,
172
- e.g. 'sts'.
173
- """
174
-
175
- return list(get_args(Method))