ob-metaflow 2.10.7.4__py2.py3-none-any.whl → 2.10.9.1__py2.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 ob-metaflow might be problematic. Click here for more details.
- metaflow/cards.py +2 -0
- metaflow/decorators.py +1 -1
- metaflow/metaflow_config.py +2 -0
- metaflow/plugins/__init__.py +4 -0
- metaflow/plugins/airflow/airflow_cli.py +1 -1
- metaflow/plugins/argo/argo_workflows_cli.py +1 -1
- metaflow/plugins/aws/aws_utils.py +1 -1
- metaflow/plugins/aws/batch/batch.py +4 -0
- metaflow/plugins/aws/batch/batch_cli.py +3 -0
- metaflow/plugins/aws/batch/batch_client.py +40 -11
- metaflow/plugins/aws/batch/batch_decorator.py +1 -0
- metaflow/plugins/aws/step_functions/step_functions.py +1 -0
- metaflow/plugins/aws/step_functions/step_functions_cli.py +1 -1
- metaflow/plugins/azure/azure_exceptions.py +1 -1
- metaflow/plugins/cards/card_cli.py +413 -28
- metaflow/plugins/cards/card_client.py +16 -7
- metaflow/plugins/cards/card_creator.py +228 -0
- metaflow/plugins/cards/card_datastore.py +124 -26
- metaflow/plugins/cards/card_decorator.py +40 -86
- metaflow/plugins/cards/card_modules/base.html +12 -0
- metaflow/plugins/cards/card_modules/basic.py +74 -8
- metaflow/plugins/cards/card_modules/bundle.css +1 -170
- metaflow/plugins/cards/card_modules/card.py +65 -0
- metaflow/plugins/cards/card_modules/components.py +446 -81
- metaflow/plugins/cards/card_modules/convert_to_native_type.py +9 -3
- metaflow/plugins/cards/card_modules/main.js +250 -21
- metaflow/plugins/cards/card_modules/test_cards.py +117 -0
- metaflow/plugins/cards/card_resolver.py +0 -2
- metaflow/plugins/cards/card_server.py +361 -0
- metaflow/plugins/cards/component_serializer.py +506 -42
- metaflow/plugins/cards/exception.py +20 -1
- metaflow/plugins/datastores/azure_storage.py +1 -2
- metaflow/plugins/datastores/gs_storage.py +1 -2
- metaflow/plugins/datastores/s3_storage.py +2 -1
- metaflow/plugins/datatools/s3/s3.py +24 -11
- metaflow/plugins/env_escape/client.py +2 -12
- metaflow/plugins/env_escape/client_modules.py +18 -14
- metaflow/plugins/env_escape/server.py +18 -11
- metaflow/plugins/env_escape/utils.py +12 -0
- metaflow/plugins/gcp/gs_exceptions.py +1 -1
- metaflow/plugins/gcp/gs_utils.py +1 -1
- metaflow/plugins/pypi/conda_environment.py +5 -6
- metaflow/plugins/pypi/pip.py +2 -2
- metaflow/plugins/pypi/utils.py +15 -0
- metaflow/task.py +1 -0
- metaflow/version.py +1 -1
- {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/METADATA +1 -1
- {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/RECORD +52 -50
- {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/LICENSE +0 -0
- {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/WHEEL +0 -0
- {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/entry_points.txt +0 -0
- {ob_metaflow-2.10.7.4.dist-info → ob_metaflow-2.10.9.1.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
from .card_modules import MetaflowCardComponent
|
|
2
2
|
from .card_modules.basic import ErrorComponent, SectionComponent
|
|
3
|
-
from .card_modules.components import
|
|
3
|
+
from .card_modules.components import (
|
|
4
|
+
UserComponent,
|
|
5
|
+
create_component_id,
|
|
6
|
+
StubComponent,
|
|
7
|
+
)
|
|
8
|
+
from .exception import ComponentOverwriteNotSupportedException
|
|
9
|
+
from metaflow.metaflow_config import RUNTIME_CARD_RENDER_INTERVAL
|
|
4
10
|
import uuid
|
|
5
11
|
import json
|
|
12
|
+
import platform
|
|
13
|
+
from collections import OrderedDict
|
|
14
|
+
import time
|
|
6
15
|
|
|
7
16
|
_TYPE = type
|
|
8
17
|
|
|
@@ -16,11 +25,374 @@ def get_card_class(card_type):
|
|
|
16
25
|
return filtered_cards[0]
|
|
17
26
|
|
|
18
27
|
|
|
28
|
+
def _component_is_valid(component):
|
|
29
|
+
"""
|
|
30
|
+
Validates if the component is of the correct class.
|
|
31
|
+
"""
|
|
32
|
+
return issubclass(type(component), MetaflowCardComponent)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def warning_message(message, logger=None, ts=False):
|
|
36
|
+
if logger:
|
|
37
|
+
msg = "[@card WARNING] %s" % message
|
|
38
|
+
logger(msg, timestamp=ts, bad=True)
|
|
39
|
+
|
|
40
|
+
|
|
19
41
|
class WarningComponent(ErrorComponent):
|
|
20
42
|
def __init__(self, warning_message):
|
|
21
43
|
super().__init__("@card WARNING", warning_message)
|
|
22
44
|
|
|
23
45
|
|
|
46
|
+
class ComponentStore:
|
|
47
|
+
"""
|
|
48
|
+
The `ComponentStore` object helps store the components for a single card in memory.
|
|
49
|
+
This class has combination of a array/dictionary like interfaces to access/change the stored components.
|
|
50
|
+
|
|
51
|
+
It exposes the `append` /`extend` methods (like an array) to add components.
|
|
52
|
+
It also exposes the `__getitem__`/`__setitem__` methods (like a dictionary) to access the components by their Ids.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def _set_component_map(self):
|
|
56
|
+
"""
|
|
57
|
+
The `_component_map` attribute is supposed to be a dictionary so that we can access the components by their ids.
|
|
58
|
+
But we also want to maintain order in which components are inserted since all of these components are going to be visible on a UI.
|
|
59
|
+
Since python3.6 dictionaries are ordered by default so we can use the default python `dict`.
|
|
60
|
+
For python3.5 and below we need to use an OrderedDict since `dict`'s are not ordered by default.
|
|
61
|
+
"""
|
|
62
|
+
python_version = int(platform.python_version_tuple()[0]) * 10 + int(
|
|
63
|
+
platform.python_version_tuple()[1]
|
|
64
|
+
)
|
|
65
|
+
if python_version < 36:
|
|
66
|
+
self._component_map = OrderedDict()
|
|
67
|
+
else:
|
|
68
|
+
self._component_map = {}
|
|
69
|
+
|
|
70
|
+
def __init__(self, logger, card_type=None, components=None, user_set_id=None):
|
|
71
|
+
self._logger = logger
|
|
72
|
+
self._card_type = card_type
|
|
73
|
+
self._user_set_id = user_set_id
|
|
74
|
+
self._layout_last_changed_on = time.time()
|
|
75
|
+
self._set_component_map()
|
|
76
|
+
if components is not None:
|
|
77
|
+
for c in list(components):
|
|
78
|
+
self._store_component(c, component_id=None)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def layout_last_changed_on(self):
|
|
82
|
+
"""This property helps the CardComponentManager identify when the layout of the card has changed so that it can trigger a re-render of the card."""
|
|
83
|
+
return self._layout_last_changed_on
|
|
84
|
+
|
|
85
|
+
def _realtime_updateable_components(self):
|
|
86
|
+
for c in self._component_map.values():
|
|
87
|
+
if c.REALTIME_UPDATABLE:
|
|
88
|
+
yield c
|
|
89
|
+
|
|
90
|
+
def _store_component(self, component, component_id=None):
|
|
91
|
+
if not _component_is_valid(component):
|
|
92
|
+
warning_message(
|
|
93
|
+
"Component (%s) is not a valid MetaflowCardComponent. It will not be stored."
|
|
94
|
+
% str(component),
|
|
95
|
+
self._logger,
|
|
96
|
+
)
|
|
97
|
+
return
|
|
98
|
+
if component_id is not None:
|
|
99
|
+
component.component_id = component_id
|
|
100
|
+
elif component.component_id is None:
|
|
101
|
+
component.component_id = create_component_id(component)
|
|
102
|
+
setattr(component, "_logger", self._logger)
|
|
103
|
+
self._component_map[component.component_id] = component
|
|
104
|
+
self._layout_last_changed_on = time.time()
|
|
105
|
+
|
|
106
|
+
def _remove_component(self, component_id):
|
|
107
|
+
del self._component_map[component_id]
|
|
108
|
+
self._layout_last_changed_on = time.time()
|
|
109
|
+
|
|
110
|
+
def __iter__(self):
|
|
111
|
+
return iter(self._component_map.values())
|
|
112
|
+
|
|
113
|
+
def __setitem__(self, key, value):
|
|
114
|
+
if self._component_map.get(key) is not None:
|
|
115
|
+
# This is the equivalent of calling `current.card.components["mycomponent"] = Markdown("## New Component")`
|
|
116
|
+
# We don't support the replacement of individual components in the card.
|
|
117
|
+
# Instead we support rewriting the entire component array instead.
|
|
118
|
+
# So users can run `current.card[ID] = [FirstComponent, SecondComponent]` which will instantiate an entirely
|
|
119
|
+
# new ComponentStore.
|
|
120
|
+
# So we should throw an error over here, since it is clearly an operation which is not supported.
|
|
121
|
+
raise ComponentOverwriteNotSupportedException(
|
|
122
|
+
key, self._user_set_id, self._card_type
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
self._store_component(value, component_id=key)
|
|
126
|
+
|
|
127
|
+
def __getitem__(self, key):
|
|
128
|
+
if key not in self._component_map:
|
|
129
|
+
# Store a stub-component in place since `key` doesnt exist.
|
|
130
|
+
# If the user does a `current.card.append(component, id=key)`
|
|
131
|
+
# then the stub component will be replaced by the actual component.
|
|
132
|
+
self._store_component(StubComponent(key), component_id=key)
|
|
133
|
+
return self._component_map[key]
|
|
134
|
+
|
|
135
|
+
def __delitem__(self, key):
|
|
136
|
+
if key not in self._component_map:
|
|
137
|
+
raise KeyError(
|
|
138
|
+
"MetaflowCardComponent with id `%s` not found. Available components for the cards include : %s"
|
|
139
|
+
% (key, ", ".join(self.keys()))
|
|
140
|
+
)
|
|
141
|
+
self._remove_component(key)
|
|
142
|
+
|
|
143
|
+
def __contains__(self, key):
|
|
144
|
+
return key in self._component_map
|
|
145
|
+
|
|
146
|
+
def append(self, component, id=None):
|
|
147
|
+
self._store_component(component, component_id=id)
|
|
148
|
+
|
|
149
|
+
def extend(self, components):
|
|
150
|
+
for c in components:
|
|
151
|
+
self._store_component(c, component_id=None)
|
|
152
|
+
|
|
153
|
+
def clear(self):
|
|
154
|
+
self._component_map.clear()
|
|
155
|
+
|
|
156
|
+
def keys(self):
|
|
157
|
+
return list(self._component_map.keys())
|
|
158
|
+
|
|
159
|
+
def values(self):
|
|
160
|
+
return self._component_map.values()
|
|
161
|
+
|
|
162
|
+
def __str__(self):
|
|
163
|
+
return "Card components present in the card: `%s` " % ("`, `".join(self.keys()))
|
|
164
|
+
|
|
165
|
+
def __len__(self):
|
|
166
|
+
return len(self._component_map)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _object_is_json_serializable(obj):
|
|
170
|
+
try:
|
|
171
|
+
json.dumps(obj)
|
|
172
|
+
return True
|
|
173
|
+
except TypeError as e:
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class CardComponentManager:
|
|
178
|
+
"""
|
|
179
|
+
This class manages the card's state for a single card.
|
|
180
|
+
- It uses the `ComponentStore` to manage the storage of the components
|
|
181
|
+
- It exposes methods to add, remove and access the components.
|
|
182
|
+
- It exposes a `refresh` method that will allow refreshing a card with new data
|
|
183
|
+
for realtime(ish) updates.
|
|
184
|
+
- The `CardComponentCollector` exposes convenience methods similar to this class for a default
|
|
185
|
+
editable card. These methods include :
|
|
186
|
+
- `append`
|
|
187
|
+
- `extend`
|
|
188
|
+
- `clear`
|
|
189
|
+
- `refresh`
|
|
190
|
+
- `components`
|
|
191
|
+
- `__iter__`
|
|
192
|
+
|
|
193
|
+
## Usage Patterns :
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
current.card["mycardid"].append(component, id="comp123")
|
|
197
|
+
current.card["mycardid"].extend([component])
|
|
198
|
+
current.card["mycardid"].refresh(data) # refreshes the card with new data
|
|
199
|
+
current.card["mycardid"].components["comp123"] # returns the component with id "comp123"
|
|
200
|
+
current.card["mycardid"].components["comp123"].update()
|
|
201
|
+
current.card["mycardid"].components.clear() # Wipe all the components
|
|
202
|
+
del current.card["mycardid"].components["mycomponentid"] # Delete a component
|
|
203
|
+
```
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
card_uuid,
|
|
209
|
+
decorator_attributes,
|
|
210
|
+
card_creator,
|
|
211
|
+
components=None,
|
|
212
|
+
logger=None,
|
|
213
|
+
no_warnings=False,
|
|
214
|
+
user_set_card_id=None,
|
|
215
|
+
runtime_card=False,
|
|
216
|
+
card_options=None,
|
|
217
|
+
refresh_interval=5,
|
|
218
|
+
):
|
|
219
|
+
self._card_creator_args = dict(
|
|
220
|
+
card_uuid=card_uuid,
|
|
221
|
+
user_set_card_id=user_set_card_id,
|
|
222
|
+
runtime_card=runtime_card,
|
|
223
|
+
decorator_attributes=decorator_attributes,
|
|
224
|
+
card_options=card_options,
|
|
225
|
+
logger=logger,
|
|
226
|
+
)
|
|
227
|
+
self._card_creator = card_creator
|
|
228
|
+
self._refresh_interval = refresh_interval
|
|
229
|
+
self._last_layout_change = None
|
|
230
|
+
self._latest_user_data = None
|
|
231
|
+
self._last_refresh = 0
|
|
232
|
+
self._last_render = 0
|
|
233
|
+
self._render_seq = 0
|
|
234
|
+
self._logger = logger
|
|
235
|
+
self._no_warnings = no_warnings
|
|
236
|
+
self._warn_once = {
|
|
237
|
+
"update": {},
|
|
238
|
+
"not_implemented": {},
|
|
239
|
+
}
|
|
240
|
+
card_type = decorator_attributes["type"]
|
|
241
|
+
|
|
242
|
+
if components is None:
|
|
243
|
+
self._components = ComponentStore(
|
|
244
|
+
logger=self._logger,
|
|
245
|
+
card_type=card_type,
|
|
246
|
+
user_set_id=user_set_card_id,
|
|
247
|
+
components=None,
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
self._components = ComponentStore(
|
|
251
|
+
logger=self._logger,
|
|
252
|
+
card_type=card_type,
|
|
253
|
+
user_set_id=user_set_card_id,
|
|
254
|
+
components=list(components),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def append(self, component, id=None):
|
|
258
|
+
self._components.append(component, id=id)
|
|
259
|
+
|
|
260
|
+
def extend(self, components):
|
|
261
|
+
self._components.extend(components)
|
|
262
|
+
|
|
263
|
+
def clear(self):
|
|
264
|
+
self._components.clear()
|
|
265
|
+
|
|
266
|
+
def _card_proc(self, mode, sync=False):
|
|
267
|
+
self._card_creator.create(**self._card_creator_args, mode=mode, sync=sync)
|
|
268
|
+
|
|
269
|
+
def refresh(self, data=None, force=False):
|
|
270
|
+
self._latest_user_data = data
|
|
271
|
+
nu = time.time()
|
|
272
|
+
first_render = True if self._last_render == 0 else False
|
|
273
|
+
|
|
274
|
+
if nu - self._last_refresh < self._refresh_interval:
|
|
275
|
+
# rate limit refreshes: silently ignore requests that
|
|
276
|
+
# happen too frequently
|
|
277
|
+
return
|
|
278
|
+
self._last_refresh = nu
|
|
279
|
+
|
|
280
|
+
# This block of code will render the card in `render_runtime` mode when:
|
|
281
|
+
# 1. refresh is called with `force=True`
|
|
282
|
+
# 2. Layout of the components in the card has changed. i.e. The actual elements in the component array have changed.
|
|
283
|
+
# 3. The last time the card was rendered was more the minimum interval after which they should be rendered.
|
|
284
|
+
last_rendered_before_minimum_interval = (
|
|
285
|
+
nu - self._last_refresh
|
|
286
|
+
) > RUNTIME_CARD_RENDER_INTERVAL
|
|
287
|
+
layout_has_changed = (
|
|
288
|
+
self._last_layout_change != self.components.layout_last_changed_on
|
|
289
|
+
or self._last_layout_change is None
|
|
290
|
+
)
|
|
291
|
+
if force or last_rendered_before_minimum_interval or layout_has_changed:
|
|
292
|
+
self._render_seq += 1
|
|
293
|
+
self._last_render = nu
|
|
294
|
+
self._card_proc("render_runtime")
|
|
295
|
+
# The below `if not first_render` condition is a special case for the following scenario:
|
|
296
|
+
# Lets assume the case that the user is only doing `current.card.append` followed by `refresh`.
|
|
297
|
+
# In this case, there will be no process executed in `refresh` mode since `layout_has_changed`
|
|
298
|
+
# will always be true and as a result there will be no data update that informs the UI of the RELOAD_TOKEN change.
|
|
299
|
+
# This will cause the UI to seek for the data update object but will constantly find None. So if it is not
|
|
300
|
+
# the first render then we should also have a `refresh` call followed by a `render_runtime` call so
|
|
301
|
+
# that the UI can always be updated with the latest data.
|
|
302
|
+
if not first_render:
|
|
303
|
+
# For the general case, the CardCreator's ProcessManager run's the `refresh` / `render_runtime` in a asynchronous manner.
|
|
304
|
+
# Due to this when the `render_runtime` call is happening, an immediately subsequent call to `refresh` will not be able to
|
|
305
|
+
# execute since the card-process manager will be busy executing the `render_runtime` call and ignore the `refresh` call.
|
|
306
|
+
# Hence we need to pass the `sync=True` argument to the `refresh` call so that the `refresh` call is executed synchronously and waits for the
|
|
307
|
+
# `render_runtime` call to finish.
|
|
308
|
+
self._card_proc("refresh", sync=True)
|
|
309
|
+
# We set self._last_layout_change so that when self._last_layout_change is not the same
|
|
310
|
+
# as `self.components.layout_last_changed_on`, then the component array itself
|
|
311
|
+
# has been modified. So we should force a re-render of the card.
|
|
312
|
+
self._last_layout_change = self.components.layout_last_changed_on
|
|
313
|
+
else:
|
|
314
|
+
self._card_proc("refresh")
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def components(self):
|
|
318
|
+
return self._components
|
|
319
|
+
|
|
320
|
+
def _warning(self, message):
|
|
321
|
+
msg = "[@card WARNING] %s" % message
|
|
322
|
+
self._logger(msg, timestamp=False, bad=True)
|
|
323
|
+
|
|
324
|
+
def _get_latest_data(self, final=False, mode=None):
|
|
325
|
+
"""
|
|
326
|
+
This function returns the data object that is passed down to :
|
|
327
|
+
- `MetaflowCard.render_runtime`
|
|
328
|
+
- `MetaflowCard.refresh`
|
|
329
|
+
- `MetaflowCard.reload_content_token`
|
|
330
|
+
|
|
331
|
+
The return value of this function contains all the necessary state information for Metaflow Cards to make decisions on the following:
|
|
332
|
+
1. What components are rendered
|
|
333
|
+
2. Should the card be reloaded on the UI
|
|
334
|
+
3. What data to pass down to the card.
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
final : bool, optional
|
|
339
|
+
If True, it implies that the final "rendering" sequence is taking place (which involves calling a `render` and a `refresh` function.)
|
|
340
|
+
When final is set the `render_seq` is set to "final" so that the reload token in the card is set to final
|
|
341
|
+
and the card is not reloaded again on the user interface.
|
|
342
|
+
mode : str
|
|
343
|
+
This parameter is passed down to the object returned by this function. Can be one of `render_runtime` / `refresh` / `render`
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
dict
|
|
348
|
+
A dictionary of the form :
|
|
349
|
+
```python
|
|
350
|
+
{
|
|
351
|
+
"user": user_data, # any passed to `current.card.refresh` function
|
|
352
|
+
"components": component_dict, # all rendered REALTIME_UPDATABLE components
|
|
353
|
+
"render_seq": seq,
|
|
354
|
+
# `render_seq` is a counter that is incremented every time `render_runtime` is called.
|
|
355
|
+
# If a metaflow card has a RELOAD_POLICY_ALWAYS set then the reload token will be set to this value
|
|
356
|
+
# so that the card reload on the UI everytime `render_runtime` is called.
|
|
357
|
+
"component_update_ts": self.components.layout_last_changed_on,
|
|
358
|
+
# `component_update_ts` is the timestamp of the last time the component array was modified.
|
|
359
|
+
# `component_update_ts` can get used by the `reload_content_token` to make decisions on weather to
|
|
360
|
+
# reload the card on the UI when component array has changed.
|
|
361
|
+
"mode": mode,
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
"""
|
|
365
|
+
seq = "final" if final else self._render_seq
|
|
366
|
+
# Extract all the runtime-updatable components as a dictionary
|
|
367
|
+
component_dict = {}
|
|
368
|
+
for component in self._components._realtime_updateable_components():
|
|
369
|
+
rendered_comp = _render_card_component(component)
|
|
370
|
+
if rendered_comp is not None:
|
|
371
|
+
component_dict.update({component.component_id: rendered_comp})
|
|
372
|
+
|
|
373
|
+
# Verify _latest_user_data is json serializable
|
|
374
|
+
user_data = {}
|
|
375
|
+
if self._latest_user_data is not None and not _object_is_json_serializable(
|
|
376
|
+
self._latest_user_data
|
|
377
|
+
):
|
|
378
|
+
self._warning(
|
|
379
|
+
"Data provided to `refresh` is not JSON serializable. It will be ignored."
|
|
380
|
+
)
|
|
381
|
+
else:
|
|
382
|
+
user_data = self._latest_user_data
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
"user": user_data,
|
|
386
|
+
"components": component_dict,
|
|
387
|
+
"render_seq": seq,
|
|
388
|
+
"component_update_ts": self.components.layout_last_changed_on,
|
|
389
|
+
"mode": mode,
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
def __iter__(self):
|
|
393
|
+
return iter(self._components)
|
|
394
|
+
|
|
395
|
+
|
|
24
396
|
class CardComponentCollector:
|
|
25
397
|
"""
|
|
26
398
|
This class helps collect `MetaflowCardComponent`s during runtime execution
|
|
@@ -42,25 +414,34 @@ class CardComponentCollector:
|
|
|
42
414
|
- [x] by looking it up by its type, e.g. `current.card.get(type='pytorch')`.
|
|
43
415
|
"""
|
|
44
416
|
|
|
45
|
-
def __init__(self, logger=None):
|
|
417
|
+
def __init__(self, logger=None, card_creator=None):
|
|
46
418
|
from metaflow.metaflow_config import CARD_NO_WARNING
|
|
47
419
|
|
|
48
|
-
self.
|
|
420
|
+
self._card_component_store = (
|
|
421
|
+
# Each key in the dictionary is the UUID of an individual card.
|
|
422
|
+
# value is of type `CardComponentManager`, holding a list of MetaflowCardComponents for that particular card
|
|
49
423
|
{}
|
|
50
|
-
)
|
|
424
|
+
)
|
|
51
425
|
self._cards_meta = (
|
|
52
426
|
{}
|
|
53
427
|
) # a `dict` of (card_uuid, `dict)` holding all metadata about all @card decorators on the `current` @step.
|
|
54
428
|
self._card_id_map = {} # card_id to uuid map for all cards with ids
|
|
55
429
|
self._logger = logger
|
|
430
|
+
self._card_creator = card_creator
|
|
56
431
|
# `self._default_editable_card` holds the uuid of the card that is default editable. This card has access to `append`/`extend` methods of `self`
|
|
57
432
|
self._default_editable_card = None
|
|
58
|
-
self._warned_once = {
|
|
433
|
+
self._warned_once = {
|
|
434
|
+
"__getitem__": {},
|
|
435
|
+
"append": False,
|
|
436
|
+
"extend": False,
|
|
437
|
+
"update": False,
|
|
438
|
+
"update_no_id": False,
|
|
439
|
+
}
|
|
59
440
|
self._no_warnings = True if CARD_NO_WARNING else False
|
|
60
441
|
|
|
61
442
|
@staticmethod
|
|
62
443
|
def create_uuid():
|
|
63
|
-
return str(uuid.uuid4())
|
|
444
|
+
return str(uuid.uuid4()).replace("-", "")
|
|
64
445
|
|
|
65
446
|
def _log(self, *args, **kwargs):
|
|
66
447
|
if self._logger:
|
|
@@ -70,9 +451,13 @@ class CardComponentCollector:
|
|
|
70
451
|
self,
|
|
71
452
|
card_type,
|
|
72
453
|
card_id,
|
|
454
|
+
decorator_attributes,
|
|
455
|
+
card_options,
|
|
73
456
|
editable=False,
|
|
74
457
|
customize=False,
|
|
75
458
|
suppress_warnings=False,
|
|
459
|
+
runtime_card=False,
|
|
460
|
+
refresh_interval=5,
|
|
76
461
|
):
|
|
77
462
|
"""
|
|
78
463
|
This function helps collect cards from all the card decorators.
|
|
@@ -95,9 +480,24 @@ class CardComponentCollector:
|
|
|
95
480
|
editable=editable,
|
|
96
481
|
customize=customize,
|
|
97
482
|
suppress_warnings=suppress_warnings,
|
|
483
|
+
runtime_card=runtime_card,
|
|
484
|
+
decorator_attributes=decorator_attributes,
|
|
485
|
+
card_options=card_options,
|
|
486
|
+
refresh_interval=refresh_interval,
|
|
98
487
|
)
|
|
99
488
|
self._cards_meta[card_uuid] = card_metadata
|
|
100
|
-
self.
|
|
489
|
+
self._card_component_store[card_uuid] = CardComponentManager(
|
|
490
|
+
card_uuid,
|
|
491
|
+
decorator_attributes,
|
|
492
|
+
self._card_creator,
|
|
493
|
+
components=None,
|
|
494
|
+
logger=self._logger,
|
|
495
|
+
no_warnings=self._no_warnings,
|
|
496
|
+
user_set_card_id=card_id,
|
|
497
|
+
runtime_card=runtime_card,
|
|
498
|
+
card_options=card_options,
|
|
499
|
+
refresh_interval=refresh_interval,
|
|
500
|
+
)
|
|
101
501
|
return card_metadata
|
|
102
502
|
|
|
103
503
|
def _warning(self, message):
|
|
@@ -107,9 +507,9 @@ class CardComponentCollector:
|
|
|
107
507
|
def _add_warning_to_cards(self, warn_msg):
|
|
108
508
|
if self._no_warnings:
|
|
109
509
|
return
|
|
110
|
-
for card_id in self.
|
|
510
|
+
for card_id in self._card_component_store:
|
|
111
511
|
if not self._cards_meta[card_id]["suppress_warnings"]:
|
|
112
|
-
self.
|
|
512
|
+
self._card_component_store[card_id].append(WarningComponent(warn_msg))
|
|
113
513
|
|
|
114
514
|
def get(self, type=None):
|
|
115
515
|
"""`get`
|
|
@@ -128,7 +528,7 @@ class CardComponentCollector:
|
|
|
128
528
|
for card_meta in self._cards_meta.values()
|
|
129
529
|
if card_meta["type"] == card_type
|
|
130
530
|
]
|
|
131
|
-
return [self.
|
|
531
|
+
return [self._card_component_store[uuid] for uuid in card_uuids]
|
|
132
532
|
|
|
133
533
|
def _finalize(self):
|
|
134
534
|
"""
|
|
@@ -229,13 +629,13 @@ class CardComponentCollector:
|
|
|
229
629
|
|
|
230
630
|
Returns
|
|
231
631
|
-------
|
|
232
|
-
|
|
632
|
+
CardComponentManager
|
|
233
633
|
An object with `append` and `extend` calls which allow you to
|
|
234
634
|
add components to the chosen card.
|
|
235
635
|
"""
|
|
236
636
|
if key in self._card_id_map:
|
|
237
637
|
card_uuid = self._card_id_map[key]
|
|
238
|
-
return self.
|
|
638
|
+
return self._card_component_store[card_uuid]
|
|
239
639
|
if key not in self._warned_once["__getitem__"]:
|
|
240
640
|
_warn_msg = [
|
|
241
641
|
"`current.card['%s']` is not present. Please set the `id` argument in @card to '%s' to access `current.card['%s']`."
|
|
@@ -246,6 +646,7 @@ class CardComponentCollector:
|
|
|
246
646
|
self._warning(" ".join(_warn_msg))
|
|
247
647
|
self._add_warning_to_cards("\n".join(_warn_msg))
|
|
248
648
|
self._warned_once["__getitem__"][key] = True
|
|
649
|
+
|
|
249
650
|
return []
|
|
250
651
|
|
|
251
652
|
def __setitem__(self, key, value):
|
|
@@ -263,7 +664,7 @@ class CardComponentCollector:
|
|
|
263
664
|
key: str
|
|
264
665
|
Card ID.
|
|
265
666
|
|
|
266
|
-
value: List[
|
|
667
|
+
value: List[MetaflowCardComponent]
|
|
267
668
|
List of card components to assign to this card.
|
|
268
669
|
"""
|
|
269
670
|
if key in self._card_id_map:
|
|
@@ -275,7 +676,18 @@ class CardComponentCollector:
|
|
|
275
676
|
)
|
|
276
677
|
self._warning(_warning_msg)
|
|
277
678
|
return
|
|
278
|
-
self.
|
|
679
|
+
self._card_component_store[card_uuid] = CardComponentManager(
|
|
680
|
+
card_uuid,
|
|
681
|
+
self._cards_meta[card_uuid]["decorator_attributes"],
|
|
682
|
+
self._card_creator,
|
|
683
|
+
components=value,
|
|
684
|
+
logger=self._logger,
|
|
685
|
+
no_warnings=self._no_warnings,
|
|
686
|
+
user_set_card_id=key,
|
|
687
|
+
card_options=self._cards_meta[card_uuid]["card_options"],
|
|
688
|
+
runtime_card=self._cards_meta[card_uuid]["runtime_card"],
|
|
689
|
+
refresh_interval=self._cards_meta[card_uuid]["refresh_interval"],
|
|
690
|
+
)
|
|
279
691
|
return
|
|
280
692
|
|
|
281
693
|
self._warning(
|
|
@@ -283,18 +695,18 @@ class CardComponentCollector:
|
|
|
283
695
|
% (key, key, key)
|
|
284
696
|
)
|
|
285
697
|
|
|
286
|
-
def append(self, component):
|
|
698
|
+
def append(self, component, id=None):
|
|
287
699
|
"""
|
|
288
700
|
Appends a component to the current card.
|
|
289
701
|
|
|
290
702
|
Parameters
|
|
291
703
|
----------
|
|
292
|
-
component :
|
|
704
|
+
component : MetaflowCardComponent
|
|
293
705
|
Card component to add to this card.
|
|
294
706
|
"""
|
|
295
707
|
if self._default_editable_card is None:
|
|
296
708
|
if (
|
|
297
|
-
len(self.
|
|
709
|
+
len(self._card_component_store) == 1
|
|
298
710
|
): # if there is one card which is not the _default_editable_card then the card is not editable
|
|
299
711
|
card_type = list(self._cards_meta.values())[0]["type"]
|
|
300
712
|
if list(self._cards_meta.values())[0]["exists"]:
|
|
@@ -324,7 +736,7 @@ class CardComponentCollector:
|
|
|
324
736
|
self._warned_once["append"] = True
|
|
325
737
|
|
|
326
738
|
return
|
|
327
|
-
self.
|
|
739
|
+
self._card_component_store[self._default_editable_card].append(component, id=id)
|
|
328
740
|
|
|
329
741
|
def extend(self, components):
|
|
330
742
|
"""
|
|
@@ -332,12 +744,12 @@ class CardComponentCollector:
|
|
|
332
744
|
|
|
333
745
|
Parameters
|
|
334
746
|
----------
|
|
335
|
-
component : Iterator[
|
|
747
|
+
component : Iterator[MetaflowCardComponent]
|
|
336
748
|
Card components to add to this card.
|
|
337
749
|
"""
|
|
338
750
|
if self._default_editable_card is None:
|
|
339
751
|
# if there is one card which is not the _default_editable_card then the card is not editable
|
|
340
|
-
if len(self.
|
|
752
|
+
if len(self._card_component_store) == 1:
|
|
341
753
|
card_type = list(self._cards_meta.values())[0]["type"]
|
|
342
754
|
_warning_msg = [
|
|
343
755
|
"Card of type `%s` is not an editable card." % card_type,
|
|
@@ -357,7 +769,52 @@ class CardComponentCollector:
|
|
|
357
769
|
|
|
358
770
|
return
|
|
359
771
|
|
|
360
|
-
self.
|
|
772
|
+
self._card_component_store[self._default_editable_card].extend(components)
|
|
773
|
+
|
|
774
|
+
@property
|
|
775
|
+
def components(self):
|
|
776
|
+
# FIXME: document
|
|
777
|
+
if self._default_editable_card is None:
|
|
778
|
+
if len(self._card_component_store) == 1:
|
|
779
|
+
card_type = list(self._cards_meta.values())[0]["type"]
|
|
780
|
+
_warning_msg = [
|
|
781
|
+
"Card of type `%s` is not an editable card." % card_type,
|
|
782
|
+
"Components list will not be updated and `current.card.components` will not work for any call during this runtime execution.",
|
|
783
|
+
"Please use an editable card", # todo : link to documentation
|
|
784
|
+
]
|
|
785
|
+
else:
|
|
786
|
+
_warning_msg = [
|
|
787
|
+
"`current.card.components` cannot disambiguate between multiple @card decorators.",
|
|
788
|
+
"Components list will not be accessible and `current.card.components` will not work for any call during this runtime execution.",
|
|
789
|
+
"To fix this set the `id` argument in all @card when using multiple @card decorators over a single @step and reference `current.card[ID].components`", # todo : Add Link to documentation
|
|
790
|
+
"to update/access the appropriate card component.",
|
|
791
|
+
]
|
|
792
|
+
if not self._warned_once["components"]:
|
|
793
|
+
self._warning(" ".join(_warning_msg))
|
|
794
|
+
self._warned_once["components"] = True
|
|
795
|
+
return
|
|
796
|
+
|
|
797
|
+
return self._card_component_store[self._default_editable_card].components
|
|
798
|
+
|
|
799
|
+
def clear(self):
|
|
800
|
+
# FIXME: document
|
|
801
|
+
if self._default_editable_card is not None:
|
|
802
|
+
self._card_component_store[self._default_editable_card].clear()
|
|
803
|
+
|
|
804
|
+
def refresh(self, *args, **kwargs):
|
|
805
|
+
# FIXME: document
|
|
806
|
+
if self._default_editable_card is not None:
|
|
807
|
+
self._card_component_store[self._default_editable_card].refresh(
|
|
808
|
+
*args, **kwargs
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
def _get_latest_data(self, card_uuid, final=False, mode=None):
|
|
812
|
+
"""
|
|
813
|
+
Returns latest data so it can be used in the final render() call
|
|
814
|
+
"""
|
|
815
|
+
return self._card_component_store[card_uuid]._get_latest_data(
|
|
816
|
+
final=final, mode=mode
|
|
817
|
+
)
|
|
361
818
|
|
|
362
819
|
def _serialize_components(self, card_uuid):
|
|
363
820
|
"""
|
|
@@ -366,36 +823,43 @@ class CardComponentCollector:
|
|
|
366
823
|
don't render safely then we don't add them to the final list of serialized functions
|
|
367
824
|
"""
|
|
368
825
|
serialized_components = []
|
|
369
|
-
if card_uuid not in self.
|
|
826
|
+
if card_uuid not in self._card_component_store:
|
|
370
827
|
return []
|
|
371
828
|
has_user_components = any(
|
|
372
829
|
[
|
|
373
830
|
issubclass(type(component), UserComponent)
|
|
374
|
-
for component in self.
|
|
831
|
+
for component in self._card_component_store[card_uuid]
|
|
375
832
|
]
|
|
376
833
|
)
|
|
377
|
-
for component in self.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
try:
|
|
381
|
-
rendered_obj = component.render()
|
|
382
|
-
except:
|
|
834
|
+
for component in self._card_component_store[card_uuid]:
|
|
835
|
+
rendered_obj = _render_card_component(component)
|
|
836
|
+
if rendered_obj is None:
|
|
383
837
|
continue
|
|
384
|
-
|
|
385
|
-
if not (type(rendered_obj) == str or type(rendered_obj) == dict):
|
|
386
|
-
continue
|
|
387
|
-
else:
|
|
388
|
-
# Since `UserComponent`s are safely_rendered using render_tools.py
|
|
389
|
-
# we don't need to check JSON serialization as @render_tools.render_safely
|
|
390
|
-
# decorator ensures this check so there is no need to re-serialize
|
|
391
|
-
if not issubclass(type(component), UserComponent):
|
|
392
|
-
try: # check if rendered object is json serializable.
|
|
393
|
-
json.dumps(rendered_obj)
|
|
394
|
-
except (TypeError, OverflowError) as e:
|
|
395
|
-
continue
|
|
396
|
-
serialized_components.append(rendered_obj)
|
|
838
|
+
serialized_components.append(rendered_obj)
|
|
397
839
|
if has_user_components and len(serialized_components) > 0:
|
|
398
840
|
serialized_components = [
|
|
399
841
|
SectionComponent(contents=serialized_components).render()
|
|
400
842
|
]
|
|
401
843
|
return serialized_components
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def _render_card_component(component):
|
|
847
|
+
if not _component_is_valid(component):
|
|
848
|
+
return None
|
|
849
|
+
try:
|
|
850
|
+
rendered_obj = component.render()
|
|
851
|
+
except:
|
|
852
|
+
return None
|
|
853
|
+
else:
|
|
854
|
+
if not (type(rendered_obj) == str or type(rendered_obj) == dict):
|
|
855
|
+
return None
|
|
856
|
+
else:
|
|
857
|
+
# Since `UserComponent`s are safely_rendered using render_tools.py
|
|
858
|
+
# we don't need to check JSON serialization as @render_tools.render_safely
|
|
859
|
+
# decorator ensures this check so there is no need to re-serialize
|
|
860
|
+
if not issubclass(type(component), UserComponent):
|
|
861
|
+
try: # check if rendered object is json serializable.
|
|
862
|
+
json.dumps(rendered_obj)
|
|
863
|
+
except (TypeError, OverflowError) as e:
|
|
864
|
+
return None
|
|
865
|
+
return rendered_obj
|