Repository navigation
Anchor a relative TaskEnv dependencies_from to the author's file (v0.1.18) - #61
Merged
Volv-G merged 1 commit intoSep 23, 2026
Conversation
…1.18) TaskEnv.__post_init__ reached the definition site with a fixed f_back.f_back, which assumes exactly one intermediate frame: the dataclass-generated __init__. Every subclass that overrides __post_init__ and calls super() inserts another frame, so a relative dependencies_from passed to such a subclass resolved against the module that DEFINES the subclass -- for a downstream env that is the installed package -- instead of the pipeline file that wrote the path. Find the boundary by identity instead of by counting: walk the stack and take the frame after the outermost frame still constructing this object. A construction frame binds the object to self (the base and every subclass __post_init__, plus the generated __init__, whose exec'd code object is additionally identifiable by co_filename "<string>"); nothing outside the construction can hold the object yet. Adding frames no longer changes the answer, so the result is the author's file at any dataclass subclass depth. TaskEnv.from_config keeps its own single-frame lookup, which is correct and documented as not supporting a wrapping subclass, and keeps pre-resolving a relative dependencies_from against the config file's directory.
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.
(AI-assisted)
A relative
TaskEnv.dependencies_fromresolved against the wrong file whenever it was passed to aTaskEnvsubclass that overrides__post_init__.__post_init__reached the definition site with a fixedf_back.f_back, which assumes exactly one intermediate frame — the dataclass-generated__init__:Every subclass that overrides
__post_init__and callssuper()inserts another frame:A dataclass-generated
__init__isexec'd with the globals of the module that defines the dataclass, sof_globals["__file__"]was the subclass's own module — for a downstream env shipped in an installed package, a path inside that package — and the pip-dependency manifest silently pointed there. Downstream,DbtEnv(..., dependencies_from="rel.toml")written in/tmp/fr/probe.pyresolved to.../site-packages/tangle_deploy/python_pipeline/dbt/rel.toml.from_configis unaffected (see below), so subclasses were the whole blast radius — and sincefrom_configis documented as inherited by "anyTaskEnvdataclass subclass", subclassing is a supported use.Fix
Find the boundary by identity, not by counting frames: walk the stack and take the frame after the outermost frame that is still constructing this object.
A construction frame binds the object under construction to
self—TaskEnv.__post_init__, every subclass__post_init__that calledsuper(), and the generated__init__— and nothing outside the construction can hold that object yet, so the test is exact. The generated__init__is additionally recognizable by itsexec'd code object (co_filename == "<string>"). Adding frames no longer changes the answer, so the result is the author's file at any subclass depth."Outermost" rather than "first frame that is not
self" matters: a subclass may reach the base validation through a plain helper function, whose frame binds the env under some other name. The generated__init__further out still marks the boundary.Unchanged:
TaskEnv(...)— same answer as before.TaskEnv.from_config— keeps its own single-framef_backlookup, which is correct and documented as not supporting a wrapping/delegating subclass, and keeps pre-resolving a relativedependencies_fromagainst the config file's directory. (That pre-resolution is also whyfrom_confignever exercised the buggy walk: it hands the constructor an absolute path.)__file__at all (exec'd or interactive code).Tests
tests/test_task_env_dependencies_anchor.py— 8 tests. Each keeps three directories apart and asserts which one wins:All three hold a
deps.toml, so a misresolution lands on a file that exists and cannot hide behind a "file not found". The envs are always constructed from a written-to-disk author module rather than from the test file, so the expected anchor is a genuine third file and the frame stack is the real one.Covered: depth 0, depth 1, depth 2, a subclass delegating through a library-level helper function, construction inside a function / a nested function / a method of an unrelated object, a subclass declared in the author's own file, absolute paths, and
from_configanchoring for a depth-2 subclass.Mutation-checked — the tests fail for the shipped code and for the obvious "one more frame" patch:
f_back.f_backf_back.f_back.f_backThe method case also rules out a naive "skip any frame with a
selflocal":Factory().build()has aselfthat is a different object.Full suite: 1548 passed on Python 3.13 and on 3.12 (the CI matrix).
git diff --checkclean.Version
0.1.17→0.1.18across the four surfaces:pyproject.toml,packages/tangle-cli/src/tangle_cli/__init__.py,tests/test_packaging.py, and the editable self-entry inuv.lock(hand-edited,1 1diff; the lockfile was not regenerated).uv buildproducestangle_cli-0.1.18, and both the wheel and the sdist report0.1.18fromtangle version/tangle-cli version.