# Trouble replicating aunique() functionality

**URL:** <https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186>\
**Category:** Help\
**Created:** [September 23, 2017, 1:42am UTC](https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186 "2017-09-23T01:42:32Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![RollingStar](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.beets.io/rollingstar/32/728_2.png) [@RollingStar](https://discourse.beets.io/u/RollingStar)\
**Post date:** [September 23, 2017, 1:42am UTC](https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186/1 "2017-09-23T01:42:32Z")

</div>

I’m trying to replicate the functionality of aunique() in my own plugin. Eventual goal is a function called solo() that returns True or False depending on how many albums match the disambiguators given. I copy and paste the entire aunique() function from [library.py](http://library.py), but that’s not enough. My copy of aunique returns this: ˂’str’ object has no attribute 'item’˃. Looking at the source, I see where ‘.item’ attributes are used. I assume that aunique gains functionality from the rest of [library.py](http://library.py), but I don’t understand what I should do (what code should be copied) to add that functionality to my plugin.

---

<div class="post-metadata">

**Author:** ![adrian](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.beets.io/adrian/32/4_2.png) [@adrian](https://discourse.beets.io/u/adrian)\
**Post date:** [September 23, 2017, 9:33am UTC](https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186/2 "2017-09-23T09:33:39Z")

</div>

Hmmm—it’s pretty hard to say without seeing the code you’re using currently. Is this for an `inline` definition, or a new custom plugin? The latter, FWIW, might be the easiest to get to match `%aunique{}` exactly, because an `inline` definition has a different structure than an ordinary Python function.

---

<div class="post-metadata">

**Author:** ![RollingStar](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.beets.io/rollingstar/32/728_2.png) [@RollingStar](https://discourse.beets.io/u/RollingStar)\
**Post date:** [September 23, 2017, 6:32pm UTC](https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186/3 "2017-09-23T18:32:20Z")

</div>

Yeah, I opted for a plugin because it looked more robust for advanced python coding. I use this album: [http://freemusicarchive.org/music/Broke\_For\_Free/Directionless\_EP/](http://freemusicarchive.org/music/Broke_For_Free/Directionless_EP/)

Here is [solo.py](http://solo.py). After the first few lines it is 100% a copy-paste of aunique from here: [https://github.com/beetbox/beets/blob/master/beets/library.py#L1487](https://github.com/beetbox/beets/blob/master/beets/library.py#L1487)

```
from beets.plugins import BeetsPlugin

class SoloPlugin(BeetsPlugin):
	def __init__ (self):
		super(SoloPlugin, self). __init__ ()
		self.template_funcs['solo'] = tmpl_solo

def tmpl_solo(self, keys=None, disam=None, bracket=None):
	"""Generate a string that is guaranteed to be unique among all
	albums in the library who share the same set of keys. A fields
	from "disam" is used in the string if one is sufficient to
	disambiguate the albums. Otherwise, a fallback opaque value is
	used. Both "keys" and "disam" should be given as
	whitespace-separated lists of field names, while "bracket" is a
	pair of characters to be used as brackets surrounding the
	disambiguator or empty to have no brackets.
	"""
	# Fast paths: no album, no item or library, or memoized value.
	if not self.item or not self.lib:
		return u''
	if self.item.album_id is None:
		return u''
	memokey = ('aunique', keys, disam, self.item.album_id)
	memoval = self.lib._memotable.get(memokey)
	if memoval is not None:
		return memoval

	keys = keys or 'albumartist album'
	disam = disam or 'albumtype year label catalognum albumdisambig'
	if bracket is None:
		bracket = '[]'
	keys = keys.split()
	disam = disam.split()

	# Assign a left and right bracket or leave blank if argument is empty.
	if len(bracket) == 2:
		bracket_l = bracket[0]
		bracket_r = bracket[1]
	else:
		bracket_l = u''
		bracket_r = u''

	album = self.lib.get_album(self.item)
	if not album:
		# Do nothing for singletons.
		self.lib._memotable[memokey] = u''
		return u''

	# Find matching albums to disambiguate with.
	subqueries = []
	for key in keys:
		value = album.get(key, '')
		subqueries.append(dbcore.MatchQuery(key, value))
	albums = self.lib.albums(dbcore.AndQuery(subqueries))

	# If there's only one album to matching these details, then do
	# nothing.
	if len(albums) == 1:
		self.lib._memotable[memokey] = u''
		return u''

	# Find the first disambiguator that distinguishes the albums.
	for disambiguator in disam:
		# Get the value for each album for the current field.
		disam_values = set([a.get(disambiguator, '') for a in albums])

		# If the set of unique values is equal to the number of
		# albums in the disambiguation set, we're done -- this is
		# sufficient disambiguation.
		if len(disam_values) == len(albums):
			break

	else:
		# No disambiguator distinguished all fields.
		res = u' {1}{0}{2}'.format(album.id, bracket_l, bracket_r)
		self.lib._memotable[memokey] = res
		return res

	# Flatten disambiguation value into a string.
	disam_value = album.formatted(True).get(disambiguator)

	# Return empty string if disambiguator is empty.
	if disam_value:
		res = u' {1}{0}{2}'.format(disam_value, bracket_l, bracket_r)
	else:
		res = u''

	self.lib._memotable[memokey] = res
	return res

```

Here is my config.yaml:

```
directory: e:\test\music\
library: e:\test\music\beetslibrary.bib

import:
	copy: yes
	duplicate_action: keep

plugins: solo
pluginpath: C:\apps\dial-beets\beetsplug

paths:
	default: a%aunique{albumartist,year,}b%solo{albumartist,year,}c/$title

```

I’m aiming to replicate aunique exactly, so I can debug my implementation of the functionality that is based on aunique.

After importing the album a few times, the folders look like this:

```
a 7b_'str' object has no attribute 'item'_c

```

The `7` with a leading space comes from aunique, working perfectly. My copy of aunique, in the “solo” function, is returning: `_'str' object has no attribute 'item'_`. Just to test, when I make this `solo.py`, it works as expected.

```
from beets.plugins import BeetsPlugin

class SoloPlugin(BeetsPlugin):
    def __init__ (self):
        super(SoloPlugin, self). __init__ ()
        self.template_funcs['solo'] = tmpl_solo

def tmpl_solo(self, keys=None, disam=None, bracket=None):
    return 'my_test_str2'

```

It returns `a 8bmy_test_str2c`.

---

<div class="post-metadata">

**Author:** ![adrian](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.beets.io/adrian/32/4_2.png) [@adrian](https://discourse.beets.io/u/adrian)\
**Post date:** [September 23, 2017, 10:51pm UTC](https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186/4 "2017-09-23T22:51:41Z")

</div>

The most immediate problem here is that your function has a `self` parameter, but it’s not a method. In Python, only methods get passed `self` automatically. And the reference to `self.item` won’t work without a reference to the item being templated.

It might be helpful to try using print-debugging inside the code to see what parameters are being passed, etc. It’s a time-honored tradition: just insert a few print() calls here and there to check whether the values are what you expect them to be, and you may be surprised. 🙂

---

<div class="post-metadata">

**Author:** ![RollingStar](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.beets.io/rollingstar/32/728_2.png) [@RollingStar](https://discourse.beets.io/u/RollingStar)\
**Post date:** [December 20, 2017, 5:50am UTC](https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186/5 "2017-12-20T05:50:08Z")

</div>

Thank you. I’ve spent some time with this and still don’t have it working properly. There are only [four path-format plugins](https://beets.readthedocs.io/en/v1.4.5/plugins/#path-formats) within beets, so I don’t have much to work from.

I could probably use some help understanding this:

> And the reference to `self.item` won’t work without a reference to the item being templated.

I can’t get my plugin to inherit Library `lib` and/or Item `item` attributes, which I assume are higher up in the hierarchy somewhere. aunique is of this class:  
`class DefaultTemplateFunctions(object):`

and `object` is a class in Python. Since it’s built-in to Python, I assume `lib` and `item` don’t come from there. I notice that aunique is the only item within the `DefaultTemplateFunctions` class to not be a [staticmethod](https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python). So maybe the magic happens from the `DefaultTemplateFunctions` class? In the `init` we have this, and a comment specifically referencing `aunique`.

```auto
self.item = item
self.lib = lib

```

But that’s as close as I can find to where `item` or `lib`s come from. It looks like when plugins add commands to the beet command line, they can just call functions like this: `myFunction(lib, item)` and have them work as expected after some setup with `register_listener`. Take `importadded.py`:

```auto
register = self.register_listener
#snip
register('item_imported', self.update_item_times)
#snip
def update_item_times(self, lib, item):

```

These would presumably get `lib` and `item`s from the `BeetsPlugin` class? This is my OS:

- Windows 10 x64
- Python 3.6.2
- beets 1.4.5

---

<div class="post-metadata">

**Author:** ![adrian](https://yyz1.discourse-cdn.com/flex029/user_avatar/discourse.beets.io/adrian/32/4_2.png) [@adrian](https://discourse.beets.io/u/adrian)\
**Post date:** [December 20, 2017, 3:21pm UTC](https://discourse.beets.io/t/trouble-replicating-aunique-functionality/186/6 "2017-12-20T15:21:54Z")

</div>

Hello! Have you considered starting with the second example in this section of the docs?  
[http://docs.beets.io/en/v1.4.5/dev/plugins.html#add-path-format-functions-and-fields](http://docs.beets.io/en/v1.4.5/dev/plugins.html#add-path-format-functions-and-fields)

That one, which adds a `disc_and_track` field, shows how the callback function registered with `template_fields` gets an `item` as a parameter. Would that be enough to pull out the information your code needs!
