mlrun 1.5.0rc1__py3-none-any.whl → 1.5.0rc2__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 mlrun might be problematic. Click here for more details.

Files changed (119) hide show
  1. mlrun/__init__.py +2 -35
  2. mlrun/__main__.py +1 -40
  3. mlrun/api/api/api.py +6 -0
  4. mlrun/api/api/endpoints/feature_store.py +0 -4
  5. mlrun/api/api/endpoints/files.py +14 -2
  6. mlrun/api/api/endpoints/functions.py +6 -1
  7. mlrun/api/api/endpoints/logs.py +17 -3
  8. mlrun/api/api/endpoints/pipelines.py +1 -5
  9. mlrun/api/api/endpoints/projects.py +88 -0
  10. mlrun/api/api/endpoints/runs.py +48 -6
  11. mlrun/api/api/endpoints/workflows.py +355 -0
  12. mlrun/api/api/utils.py +1 -1
  13. mlrun/api/crud/__init__.py +1 -0
  14. mlrun/api/crud/client_spec.py +3 -0
  15. mlrun/api/crud/model_monitoring/deployment.py +36 -7
  16. mlrun/api/crud/model_monitoring/grafana.py +1 -1
  17. mlrun/api/crud/model_monitoring/helpers.py +32 -2
  18. mlrun/api/crud/model_monitoring/model_endpoints.py +27 -5
  19. mlrun/api/crud/notifications.py +9 -4
  20. mlrun/api/crud/pipelines.py +4 -9
  21. mlrun/api/crud/runtime_resources.py +4 -3
  22. mlrun/api/crud/secrets.py +21 -0
  23. mlrun/api/crud/workflows.py +352 -0
  24. mlrun/api/db/base.py +16 -1
  25. mlrun/api/db/sqldb/db.py +97 -16
  26. mlrun/api/launcher.py +26 -7
  27. mlrun/api/main.py +3 -4
  28. mlrun/{mlutils → api/rundb}/__init__.py +2 -6
  29. mlrun/{db → api/rundb}/sqldb.py +35 -83
  30. mlrun/api/runtime_handlers/__init__.py +56 -0
  31. mlrun/api/runtime_handlers/base.py +1247 -0
  32. mlrun/api/runtime_handlers/daskjob.py +209 -0
  33. mlrun/api/runtime_handlers/kubejob.py +37 -0
  34. mlrun/api/runtime_handlers/mpijob.py +147 -0
  35. mlrun/api/runtime_handlers/remotesparkjob.py +29 -0
  36. mlrun/api/runtime_handlers/sparkjob.py +148 -0
  37. mlrun/api/utils/builder.py +1 -4
  38. mlrun/api/utils/clients/chief.py +14 -0
  39. mlrun/api/utils/scheduler.py +98 -15
  40. mlrun/api/utils/singletons/db.py +4 -0
  41. mlrun/artifacts/manager.py +1 -2
  42. mlrun/common/schemas/__init__.py +6 -0
  43. mlrun/common/schemas/auth.py +4 -1
  44. mlrun/common/schemas/client_spec.py +1 -1
  45. mlrun/common/schemas/model_monitoring/__init__.py +1 -0
  46. mlrun/common/schemas/model_monitoring/constants.py +11 -0
  47. mlrun/common/schemas/project.py +1 -0
  48. mlrun/common/schemas/runs.py +1 -8
  49. mlrun/common/schemas/schedule.py +1 -8
  50. mlrun/common/schemas/workflow.py +54 -0
  51. mlrun/config.py +42 -40
  52. mlrun/datastore/sources.py +1 -1
  53. mlrun/db/__init__.py +4 -68
  54. mlrun/db/base.py +12 -0
  55. mlrun/db/factory.py +65 -0
  56. mlrun/db/httpdb.py +175 -19
  57. mlrun/db/nopdb.py +4 -2
  58. mlrun/execution.py +4 -2
  59. mlrun/feature_store/__init__.py +1 -0
  60. mlrun/feature_store/api.py +1 -2
  61. mlrun/feature_store/feature_set.py +0 -10
  62. mlrun/feature_store/feature_vector.py +340 -2
  63. mlrun/feature_store/ingestion.py +5 -10
  64. mlrun/feature_store/retrieval/base.py +118 -104
  65. mlrun/feature_store/retrieval/dask_merger.py +17 -10
  66. mlrun/feature_store/retrieval/job.py +4 -1
  67. mlrun/feature_store/retrieval/local_merger.py +18 -18
  68. mlrun/feature_store/retrieval/spark_merger.py +21 -14
  69. mlrun/feature_store/retrieval/storey_merger.py +21 -15
  70. mlrun/kfpops.py +3 -9
  71. mlrun/launcher/base.py +3 -3
  72. mlrun/launcher/client.py +3 -2
  73. mlrun/launcher/factory.py +16 -13
  74. mlrun/lists.py +0 -11
  75. mlrun/model.py +9 -15
  76. mlrun/model_monitoring/helpers.py +15 -25
  77. mlrun/model_monitoring/model_monitoring_batch.py +72 -4
  78. mlrun/model_monitoring/prometheus.py +219 -0
  79. mlrun/model_monitoring/stores/__init__.py +15 -9
  80. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +3 -1
  81. mlrun/model_monitoring/stream_processing.py +181 -29
  82. mlrun/package/packager.py +6 -8
  83. mlrun/package/packagers/default_packager.py +121 -10
  84. mlrun/platforms/__init__.py +0 -2
  85. mlrun/platforms/iguazio.py +0 -56
  86. mlrun/projects/pipelines.py +57 -158
  87. mlrun/projects/project.py +6 -32
  88. mlrun/render.py +1 -1
  89. mlrun/run.py +2 -124
  90. mlrun/runtimes/__init__.py +6 -42
  91. mlrun/runtimes/base.py +26 -1241
  92. mlrun/runtimes/daskjob.py +2 -198
  93. mlrun/runtimes/function.py +16 -5
  94. mlrun/runtimes/kubejob.py +5 -29
  95. mlrun/runtimes/mpijob/__init__.py +2 -2
  96. mlrun/runtimes/mpijob/abstract.py +10 -1
  97. mlrun/runtimes/mpijob/v1.py +0 -76
  98. mlrun/runtimes/mpijob/v1alpha1.py +1 -74
  99. mlrun/runtimes/nuclio.py +3 -2
  100. mlrun/runtimes/pod.py +0 -10
  101. mlrun/runtimes/remotesparkjob.py +1 -15
  102. mlrun/runtimes/serving.py +1 -1
  103. mlrun/runtimes/sparkjob/__init__.py +0 -1
  104. mlrun/runtimes/sparkjob/abstract.py +4 -131
  105. mlrun/serving/states.py +1 -1
  106. mlrun/utils/db.py +0 -2
  107. mlrun/utils/helpers.py +19 -13
  108. mlrun/utils/notifications/notification_pusher.py +5 -25
  109. mlrun/utils/regex.py +7 -2
  110. mlrun/utils/version/version.json +2 -2
  111. {mlrun-1.5.0rc1.dist-info → mlrun-1.5.0rc2.dist-info}/METADATA +24 -23
  112. {mlrun-1.5.0rc1.dist-info → mlrun-1.5.0rc2.dist-info}/RECORD +116 -107
  113. {mlrun-1.5.0rc1.dist-info → mlrun-1.5.0rc2.dist-info}/WHEEL +1 -1
  114. mlrun/mlutils/data.py +0 -160
  115. mlrun/mlutils/models.py +0 -78
  116. mlrun/mlutils/plots.py +0 -902
  117. {mlrun-1.5.0rc1.dist-info → mlrun-1.5.0rc2.dist-info}/LICENSE +0 -0
  118. {mlrun-1.5.0rc1.dist-info → mlrun-1.5.0rc2.dist-info}/entry_points.txt +0 -0
  119. {mlrun-1.5.0rc1.dist-info → mlrun-1.5.0rc2.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
  import collections
15
15
  import logging
16
+ import typing
16
17
  from copy import copy
17
18
  from enum import Enum
18
19
  from typing import List, Union
@@ -30,8 +31,16 @@ from ..feature_store.common import (
30
31
  parse_feature_string,
31
32
  parse_project_name_from_feature_string,
32
33
  )
33
- from ..features import Feature
34
- from ..model import DataSource, DataTarget, ModelObj, ObjectList, VersionedObjMetadata
34
+ from ..feature_store.feature_set import FeatureSet
35
+ from ..features import Entity, Feature
36
+ from ..model import (
37
+ DataSource,
38
+ DataTarget,
39
+ ModelObj,
40
+ ObjectDict,
41
+ ObjectList,
42
+ VersionedObjMetadata,
43
+ )
35
44
  from ..runtimes.function_reference import FunctionReference
36
45
  from ..serving.states import RootFlowStep
37
46
  from ..utils import StorePrefix
@@ -50,17 +59,25 @@ class FeatureVectorSpec(ModelObj):
50
59
  with_indexes=None,
51
60
  function=None,
52
61
  analysis=None,
62
+ relations=None,
63
+ join_graph=None,
53
64
  ):
54
65
  self._graph: RootFlowStep = None
55
66
  self._entity_fields: ObjectList = None
56
67
  self._entity_source: DataSource = None
57
68
  self._function: FunctionReference = None
69
+ self._relations: typing.Dict[str, ObjectDict] = None
70
+ self._join_graph: JoinGraph = None
58
71
 
59
72
  self.description = description
60
73
  self.features: List[str] = features or []
61
74
  self.entity_source = entity_source
62
75
  self.entity_fields = entity_fields or []
63
76
  self.graph = graph
77
+ self.join_graph = join_graph
78
+ self.relations: typing.Dict[str, typing.Dict[str, Union[Entity, str]]] = (
79
+ relations or {}
80
+ )
64
81
  self.timestamp_field = timestamp_field
65
82
  self.label_feature = label_feature
66
83
  self.with_indexes = with_indexes
@@ -104,6 +121,38 @@ class FeatureVectorSpec(ModelObj):
104
121
  def function(self, function):
105
122
  self._function = self._verify_dict(function, "function", FunctionReference)
106
123
 
124
+ @property
125
+ def relations(self) -> typing.Dict[str, ObjectDict]:
126
+ """feature set relations dict"""
127
+ return self._relations
128
+
129
+ @relations.setter
130
+ def relations(
131
+ self, relations: typing.Dict[str, typing.Dict[str, Union[Entity, str]]]
132
+ ):
133
+ temp_relations = {}
134
+ for fs_name, relation in relations.items():
135
+ for col, ent in relation.items():
136
+ if isinstance(ent, str):
137
+ relation[col] = Entity(ent)
138
+ temp_relations[fs_name] = ObjectDict.from_dict(
139
+ {"entity": Entity}, relation, "entity"
140
+ )
141
+ self._relations = ObjectDict.from_dict(
142
+ {"object_dict": ObjectDict}, temp_relations, "object_dict"
143
+ )
144
+
145
+ @property
146
+ def join_graph(self):
147
+ return self._join_graph
148
+
149
+ @join_graph.setter
150
+ def join_graph(self, join_graph):
151
+ if join_graph is not None:
152
+ self._join_graph = self._verify_dict(join_graph, "join_graph", JoinGraph)
153
+ else:
154
+ self._join_graph = None
155
+
107
156
 
108
157
  class FeatureVectorStatus(ModelObj):
109
158
  def __init__(
@@ -151,6 +200,267 @@ class FeatureVectorStatus(ModelObj):
151
200
  self._features = ObjectList.from_list(Feature, features)
152
201
 
153
202
 
203
+ class JoinGraph(ModelObj):
204
+ """
205
+ explain here about the class
206
+ """
207
+
208
+ default_graph_name = "$__join_graph_fv__$"
209
+ first_join_type = "first"
210
+ _dict_fields = ["name", "first_feature_set", "steps"]
211
+
212
+ def __init__(
213
+ self,
214
+ name: str = None,
215
+ first_feature_set: Union[str, FeatureSet] = None,
216
+ ):
217
+ """
218
+ JoinGraph is a class that represents a graph of data joins between feature sets. It allows users to define
219
+ data joins step by step, specifying the join type for each step. The graph can be used to build a sequence of
220
+ joins that will be executed in order, allowing the creation of complex join operations between feature sets.
221
+
222
+
223
+ Example:
224
+ # Create a new JoinGraph and add steps for joining feature sets.
225
+ join_graph = JoinGraph(name="my_join_graph", first_feature_set="featureset1")
226
+ join_graph.inner("featureset2")
227
+ join_graph.left("featureset3", asof_join=True)
228
+
229
+
230
+ :param name: (str, optional) The name of the join graph. If not provided,
231
+ a default name will be used.
232
+ :param first_feature_set: (str or FeatureSet, optional) The first feature set to join. It can be
233
+ specified either as a string representing the name of the feature set or as a
234
+ FeatureSet object.
235
+ """
236
+ self.name = name or self.default_graph_name
237
+ self._steps: ObjectList = None
238
+ self._feature_sets = None
239
+ if first_feature_set:
240
+ self._start(first_feature_set)
241
+
242
+ def inner(self, other_operand: typing.Union[str, FeatureSet]):
243
+ """
244
+ Specifies an inner join with the given feature set
245
+
246
+ :param other_operand: (str or FeatureSet) The name of the feature set or a FeatureSet object to join with.
247
+
248
+ :return: JoinGraph: The updated JoinGraph object with the specified inner join.
249
+ """
250
+ return self._join_operands(other_operand, "inner")
251
+
252
+ def outer(self, other_operand: typing.Union[str, FeatureSet]):
253
+ """
254
+ Specifies an outer join with the given feature set
255
+
256
+ :param other_operand: (str or FeatureSet) The name of the feature set or a FeatureSet object to join with.
257
+ :return: JoinGraph: The updated JoinGraph object with the specified outer join.
258
+ """
259
+ return self._join_operands(other_operand, "outer")
260
+
261
+ def left(self, other_operand: typing.Union[str, FeatureSet], asof_join):
262
+ """
263
+ Specifies a left join with the given feature set
264
+
265
+ :param other_operand: (str or FeatureSet) The name of the feature set or a FeatureSet object to join with.
266
+ :param asof_join: (bool) A flag indicating whether to perform an as-of join.
267
+
268
+ :return: JoinGraph: The updated JoinGraph object with the specified left join.
269
+ """
270
+ return self._join_operands(other_operand, "left", asof_join=asof_join)
271
+
272
+ def right(self, other_operand: typing.Union[str, FeatureSet]):
273
+ """
274
+ Specifies a right join with the given feature set
275
+
276
+ :param other_operand: (str or FeatureSet) The name of the feature set or a FeatureSet object to join with.
277
+
278
+ :return: JoinGraph: The updated JoinGraph object with the specified right join.
279
+ """
280
+ return self._join_operands(other_operand, "right")
281
+
282
+ def _join_operands(
283
+ self,
284
+ other_operand: typing.Union[str, FeatureSet],
285
+ join_type: str,
286
+ asof_join: bool = False,
287
+ ):
288
+ if isinstance(other_operand, FeatureSet):
289
+ other_operand = other_operand.metadata.name
290
+
291
+ first_key_num = len(self._steps.keys()) if self._steps else 0
292
+ left_last_step_name, left_all_feature_sets = (
293
+ self.last_step_name,
294
+ self.all_feature_sets_names,
295
+ )
296
+ is_first_fs = (
297
+ join_type == JoinGraph.first_join_type or left_all_feature_sets == self.name
298
+ )
299
+ # create_new_step
300
+ new_step = _JoinStep(
301
+ f"step_{first_key_num}",
302
+ left_last_step_name if not is_first_fs else "",
303
+ other_operand,
304
+ left_all_feature_sets if not is_first_fs else [],
305
+ other_operand,
306
+ join_type,
307
+ asof_join,
308
+ )
309
+
310
+ if self.steps is not None:
311
+ self.steps.update(new_step)
312
+ else:
313
+ self.steps = [new_step]
314
+ return self
315
+
316
+ def _start(self, other_operand: typing.Union[str, FeatureSet]):
317
+ return self._join_operands(other_operand, JoinGraph.first_join_type)
318
+
319
+ def _init_all_join_keys(self, feature_set_objects, vector):
320
+ for step in self.steps:
321
+ step.init_join_keys(feature_set_objects, vector)
322
+
323
+ @property
324
+ def all_feature_sets_names(self):
325
+ """
326
+ Returns a list of all feature set names included in the join graph.
327
+
328
+ :return: List[str]: A list of feature set names.
329
+ """
330
+ if self._steps:
331
+ return self._steps[-1].left_feature_set_names + [
332
+ self._steps[-1].right_feature_set_name
333
+ ]
334
+ else:
335
+ return self.name
336
+
337
+ @property
338
+ def last_step_name(self):
339
+ """
340
+ Returns the name of the last step in the join graph.
341
+
342
+ :return: str: The name of the last step.
343
+ """
344
+ if self._steps:
345
+ return self._steps[-1].name
346
+ else:
347
+ return self.name
348
+
349
+ @property
350
+ def steps(self):
351
+ """
352
+ Returns the list of join steps as ObjectList, which can be used to iterate over the steps
353
+ or access the properties of each step.
354
+ :return: ObjectList: The list of join steps.
355
+ """
356
+ return self._steps
357
+
358
+ @steps.setter
359
+ def steps(self, steps):
360
+ """
361
+ Setter for the steps property. It allows updating the join steps.
362
+
363
+ :param steps: (List[_JoinStep]) The list of join steps.
364
+ """
365
+ self._steps = ObjectList.from_list(child_class=_JoinStep, children=steps)
366
+
367
+
368
+ class _JoinStep(ModelObj):
369
+ def __init__(
370
+ self,
371
+ name: str = None,
372
+ left_step_name: str = None,
373
+ right_step_name: str = None,
374
+ left_feature_set_names: Union[str, List[str]] = None,
375
+ right_feature_set_name: str = None,
376
+ join_type: str = "inner",
377
+ asof_join: bool = False,
378
+ ):
379
+ self.name = name
380
+ self.left_step_name = left_step_name
381
+ self.right_step_name = right_step_name
382
+ self.left_feature_set_names = (
383
+ left_feature_set_names
384
+ if isinstance(left_feature_set_names, list)
385
+ else [left_feature_set_names]
386
+ )
387
+ self.right_feature_set_name = right_feature_set_name
388
+ self.join_type = join_type
389
+ self.asof_join = asof_join
390
+
391
+ self.left_keys = []
392
+ self.right_keys = []
393
+
394
+ def init_join_keys(
395
+ self,
396
+ feature_set_objects: ObjectList,
397
+ vector,
398
+ ):
399
+ self.left_keys = []
400
+ self.right_keys = []
401
+
402
+ if (
403
+ self.join_type == JoinGraph.first_join_type
404
+ or not self.left_feature_set_names
405
+ ):
406
+ self.left_keys = self.right_keys = list(
407
+ feature_set_objects[self.right_feature_set_name].spec.entities.keys()
408
+ )
409
+ self.join_type = (
410
+ "inner"
411
+ if self.join_type == JoinGraph.first_join_type
412
+ else self.join_type
413
+ )
414
+ return
415
+
416
+ for left_fset in self.left_feature_set_names:
417
+ left_keys, right_keys = self._check_relation(
418
+ vector.get_feature_set_relations(feature_set_objects[left_fset]),
419
+ list(feature_set_objects[left_fset].spec.entities),
420
+ feature_set_objects[self.right_feature_set_name],
421
+ )
422
+ self.left_keys.extend(left_keys)
423
+ self.right_keys.extend(right_keys)
424
+
425
+ if not self.left_keys:
426
+ raise mlrun.errors.MLRunRuntimeError(
427
+ f"{self.name} can't be preform due to undefined relation between "
428
+ f"{self.left_feature_set_names} to {self.right_feature_set_name}"
429
+ )
430
+
431
+ @staticmethod
432
+ def _check_relation(
433
+ relation: typing.Dict[str, Union[str, Entity]],
434
+ entities: List[Entity],
435
+ right_fset_fields,
436
+ ):
437
+ right_feature_set_entity_list = right_fset_fields.spec.entities
438
+
439
+ if all(ent in entities for ent in right_feature_set_entity_list) and len(
440
+ right_feature_set_entity_list
441
+ ) == len(entities):
442
+ # entities wise
443
+ return list(right_feature_set_entity_list.keys()), list(
444
+ right_feature_set_entity_list.keys()
445
+ )
446
+ curr_col_relation_list = list(
447
+ map(
448
+ lambda ent: (
449
+ list(relation.keys())[list(relation.values()).index(ent)]
450
+ if ent in list(relation.values())
451
+ else False
452
+ ),
453
+ right_feature_set_entity_list,
454
+ )
455
+ )
456
+
457
+ if all(curr_col_relation_list):
458
+ # relation wise
459
+ return curr_col_relation_list, list(right_feature_set_entity_list.keys())
460
+
461
+ return [], []
462
+
463
+
154
464
  class FeatureVector(ModelObj):
155
465
  """Feature vector, specify selected features, their metadata and material views"""
156
466
 
@@ -164,6 +474,8 @@ class FeatureVector(ModelObj):
164
474
  label_feature=None,
165
475
  description=None,
166
476
  with_indexes=None,
477
+ join_graph: JoinGraph = None,
478
+ relations: typing.Dict[str, typing.Dict[str, Union[Entity, str]]] = None,
167
479
  ):
168
480
  """Feature vector, specify selected features, their metadata and material views
169
481
 
@@ -186,6 +498,18 @@ class FeatureVector(ModelObj):
186
498
  :param label_feature: feature name to be used as label data
187
499
  :param description: text description of the vector
188
500
  :param with_indexes: whether to keep the entity and timestamp columns in the response
501
+ :param join_graph: An optional JoinGraph object representing the graph of data joins
502
+ between feature sets for this feature vector, specified the order and the join types.
503
+ :param relations: {<feature_set name>: {<column_name>: <other entity object/name>, ...}...}
504
+ An optional dictionary specifying the relations between feature sets in the
505
+ feature vector. The keys of the dictionary are feature set names, and the values
506
+ are dictionaries where the keys represent column names(of the feature set),
507
+ and the values represent the target entities to join with.
508
+ The relations provided here will take precedence over the relations that were specified
509
+ on the feature sets themselves. In case a specific feature set is not mentioned as a key
510
+ here, the function will fall back to using the default relations defined in the
511
+ feature set.
512
+
189
513
  """
190
514
  self._spec: FeatureVectorSpec = None
191
515
  self._metadata = None
@@ -196,6 +520,8 @@ class FeatureVector(ModelObj):
196
520
  features=features,
197
521
  label_feature=label_feature,
198
522
  with_indexes=with_indexes,
523
+ relations=relations,
524
+ join_graph=join_graph,
199
525
  )
200
526
  self.metadata = VersionedObjMetadata(name=name)
201
527
  self.status = None
@@ -380,6 +706,18 @@ class FeatureVector(ModelObj):
380
706
  self.status.index_keys = index_keys
381
707
  return feature_set_objects, feature_set_fields
382
708
 
709
+ def get_feature_set_relations(self, feature_set: Union[str, FeatureSet]):
710
+ if isinstance(feature_set, str):
711
+ feature_set = get_feature_set_by_uri(
712
+ feature_set,
713
+ self.metadata.project,
714
+ )
715
+ name = feature_set.metadata.name
716
+ feature_set_relations = feature_set.spec.relations or {}
717
+ if self.spec.relations and name in self.spec.relations:
718
+ feature_set_relations = self.spec.relations[name]
719
+ return feature_set_relations
720
+
383
721
 
384
722
  class OnlineVectorService:
385
723
  """get_online_feature_service response object"""
@@ -278,17 +278,12 @@ def run_ingestion_job(name, featureset, run_config, schedule=None, spark_service
278
278
  # when running in server side we want to set the function db connection to the actual DB and not to use the httpdb
279
279
  function.set_db_connection(featureset._get_run_db())
280
280
 
281
- # when running on server side there are multiple enrichments and validations to be applied on a function,
282
- # auth_info is an attribute which is been added only on server side.
283
- if run_config.auth_info:
284
- # using from to not conflict with other mlrun imports
285
- from mlrun.api.api.utils import apply_enrichment_and_validation_on_function
286
-
287
- # apply_enrichment_and_validation_on_function is a server side function we don't want to import it on client
288
- apply_enrichment_and_validation_on_function(function, run_config.auth_info)
289
-
290
281
  run = function.run(
291
- task, schedule=schedule, local=run_config.local, watch=run_config.watch
282
+ task,
283
+ schedule=schedule,
284
+ local=run_config.local,
285
+ watch=run_config.watch,
286
+ auth_info=run_config.auth_info,
292
287
  )
293
288
  if run_config.watch:
294
289
  featureset.reload()