For our internal use-cases, I have created support...
# development
a
For our internal use-cases, I have created support for "dynamic" rules. These are similar to and interoperate with `@rule`s, but also permit inter-module mutual recursion and awaiting on functions indirectly (i.e. non-statically-determinable calls; e.g. awaiting functions held in variables). I'd like the temperature from maintainers on potentially pursuing public-facing work on this. <thread>
Apologies if this is the wrong channel. Will move on request. For more context, see: - Issue 23069 - The tail of this slack thread. - This Slack thread.
Support for dynamic rules is through an interface similar to Pants'
@rule
and `collect_rules()`:
@dynamic_rule
and
collect_dynamic_rules()
. For typical cases (i.e., straightforward rules that happen to also require inter-module mutual recursion or otherwise depend on function-internal imports), the user experience is identical: decorate rules that need to be dynamic with
@dynamic_rule
instead, and any
rules.py
files with dynamic rules must call
collect_dynamic_rules()
as part of its
rules()
function (that in turn are called by the plugin's
register.py
). For the indirect-call case, a little more work is required for the author. In our use-case, this extra work is ~trivial. `@rule`s and `@dynamic_rule`s can call each other, and `@dynamic_rule`s support calls with
**implicitly({<...>})
. This has been immediately useful to us, and we are going to continue with this new functionality to support our use case. It is Python-only, and though it depends on existing Pants' Python source, it doesn't need to live in the Pants source code for us to make effective use of it. As far as I am aware, the implementation is not fundamentally "cheating" or otherwise adversely undermining Pants' assumptions, though that may not be true, and otherwise may still be more limited than
@rule
in other subtle ways. Plus the usual caveats about bugs, deficiencies in completeness, etc. Are the Pants maintainers potentially interested in this capability, or its implementation? In order of increasing effort on my part, I could: (1) Do nothing. (2) Provide a brief prose explanation of the enabling mechanisms here, in this thread. (3) Update Issue 23069 with a demo plugin that includes the full implementation for
@dynamic_rule
+
collect_dynamic_rules()
, and demonstrates the capabilities of this infrastructure, so that maintainers can consider the technique and assess whether they want to pursue some version of first-class support for this functionality themselves. (4) Try to put together a pull request that adds
@dynamic_rule
and
collect_dynamic_rules()
, for consideration. (5) Try to put together a pull request that builds this capability directly into
@rule
and
collect_rules()
, for consideration. I understand that this might be beyond or counter to what Pants wants to offer directly, I understand there may turn out to be fundamental flaws to this approach that disqualify it from first-class support even if it's still useful to us specifically, and I understand that even if any of (2-5) sound interesting to a maintainer to consider that there's no obligation to pursue it further. I'd rather avoid more effort than would be reasonably interesting to maintainers, but I'm also happy to pursue a higher-effort item later if it turns out one of the lower-effort items piqued interest in further work (e.g., I could provide (2) on request, and then follow up with (3)+ on request if the mechanism is intriguing enough to maintainers to see a working implementation). Thoughts?
h
I think this is fascinating, and a potentially valuable addition to the toolbox! I would love to see (4) or even (5) happen. Maybe start with (4) as that seems like a lot less work, and we can discuss that approach vs (5) on a code review... Before that though could you give a taste of the "extra work" you refer to in the indirect case?
a
Sure! And, predictably, the support for indirect calls is not magic and doesn't somehow avoid the core requirements that the Pants engine needs for each rule: the list of awaitables that might be awaited on within the rule. So, the "extra work" in the indirect case is in providing that list of awaitables that cannot otherwise be statically determined. In our use-case, this is no burden, because the one "dynamic" rule that we need this support for indirection in is a dispatch/broker function for which all possible "dispatchable-to" awaitables were explicitly "registered". So, we can programmatically provide that list of awaitables at the time that they're needed. In practice, that looks like passing an argument into the decorator, something like:
@dynamic_rule(awaitables=<...>)
.
And here's (2), since there's tentative interest, to give some more background and gauge whether there's still interest in case you see obvious flaws in the general case: Background: • Decorator-resolution time (i.e.
@rule
) happens early, and any potential circular imports have not already been resolved. •
_AwaitablesCollector
does its work while a
@rule
is being resolved. It does not visit
import
AST nodes inside functions, because otherwise any function that nevertheless does do function-internal imports to avoid some (non-rule-related) circular import issue would be victims of timing as those import resolutions could fail in the AST visit logic. I.e., were
_AwaitablesCollector
to try to resolve function-local imports at decorator-resolution time, then they're effectively elevated back up to module-level imports susceptible to the same circular import failures they may have been trying to avoid. • Because
_AwaitablesCollector
does not resolve function-local imports due to the timing of its work, any awaitable symbols imported only locally in the function are invisible to the resolution of each
@rule
, causing runtime failure when such a symbol is invoked because the awaitable was never registered at
@rule
-resolution time. This is, to my naive understanding, fundamentally why
@rule
doesn't support cross-module mutual recursion today. Then: The key observation (theoretically! hopefully! seems-to-work-ally!) that enables
@dynamic_rule
is this
: we do not actually need to resolve the awaitables associated with the decorated rule at decorator-resolution time. What
@dynamic_rule
does is set up the work needed to generate
TaskRule
and
AwaitableConstraints
objects for the new rule, but actually executing that work is delayed until
collect_dynamic_rules()
is invoked. Because
collect_dynamic_rules()
is called later, during `register.py`/`rules()`-resolution time, module imports have in-general been resolved (or resolved-enough). So, we make an enhanced
class _AwaitablesCollectorDynamic( _AwaitablesCollector )
that simply adds visits for the import nodes that can "see" these circular-import-avoiding symbol imports. With this, a complete set of awaitables can be determined, even for rules making cross-module recursive calls. Again, I don't know if there are fundamental caveats to this that make it unworkable in the general case (i.e., integration with
@rule
) or unworkable even as a distinct
@dynamic_rule
. I am not an expert on the breadth of magic that the Pants engine provides through its rules. But it does seem to work, and support all the functionality we've needed so far, most notably
**implicitly()
and the fill-in-the-blanks argument magic in general.
h
Sounds very promising. I would love to see the code.
a
Great! I'll put something together. There'll likely be a bit of turnaround time on this, but we're definitely motivated to try to upstream this. Thank you!
h
Excellent! Let us know if you need any help
a
For what it's worth: I have not forgotten about this. We use the capability described in this thread as critical infrastructure to enable our use-case, and as far as I'm aware what we're doing is not possible otherwise. We're in a crunch to flesh out a Pants-based PoC solution for evaluation, and I haven't been able to spare the time to (try to) put together a PR for this in the meantime. I will do my best to realize such a PR when there is less time pressure, which in theory is "soon" (within some few months). Short of a PR, I will endeavor to at least provide additional materials for consideration by maintainers.
h
No worries, glad this is getting you to a useful solution!