digitalhub 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
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.
Potentially problematic release.
This version of digitalhub might be problematic. Click here for more details.
- digitalhub/__init__.py +1 -1
- digitalhub/context/api.py +5 -5
- digitalhub/context/builder.py +3 -5
- digitalhub/context/context.py +9 -1
- digitalhub/entities/_base/executable/entity.py +105 -57
- digitalhub/entities/_base/material/entity.py +11 -18
- digitalhub/entities/_base/material/utils.py +1 -1
- digitalhub/entities/_commons/metrics.py +64 -30
- digitalhub/entities/_commons/utils.py +36 -9
- digitalhub/entities/_processors/base.py +150 -79
- digitalhub/entities/_processors/context.py +366 -215
- digitalhub/entities/_processors/utils.py +74 -30
- digitalhub/entities/artifact/crud.py +4 -0
- digitalhub/entities/artifact/utils.py +28 -13
- digitalhub/entities/dataitem/crud.py +14 -2
- digitalhub/entities/dataitem/table/entity.py +3 -3
- digitalhub/entities/dataitem/utils.py +84 -35
- digitalhub/entities/model/crud.py +4 -0
- digitalhub/entities/model/utils.py +28 -13
- digitalhub/entities/project/_base/entity.py +0 -2
- digitalhub/entities/run/_base/entity.py +2 -2
- digitalhub/entities/task/_base/models.py +12 -3
- digitalhub/entities/trigger/_base/entity.py +11 -0
- digitalhub/factory/factory.py +25 -3
- digitalhub/factory/utils.py +11 -3
- digitalhub/runtimes/_base.py +1 -1
- digitalhub/runtimes/builder.py +18 -1
- digitalhub/stores/client/__init__.py +12 -0
- digitalhub/stores/client/_base/api_builder.py +14 -0
- digitalhub/stores/client/_base/client.py +93 -0
- digitalhub/stores/client/_base/key_builder.py +28 -0
- digitalhub/stores/client/_base/params_builder.py +14 -0
- digitalhub/stores/client/api.py +10 -5
- digitalhub/stores/client/builder.py +3 -1
- digitalhub/stores/client/dhcore/api_builder.py +17 -0
- digitalhub/stores/client/dhcore/client.py +325 -70
- digitalhub/stores/client/dhcore/configurator.py +485 -193
- digitalhub/stores/client/dhcore/enums.py +3 -0
- digitalhub/stores/client/dhcore/error_parser.py +35 -1
- digitalhub/stores/client/dhcore/params_builder.py +113 -17
- digitalhub/stores/client/dhcore/utils.py +40 -22
- digitalhub/stores/client/local/api_builder.py +17 -0
- digitalhub/stores/client/local/client.py +6 -8
- digitalhub/stores/credentials/api.py +35 -0
- digitalhub/stores/credentials/configurator.py +210 -0
- digitalhub/stores/credentials/enums.py +68 -0
- digitalhub/stores/credentials/handler.py +176 -0
- digitalhub/stores/{configurator → credentials}/ini_module.py +60 -28
- digitalhub/stores/credentials/store.py +81 -0
- digitalhub/stores/data/_base/store.py +27 -9
- digitalhub/stores/data/api.py +49 -9
- digitalhub/stores/data/builder.py +90 -41
- digitalhub/stores/data/local/store.py +4 -7
- digitalhub/stores/data/remote/store.py +4 -7
- digitalhub/stores/data/s3/configurator.py +65 -80
- digitalhub/stores/data/s3/store.py +69 -81
- digitalhub/stores/data/s3/utils.py +10 -10
- digitalhub/stores/data/sql/configurator.py +76 -73
- digitalhub/stores/data/sql/store.py +191 -102
- digitalhub/utils/exceptions.py +6 -0
- digitalhub/utils/file_utils.py +53 -30
- digitalhub/utils/generic_utils.py +41 -33
- digitalhub/utils/git_utils.py +24 -14
- digitalhub/utils/io_utils.py +19 -18
- digitalhub/utils/uri_utils.py +31 -31
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/METADATA +1 -1
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/RECORD +71 -74
- digitalhub/entities/_commons/types.py +0 -9
- digitalhub/stores/configurator/api.py +0 -35
- digitalhub/stores/configurator/configurator.py +0 -202
- digitalhub/stores/configurator/credentials_store.py +0 -69
- digitalhub/stores/configurator/enums.py +0 -25
- digitalhub/stores/data/s3/enums.py +0 -20
- digitalhub/stores/data/sql/enums.py +0 -20
- digitalhub/stores/data/utils.py +0 -38
- /digitalhub/stores/{configurator → credentials}/__init__.py +0 -0
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/WHEEL +0 -0
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/licenses/AUTHORS +0 -0
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,92 +6,141 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import typing
|
|
8
8
|
|
|
9
|
-
from digitalhub.stores.configurator.api import get_current_env
|
|
10
9
|
from digitalhub.stores.data.local.store import LocalStore
|
|
11
10
|
from digitalhub.stores.data.remote.store import RemoteStore
|
|
11
|
+
from digitalhub.stores.data.s3.configurator import S3StoreConfigurator
|
|
12
12
|
from digitalhub.stores.data.s3.store import S3Store
|
|
13
|
+
from digitalhub.stores.data.sql.configurator import SqlStoreConfigurator
|
|
13
14
|
from digitalhub.stores.data.sql.store import SqlStore
|
|
14
15
|
from digitalhub.utils.uri_utils import SchemeCategory, map_uri_scheme
|
|
15
16
|
|
|
16
17
|
if typing.TYPE_CHECKING:
|
|
18
|
+
from digitalhub.stores.credentials.configurator import Configurator
|
|
17
19
|
from digitalhub.stores.data._base.store import Store
|
|
20
|
+
from digitalhub.utils.exceptions import StoreError
|
|
18
21
|
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
class StoreInfo:
|
|
21
24
|
"""
|
|
22
|
-
|
|
25
|
+
Container for store class and configurator information.
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
type : str
|
|
27
|
-
Store type.
|
|
27
|
+
Holds store class references and their associated configurators
|
|
28
|
+
for registration and instantiation in the store builder system.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Store
|
|
32
|
-
The store class.
|
|
30
|
+
Attributes
|
|
31
|
+
----------
|
|
32
|
+
_store : Store
|
|
33
|
+
The store class to be instantiated.
|
|
34
|
+
_configurator : Configurator or None
|
|
35
|
+
The configurator class for store configuration, if required.
|
|
33
36
|
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if type == SchemeCategory.REMOTE.value:
|
|
39
|
-
return RemoteStore
|
|
40
|
-
if type == SchemeCategory.SQL.value:
|
|
41
|
-
return SqlStore
|
|
42
|
-
raise ValueError(f"Unknown store type: {type}")
|
|
37
|
+
|
|
38
|
+
def __init__(self, store: Store, configurator: Configurator | None = None) -> None:
|
|
39
|
+
self._store = store
|
|
40
|
+
self._configurator = configurator
|
|
43
41
|
|
|
44
42
|
|
|
45
43
|
class StoreBuilder:
|
|
46
44
|
"""
|
|
47
|
-
Store
|
|
45
|
+
Store factory and registry for managing data store instances.
|
|
46
|
+
|
|
47
|
+
Provides registration, instantiation, and caching of data store
|
|
48
|
+
instances based on URI schemes. Supports various store types
|
|
49
|
+
including S3, SQL, local, and remote stores with their respective
|
|
50
|
+
configurators.
|
|
51
|
+
|
|
52
|
+
Attributes
|
|
53
|
+
----------
|
|
54
|
+
_builders : dict[str, StoreInfo]
|
|
55
|
+
Registry of store types mapped to their StoreInfo instances.
|
|
56
|
+
_instances : dict[str, Store]
|
|
57
|
+
Cache of instantiated store instances by store type.
|
|
48
58
|
"""
|
|
49
59
|
|
|
50
60
|
def __init__(self) -> None:
|
|
61
|
+
self._builders: dict[str, StoreInfo] = {}
|
|
51
62
|
self._instances: dict[str, dict[str, Store]] = {}
|
|
52
63
|
|
|
53
|
-
def
|
|
64
|
+
def register(
|
|
65
|
+
self,
|
|
66
|
+
store_type: str,
|
|
67
|
+
store: Store,
|
|
68
|
+
configurator: Configurator | None = None,
|
|
69
|
+
) -> None:
|
|
54
70
|
"""
|
|
55
|
-
|
|
71
|
+
Register a store type with its class and optional configurator.
|
|
72
|
+
|
|
73
|
+
Adds a new store type to the builder registry, associating it
|
|
74
|
+
with a store class and optional configurator for later instantiation.
|
|
56
75
|
|
|
57
76
|
Parameters
|
|
58
77
|
----------
|
|
59
78
|
store_type : str
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
The unique identifier for the store type (e.g., 's3', 'sql').
|
|
80
|
+
store : Store
|
|
81
|
+
The store class to register for this type.
|
|
82
|
+
configurator : Configurator, optional
|
|
83
|
+
The configurator class for store configuration.
|
|
84
|
+
If None, the store will be instantiated without configuration.
|
|
62
85
|
|
|
63
86
|
Returns
|
|
64
87
|
-------
|
|
65
88
|
None
|
|
89
|
+
|
|
90
|
+
Raises
|
|
91
|
+
------
|
|
92
|
+
StoreError
|
|
93
|
+
If the store type is already registered in the builder.
|
|
66
94
|
"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
if store_type not in self._builders:
|
|
96
|
+
self._builders[store_type] = StoreInfo(store, configurator)
|
|
97
|
+
else:
|
|
98
|
+
raise StoreError(f"Store type {store_type} already registered")
|
|
71
99
|
|
|
72
|
-
def get(self,
|
|
100
|
+
def get(self, uri: str) -> Store:
|
|
73
101
|
"""
|
|
74
|
-
Get a store instance
|
|
102
|
+
Get or create a store instance based on URI scheme.
|
|
103
|
+
|
|
104
|
+
Determines the appropriate store type from the URI scheme,
|
|
105
|
+
instantiates the store if not already cached, and returns
|
|
106
|
+
the store instance. Store instances are cached for reuse.
|
|
75
107
|
|
|
76
108
|
Parameters
|
|
77
109
|
----------
|
|
78
110
|
uri : str
|
|
79
|
-
URI to parse.
|
|
80
|
-
|
|
81
|
-
|
|
111
|
+
The URI to parse for determining the store type.
|
|
112
|
+
The scheme (e.g., 's3://', 'sql://') determines which
|
|
113
|
+
store type to instantiate.
|
|
82
114
|
|
|
83
115
|
Returns
|
|
84
116
|
-------
|
|
85
117
|
Store
|
|
86
|
-
The store instance.
|
|
118
|
+
The store instance appropriate for handling the given URI.
|
|
119
|
+
|
|
120
|
+
Raises
|
|
121
|
+
------
|
|
122
|
+
KeyError
|
|
123
|
+
If no store is registered for the URI scheme.
|
|
87
124
|
"""
|
|
88
|
-
env = get_current_env()
|
|
89
125
|
store_type = map_uri_scheme(uri)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
self.
|
|
94
|
-
|
|
126
|
+
|
|
127
|
+
# Build the store instance if not already present
|
|
128
|
+
if store_type not in self._instances:
|
|
129
|
+
store_info = self._builders[store_type]
|
|
130
|
+
store_cls = store_info._store
|
|
131
|
+
cfgrt_cls = store_info._configurator
|
|
132
|
+
|
|
133
|
+
if cfgrt_cls is None:
|
|
134
|
+
store = store_cls()
|
|
135
|
+
else:
|
|
136
|
+
store = store_cls(cfgrt_cls())
|
|
137
|
+
self._instances[store_type] = store
|
|
138
|
+
|
|
139
|
+
return self._instances[store_type]
|
|
95
140
|
|
|
96
141
|
|
|
97
142
|
store_builder = StoreBuilder()
|
|
143
|
+
store_builder.register(SchemeCategory.S3.value, S3Store, S3StoreConfigurator)
|
|
144
|
+
store_builder.register(SchemeCategory.SQL.value, SqlStore, SqlStoreConfigurator)
|
|
145
|
+
store_builder.register(SchemeCategory.LOCAL.value, LocalStore)
|
|
146
|
+
store_builder.register(SchemeCategory.REMOTE.value, RemoteStore)
|
|
@@ -29,7 +29,6 @@ class LocalStore(Store):
|
|
|
29
29
|
self,
|
|
30
30
|
root: str,
|
|
31
31
|
dst: Path,
|
|
32
|
-
src: list[str],
|
|
33
32
|
overwrite: bool = False,
|
|
34
33
|
) -> str:
|
|
35
34
|
"""
|
|
@@ -37,19 +36,17 @@ class LocalStore(Store):
|
|
|
37
36
|
|
|
38
37
|
Parameters
|
|
39
38
|
----------
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
src : str
|
|
40
|
+
Path of the material entity.
|
|
42
41
|
dst : str
|
|
43
|
-
The destination of the
|
|
44
|
-
src : list[str]
|
|
45
|
-
List of sources.
|
|
42
|
+
The destination of the material entity on local filesystem.
|
|
46
43
|
overwrite : bool
|
|
47
44
|
Specify if overwrite existing file(s).
|
|
48
45
|
|
|
49
46
|
Returns
|
|
50
47
|
-------
|
|
51
48
|
str
|
|
52
|
-
Destination path of the downloaded
|
|
49
|
+
Destination path of the downloaded files.
|
|
53
50
|
"""
|
|
54
51
|
raise StoreError("Local store does not support download.")
|
|
55
52
|
|
|
@@ -28,7 +28,6 @@ class RemoteStore(Store):
|
|
|
28
28
|
self,
|
|
29
29
|
root: str,
|
|
30
30
|
dst: Path,
|
|
31
|
-
src: list[str],
|
|
32
31
|
overwrite: bool = False,
|
|
33
32
|
) -> str:
|
|
34
33
|
"""
|
|
@@ -36,19 +35,17 @@ class RemoteStore(Store):
|
|
|
36
35
|
|
|
37
36
|
Parameters
|
|
38
37
|
----------
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
src : str
|
|
39
|
+
Path of the material entity.
|
|
41
40
|
dst : str
|
|
42
|
-
The destination of the
|
|
43
|
-
src : list[str]
|
|
44
|
-
List of sources.
|
|
41
|
+
The destination of the material entity on local filesystem.
|
|
45
42
|
overwrite : bool
|
|
46
43
|
Specify if overwrite existing file(s).
|
|
47
44
|
|
|
48
45
|
Returns
|
|
49
46
|
-------
|
|
50
47
|
str
|
|
51
|
-
Destination path of the downloaded
|
|
48
|
+
Destination path of the downloaded files.
|
|
52
49
|
"""
|
|
53
50
|
# Handle destination
|
|
54
51
|
if dst is None:
|
|
@@ -4,127 +4,112 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
7
9
|
from botocore.config import Config
|
|
8
10
|
|
|
9
|
-
from digitalhub.stores.
|
|
10
|
-
from digitalhub.stores.configurator
|
|
11
|
-
from digitalhub.stores.
|
|
12
|
-
from digitalhub.utils.exceptions import StoreError
|
|
11
|
+
from digitalhub.stores.client.dhcore.utils import refresh_token
|
|
12
|
+
from digitalhub.stores.credentials.configurator import Configurator
|
|
13
|
+
from digitalhub.stores.credentials.enums import CredsEnvVar
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
class S3StoreConfigurator:
|
|
16
|
+
class S3StoreConfigurator(Configurator):
|
|
16
17
|
"""
|
|
17
18
|
Configure the store by getting the credentials from user
|
|
18
19
|
provided config or from environment.
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
keys = [
|
|
23
|
+
CredsEnvVar.S3_ENDPOINT_URL.value,
|
|
24
|
+
CredsEnvVar.S3_ACCESS_KEY_ID.value,
|
|
25
|
+
CredsEnvVar.S3_SECRET_ACCESS_KEY.value,
|
|
26
|
+
CredsEnvVar.S3_REGION.value,
|
|
27
|
+
CredsEnvVar.S3_SIGNATURE_VERSION.value,
|
|
28
|
+
CredsEnvVar.S3_SESSION_TOKEN.value,
|
|
29
|
+
CredsEnvVar.S3_PATH_STYLE.value,
|
|
30
|
+
CredsEnvVar.S3_CREDENTIALS_EXPIRATION.value,
|
|
25
31
|
]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
required_keys = [
|
|
33
|
+
CredsEnvVar.S3_ENDPOINT_URL.value,
|
|
34
|
+
CredsEnvVar.S3_ACCESS_KEY_ID.value,
|
|
35
|
+
CredsEnvVar.S3_SECRET_ACCESS_KEY.value,
|
|
30
36
|
]
|
|
31
37
|
|
|
38
|
+
def __init__(self):
|
|
39
|
+
super().__init__()
|
|
40
|
+
self.load_configs()
|
|
41
|
+
|
|
32
42
|
##############################
|
|
33
43
|
# Configuration methods
|
|
34
44
|
##############################
|
|
35
45
|
|
|
36
|
-
def
|
|
46
|
+
def load_env_vars(self) -> None:
|
|
37
47
|
"""
|
|
38
|
-
|
|
39
|
-
session token and other config).
|
|
40
|
-
|
|
41
|
-
Parameters
|
|
42
|
-
----------
|
|
43
|
-
origin : str
|
|
44
|
-
The origin of the credentials.
|
|
48
|
+
Loads the credentials from the environment variables.
|
|
45
49
|
|
|
46
50
|
Returns
|
|
47
51
|
-------
|
|
48
|
-
|
|
49
|
-
The credentials.
|
|
52
|
+
None
|
|
50
53
|
"""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
elif origin == CredsOrigin.FILE.value:
|
|
54
|
-
creds = self._get_file_config()
|
|
55
|
-
else:
|
|
56
|
-
raise StoreError(f"Unknown origin: {origin}")
|
|
57
|
-
return {
|
|
58
|
-
"endpoint_url": creds[S3StoreEnv.ENDPOINT_URL.value],
|
|
59
|
-
"aws_access_key_id": creds[S3StoreEnv.ACCESS_KEY_ID.value],
|
|
60
|
-
"aws_secret_access_key": creds[S3StoreEnv.SECRET_ACCESS_KEY.value],
|
|
61
|
-
"aws_session_token": creds[S3StoreEnv.SESSION_TOKEN.value],
|
|
62
|
-
"config": Config(
|
|
63
|
-
region_name=creds[S3StoreEnv.REGION.value],
|
|
64
|
-
signature_version=creds[S3StoreEnv.SIGNATURE_VERSION.value],
|
|
65
|
-
),
|
|
66
|
-
}
|
|
54
|
+
env_creds = self._creds_handler.load_from_env(self.keys)
|
|
55
|
+
self._creds_handler.set_credentials(self._env, env_creds)
|
|
67
56
|
|
|
68
|
-
def
|
|
57
|
+
def load_file_vars(self) -> None:
|
|
69
58
|
"""
|
|
70
|
-
|
|
59
|
+
Loads the credentials from a file.
|
|
71
60
|
|
|
72
61
|
Returns
|
|
73
62
|
-------
|
|
74
|
-
|
|
75
|
-
The credentials.
|
|
63
|
+
None
|
|
76
64
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
self._set_credentials(credentials)
|
|
81
|
-
return credentials
|
|
65
|
+
file_creds = self._creds_handler.load_from_file(self.keys)
|
|
66
|
+
self._creds_handler.set_credentials(self._file, file_creds)
|
|
82
67
|
|
|
83
|
-
def
|
|
68
|
+
def get_client_config(self) -> dict:
|
|
84
69
|
"""
|
|
85
|
-
|
|
70
|
+
Gets S3 credentials (access key, secret key, session token, and other config).
|
|
86
71
|
|
|
87
72
|
Returns
|
|
88
73
|
-------
|
|
89
74
|
dict
|
|
90
|
-
|
|
75
|
+
Dictionary containing S3 credentials and configuration.
|
|
91
76
|
"""
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
creds = self.get_credentials(self._origin)
|
|
78
|
+
expired = creds[CredsEnvVar.S3_CREDENTIALS_EXPIRATION.value]
|
|
79
|
+
if self._origin == self._file and self._is_expired(expired):
|
|
80
|
+
refresh_token()
|
|
81
|
+
self.load_file_vars()
|
|
82
|
+
creds = self.get_credentials(self._origin)
|
|
83
|
+
return {
|
|
84
|
+
"endpoint_url": creds[CredsEnvVar.S3_ENDPOINT_URL.value],
|
|
85
|
+
"aws_access_key_id": creds[CredsEnvVar.S3_ACCESS_KEY_ID.value],
|
|
86
|
+
"aws_secret_access_key": creds[CredsEnvVar.S3_SECRET_ACCESS_KEY.value],
|
|
87
|
+
"aws_session_token": creds[CredsEnvVar.S3_SESSION_TOKEN.value],
|
|
88
|
+
"config": Config(
|
|
89
|
+
region_name=creds[CredsEnvVar.S3_REGION.value],
|
|
90
|
+
signature_version=creds[CredsEnvVar.S3_SIGNATURE_VERSION.value],
|
|
91
|
+
),
|
|
94
92
|
}
|
|
95
|
-
self._set_credentials(credentials)
|
|
96
|
-
return credentials
|
|
97
|
-
|
|
98
|
-
def _check_credentials(self, credentials: dict) -> None:
|
|
99
|
-
"""
|
|
100
|
-
Check for missing credentials.
|
|
101
|
-
|
|
102
|
-
Parameters
|
|
103
|
-
----------
|
|
104
|
-
credentials : dict
|
|
105
|
-
The credentials.
|
|
106
93
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
None
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _is_expired(timestamp: str | None) -> bool:
|
|
110
96
|
"""
|
|
111
|
-
|
|
112
|
-
if missing_vars:
|
|
113
|
-
raise StoreError(f"Missing credentials for S3 store: {', '.join(missing_vars)}")
|
|
114
|
-
|
|
115
|
-
def _set_credentials(self, credentials: dict) -> None:
|
|
116
|
-
"""
|
|
117
|
-
Set the store credentials into the configurator.
|
|
97
|
+
Determines whether a given timestamp is after the current UTC time.
|
|
118
98
|
|
|
119
99
|
Parameters
|
|
120
100
|
----------
|
|
121
|
-
|
|
122
|
-
|
|
101
|
+
timestamp : str or None
|
|
102
|
+
Timestamp string in the format 'YYYY-MM-DDTHH:MM:SSZ'.
|
|
123
103
|
|
|
124
104
|
Returns
|
|
125
105
|
-------
|
|
126
|
-
|
|
106
|
+
bool
|
|
107
|
+
True if the given timestamp is later than the current UTC time,
|
|
108
|
+
otherwise False.
|
|
127
109
|
"""
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
110
|
+
if timestamp is None:
|
|
111
|
+
return False
|
|
112
|
+
dt = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")
|
|
113
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
114
|
+
now = datetime.now(timezone.utc) + datetime.timedelta(seconds=120)
|
|
115
|
+
return dt < now
|