Repository navigation
Implement mapped value unpacking - #21641
Conversation
7d6ba36 to
30b1212
Compare
|
This is actually working |
This new expansion logic should cover literal expansions and expanding against multiple map() arguments. I think the logic is still not quite right (e.g. mapping against a *mapped* upstream, and *when* we map exactly), but this new logic should be useful for what we're going to need to fix anyway.
This method is public API and may be subclassed, so we can't change its semantics, specifically we need to handle it returning None (as is the case currently in main).
The previous name looks too much like "context" and is an unnecessary hinderance to maintenance.
So we don't need to remove the task from the group ourselves.
Since a task can potentially be referred to multiple times by a downstream, the task-arg relation needs to be a multimap instead of a simple one-to-one.
1b23977 to
ba825fc
Compare
ashb
left a comment
There was a problem hiding this comment.
LGTM. couple of possible changes, but those can be done later (or not at all)
There was a problem hiding this comment.
I do wonder if we'll need an escape hatch for this.
I guess we can add that when someone finds a specific case.
There was a problem hiding this comment.
Escape hatch for if an operator cannot correctly run __init__ with mocks?
I think eventually we may want to create a separate API for validation without actually creating the operator, perhaps a classmethod called validate that can be called here.
There was a problem hiding this comment.
Yeah, I was thinking an escape hatch for when an Operator ctor does something that causes it to fail when given a Mock.
A proper class-level validate(**kwargs) sounds like a better idea.
There was a problem hiding this comment.
Future possible enhancement (absolutely not in this pr):
Set spec for the mock based on the type hint of the param we are are creating the mock for.
|
The PR most likely needs to run full matrix of tests because it modifies parts of the core of Airflow. However, committers might decide to merge it quickly and take the risk. If they don't merge it quickly - please rebase it to the latest main at your convenience, or amend the last commit of the PR, and push it with --force-with-lease. |
dfc3f08 to
5198f74
Compare
This is another PR that is likely much too big. Many (sort of interrelated) things done in one.
Rewrite
@taskmapped operators expansion so it behave correctlyPreviously, a mapped
@taskwas not expanded correctly because its mapped argument would look something like this:this cannot be expanded using normal logic, because
PythonOperatornatively expects something like this instead:so additional logic is implemented to modify
expand_mapped_taskto correctly expand.Re-implement
expand_mapped_tasklogic to be able to expand literalsPreviously,
expand_mapped_taskonly looks in TaskMap and cannot expand e.g..map(arg=[1, 2, 3]). This implements a more sophisticated logic to collect information from mapped arguments and expand literal and XComArg inputs properly. Some refactoring was also done so the same logic can be reused for task-runtime value unpacking.Additional checks added to ensure correct literal types are passed to
.map()Basically making sure it only receives lists and dicts (or XComArg, which is already checked separately when the upstream pushes to XCom).
Extend
TaskInstance.render_templatesto “unpack” mapped values for task executionThis is the main thing. After all template fields are rendered, additional process is done to “unpack” mapped values into individual ones based on map_index, which further mutates the operator object held by a TaskInstance. This reuses similar logic from
expand_mapped_taskto calculate the total length of map, so it can locate its map_index inside the series.Previously,
render_template_fieldswas only implemented on BaseOperator, and a MappedOperator is first unmapped before templates are rendered. This approach was unforuantely wrong, since value unpacking (which needs to happen after template rendering resolves XComArg) needs to be aware of the original MappedOperator (mainly to access the user-supplied mapped kwargs). So the new logic delays unmapping until duringrender_templates. If a TaskInstance’staskis a MappedOperator, itsrender_templateswould call the MappedOperator’srender_template_fields, which unmaps itself, callsrender_template_fieldson the unmapped operator, unpacks values for the unmapped operator, and then returns the unmapped operator. The TaskInstance would reassign itstaskto the unmapped, rendered, unpacked operator. This means thatTaskInstance.render_templatesnow may have a side effect of mutating its owntaskattribute. I don’t particularly like this, but couldn’t find a cleaner approach without breaking much of the existing interface (complicated by the fact that bothBaseOperator.render_template_fieldsandBaseOperator.render_templateare public API). I think this is close to the best possible interface considering existing constraints (and I try to document this as clearly as possible for future maintainability).A few tests were added to check this is working (see
TestMappedTaskInstanceReceiveValue).