Repository: apache/airflow
File: airflow-core/src/airflow/models/serialized_dag.py (Python)

Code before:
        """
        Try to reuse existing deadline UUIDs if the deadline definitions haven't changed.

        Returns None if Deadline hashes are not all identical, indicating they need to be updated.

        :param existing_deadline_uuids: List of UUID strings from existing serialized dag
        :param new_deadline_data: List of new deadline alert data dicts from the DAG
        :param session: Database session
        :return: UUID mapping dict if all match, None if any mismatch detected
        """
        if len(existing_deadline_uuids) != len(new_deadline_data):
            return None

        existing_alerts = session.scalars(
            select(DeadlineAlertModel).where(DeadlineAlertModel.id.in_(existing_deadline_uuids))
        ).all()

        if len(existing_alerts) != len(existing_deadline_uuids):
            return None

        new_alerts_temp = []
        for deadline_alert in new_deadline_data:
            deadline_data = deadline_alert.get(Encoding.VAR, deadline_alert)
            # Create a temporary alert for comparison
            temp_alert = DeadlineAlertModel(
                id="temp",  # id is required for the object but isn't included in the __eq__
                reference=deadline_data[DeadlineAlertFields.REFERENCE],
                interval=deadline_data[DeadlineAlertFields.INTERVAL],
                callback_def=deadline_data[DeadlineAlertFields.CALLBACK],
            )
            new_alerts_temp.append((temp_alert, deadline_data))

        matched_uuids = set()
        uuid_mapping = {}

        for new_alert_temp, deadline_data in new_alerts_temp:
            # Find a matching existing alert using DeadlineAlert.__eq__
            found_match = False
            for existing_alert in existing_alerts:
                if existing_alert.id in matched_uuids:
                    continue  # Already matched to another new deadline

                if new_alert_temp == existing_alert:
                    # Found a match, reuse this UUID
                    uuid_mapping[str(existing_alert.id)] = deadline_data
                    matched_uuids.add(existing_alert.id)
                    found_match = True
                    break

            if not found_match:
                # New deadline data doesn't match any existing alert, need new UUIDs

Review comment (bug):
Is it possible to have multiple DeadlineAlertModels with the same property but different IDs?

DeadlineAlertModel(id=1, reference="the same"...)
DeadlineAlertModel(id=2, reference="the same"...)

If it's possible, it looks incorrect to use `__eq__` to me. Comparing those attributes directly might seem lengthy, but it is probably more accurate and less confusing.

Code after:
        Try to reuse existing deadline UUIDs if the deadline definitions haven't changed.

        Returns None if Deadline hashes are not all identical, indicating they need to be updated.

        :param existing_deadline_uuids: List of UUID strings from existing serialized Dag
        :param new_deadline_data: List of new deadline alert data dicts from the Dag
        :param session: Database session
        :return: UUID mapping dict if all match, None if any mismatch detected
        """

        def _definitions_match(deadline_data: dict, existing: DeadlineAlertModel) -> bool:
            """Check if raw deadline data matches an existing DeadlineAlert's definition."""
            return (
                deadline_data[DeadlineAlertFields.REFERENCE] == existing.reference
                and deadline_data[DeadlineAlertFields.INTERVAL] == existing.interval
                and deadline_data[DeadlineAlertFields.CALLBACK] == existing.callback_def
            )

        if len(existing_deadline_uuids) != len(new_deadline_data):
            return None

        existing_deadline_uuids_as_uuid = [UUID(uid) for uid in existing_deadline_uuids]
        existing_alerts = session.scalars(
            select(DeadlineAlertModel).where(DeadlineAlertModel.id.in_(existing_deadline_uuids_as_uuid))
        ).all()

        if len(existing_alerts) != len(existing_deadline_uuids):
            return None

        matched_uuids: set[UUID] = set()
        uuid_mapping: dict[str, dict] = {}

        for deadline_alert in new_deadline_data:
            deadline_data = deadline_alert.get(Encoding.VAR, deadline_alert)

            found_match = False
            for existing_alert in existing_alerts:
                if existing_alert.id in matched_uuids:
                    continue  # Already matched to another new deadline

                if _definitions_match(deadline_data, existing_alert):
                    # Found a match, reuse this UUID
                    uuid_mapping[str(existing_alert.id)] = deadline_data
                    matched_uuids.add(existing_alert.id)
                    found_match = True
                    break

            if not found_match:
                # Any mismatch triggers full regeneration of all UUIDs. This is intentional:
                # deadlines may be interdependent (e.g. a custom DeadlineReference relative
                # to another deadline), so partial reuse would risk stale cross-references.