Repository navigation
Show each native Dag's own source in the Code tab - #74249
Merged
jason810496 merged 1 commit intoOct 6, 2026
Merged
jason810496 merged 1 commit into
jason810496 merged 1 commit into
Conversation
A packed TypeScript bundle can declare several Dags, each mapped to its own source file. The Code tab showed the bundle's entry module for every one of them: the Dag processor read a bundle's source once per file and stored it under the file's own fileloc, so every Dag sharing that file collided on the same key and only one entry survived. get_source_code now takes the Dag's own dag_id, and NodeDagImporter resolves that Dag's mapped file instead of always falling back to the entry module. Every Dag importer's signature grew the same parameter for a uniform interface; only NodeDagImporter uses it so far. Source code read for a native Dag file is now keyed by dag_id end to end, since a fileloc is not unique per Dag the way a dag_id is.
Member
Author
pierrejeambrun
marked this pull request as ready for review
October 6, 2026 09:08
pierrejeambrun
requested review from
amoghrajesh,
ashb,
ephraimbuddy,
jason810496,
jedcunningham and
uranusjr
as code owners
October 6, 2026 09:08
jason810496
approved these changes
Oct 6, 2026
jason810496
left a comment
Member
There was a problem hiding this comment.
Nice, thanks!
I just adjust the "closes" as "relates" in the PR description.
| self.dag_source_codes.pop(fileloc, None) | ||
| # Keyed by dag_id, not fileloc: a bundle can embed several Dags' own source under one | ||
| # fileloc, so a stale entry is found by which dag_ids this fileloc previously bagged. | ||
| for dag_id, dag in list(self.dags.items()): |
Member
There was a problem hiding this comment.
Here're some notes in my mind to solve the further write amplification if there's anyone complain in the future.
Suggested change
| for dag_id, dag in list(self.dags.items()): | |
| # TODO: store the file content more efficient (e.g. if multiple dag_ids will store the exact same DagCode content) | |
| # There're two parts: 1) the Dagbag in-memory path 2) the DB persistent layer | |
| # The bigger picture might be normalized the DagCode table, so we won't store the exact same DagCode content twice. | |
| for dag_id, dag in list(self.dags.items()): |
Member
There was a problem hiding this comment.
I will track a issue for this item.
Just merged the PR as my Go SDK DagImporter will need the core change of this PR as well.
eladkal
approved these changes
Oct 6, 2026
jason810496
merged commit Oct 6, 2026
ca9bab3
into
apache:jason/lang-sdk-e2e/05-node-dag-importer
2 checks passed
Open
1 task done
jason810496
pushed a commit
that referenced
this pull request
Oct 6, 2026
A packed TypeScript bundle can declare several Dags, each mapped to its own source file. The Code tab showed the bundle's entry module for every one of them: the Dag processor read a bundle's source once per file and stored it under the file's own fileloc, so every Dag sharing that file collided on the same key and only one entry survived. get_source_code now takes the Dag's own dag_id, and NodeDagImporter resolves that Dag's mapped file instead of always falling back to the entry module. Every Dag importer's signature grew the same parameter for a uniform interface; only NodeDagImporter uses it so far. Source code read for a native Dag file is now keyed by dag_id end to end, since a fileloc is not unique per Dag the way a dag_id is.
1 task done
jason810496
added a commit
that referenced
this pull request
Oct 6, 2026
* Parse native TypeScript Dags with the Node coordinator A NodeCoordinator configured without bundles_root now hands out a Dag importer, so the Dag bundles it serves have their *.min.mjs bundles parsed by running them with node. Only files that start with the airflow-ts-pack layout header are parsed. The Dag source shown for such a Dag is the bundle's entry module, as the bundle embeds it, or a short notice when there is none to show. * Document parsing native TypeScript Dags Describe the coordinator configuration that parses native Dags, what the Dag processor needs for it, what the Code view shows, the cost of handler-only bundles, and that cluster policies and some CLI commands do not apply to a native Dag. * Share the bundle section mutation helper between Node tests * Run a native TypeScript Dag's task from its own bundle A task used to run the first sorted *.min.mjs bundle that declares its dag_id, which can differ from the file the Dag was parsed from. When the task's Dag file is a bundle under the scan roots that still declares the Dag, run that file. Otherwise fall back to the search. * Say when the Dag processor needs Node.js for TypeScript bundles The Dag processor runs node on every packed bundle a Node coordinator serves, handler-only ones included, not only on bundles that declare TypeScript Dags. * Align the TypeScript build docs with the Code view The build section said each Dag's own file is embedded so Airflow can display it, but the Code view shows the entry module for every Dag in the bundle. Say what is embedded and what the Code view shows today. * Warn against defining a Dag in both TypeScript and Python A native TypeScript Dag and a Python stub with the same dag_id in one bundle overwrite each other's Dag on every parse. Tell users to remove the stub when they move a Dag to new Dag(...), as java.rst does. * Import NodeDagImporter at the top of the Node coordinator The importer only imports the coordinator for type checking, so there is no cycle. This matches how JavaCoordinator imports JavaDagImporter. * Let NodeDagImporter source read errors reach the caller Both callers of get_source_code already catch errors and store a placeholder, as they do for the Java and Python importers. Raising reports a broken bundle in one place with one wording. * Pin the Code view to the entry module in Node bundle tests The tests used a Dag's own file as the entrypoint, so a resolver that returned the first dag_source_paths entry would still pass. Use a separate main.ts entrypoint instead. * Hand out the Node Dag importer through get_dag_importer * Let the Node Dag importer find its coordinator The importer names the NodeCoordinator class that parses its files and is registered in a Dag bundle whenever a NodeCoordinator is configured. The coordinator no longer hands it out or picks the task's bundle itself, so a task scans the bundle for its Dag as before. * Run a native TypeScript Dag's own bundle through the native task branch NodeCoordinator builds the command for the bundle its Dag was parsed from with _build_dag_file_command. The runtime reports a Dag the bundle does not declare, so the coordinator does not check the Dag id. * Document how a NodeCoordinator is picked for parsing TypeScript Dags Once a NodeCoordinator is configured, every Dag bundle is parsed. With several, dag_bundle_to_coordinator picks one per Dag bundle. Artifact-only Dag bundles list * in .airflowignore, and a native task runs on the coordinator its queue routes to. * Say tasks list runs a native TypeScript Dag's bundle airflow tasks list parses a native TypeScript Dag, so it is not among the commands that refuse one. Also break the new TypeScript SDK text only at sentence or clause ends. * Show each native Dag's own source in the Code tab (#74249) A packed TypeScript bundle can declare several Dags, each mapped to its own source file. The Code tab showed the bundle's entry module for every one of them: the Dag processor read a bundle's source once per file and stored it under the file's own fileloc, so every Dag sharing that file collided on the same key and only one entry survived. get_source_code now takes the Dag's own dag_id, and NodeDagImporter resolves that Dag's mapped file instead of always falling back to the entry module. Every Dag importer's signature grew the same parameter for a uniform interface; only NodeDagImporter uses it so far. Source code read for a native Dag file is now keyed by dag_id end to end, since a fileloc is not unique per Dag the way a dag_id is. --------- Co-authored-by: Pierre Jeambrun <pierrejbrun@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Stacked on #74037.
Why
A packed TypeScript bundle can declare several Dags, each mapped to its own source file via
dag_source_paths. The Code tab showed the bundle's entry module for every one of them instead:NodeDagImporter.get_source_codealways read the entrypoint, and the Dag processor read a bundle's source once per file and stored it under the file's own fileloc — every Dag sharing that file collided on the same key, so only one entry survived.What changes
AbstractDagImporter.get_source_codetakes the Dag's owndag_idnow.NodeDagImporterresolves that Dag's own mapped file, falling back to the entry module when there is none (an unmapped, dynamically-constructed Dag). Every other importer's signature grew the same parameter for a uniform interface; their implementation is unchanged — onlyNodeDagImporteruses it so far.dag_idend to end (DagBag.dag_source_codes,DagFileParsingResult.dag_source_codes,update_dag_parsing_results_in_db's lookup), since a fileloc is not unique per Dag the way adag_idis — a plain Python file with several@dag-decorated functions has the same property, it just happened to be harmless there because every Dag in that case shares identical source anyway.Out of scope
JavaDagImporteralready has the per-dag_idlookup machinery in_find_source_entry, unused byget_source_code. Wiring it in is left for a follow-up; this PR only updates its signature.relates: #74241
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 4.7) following the guidelines