mlrun 1.8.0rc19__py3-none-any.whl → 1.8.0rc26__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.
- mlrun/__init__.py +37 -3
- mlrun/__main__.py +5 -0
- mlrun/alerts/alert.py +1 -0
- mlrun/artifacts/document.py +78 -36
- mlrun/common/formatters/feature_set.py +1 -0
- mlrun/common/runtimes/constants.py +17 -0
- mlrun/common/schemas/alert.py +3 -0
- mlrun/common/schemas/client_spec.py +0 -1
- mlrun/common/schemas/model_monitoring/constants.py +32 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +2 -0
- mlrun/common/schemas/workflow.py +1 -0
- mlrun/config.py +39 -6
- mlrun/datastore/datastore_profile.py +58 -16
- mlrun/datastore/sources.py +7 -1
- mlrun/datastore/vectorstore.py +20 -1
- mlrun/db/base.py +20 -0
- mlrun/db/httpdb.py +97 -10
- mlrun/db/nopdb.py +19 -0
- mlrun/errors.py +4 -0
- mlrun/execution.py +15 -6
- mlrun/frameworks/_common/model_handler.py +0 -2
- mlrun/launcher/client.py +2 -2
- mlrun/launcher/local.py +5 -1
- mlrun/model_monitoring/applications/_application_steps.py +3 -1
- mlrun/model_monitoring/controller.py +266 -103
- mlrun/model_monitoring/db/tsdb/__init__.py +11 -23
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +2 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +20 -21
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +35 -34
- mlrun/model_monitoring/helpers.py +16 -10
- mlrun/model_monitoring/stream_processing.py +106 -35
- mlrun/package/context_handler.py +1 -1
- mlrun/package/packagers_manager.py +4 -18
- mlrun/projects/pipelines.py +18 -5
- mlrun/projects/project.py +156 -39
- mlrun/runtimes/nuclio/serving.py +22 -13
- mlrun/runtimes/sparkjob/spark3job.py +1 -1
- mlrun/secrets.py +1 -1
- mlrun/serving/server.py +11 -3
- mlrun/serving/states.py +65 -8
- mlrun/serving/v2_serving.py +67 -44
- mlrun/utils/helpers.py +111 -23
- mlrun/utils/notifications/notification/base.py +6 -1
- mlrun/utils/notifications/notification/slack.py +5 -1
- mlrun/utils/notifications/notification_pusher.py +67 -36
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.8.0rc19.dist-info → mlrun-1.8.0rc26.dist-info}/METADATA +33 -16
- {mlrun-1.8.0rc19.dist-info → mlrun-1.8.0rc26.dist-info}/RECORD +52 -52
- {mlrun-1.8.0rc19.dist-info → mlrun-1.8.0rc26.dist-info}/WHEEL +1 -1
- {mlrun-1.8.0rc19.dist-info → mlrun-1.8.0rc26.dist-info}/LICENSE +0 -0
- {mlrun-1.8.0rc19.dist-info → mlrun-1.8.0rc26.dist-info}/entry_points.txt +0 -0
- {mlrun-1.8.0rc19.dist-info → mlrun-1.8.0rc26.dist-info}/top_level.txt +0 -0
mlrun/datastore/sources.py
CHANGED
|
@@ -1089,9 +1089,10 @@ class KafkaSource(OnlineSource):
|
|
|
1089
1089
|
attributes["partitions"] = partitions
|
|
1090
1090
|
sasl = attributes.pop("sasl", {})
|
|
1091
1091
|
if sasl_user and sasl_pass:
|
|
1092
|
-
sasl["
|
|
1092
|
+
sasl["enable"] = True
|
|
1093
1093
|
sasl["user"] = sasl_user
|
|
1094
1094
|
sasl["password"] = sasl_pass
|
|
1095
|
+
sasl["mechanism"] = "PLAIN"
|
|
1095
1096
|
if sasl:
|
|
1096
1097
|
attributes["sasl"] = sasl
|
|
1097
1098
|
super().__init__(attributes=attributes, **kwargs)
|
|
@@ -1127,8 +1128,13 @@ class KafkaSource(OnlineSource):
|
|
|
1127
1128
|
extra_attributes["workerAllocationMode"] = extra_attributes.get(
|
|
1128
1129
|
"worker_allocation_mode", "static"
|
|
1129
1130
|
)
|
|
1131
|
+
else:
|
|
1132
|
+
extra_attributes["workerAllocationMode"] = extra_attributes.get(
|
|
1133
|
+
"worker_allocation_mode", "pool"
|
|
1134
|
+
)
|
|
1130
1135
|
|
|
1131
1136
|
trigger_kwargs = {}
|
|
1137
|
+
|
|
1132
1138
|
if "max_workers" in extra_attributes:
|
|
1133
1139
|
trigger_kwargs = {"max_workers": extra_attributes.pop("max_workers")}
|
|
1134
1140
|
|
mlrun/datastore/vectorstore.py
CHANGED
|
@@ -48,6 +48,18 @@ def _extract_collection_name(vectorstore: "VectorStore") -> str: # noqa: F821
|
|
|
48
48
|
else:
|
|
49
49
|
return getattr(obj, pattern, None)
|
|
50
50
|
|
|
51
|
+
if type(vectorstore).__name__ == "PineconeVectorStore":
|
|
52
|
+
try:
|
|
53
|
+
url = (
|
|
54
|
+
vectorstore._index.config.host
|
|
55
|
+
if hasattr(vectorstore._index, "config")
|
|
56
|
+
else vectorstore._index._config.host
|
|
57
|
+
)
|
|
58
|
+
index_name = url.split("//")[1].split("-")[0]
|
|
59
|
+
return index_name
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
51
63
|
for pattern in patterns:
|
|
52
64
|
try:
|
|
53
65
|
value = resolve_attribute(vectorstore, pattern)
|
|
@@ -254,7 +266,14 @@ class VectorStoreCollection:
|
|
|
254
266
|
elif store_class == "chroma":
|
|
255
267
|
where = {DocumentArtifact.METADATA_SOURCE_KEY: artifact.get_source()}
|
|
256
268
|
self._collection_impl.delete(where=where)
|
|
257
|
-
|
|
269
|
+
elif store_class == "pineconevectorstore":
|
|
270
|
+
filter = {
|
|
271
|
+
DocumentArtifact.METADATA_SOURCE_KEY: {"$eq": artifact.get_source()}
|
|
272
|
+
}
|
|
273
|
+
self._collection_impl.delete(filter=filter)
|
|
274
|
+
elif store_class == "mongodbatlasvectorsearch":
|
|
275
|
+
filter = {DocumentArtifact.METADATA_SOURCE_KEY: artifact.get_source()}
|
|
276
|
+
self._collection_impl.collection.delete_many(filter=filter)
|
|
258
277
|
elif (
|
|
259
278
|
hasattr(self._collection_impl, "delete")
|
|
260
279
|
and "filter"
|
mlrun/db/base.py
CHANGED
|
@@ -68,6 +68,18 @@ class RunDBInterface(ABC):
|
|
|
68
68
|
):
|
|
69
69
|
pass
|
|
70
70
|
|
|
71
|
+
def refresh_smtp_configuration(self):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def push_pipeline_notifications(
|
|
75
|
+
self,
|
|
76
|
+
pipeline_id,
|
|
77
|
+
project="",
|
|
78
|
+
notifications=None,
|
|
79
|
+
timeout=45,
|
|
80
|
+
):
|
|
81
|
+
pass
|
|
82
|
+
|
|
71
83
|
@abstractmethod
|
|
72
84
|
def read_run(
|
|
73
85
|
self,
|
|
@@ -917,6 +929,14 @@ class RunDBInterface(ABC):
|
|
|
917
929
|
):
|
|
918
930
|
pass
|
|
919
931
|
|
|
932
|
+
@abstractmethod
|
|
933
|
+
def get_alert_activation(
|
|
934
|
+
self,
|
|
935
|
+
project,
|
|
936
|
+
activation_id,
|
|
937
|
+
) -> mlrun.common.schemas.AlertActivation:
|
|
938
|
+
pass
|
|
939
|
+
|
|
920
940
|
def update_alert_activation(
|
|
921
941
|
self,
|
|
922
942
|
activation_id: int,
|
mlrun/db/httpdb.py
CHANGED
|
@@ -559,14 +559,6 @@ class HTTPRunDB(RunDBInterface):
|
|
|
559
559
|
server_cfg.get("external_platform_tracking")
|
|
560
560
|
or config.external_platform_tracking
|
|
561
561
|
)
|
|
562
|
-
config.model_endpoint_monitoring.tsdb_connection = (
|
|
563
|
-
server_cfg.get("model_monitoring_tsdb_connection")
|
|
564
|
-
or config.model_endpoint_monitoring.tsdb_connection
|
|
565
|
-
)
|
|
566
|
-
config.model_endpoint_monitoring.stream_connection = (
|
|
567
|
-
server_cfg.get("stream_connection")
|
|
568
|
-
or config.model_endpoint_monitoring.stream_connection
|
|
569
|
-
)
|
|
570
562
|
config.packagers = server_cfg.get("packagers") or config.packagers
|
|
571
563
|
server_data_prefixes = server_cfg.get("feature_store_data_prefixes") or {}
|
|
572
564
|
for prefix in ["default", "nosql", "redisnosql"]:
|
|
@@ -771,7 +763,6 @@ class HTTPRunDB(RunDBInterface):
|
|
|
771
763
|
:returns: :py:class:`~mlrun.common.schemas.BackgroundTask`.
|
|
772
764
|
"""
|
|
773
765
|
project = project or config.default_project
|
|
774
|
-
|
|
775
766
|
response = self.api_call(
|
|
776
767
|
"POST",
|
|
777
768
|
path=f"projects/{project}/runs/{uid}/push-notifications",
|
|
@@ -780,9 +771,84 @@ class HTTPRunDB(RunDBInterface):
|
|
|
780
771
|
)
|
|
781
772
|
if response.status_code == http.HTTPStatus.ACCEPTED:
|
|
782
773
|
background_task = mlrun.common.schemas.BackgroundTask(**response.json())
|
|
783
|
-
|
|
774
|
+
background_task = self._wait_for_background_task_to_reach_terminal_state(
|
|
784
775
|
background_task.metadata.name, project=project
|
|
785
776
|
)
|
|
777
|
+
if (
|
|
778
|
+
background_task.status.state
|
|
779
|
+
== mlrun.common.schemas.BackgroundTaskState.succeeded
|
|
780
|
+
):
|
|
781
|
+
logger.info(
|
|
782
|
+
"Notifications for the run have been pushed",
|
|
783
|
+
project=project,
|
|
784
|
+
run_id=uid,
|
|
785
|
+
)
|
|
786
|
+
elif (
|
|
787
|
+
background_task.status.state
|
|
788
|
+
== mlrun.common.schemas.BackgroundTaskState.failed
|
|
789
|
+
):
|
|
790
|
+
logger.error(
|
|
791
|
+
"Failed to push run notifications",
|
|
792
|
+
project=project,
|
|
793
|
+
run_id=uid,
|
|
794
|
+
error=background_task.status.error,
|
|
795
|
+
)
|
|
796
|
+
return None
|
|
797
|
+
|
|
798
|
+
def push_pipeline_notifications(
|
|
799
|
+
self,
|
|
800
|
+
pipeline_id,
|
|
801
|
+
project="",
|
|
802
|
+
notifications=None,
|
|
803
|
+
timeout=45,
|
|
804
|
+
):
|
|
805
|
+
"""
|
|
806
|
+
Push notifications for a pipeline.
|
|
807
|
+
|
|
808
|
+
:param pipeline_id: Unique ID of the pipeline(KFP).
|
|
809
|
+
:param project: Project that the run belongs to.
|
|
810
|
+
:param notifications: List of notifications to push.
|
|
811
|
+
:returns: :py:class:`~mlrun.common.schemas.BackgroundTask`.
|
|
812
|
+
"""
|
|
813
|
+
if notifications is None or type(notifications) is not list:
|
|
814
|
+
raise MLRunInvalidArgumentError(
|
|
815
|
+
"The 'notifications' parameter must be a list."
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
project = project or config.default_project
|
|
819
|
+
|
|
820
|
+
response = self.api_call(
|
|
821
|
+
"POST",
|
|
822
|
+
path=f"projects/{project}/pipelines/{pipeline_id}/push-notifications",
|
|
823
|
+
error="Failed push notifications",
|
|
824
|
+
body=_as_json([notification.to_dict() for notification in notifications]),
|
|
825
|
+
timeout=timeout,
|
|
826
|
+
)
|
|
827
|
+
if response.status_code == http.HTTPStatus.ACCEPTED:
|
|
828
|
+
background_task = mlrun.common.schemas.BackgroundTask(**response.json())
|
|
829
|
+
background_task = self._wait_for_background_task_to_reach_terminal_state(
|
|
830
|
+
background_task.metadata.name, project=project
|
|
831
|
+
)
|
|
832
|
+
if (
|
|
833
|
+
background_task.status.state
|
|
834
|
+
== mlrun.common.schemas.BackgroundTaskState.succeeded
|
|
835
|
+
):
|
|
836
|
+
logger.info(
|
|
837
|
+
"Pipeline notifications have been pushed",
|
|
838
|
+
project=project,
|
|
839
|
+
pipeline_id=pipeline_id,
|
|
840
|
+
)
|
|
841
|
+
elif (
|
|
842
|
+
background_task.status.state
|
|
843
|
+
== mlrun.common.schemas.BackgroundTaskState.failed
|
|
844
|
+
):
|
|
845
|
+
logger.error(
|
|
846
|
+
"Failed to push pipeline notifications",
|
|
847
|
+
project=project,
|
|
848
|
+
pipeline_id=pipeline_id,
|
|
849
|
+
error=background_task.status.error,
|
|
850
|
+
)
|
|
851
|
+
|
|
786
852
|
return None
|
|
787
853
|
|
|
788
854
|
def read_run(
|
|
@@ -4955,6 +5021,27 @@ class HTTPRunDB(RunDBInterface):
|
|
|
4955
5021
|
**kwargs,
|
|
4956
5022
|
)
|
|
4957
5023
|
|
|
5024
|
+
def get_alert_activation(
|
|
5025
|
+
self,
|
|
5026
|
+
project,
|
|
5027
|
+
activation_id,
|
|
5028
|
+
) -> mlrun.common.schemas.AlertActivation:
|
|
5029
|
+
"""
|
|
5030
|
+
Retrieve the alert activation by id
|
|
5031
|
+
|
|
5032
|
+
:param project: Project name for which the summary belongs.
|
|
5033
|
+
:param activation_id: alert activation id.
|
|
5034
|
+
:returns: alert activation object.
|
|
5035
|
+
"""
|
|
5036
|
+
project = project or config.default_project
|
|
5037
|
+
|
|
5038
|
+
error = "get alert activation"
|
|
5039
|
+
path = f"projects/{project}/alert-activations/{activation_id}"
|
|
5040
|
+
|
|
5041
|
+
response = self.api_call("GET", path, error)
|
|
5042
|
+
|
|
5043
|
+
return mlrun.common.schemas.AlertActivation(**response.json())
|
|
5044
|
+
|
|
4958
5045
|
def get_project_summary(
|
|
4959
5046
|
self, project: Optional[str] = None
|
|
4960
5047
|
) -> mlrun.common.schemas.ProjectSummary:
|
mlrun/db/nopdb.py
CHANGED
|
@@ -84,6 +84,18 @@ class NopDB(RunDBInterface):
|
|
|
84
84
|
):
|
|
85
85
|
pass
|
|
86
86
|
|
|
87
|
+
def refresh_smtp_configuration(self):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
def push_pipeline_notifications(
|
|
91
|
+
self,
|
|
92
|
+
pipeline_id,
|
|
93
|
+
project="",
|
|
94
|
+
notifications=None,
|
|
95
|
+
timeout=45,
|
|
96
|
+
):
|
|
97
|
+
pass
|
|
98
|
+
|
|
87
99
|
def list_runtime_resources(
|
|
88
100
|
self,
|
|
89
101
|
project: Optional[str] = None,
|
|
@@ -936,5 +948,12 @@ class NopDB(RunDBInterface):
|
|
|
936
948
|
):
|
|
937
949
|
pass
|
|
938
950
|
|
|
951
|
+
def get_alert_activation(
|
|
952
|
+
self,
|
|
953
|
+
project,
|
|
954
|
+
activation_id,
|
|
955
|
+
) -> mlrun.common.schemas.AlertActivation:
|
|
956
|
+
pass
|
|
957
|
+
|
|
939
958
|
def get_project_summary(self, project: str):
|
|
940
959
|
pass
|
mlrun/errors.py
CHANGED
|
@@ -174,6 +174,10 @@ class MLRunInvalidArgumentError(MLRunHTTPStatusError, ValueError):
|
|
|
174
174
|
error_status_code = HTTPStatus.BAD_REQUEST.value
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
class MLRunModelLimitExceededError(MLRunHTTPStatusError, ValueError):
|
|
178
|
+
error_status_code = HTTPStatus.BAD_REQUEST.value
|
|
179
|
+
|
|
180
|
+
|
|
177
181
|
class MLRunInvalidArgumentTypeError(MLRunHTTPStatusError, TypeError):
|
|
178
182
|
error_status_code = HTTPStatus.BAD_REQUEST.value
|
|
179
183
|
|
mlrun/execution.py
CHANGED
|
@@ -876,7 +876,7 @@ class MLClientCtx:
|
|
|
876
876
|
|
|
877
877
|
def log_document(
|
|
878
878
|
self,
|
|
879
|
-
key: str,
|
|
879
|
+
key: str = "",
|
|
880
880
|
tag: str = "",
|
|
881
881
|
local_path: str = "",
|
|
882
882
|
artifact_path: Optional[str] = None,
|
|
@@ -890,7 +890,8 @@ class MLClientCtx:
|
|
|
890
890
|
"""
|
|
891
891
|
Log a document as an artifact.
|
|
892
892
|
|
|
893
|
-
:param key:
|
|
893
|
+
:param key: Optional artifact key. If not provided, will be derived from local_path
|
|
894
|
+
or target_path using DocumentArtifact.key_from_source()
|
|
894
895
|
:param tag: Version tag
|
|
895
896
|
:param local_path: path to the local file we upload, will also be use
|
|
896
897
|
as the destination subpath (under "artifact_path")
|
|
@@ -923,7 +924,6 @@ class MLClientCtx:
|
|
|
923
924
|
Example:
|
|
924
925
|
>>> # Log a PDF document with custom loader
|
|
925
926
|
>>> project.log_document(
|
|
926
|
-
... key="my_doc",
|
|
927
927
|
... local_path="path/to/doc.pdf",
|
|
928
928
|
... document_loader_spec=DocumentLoaderSpec(
|
|
929
929
|
... loader_class_name="langchain_community.document_loaders.PDFLoader",
|
|
@@ -932,10 +932,19 @@ class MLClientCtx:
|
|
|
932
932
|
... ),
|
|
933
933
|
... )
|
|
934
934
|
"""
|
|
935
|
+
|
|
936
|
+
if not key and not local_path and not target_path:
|
|
937
|
+
raise ValueError(
|
|
938
|
+
"Must provide either 'key' parameter or 'local_path'/'target_path' to derive the key from"
|
|
939
|
+
)
|
|
940
|
+
if not key:
|
|
941
|
+
key = DocumentArtifact.key_from_source(local_path or target_path)
|
|
942
|
+
|
|
935
943
|
doc_artifact = DocumentArtifact(
|
|
936
944
|
key=key,
|
|
937
945
|
original_source=local_path or target_path,
|
|
938
946
|
document_loader_spec=document_loader_spec,
|
|
947
|
+
collections=kwargs.pop("collections", None),
|
|
939
948
|
**kwargs,
|
|
940
949
|
)
|
|
941
950
|
|
|
@@ -964,12 +973,12 @@ class MLClientCtx:
|
|
|
964
973
|
def get_artifact(
|
|
965
974
|
self, key, tag=None, iter=None, tree=None, uid=None
|
|
966
975
|
) -> Optional[Artifact]:
|
|
967
|
-
|
|
976
|
+
cached_artifact_uri = self._artifacts_manager.artifact_uris.get(key, None)
|
|
977
|
+
if tag or iter or tree or uid or (not cached_artifact_uri):
|
|
968
978
|
project = self.get_project_object()
|
|
969
979
|
return project.get_artifact(key=key, tag=tag, iter=iter, tree=tree, uid=uid)
|
|
970
980
|
else:
|
|
971
|
-
|
|
972
|
-
return self.get_store_resource(artifact_uri)
|
|
981
|
+
return self.get_store_resource(cached_artifact_uri)
|
|
973
982
|
|
|
974
983
|
def update_artifact(self, artifact_object: Artifact):
|
|
975
984
|
"""Update an artifact object in the DB and the cached uri"""
|
|
@@ -976,7 +976,6 @@ class ModelHandler(ABC, Generic[CommonTypes.ModelType, CommonTypes.IOSampleType]
|
|
|
976
976
|
custom_objects_map_json,
|
|
977
977
|
local_path=custom_objects_map_json,
|
|
978
978
|
artifact_path=self._context.artifact_path,
|
|
979
|
-
db_key=False,
|
|
980
979
|
)
|
|
981
980
|
|
|
982
981
|
# Zip the custom objects directory:
|
|
@@ -997,7 +996,6 @@ class ModelHandler(ABC, Generic[CommonTypes.ModelType, CommonTypes.IOSampleType]
|
|
|
997
996
|
custom_objects_zip,
|
|
998
997
|
local_path=custom_objects_zip,
|
|
999
998
|
artifact_path=self._context.artifact_path,
|
|
1000
|
-
db_key=False,
|
|
1001
999
|
)
|
|
1002
1000
|
|
|
1003
1001
|
return artifacts
|
mlrun/launcher/client.py
CHANGED
|
@@ -134,7 +134,7 @@ class ClientBaseLauncher(launcher.BaseLauncher, abc.ABC):
|
|
|
134
134
|
if mlrun.utils.is_jupyter and mlrun.mlconf.ipython_widget:
|
|
135
135
|
results_tbl.show()
|
|
136
136
|
print()
|
|
137
|
-
ui_url = mlrun.utils.
|
|
137
|
+
ui_url = mlrun.utils.get_run_url(project, uid=uid, name=run.metadata.name)
|
|
138
138
|
if ui_url:
|
|
139
139
|
ui_url = f' or <a href="{ui_url}" target="_blank">click here</a> to open in UI'
|
|
140
140
|
IPython.display.display(
|
|
@@ -150,6 +150,6 @@ class ClientBaseLauncher(launcher.BaseLauncher, abc.ABC):
|
|
|
150
150
|
mlrun.utils.logger.info(
|
|
151
151
|
"To track results use the CLI", info_cmd=info_cmd, logs_cmd=logs_cmd
|
|
152
152
|
)
|
|
153
|
-
ui_url = mlrun.utils.
|
|
153
|
+
ui_url = mlrun.utils.get_run_url(project, uid=uid, name=run.metadata.name)
|
|
154
154
|
if ui_url:
|
|
155
155
|
mlrun.utils.logger.info("Or click for UI", ui_url=ui_url)
|
mlrun/launcher/local.py
CHANGED
|
@@ -281,5 +281,9 @@ class ClientLocalLauncher(launcher.ClientBaseLauncher):
|
|
|
281
281
|
# once the run is completed, and we can just push the notifications.
|
|
282
282
|
# Only push from jupyter, not from the CLI.
|
|
283
283
|
# "handler" and "dask" kinds are special cases of local runs which don't set local=True
|
|
284
|
-
if self._is_run_local or runtime.kind in ["handler"
|
|
284
|
+
if self._is_run_local or runtime.kind in ["handler"]:
|
|
285
285
|
mlrun.utils.notifications.NotificationPusher([runobj]).push()
|
|
286
|
+
elif runtime.kind in ["dask"]:
|
|
287
|
+
runtime._get_db().push_run_notifications(
|
|
288
|
+
uid=runobj.metadata.uid, project=runobj.metadata.project
|
|
289
|
+
)
|
|
@@ -166,7 +166,9 @@ class _ApplicationErrorHandler(StepToDict):
|
|
|
166
166
|
"Endpoint ID": event.body.endpoint_id,
|
|
167
167
|
"Application Class": event.body.application_name,
|
|
168
168
|
"Error": "".join(
|
|
169
|
-
traceback.format_exception(
|
|
169
|
+
traceback.format_exception(
|
|
170
|
+
None, value=event.error, tb=event.error.__traceback__
|
|
171
|
+
)
|
|
170
172
|
),
|
|
171
173
|
"Timestamp": event.timestamp,
|
|
172
174
|
}
|