diff --git a/mlrun/api/db/sqldb/db.py b/mlrun/api/db/sqldb/db.py index 1869e5ea3c4..d1c18121bd1 100644 --- a/mlrun/api/db/sqldb/db.py +++ b/mlrun/api/db/sqldb/db.py @@ -1982,7 +1982,7 @@ def _try_commit_obj(): # We want to retry only when database is locked so for any other scenario escalate to fatal failure try: raise mlrun.errors.MLRunConflictError( - f"Conflict - {cls} already exists" + f"Conflict - {cls} already exists: {obj.get_identifier_string()}" ) from err except mlrun.errors.MLRunConflictError as exc: raise mlrun.utils.helpers.FatalFailureException( diff --git a/mlrun/api/db/sqldb/models.py b/mlrun/api/db/sqldb/models.py index 02c3002f370..2cbb1f4098c 100644 --- a/mlrun/api/db/sqldb/models.py +++ b/mlrun/api/db/sqldb/models.py @@ -143,6 +143,9 @@ class Artifact(Base, HasStruct): body = Column(BLOB) labels = relationship(Label) + def get_identifier_string(self) -> str: + return f"{self.project}/{self.key}/{self.uid}" + class Function(Base, HasStruct): __tablename__ = "functions" __table_args__ = ( @@ -161,6 +164,9 @@ class Function(Base, HasStruct): updated = Column(TIMESTAMP) labels = relationship(Label) + def get_identifier_string(self) -> str: + return f"{self.project}/{self.name}/{self.uid}" + class Log(Base, BaseModel): __tablename__ = "logs" @@ -170,6 +176,9 @@ class Log(Base, BaseModel): # TODO: change to JSON, see mlrun/api/schemas/function.py::FunctionState for reasoning body = Column(BLOB) + def get_identifier_string(self) -> str: + return f"{self.project}/{self.uid}" + class Run(Base, HasStruct): __tablename__ = "runs" __table_args__ = ( @@ -189,6 +198,9 @@ class Run(Base, HasStruct): start_time = Column(TIMESTAMP) labels = relationship(Label) + def get_identifier_string(self) -> str: + return f"{self.project}/{self.uid}/{self.iteration}" + class Schedule(Base, BaseModel): __tablename__ = "schedules_v2" __table_args__ = (UniqueConstraint("project", "name", name="_schedules_v2_uc"),) @@ -209,6 +221,9 @@ class Schedule(Base, BaseModel): labels = relationship(Label, cascade="all, delete-orphan") concurrency_limit = Column(Integer, nullable=False) + def get_identifier_string(self) -> str: + return f"{self.project}/{self.name}" + @property def scheduled_object(self): return pickle.loads(self.struct) @@ -262,6 +277,9 @@ class Project(Base, BaseModel): labels = relationship(Label, cascade="all, delete-orphan") + def get_identifier_string(self) -> str: + return f"{self.name}" + @property def full_object(self): if self._full_object: @@ -282,6 +300,9 @@ class Feature(Base, BaseModel): Label = make_label(__tablename__) labels = relationship(Label, cascade="all, delete-orphan") + def get_identifier_string(self) -> str: + return f"{self.project}/{self.name}" + class Entity(Base, BaseModel): __tablename__ = "entities" id = Column(Integer, primary_key=True) @@ -293,6 +314,9 @@ class Entity(Base, BaseModel): Label = make_label(__tablename__) labels = relationship(Label, cascade="all, delete-orphan") + def get_identifier_string(self) -> str: + return f"{self.project}/{self.name}" + class FeatureSet(Base, BaseModel): __tablename__ = "feature_sets" __table_args__ = ( @@ -317,6 +341,9 @@ class FeatureSet(Base, BaseModel): features = relationship(Feature, cascade="all, delete-orphan") entities = relationship(Entity, cascade="all, delete-orphan") + def get_identifier_string(self) -> str: + return f"{self.project}/{self.name}/{self.uid}" + @property def full_object(self): if self._full_object: @@ -347,6 +374,9 @@ class FeatureVector(Base, BaseModel): labels = relationship(Label, cascade="all, delete-orphan") + def get_identifier_string(self) -> str: + return f"{self.project}/{self.name}/{self.uid}" + @property def full_object(self): if self._full_object: @@ -368,6 +398,9 @@ class MarketplaceSource(Base, BaseModel): _full_object = Column("object", JSON) + def get_identifier_string(self) -> str: + return f"{self.project}/{self.name}" + @property def full_object(self): if self._full_object: diff --git a/tests/api/utils/test_scheduler.py b/tests/api/utils/test_scheduler.py index 936fcdd5e97..4e0a1bbe027 100644 --- a/tests/api/utils/test_scheduler.py +++ b/tests/api/utils/test_scheduler.py @@ -322,7 +322,10 @@ async def test_create_schedule_failure_already_exists( cron_trigger, ) - with pytest.raises(mlrun.errors.MLRunConflictError) as excinfo: + with pytest.raises( + mlrun.errors.MLRunConflictError, + match=rf"Conflict - Schedule already exists: {project}/{schedule_name}", + ): scheduler.create_schedule( db, mlrun.api.schemas.AuthInfo(), @@ -332,7 +335,6 @@ async def test_create_schedule_failure_already_exists( do_nothing, cron_trigger, ) - assert "Conflict - Schedule already exists" in str(excinfo.value) @pytest.mark.asyncio