-
Notifications
You must be signed in to change notification settings - Fork 17.5k
Restrict exception-node deserialization to known classes without importing the stored name #68511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,7 +41,7 @@ | |
| from dateutil import relativedelta | ||
| from pendulum.tz.timezone import FixedTimezone, Timezone | ||
|
|
||
| from airflow._shared.module_loading import import_string, qualname | ||
| from airflow._shared.module_loading import qualname | ||
| from airflow._shared.timezones.timezone import from_timestamp, parse_timezone, utcnow | ||
| from airflow.callbacks.callback_requests import DagCallbackRequest, TaskCallbackRequest | ||
| from airflow.exceptions import AirflowException, DeserializationError, SerializationError | ||
|
|
@@ -242,6 +242,31 @@ def _decode_priority_weight_strategy(var: str) -> PriorityWeightStrategy: | |
| return priority_weight_strategy_class() | ||
|
|
||
|
|
||
| # Builtin exceptions a BASE_EXC_SER node can rebuild into. A user defined subclass | ||
| # (e.g. a custom KeyError) serializes to a name absent here and won't round-trip -- | ||
| # unchanged from before, since builtins never held it either. | ||
| _DESERIALIZABLE_BUILTIN_EXCEPTIONS: dict[str, type[BaseException]] = { | ||
| "KeyError": KeyError, | ||
| "AttributeError": AttributeError, | ||
| } | ||
|
|
||
|
|
||
| def _resolve_airflow_exception(exc_cls_name: str) -> type[AirflowException]: | ||
| """ | ||
| Resolve a stored ``AirflowException`` name without importing it. | ||
|
|
||
| Read via ``vars()`` rather than ``getattr`` -- a module's deprecation-shim | ||
| ``__getattr__`` can still import on access -- which also lets pre-3.2.0 blobs | ||
| naming the old ``airflow.exceptions`` path keep resolving. | ||
| """ | ||
| module_name, _, attr_name = exc_cls_name.rpartition(".") | ||
| module = sys.modules.get(module_name) | ||
| exc_cls = vars(module).get(attr_name) if module is not None else None | ||
| if not (isinstance(exc_cls, type) and issubclass(exc_cls, AirflowException)): | ||
| raise DeserializationError(f"Refusing to deserialize unknown exception class {exc_cls_name!r}") | ||
|
potiuk marked this conversation as resolved.
|
||
| return exc_cls | ||
|
|
||
|
|
||
| def _encode_start_trigger_args(var: StartTriggerArgs) -> dict[str, Any]: | ||
| """Encode a StartTriggerArgs.""" | ||
|
|
||
|
|
@@ -664,9 +689,14 @@ def deserialize(cls, encoded_var: Any) -> Any: | |
| kwargs = deser["kwargs"] | ||
| del deser | ||
| if type_ == DAT.AIRFLOW_EXC_SER: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought there was another pr where we removed this entirely?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not in main at least. In a number of places this serialization is left for migration purposes I think. And yes I already raised a question while working on a number of those cases that having a more general solution where we get rid of similar serialization issues would be great. But I do not think we are there yet - cc: @kaxil @amoghrajesh - I think we can discuss it separately
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes — #68662. It removes the encode side entirely and makes decode legacy-only, returning That is a stronger position than this PR, which still resolves a payload-supplied name — constrained to loaded So the real question is whether anything still needs a real exception object back. If not, #68662 is the better fix and this should close in its favour. If something does, this keeps round-trip fidelity and #68662 breaks it. No attachment to this one either way — flagging it so the two do not both land. Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting |
||
| exc_cls = import_string(exc_cls_name) | ||
| exc_cls: type[BaseException] = _resolve_airflow_exception(exc_cls_name) | ||
| else: | ||
| exc_cls = import_string(f"builtins.{exc_cls_name}") | ||
| builtin_exc_cls = _DESERIALIZABLE_BUILTIN_EXCEPTIONS.get(exc_cls_name) | ||
| if builtin_exc_cls is None: | ||
| raise DeserializationError( | ||
| f"Refusing to deserialize unsupported builtin exception {exc_cls_name!r}" | ||
| ) | ||
| exc_cls = builtin_exc_cls | ||
| return exc_cls(*args, **kwargs) | ||
| elif type_ == DAT.SET: | ||
| return {cls.deserialize(v) for v in var} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.