mlrun 1.5.0rc5__py3-none-any.whl → 1.5.0rc6__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.
Files changed (47) hide show
  1. mlrun/api/api/endpoints/datastore_profile.py +35 -13
  2. mlrun/api/api/endpoints/frontend_spec.py +1 -10
  3. mlrun/api/api/endpoints/functions.py +1 -1
  4. mlrun/api/api/endpoints/hub.py +2 -6
  5. mlrun/api/crud/client_spec.py +3 -0
  6. mlrun/api/crud/datastore_profiles.py +2 -2
  7. mlrun/api/crud/hub.py +158 -142
  8. mlrun/api/crud/workflows.py +7 -3
  9. mlrun/api/db/sqldb/db.py +19 -21
  10. mlrun/api/db/sqldb/models/models_mysql.py +10 -1
  11. mlrun/api/db/sqldb/models/models_sqlite.py +11 -1
  12. mlrun/api/initial_data.py +3 -5
  13. mlrun/api/launcher.py +2 -1
  14. mlrun/api/migrations_mysql/versions/026c947c4487_altering_table_datastore_profiles_2.py +46 -0
  15. mlrun/api/migrations_sqlite/versions/026c947c4487_altering_table_datastore_profiles_2.py +46 -0
  16. mlrun/api/rundb/sqldb.py +15 -9
  17. mlrun/api/utils/db/sqlite_migration.py +1 -0
  18. mlrun/common/model_monitoring/helpers.py +3 -1
  19. mlrun/common/schemas/client_spec.py +1 -0
  20. mlrun/common/schemas/datastore_profile.py +1 -1
  21. mlrun/common/schemas/frontend_spec.py +1 -1
  22. mlrun/config.py +3 -2
  23. mlrun/datastore/datastore_profile.py +33 -21
  24. mlrun/datastore/dbfs_store.py +4 -3
  25. mlrun/datastore/redis.py +6 -0
  26. mlrun/datastore/targets.py +12 -1
  27. mlrun/db/base.py +1 -1
  28. mlrun/db/httpdb.py +10 -9
  29. mlrun/db/nopdb.py +1 -1
  30. mlrun/feature_store/api.py +4 -1
  31. mlrun/feature_store/feature_set.py +3 -1
  32. mlrun/feature_store/ingestion.py +1 -0
  33. mlrun/launcher/base.py +1 -1
  34. mlrun/model.py +7 -5
  35. mlrun/projects/pipelines.py +7 -6
  36. mlrun/projects/project.py +2 -2
  37. mlrun/run.py +1 -1
  38. mlrun/runtimes/__init__.py +1 -0
  39. mlrun/utils/helpers.py +1 -1
  40. mlrun/utils/notifications/notification/webhook.py +9 -1
  41. mlrun/utils/version/version.json +2 -2
  42. {mlrun-1.5.0rc5.dist-info → mlrun-1.5.0rc6.dist-info}/METADATA +6 -5
  43. {mlrun-1.5.0rc5.dist-info → mlrun-1.5.0rc6.dist-info}/RECORD +47 -45
  44. {mlrun-1.5.0rc5.dist-info → mlrun-1.5.0rc6.dist-info}/LICENSE +0 -0
  45. {mlrun-1.5.0rc5.dist-info → mlrun-1.5.0rc6.dist-info}/WHEEL +0 -0
  46. {mlrun-1.5.0rc5.dist-info → mlrun-1.5.0rc6.dist-info}/entry_points.txt +0 -0
  47. {mlrun-1.5.0rc5.dist-info → mlrun-1.5.0rc6.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
+ from http import HTTPStatus
16
17
 
17
18
  from fastapi import APIRouter, Depends
18
19
  from fastapi.concurrency import run_in_threadpool
@@ -24,12 +25,13 @@ import mlrun.api.crud
24
25
  import mlrun.api.utils.auth.verifier
25
26
  import mlrun.api.utils.singletons.db
26
27
  import mlrun.common.schemas
28
+ from mlrun.api.api.utils import log_and_raise
27
29
 
28
30
  router = APIRouter()
29
31
 
30
32
 
31
33
  @router.put(
32
- path="/projects/{project_name}/datastore_profiles",
34
+ path="/projects/{project_name}/datastore-profiles",
33
35
  )
34
36
  async def store_datastore_profile(
35
37
  project_name: str,
@@ -45,13 +47,20 @@ async def store_datastore_profile(
45
47
  project_name,
46
48
  auth_info.session,
47
49
  )
48
- await mlrun.api.utils.auth.verifier.AuthVerifier().query_global_resource_permissions(
50
+ await mlrun.api.utils.auth.verifier.AuthVerifier().query_project_resource_permissions(
49
51
  mlrun.common.schemas.AuthorizationResourceTypes.datastore_profile,
50
- mlrun.common.schemas.AuthorizationAction.create,
52
+ project_name,
53
+ info.name,
54
+ mlrun.common.schemas.AuthorizationAction.store,
51
55
  auth_info,
52
56
  )
53
57
  # overwrite the project
54
- info.project = project_name
58
+ if info.project != project_name:
59
+ log_and_raise(
60
+ HTTPStatus.BAD_REQUEST.value,
61
+ reason="The project name provided in the URI does not match the one specified in the DatastoreProfile",
62
+ )
63
+
55
64
  await run_in_threadpool(
56
65
  mlrun.api.utils.singletons.db.get_db().store_datastore_profile,
57
66
  db_session,
@@ -67,7 +76,7 @@ async def store_datastore_profile(
67
76
 
68
77
 
69
78
  @router.get(
70
- path="/projects/{project_name}/datastore_profiles",
79
+ path="/projects/{project_name}/datastore-profiles",
71
80
  )
72
81
  async def list_datastore_profiles(
73
82
  project_name: str,
@@ -82,20 +91,29 @@ async def list_datastore_profiles(
82
91
  project_name,
83
92
  auth_info.session,
84
93
  )
85
- await mlrun.api.utils.auth.verifier.AuthVerifier().query_global_resource_permissions(
86
- mlrun.common.schemas.AuthorizationResourceTypes.datastore_profile,
94
+ await mlrun.api.utils.auth.verifier.AuthVerifier().query_project_permissions(
95
+ project_name,
87
96
  mlrun.common.schemas.AuthorizationAction.read,
88
97
  auth_info,
89
98
  )
90
- return await run_in_threadpool(
99
+ profiles = await run_in_threadpool(
91
100
  mlrun.api.utils.singletons.db.get_db().list_datastore_profiles,
92
101
  db_session,
93
102
  project_name,
94
103
  )
104
+ if len(profiles) == 0:
105
+ return profiles
106
+ filtered_data = await mlrun.api.utils.auth.verifier.AuthVerifier().filter_project_resources_by_permissions(
107
+ mlrun.common.schemas.AuthorizationResourceTypes.datastore_profile,
108
+ profiles,
109
+ lambda profile: (project_name, profile.name),
110
+ auth_info,
111
+ )
112
+ return filtered_data
95
113
 
96
114
 
97
115
  @router.get(
98
- path="/projects/{project_name}/datastore_profiles/{profile}",
116
+ path="/projects/{project_name}/datastore-profiles/{profile}",
99
117
  )
100
118
  async def get_datastore_profile(
101
119
  project_name: str,
@@ -111,8 +129,10 @@ async def get_datastore_profile(
111
129
  project_name,
112
130
  auth_info.session,
113
131
  )
114
- await mlrun.api.utils.auth.verifier.AuthVerifier().query_global_resource_permissions(
132
+ await mlrun.api.utils.auth.verifier.AuthVerifier().query_project_resource_permissions(
115
133
  mlrun.common.schemas.AuthorizationResourceTypes.datastore_profile,
134
+ project_name,
135
+ profile,
116
136
  mlrun.common.schemas.AuthorizationAction.read,
117
137
  auth_info,
118
138
  )
@@ -125,7 +145,7 @@ async def get_datastore_profile(
125
145
 
126
146
 
127
147
  @router.delete(
128
- path="/projects/{project_name}/datastore_profiles/{profile}",
148
+ path="/projects/{project_name}/datastore-profiles/{profile}",
129
149
  )
130
150
  async def delete_datastore_profile(
131
151
  project_name: str,
@@ -141,9 +161,11 @@ async def delete_datastore_profile(
141
161
  project_name,
142
162
  auth_info.session,
143
163
  )
144
- await mlrun.api.utils.auth.verifier.AuthVerifier().query_global_resource_permissions(
164
+ await mlrun.api.utils.auth.verifier.AuthVerifier().query_project_resource_permissions(
145
165
  mlrun.common.schemas.AuthorizationResourceTypes.datastore_profile,
146
- mlrun.common.schemas.AuthorizationAction.read,
166
+ project_name,
167
+ profile,
168
+ mlrun.common.schemas.AuthorizationAction.delete,
147
169
  auth_info,
148
170
  )
149
171
  return await run_in_threadpool(
@@ -73,7 +73,7 @@ def get_frontend_spec(
73
73
  function_deployment_target_image_template=function_deployment_target_image_template,
74
74
  function_deployment_target_image_name_prefix_template=function_target_image_name_prefix_template,
75
75
  function_deployment_target_image_registries_to_enforce_prefix=registries_to_enforce_prefix,
76
- function_deployment_mlrun_command=_resolve_function_deployment_mlrun_command(),
76
+ function_deployment_mlrun_requirement=mlrun.api.utils.builder.resolve_mlrun_install_command_version(),
77
77
  auto_mount_type=config.storage.auto_mount_type,
78
78
  auto_mount_params=config.get_storage_auto_mount_params(),
79
79
  default_artifact_path=config.artifact_path,
@@ -87,15 +87,6 @@ def get_frontend_spec(
87
87
  )
88
88
 
89
89
 
90
- def _resolve_function_deployment_mlrun_command():
91
- # TODO: When UI adds a requirements section, mlrun should be specified there instead of the commands section i.e.
92
- # frontend spec will contain only the mlrun_version_specifier instead of the full command
93
- mlrun_version_specifier = (
94
- mlrun.api.utils.builder.resolve_mlrun_install_command_version()
95
- )
96
- return f'python -m pip install "{mlrun_version_specifier}"'
97
-
98
-
99
90
  def _resolve_jobs_dashboard_url(session: str) -> typing.Optional[str]:
100
91
  iguazio_client = mlrun.api.utils.clients.iguazio.Client()
101
92
  grafana_service_url = iguazio_client.try_get_grafana_service_url(session)
@@ -267,7 +267,7 @@ async def build_function(
267
267
  except ValueError:
268
268
  log_and_raise(HTTPStatus.BAD_REQUEST.value, reason="bad JSON body")
269
269
 
270
- logger.info(f"build_function:\n{data}")
270
+ logger.info("Building function", data=data)
271
271
  function = data.get("function")
272
272
  project = function.get("metadata", {}).get("project", mlrun.mlconf.default_project)
273
273
  function_name = function.get("metadata", {}).get("name")
@@ -81,13 +81,9 @@ async def list_sources(
81
81
  auth_info,
82
82
  )
83
83
 
84
- hub_sources = await run_in_threadpool(
85
- mlrun.api.utils.singletons.db.get_db().list_hub_sources, db_session
86
- )
87
-
88
84
  return await run_in_threadpool(
89
- mlrun.api.crud.Hub().filter_hub_sources,
90
- hub_sources,
85
+ mlrun.api.crud.Hub().list_hub_sources,
86
+ db_session,
91
87
  item_name,
92
88
  tag,
93
89
  version,
@@ -104,6 +104,9 @@ class ClientSpec(
104
104
  model_endpoint_monitoring_store_type=self._get_config_value_if_not_default(
105
105
  "model_endpoint_monitoring.store_type"
106
106
  ),
107
+ model_endpoint_monitoring_endpoint_store_connection=self._get_config_value_if_not_default(
108
+ "model_endpoint_monitoring.endpoint_store_connection"
109
+ ),
107
110
  packagers=self._get_config_value_if_not_default("packagers"),
108
111
  )
109
112
 
@@ -37,7 +37,7 @@ class DatastoreProfiles(
37
37
  def _store_secret(self, project, profile_name, profile_secret_json):
38
38
  if not self._in_k8s():
39
39
  raise mlrun.errors.MLRunInvalidArgumentError(
40
- "MLRun is not configured with k8s, hub source credentials cannot be stored securely"
40
+ "MLRun is not configured with k8s, datastore profile credentials cannot be stored securely"
41
41
  )
42
42
 
43
43
  adjusted_secret = {
@@ -56,7 +56,7 @@ class DatastoreProfiles(
56
56
  def _delete_secret(self, project, profile_name):
57
57
  if not self._in_k8s():
58
58
  raise mlrun.errors.MLRunInvalidArgumentError(
59
- "MLRun is not configured with k8s, hub source credentials cannot be stored securely"
59
+ "MLRun is not configured with k8s, datastore profile credentials cannot be deleted"
60
60
  )
61
61
 
62
62
  adjusted_secret = dsp.generate_secret_key(profile_name, project)
mlrun/api/crud/hub.py CHANGED
@@ -15,6 +15,9 @@
15
15
  import json
16
16
  from typing import Any, Dict, List, Optional, Tuple
17
17
 
18
+ import sqlalchemy.orm
19
+
20
+ import mlrun.api.utils.singletons.db
18
21
  import mlrun.api.utils.singletons.k8s
19
22
  import mlrun.common.schemas
20
23
  import mlrun.common.schemas.hub
@@ -35,20 +38,6 @@ class Hub(metaclass=mlrun.utils.singleton.Singleton):
35
38
  self._internal_project_name = config.hub.k8s_secrets_project_name
36
39
  self._catalogs = {}
37
40
 
38
- @staticmethod
39
- def _in_k8s():
40
- k8s_helper = mlrun.api.utils.singletons.k8s.get_k8s_helper()
41
- return (
42
- k8s_helper is not None and k8s_helper.is_running_inside_kubernetes_cluster()
43
- )
44
-
45
- @staticmethod
46
- def _generate_credentials_secret_key(source, key=""):
47
- full_key = source + secret_name_separator + key
48
- return Secrets().generate_client_project_secret_key(
49
- SecretsClientType.hub, full_key
50
- )
51
-
52
41
  def add_source(self, source: mlrun.common.schemas.hub.HubSource):
53
42
  source_name = source.metadata.name
54
43
  credentials = source.spec.credentials
@@ -74,118 +63,6 @@ class Hub(metaclass=mlrun.utils.singleton.Singleton):
74
63
  allow_internal_secrets=True,
75
64
  )
76
65
 
77
- def _store_source_credentials(self, source_name, credentials: dict):
78
- if not self._in_k8s():
79
- raise mlrun.errors.MLRunInvalidArgumentError(
80
- "MLRun is not configured with k8s, hub source credentials cannot be stored securely"
81
- )
82
-
83
- adjusted_credentials = {
84
- self._generate_credentials_secret_key(source_name, key): value
85
- for key, value in credentials.items()
86
- }
87
- Secrets().store_project_secrets(
88
- self._internal_project_name,
89
- mlrun.common.schemas.SecretsData(
90
- provider=mlrun.common.schemas.SecretProviderName.kubernetes,
91
- secrets=adjusted_credentials,
92
- ),
93
- allow_internal_secrets=True,
94
- )
95
-
96
- def _get_source_credentials(self, source_name):
97
- if not self._in_k8s():
98
- return {}
99
-
100
- secret_prefix = self._generate_credentials_secret_key(source_name)
101
- secrets = (
102
- Secrets()
103
- .list_project_secrets(
104
- self._internal_project_name,
105
- mlrun.common.schemas.SecretProviderName.kubernetes,
106
- allow_secrets_from_k8s=True,
107
- allow_internal_secrets=True,
108
- )
109
- .secrets
110
- )
111
-
112
- source_secrets = {}
113
- for key, value in secrets.items():
114
- if key.startswith(secret_prefix):
115
- source_secrets[key[len(secret_prefix) :]] = value
116
-
117
- return source_secrets
118
-
119
- @staticmethod
120
- def _get_asset_full_path(
121
- source: mlrun.common.schemas.hub.HubSource,
122
- item: mlrun.common.schemas.hub.HubItem,
123
- asset: str,
124
- ):
125
- """
126
- Combining the item path with the asset path.
127
-
128
- :param source: Hub source object.
129
- :param item: The relevant item to get the asset from.
130
- :param asset: The asset name
131
- :return: Full path to the asset, relative to the item directory.
132
- """
133
- asset_path = item.spec.assets.get(asset, None)
134
- if not asset_path:
135
- raise mlrun.errors.MLRunNotFoundError(
136
- f"Asset={asset} not found. "
137
- f"item={item.metadata.name}, version={item.metadata.version}, tag={item.metadata.tag}"
138
- )
139
- item_path = item.metadata.get_relative_path()
140
- return source.get_full_uri(item_path + asset_path)
141
-
142
- @staticmethod
143
- def _transform_catalog_dict_to_schema(
144
- source: mlrun.common.schemas.hub.HubSource, catalog_dict: Dict[str, Any]
145
- ) -> mlrun.common.schemas.hub.HubCatalog:
146
- """
147
- Transforms catalog dictionary to HubCatalog schema
148
- :param source: Hub source object.
149
- :param catalog_dict: raw catalog dict, top level keys are item names,
150
- second level keys are version tags ("latest, "1.1.0", ...) and
151
- bottom level keys include spec as a dict and all the rest is considered as metadata.
152
- :return: catalog object
153
- """
154
- catalog = mlrun.common.schemas.hub.HubCatalog(
155
- catalog=[], channel=source.spec.channel
156
- )
157
- # Loop over objects, then over object versions.
158
- for object_name, object_dict in catalog_dict.items():
159
- for version_tag, version_dict in object_dict.items():
160
- object_details_dict = version_dict.copy()
161
- spec_dict = object_details_dict.pop("spec", {})
162
- assets = object_details_dict.pop("assets", {})
163
- # We want to align all item names to be normalized.
164
- # This is necessary since the item names are originally collected from the yaml files
165
- # which may can contain underscores.
166
- object_details_dict.update(
167
- {
168
- "name": mlrun.utils.helpers.normalize_name(
169
- object_name, verbose=False
170
- )
171
- }
172
- )
173
- metadata = mlrun.common.schemas.hub.HubItemMetadata(
174
- tag=version_tag, **object_details_dict
175
- )
176
- item_uri = source.get_full_uri(metadata.get_relative_path())
177
- spec = mlrun.common.schemas.hub.HubItemSpec(
178
- item_uri=item_uri, assets=assets, **spec_dict
179
- )
180
- item = mlrun.common.schemas.hub.HubItem(
181
- metadata=metadata,
182
- spec=spec,
183
- status=mlrun.common.schemas.ObjectStatus(),
184
- )
185
- catalog.catalog.append(item)
186
-
187
- return catalog
188
-
189
66
  def get_source_catalog(
190
67
  self,
191
68
  source: mlrun.common.schemas.hub.HubSource,
@@ -265,22 +142,6 @@ class Hub(metaclass=mlrun.utils.singleton.Singleton):
265
142
  )
266
143
  return items[0]
267
144
 
268
- @staticmethod
269
- def _get_catalog_items_filtered_by_name(
270
- catalog: List[mlrun.common.schemas.hub.HubItem],
271
- item_name: str,
272
- ) -> List[mlrun.common.schemas.hub.HubItem]:
273
- """
274
- Retrieve items from catalog filtered by name
275
-
276
- :param catalog: list of items
277
- :param item_name: item name to filter by
278
-
279
- :return: list of item objects from catalog
280
- """
281
- normalized_name = mlrun.utils.helpers.normalize_name(item_name, verbose=False)
282
- return [item for item in catalog if item.metadata.name == normalized_name]
283
-
284
145
  def get_item_object_using_source_credentials(
285
146
  self, source: mlrun.common.schemas.hub.HubSource, url
286
147
  ):
@@ -323,6 +184,19 @@ class Hub(metaclass=mlrun.utils.singleton.Singleton):
323
184
  asset_path,
324
185
  )
325
186
 
187
+ def list_hub_sources(
188
+ self,
189
+ db_session: sqlalchemy.orm.Session,
190
+ item_name: Optional[str] = None,
191
+ tag: Optional[str] = None,
192
+ version: Optional[str] = None,
193
+ ) -> List[mlrun.common.schemas.IndexedHubSource]:
194
+
195
+ hub_sources = mlrun.api.utils.singletons.db.get_db().list_hub_sources(
196
+ db_session
197
+ )
198
+ return self.filter_hub_sources(hub_sources, item_name, tag, version)
199
+
326
200
  def filter_hub_sources(
327
201
  self,
328
202
  sources: List[mlrun.common.schemas.IndexedHubSource],
@@ -360,3 +234,145 @@ class Hub(metaclass=mlrun.utils.singleton.Singleton):
360
234
  filtered_sources.append(source)
361
235
  break
362
236
  return filtered_sources
237
+
238
+ @staticmethod
239
+ def _in_k8s():
240
+ k8s_helper = mlrun.api.utils.singletons.k8s.get_k8s_helper()
241
+ return (
242
+ k8s_helper is not None and k8s_helper.is_running_inside_kubernetes_cluster()
243
+ )
244
+
245
+ @staticmethod
246
+ def _generate_credentials_secret_key(source, key=""):
247
+ full_key = source + secret_name_separator + key
248
+ return Secrets().generate_client_project_secret_key(
249
+ SecretsClientType.hub, full_key
250
+ )
251
+
252
+ def _store_source_credentials(self, source_name, credentials: dict):
253
+ if not self._in_k8s():
254
+ raise mlrun.errors.MLRunInvalidArgumentError(
255
+ "MLRun is not configured with k8s, hub source credentials cannot be stored securely"
256
+ )
257
+
258
+ adjusted_credentials = {
259
+ self._generate_credentials_secret_key(source_name, key): value
260
+ for key, value in credentials.items()
261
+ }
262
+ Secrets().store_project_secrets(
263
+ self._internal_project_name,
264
+ mlrun.common.schemas.SecretsData(
265
+ provider=mlrun.common.schemas.SecretProviderName.kubernetes,
266
+ secrets=adjusted_credentials,
267
+ ),
268
+ allow_internal_secrets=True,
269
+ )
270
+
271
+ def _get_source_credentials(self, source_name):
272
+ if not self._in_k8s():
273
+ return {}
274
+
275
+ secret_prefix = self._generate_credentials_secret_key(source_name)
276
+ secrets = (
277
+ Secrets()
278
+ .list_project_secrets(
279
+ self._internal_project_name,
280
+ mlrun.common.schemas.SecretProviderName.kubernetes,
281
+ allow_secrets_from_k8s=True,
282
+ allow_internal_secrets=True,
283
+ )
284
+ .secrets
285
+ )
286
+
287
+ source_secrets = {}
288
+ for key, value in secrets.items():
289
+ if key.startswith(secret_prefix):
290
+ source_secrets[key[len(secret_prefix) :]] = value
291
+
292
+ return source_secrets
293
+
294
+ @staticmethod
295
+ def _get_asset_full_path(
296
+ source: mlrun.common.schemas.hub.HubSource,
297
+ item: mlrun.common.schemas.hub.HubItem,
298
+ asset: str,
299
+ ):
300
+ """
301
+ Combining the item path with the asset path.
302
+
303
+ :param source: Hub source object.
304
+ :param item: The relevant item to get the asset from.
305
+ :param asset: The asset name
306
+ :return: Full path to the asset, relative to the item directory.
307
+ """
308
+ asset_path = item.spec.assets.get(asset, None)
309
+ if not asset_path:
310
+ raise mlrun.errors.MLRunNotFoundError(
311
+ f"Asset={asset} not found. "
312
+ f"item={item.metadata.name}, version={item.metadata.version}, tag={item.metadata.tag}"
313
+ )
314
+ item_path = item.metadata.get_relative_path()
315
+ return source.get_full_uri(item_path + asset_path)
316
+
317
+ @staticmethod
318
+ def _transform_catalog_dict_to_schema(
319
+ source: mlrun.common.schemas.hub.HubSource, catalog_dict: Dict[str, Any]
320
+ ) -> mlrun.common.schemas.hub.HubCatalog:
321
+ """
322
+ Transforms catalog dictionary to HubCatalog schema
323
+ :param source: Hub source object.
324
+ :param catalog_dict: raw catalog dict, top level keys are item names,
325
+ second level keys are version tags ("latest, "1.1.0", ...) and
326
+ bottom level keys include spec as a dict and all the rest is considered as metadata.
327
+ :return: catalog object
328
+ """
329
+ catalog = mlrun.common.schemas.hub.HubCatalog(
330
+ catalog=[], channel=source.spec.channel
331
+ )
332
+ # Loop over objects, then over object versions.
333
+ for object_name, object_dict in catalog_dict.items():
334
+ for version_tag, version_dict in object_dict.items():
335
+ object_details_dict = version_dict.copy()
336
+ spec_dict = object_details_dict.pop("spec", {})
337
+ assets = object_details_dict.pop("assets", {})
338
+ # We want to align all item names to be normalized.
339
+ # This is necessary since the item names are originally collected from the yaml files
340
+ # which may can contain underscores.
341
+ object_details_dict.update(
342
+ {
343
+ "name": mlrun.utils.helpers.normalize_name(
344
+ object_name, verbose=False
345
+ )
346
+ }
347
+ )
348
+ metadata = mlrun.common.schemas.hub.HubItemMetadata(
349
+ tag=version_tag, **object_details_dict
350
+ )
351
+ item_uri = source.get_full_uri(metadata.get_relative_path())
352
+ spec = mlrun.common.schemas.hub.HubItemSpec(
353
+ item_uri=item_uri, assets=assets, **spec_dict
354
+ )
355
+ item = mlrun.common.schemas.hub.HubItem(
356
+ metadata=metadata,
357
+ spec=spec,
358
+ status=mlrun.common.schemas.ObjectStatus(),
359
+ )
360
+ catalog.catalog.append(item)
361
+
362
+ return catalog
363
+
364
+ @staticmethod
365
+ def _get_catalog_items_filtered_by_name(
366
+ catalog: List[mlrun.common.schemas.hub.HubItem],
367
+ item_name: str,
368
+ ) -> List[mlrun.common.schemas.hub.HubItem]:
369
+ """
370
+ Retrieve items from catalog filtered by name
371
+
372
+ :param catalog: list of items
373
+ :param item_name: item name to filter by
374
+
375
+ :return: list of item objects from catalog
376
+ """
377
+ normalized_name = mlrun.utils.helpers.normalize_name(item_name, verbose=False)
378
+ return [item for item in catalog if item.metadata.name == normalized_name]
@@ -26,6 +26,7 @@ from mlrun.api.api.utils import (
26
26
  )
27
27
  from mlrun.config import config
28
28
  from mlrun.model import Credentials, RunMetadata, RunObject, RunSpec
29
+ from mlrun.utils import fill_project_path_template
29
30
 
30
31
 
31
32
  class WorkflowRunners(
@@ -161,9 +162,12 @@ class WorkflowRunners(
161
162
  ),
162
163
  handler="mlrun.projects.load_and_run",
163
164
  scrape_metrics=config.scrape_metrics,
164
- output_path=(
165
- workflow_request.artifact_path or config.artifact_path
166
- ).replace("{{run.uid}}", meta_uid),
165
+ output_path=fill_project_path_template(
166
+ (workflow_request.artifact_path or config.artifact_path).replace(
167
+ "{{run.uid}}", meta_uid
168
+ ),
169
+ project.metadata.name,
170
+ ),
167
171
  ),
168
172
  metadata=RunMetadata(
169
173
  uid=meta_uid, name=workflow_spec.name, project=project.metadata.name