Cattrs is currently only used in two places: Serialization for operator extra links, and for Lineage.

However cattrs is not a well maintained project and doesn't support many features that attrs itself does; in short, it's not worth the brain cycles to keep cattrs.
for k in keys_to_set_none:
            setattr(dag, k, None)
        for task in dag.task_dict.values():
            SerializedBaseOperator.set_task_dag_references(task, dag)
        return dag
    @classmethod
    def _is_excluded(cls, var: Any, attrname: str, op: DAGNode):
        
        
        if attrname == "_access_control" and var is not None:
            return False
        return super()._is_excluded(var, attrname, op)
    @classmethod
    def to_dict(cls, var: Any) -> dict:
        """Stringifies DAGs and operators contained by var and returns a dict of var."""
        json_dict = {"__version": cls.SERIALIZER_VERSION, "dag": cls.serialize_dag(var)}
        
        cls.validate_schema(json_dict)
        return json_dict
    @classmethod
    def from_dict(cls, serialized_obj: dict) -> SerializedDAG:
        """Deserializes a python dict in to the DAG and operators it contains."""
        ver = serialized_obj.get("__version", "<not present>")
        if ver != cls.SERIALIZER_VERSION:
            raise ValueError(f"Unsure how to deserialize version {ver!r}")
        return cls.deserialize_dag(serialized_obj["dag"])
class TaskGroupSerialization(BaseSerialization):
    """JSON serializable representation of a task group."""
    @classmethod
    def serialize_task_group(cls, task_group: TaskGroup) -> dict[str, Any] | None: