beets
beets copied to clipboard
`atypes` not defined in python `inline`
Problem
beet fields lists albumtype, albumtypes, and atypes as available tags.
albumtype and albumtypes, are well and usable within the inline python script, however, atypes is not, and crashes with:
NameError: name 'atypes' is not defined
example:
album_fields:
test: |
import platform
log = open("log.txt", "a")
log.write("type:" + albumtype + '\n') // ok
log.write("types:" + albumtypes + '\n') // ok
log.write("atypes:" + atypes + '\n') // NameError: name 'atypes' is not defined
I have tried using it first e.g.
paths:
default: ${atypes}${test}/...
just to see if it makes a difference, but no go.
${atypes} itself in path does work completely fine.
I don't know if this is intentional, but it would be nice to be able to use the renamed values, e.g. compilation: Anthology / dj-mix: DJ-Mix inline without doing a match.
Setup
- OS: Debian beets version 1.6.0 Python version 3.9.2 plugins: albumtypes, bandcamp, chroma, copyartifacts, discogs, edit, fromfilename, inline, lastgenre, the
- Turning off plugins made problem go away (yes/no): N/A
My configuration (output of beet config) is:
Click to expand
directory: ~/data/user/gremious/public/beets_music
library: ~/data/user/gremious/public/beets_music/library.db
plugins: albumtypes fromfilename inline discogs chroma copyartifacts lastgenre bandcamp edit the
languages: en
per_disc_numbering: yes
chroma:
auto: yes
copyartifacts:
print_ignored: yes
extensions: .*
lastgenre:
count: 99
force: no
whitelist: yes
min_weight: 10
fallback:
canonical: no
source: album
auto: yes
separator: ', '
prefer_specific: no
title_case: yes
albumtypes:
types:
- album: Album
- ep: EP
- single: Single
- soundtrack: OST
- remix: Remix
- live: Live
- mixtape: Mixtape
- compilation: Compilation
- dj-mix: DJ-Mix
- demo: Demo
- other: Other
ignore_va: ''
bracket: ' '
album_fields:
test: "import platform\nlog = open(\"log.txt\", \"a\")\nlog.write(\"type:\" + albumtype + '\\n')\nlog.write(\"types:\" + albumtypes + '\\n')\nlog.write(\"atypes:\" + atypes + '\\n')\n\nret = \"\"\ntypes = albumtypes.split(' ')\n\nif len(types) <= 1:\n return albumtypes\n\n# if types[0] == \"Album\":\n # types.pop(0)\n # return types\n# else:\n # return albumtypes\n"
paths:
default: ${albumartist}/${atypes}$album/${track}. $title
comp: ${test}/${track}. $title
singleton: Misc Tracks/${artist} - ${title}
albumtype:soundtrack: Soundtracks/${albumartist} - ${album}/${track}. $artist - $title
discogs:
apikey: REDACTED
apisecret: REDACTED
tokenfile: discogs_token.json
source_weight: 0.5
user_token: REDACTED
separator: ', '
index_tracks: no
edit:
albumfields: album albumartist
itemfields: track title artist album
ignore_fields: id path
the:
the: yes
a: yes
format: '{0}, {1}'
strip: no
patterns: []
bandcamp:
include_digital_only_tracks: yes
search_max: 2
art: no
exclude_extra_fields: []
genre:
capitalize: no
maximum: 0
mode: progressive
always_include: []
comments_separator: '
---
'
pathfields: {}
item_fields: {}
Thanks for reporting!
For a little additional context, the atypes field comes from the albumtypes plugin:
https://beets.readthedocs.io/en/stable/plugins/albumtypes.html
That field is a computed field, i.e., it runs a function every time it's looked up. The underlying problem here is that the inline plugin doesn't expose these computed fields. One reason we don't currently do this is that inline fields themselves are computed—and trying to eagerly compute them to provide them to other inline fields can easily cause infinite loops.
If you're curious about where this happens in the code, here's where we get the list of fields in the inline plugin:
https://github.com/beetbox/beets/blob/36454a3883c9e5c656805152d2220e9496a7455c/beetsplug/inline.py#L101
And here is where the computed fields are excluded by default: https://github.com/beetbox/beets/blob/36454a3883c9e5c656805152d2220e9496a7455c/beets/dbcore/db.py#L492-L501
It's possible we could think of a brilliant way to resolve this and expose fields like atypes to the inline plugin, but it will require some kind of creativity… I don't see an obvious route forward at the moment.
Ah, yeah, I see the problem. I'm gonna make some assumptions so forgive me if I ask nonsense.
I think I understand the "get data -> compile -> evaluate" flow. But would you kindly elaborate on exactly where does dbcore::db::keys connect with inline to provide the fn the keys? It's a little hard for me to read this, as obj seems to come from a mystery place with no obvious call to _expr_func.
My naive thought would be getting computed fields, but filtering out all inline keys, e.g. eagerly evaluating all computed fields but only those from other plugins. Though I don't know if that's easily doable in this setup?
Also, how are inline fields are evaluated, anyway? Hard to phrase, but like, assuming this setup:
item_fields:
foo: "cool"
bar: "good"
If one were to fetch and evaluate all computed fields while parsing the python for bar - would foo already be in that list? I assume that this is true based on the problem, but, if so, is it possible for the update to the fields list to be delayed until after all inline fields are computed - that way they just do not see each other.
If I understand the first question: here, obj is an Album or Item object (i.e., a subclass of dbcore.Model). Calling dict(obj) iterates over the keys of obj, i.e., it calls something like obj.__iter__(), which in turn invokes iter(self.keys()) as listed there. I admit this is kind of convoluted!
The way inline fields work in general is that they attach custom "getter" functions to the model classes. That is, it doesn't work this way:
- Compute the value for
foo, which is"cool". Assign thefoofield of the object to"cool". - Do the same for
bar, producing the string"good"and putting it in thebarfield.
Instead, foo and bar correspond to functions that, on every lookup of item.foo or item.bar, execute code to compute results on the fly. This is important so that computed fields like this can get "fresh" values, especially when they are based on other fields that can be updated.
This registration mechanism happens here, FWIW: https://github.com/beetbox/beets/blob/2032729375ea713b6e8e03650b49f64e5c61ae1d/beets/dbcore/db.py#L328