-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
beetsplug: only one plugin can set template_fields
per field
#5002
Comments
Thanks for pointing this out; it is pretty confusing that the conflict is resolved silently and that the "winner" is nondeterministic! While I appreciate the goals in #5003 to find a resolution based on the getter's return value, I am also a teensy bit concerned that it might be a tad more complex than is necessary for the situation. In particular, it seems a little sad to make every dbcore "getter" require a list of functions instead of a single function. It's not terrible, but it does seem a little sad to add this complexity. Here's one alternative to consider: what about just making this conflict an error? If two plugins try to provide a getter for the same field, we crash with an error message. This is pretty similar to how we handle other plugin conflicts, such as providing two different types for the same field: Lines 345 to 350 in 2032729
…and it may actually yield more predictable, less opaque results for the user. |
It's definitely not ideal, but in my perception it is already complex with
Honestly, not a fan of that approach. Plugins should preferably not conflict with each other and be able to provide an API mostly independently from other plugins. For the For the |
Would you mind discussing a little more about what you use I admit I am still a bit concerned about problem with predictability when two different plugins want to define the same field. There is still an ordering effect here, so I wonder if a more specific solution to this particular case could be preferable to a general solution. |
Sure! I've been using the
Porting this to the Since contributing the
I agree, however, a part of my changes does at least reduce the number of conflicts. That means, it's not possible to rewrite artist A using one plugin and artist B using the other. The changes in my PR allow that to work again, the only thing that's still unsupported is rewriting the same artist A with both of the plugins. In case that's attempted, the Another point — this is precisely what happens already for rules concerning the same entity in either of the plugins. The rule that's evaluated first is applied, the other one is silently dropped. My changes simply port that behavior to happen across multiple plugins. |
Thanks for explaining a little bit more! To explain farther what I'm worried about here, it's not exactly about the interaction between these two specific plugins—this behavior would clearly be better for this specific case. It's about the effects on the general case of two plugins that want to provide the same field. In that setting, I am skeptical that this kind of "conditional fallback" is likely to be what people want, and a hard error is likely to offer better predictability at less cost in complexity. To try to satisfy both constraints, I'm thinking a bit about how - match: artist:foo
field: artist
replacement: bar We could consider adding a second syntax that looks more like classic - artist foo: bar The idea would be that, in the plugin, we'd detect when the |
While I think that would work for me personally, I'm still hesitant to limit this functionality to a single plugin, even if they don't cause a conflict. I can see how two plugins attempting to rewrite the same field for the same item should throw an error, and I am willing to modify my code to do that. But generally throwing an error when attempting to rewrite the same field feels a bit extreme and limiting to me. |
To explain a tiny bit more, the reason I'm pushing back somewhat on this level of generality here is that I'm concerned that this problem might be very specific to the rewrite plugin (and advancedrewrite). This plugin does something very unconventional by defining computed fields that "shadow" existing built-in fields. It is, in fact, not entirely clear this is the way it really should be working; it causes plenty of problems that the underlying metadata is "masked," and it might be better to just modify the metadata or to provide alternate names for the updated fields. This is why I'm not wild about changing the way the plugin system works to encourage more of this behavior. |
I understand what you mean. The way those rewrites are transparent to all components reading any field is convenient from a development perspective, but does have a few unexpected side effects. So, what would you suggest to fix the issue I reported? Update the advanced rewrite plugin to incorporate the simpler syntax of the rewrite plugin? In that case, I'd also suggest throwing an error if both plugins are enabled at the same time. Or do you want me to implement what I suggested in my previous post, and make plugins conflict only if they try to rewrite the same item, not just the same field? |
Thanks for your persistence here! And for helping out by talking over the various options. My preference here would be to simply produce an error when two different plugins attempt to implement a field with the same name. This will at least prevent the bad/confusing behavior if people were to stumble upon it accidentally. Separately, I'd be very interested in working on the "simple syntax" for advancedrewrite in a different PR! And maybe we want to do that first before even adding an error, so that this kind of use case can still be accommodated. |
Alright, I can do that, and work on the plugin conflict afterward. advancedrewrite:
# simple syntax
- artist ODD EYE CIRCLE: 이달의 소녀 오드아이써클
# new advanced syntax (also allows changing multiple fields for same match)
- match: "mb_artistid:dec0f331-cb08-4c8e-9c9f-aeb1f0f6d88c year:..2022"
replacements:
artist: 이달의 소녀 오드아이써클
# advanced syntax for multi-valued fields
- match: "artist:배유빈 feat. 김미현"
replacements:
artists:
- 유빈
- 미미 The changes not only introduce the new simple syntax, but also allow to apply multiple field replacements to the same query match, and further add support for the newly introduced multi-valued fields. |
Awesome! This all looks really good. |
Raises an exception if multiple plugins provide template functions for the same field. Closes beetbox#5002, supersedes beetbox#5003.
Raises an exception if multiple plugins provide template functions for the same field. Closes beetbox#5002, supersedes beetbox#5003.
Raises an exception if multiple plugins provide template functions for the same field. Closes beetbox#5002, supersedes beetbox#5003.
Problem
I ran into this when using the new
advancedrewrite
plugin on my production system together with the oldrewrite
plugin.When using these two (or any two other plugins modifying
BeetsPlugin.template_fields
) together and specifying rules that both target the same field (e.g.,artist
), only the rules of one plugin concerning this field will be applied. Even worse, which plugin will be used depends on the plugin loading order (the one loaded last will be used), which seems to be random-ish: in the importer, therewrite
plugin is loaded last, when just invokingbeet
, theadvancedrewrite
plugin is loaded last. The rules for that specific field from the previously loaded plugin(s) are silently dropped.Investigation
I've pinned this bug down to the following line:
beets.plugins.item_field_getters
. The reason this breaks is that theupdate
function on thedict
overwrites previous getters, instead of merging them somehow.Of course, the problem lies in the fact that rewrites can generally affect each other and do thus need a deterministic way of applying them.
Suggested solution (implemented in #5003)
I suggest addressing this issue the following way:
template_fields
oralbum_template_fields
so that they either return a replacement value orNone
(possibly breaking change for 3rd-party plugins).plugins.item_field_getters
.beets.dbcore.Model._get
, the list is iterated and the value of the first value that isn'tNone
is used.Setup
The text was updated successfully, but these errors were encountered: