Collections/Python

From XMMS2
Jump to: navigation, search
SOMETHING PRETTY SIMILAR TO ALTERNATIVE 5 IS IMPLEMENTED - IT NEEDS TO BE DOCUMENTED HERE

In the process of porting the collections api to python it becomes apparent that a decision has to be made about how to represent the collections structure.

There are a number of options, some of them are:

All python examples use Sync API for simplicity

Alternative 1

create a collections class which represents the various collections semantics as properties and attributes (with the occasional method if necessary).

A few examples:

Get an id list from a collection and set it to the id list of a new collection.

coll_foo = xc.Collection('Foo')
ids = coll_foo.ids
idl_coll = xc.Collection(XMMS_COLL_TYPE_IDLIST)
idl_coll.ids = ids


create a union of collection "Foo" and a custom collection (all media by Sonic Youth), and filter the resulting collection by year (only media from 2006).

coll = xc.Collection(XMMS_COLL_TYPE_AND);
# Reference to collection 'Foo'
operand = xc.Connection(XMMS_COLL_TYPE_REFERENCE);
operand['reference'] = ('Foo', 'Collections')
coll.operand.append(operand)
# All media by Sonic Youth
operand = xc.Collection(XMMS_COLL_TYPE_MATCH);
allmedia = xc.Collection(XMMS_COLL_TYPE_UNIVERSE)
operand.operand.append(allmedia)
operand['field'] = 'artist'
operand['value'] = 'Sonic Youth'
coll.operand.append(operand)
# Filter media on year 2006
last = xc.Collection(XMMS_COLL_TYPE_MATCH);
last['year'] = '2006'
last.operand.append(coll)

Alternative 2

Create a collections class with the same get/set methods as in the c bindings.


Alternative 3

Lets cut to the examples directly:

from xmmsclient import collections as c

coll = c.And(c.Reference('Collections:Foo'),
             c.Match(c.Universe(), {'field': 'artist', 'value': 'Sonic Youth'}),
             c.Match(c.Universe(), {'field': 'year', 'value': '2006'}))

How to use and modify them!

print coll.operands
# [<Reference>, <Match>, <Match>]
del coll.operands[0]

coll.operands.append(c.Reference('Collections:Party music'))

print coll.operands
# [<Match>, <Match>, <Reference>]

# make it persistant on server!
xc.coll_save(coll, 'Collections', 'My collection')
 
m = c.Match(c.Universe(), {'field': 'artist', 'value': 'Thievery Corporation'})

# get a list of ids matching collection
matchingids = xc.coll_query_id(m)

# I changed my mind..
m['value'] = 'Air'

songsbyair = xc.coll_query_info(m, ['title', 'duration', 'rating'])
for song in songsbyair:
  print "%(album)s - %(title)s (%(duration)s)" % song

Alternative 3.1

Let's start with a pseudo-class-def

class SomeOp (Collection):
  def __init__(self, *based_on, **arguments)
  def __getitem__(self, name)
  def __setitem__(self, name, val)
  def get_data(self)
  def set_data(self, data)
  def get_bases(self)            # these three might need better names
  def set_bases(self, based_on)
  def add_bases(self, *based_on)

and examples:

base = c.Reference(name="SomeColl")  # **arguments for name, *based_on nothing
jovibon = c.Match(base, field="artist", value="Jovi, Bun") # *based_on = [base]
bonjovi = c.Match()       # we can add stuff later
bonjovi.set_bases([base]) # like... here or in the next line.
bonjovi.set_data({'field': 'artist', 'value': 'Bon Jovi'})
alljovi = c.Or(jovibon, bonjovi)
cool = c.And(alljovi)     # This time, set half the data later.
cool.add_bases(c.Not(c.Contains(base, field="title", value="Life")))

jovibon['value'] = 'Jovi, Bon'  # fix the typo
cool.save(xmmsc, "SomeCoolCol") # XMMS2 is needed for saving to XMMS2.

Alternative 4

This one is based somewhat on alternative 3, with a few mods that I think make it more pythonic, and certainly requires less manual building (more automagic).

from xmmsclient import collections as c

# Get a ref to Collections:Foo
foo = c.Collection('Foo') 

# Create a filter that extracts all Bon Jovi 2k6 tracks from the whole medialib.
# Note that this filter filters on several elements, so several match items chained
# one behind the other will be created under the hood.
fil = c.Match(c.Collection(), artist='Bon Jovi', year='2006')

# Intersect the two.
intersect = c.op.And(foo, fil)

# Append another anti-filter on a playlist
intersect.append(c.op.Not(c.Playlist('Bar')))

# All track by Machinae Supremacy.
masu = c.Match(c.Collection()), artist="Machinae Supremacy")

# Combine these two into the final collection
coll = c.op.Or(intersect, masu)

Alternative 5

This is supposed to take the good parts of 4 and merge into 3.

from xmmsclient import collections as c

coll = c.And(c.CollectionRef('Foo'),
             c.Match(c.Universe(), field='artist', value='Sonic Youth'}),
             c.Match(c.Universe(), field='year', value='2006'}))

print coll.operands
# [<CollectionRef>, <Match>, <Match>]
del coll.operands[0]

coll.operands.append(c.Reference('Collections:Party music'))

print coll.operands
# [<Match>, <Match>, <CollectionRef>]

# make it persistant on server!
xc.coll_save(coll, 'Collections', 'My collection')
 
m = c.Match(c.Universe(), field='artist', value='Thievery Corporation'})

print m.attributes
# {'field': 'artist', 'value': 'Thievery Corporation'}

print m.operands
# [<Universe>]
 
# get a list of ids matching collection
matchingids = xc.coll_query_id(m)

# I changed my mind..
m.attributes['value'] = 'Air'

songsbyair = xc.coll_query_info(m, ['title', 'duration', 'rating'])
for song in songsbyair:
  print "%(album)s - %(title)s (%(duration)s)" % song


This should make clear separation between "operands" and "attributes". Instead of the c[attrib]=aval of alt3 and the c.append(operand) of alt4.

I find the automatic chaining of alt4 scary as it removes the 1:1 mapping to nodes in the dag, and when you later list that collection it will not be the same as you created. It would be better to just make a (trivial) higher level function that does it: (which makes it clear what happens, and doesn't do any magic behind the scenes -- Explicit is better than implicit)

def make_chained_matches(coll, **matches):
    for f,v in matches.iteritems:
        coll = c.Match(coll, field=f, value=v);
    return coll

Also it is important that it is easy to change the attributes. A GUI would probably do something like:

def setup_match_gui(..):
    ...
    self.match_field = SomeInputWidget()
    def on_field_change(val):
        self.collection.attributes['field'] = val
    self.match_field.on_change = on_field_change

    self.match_value = SomeInputWidget()
    def on_value_change(val):
        self.collection.attributes['value'] = val
    self.match_field.on_change = on_value_change

Alternative 6

this is at least implemented in the git version(works for me)

import xmmsclient
from xmmsclient import collections
import os
import sys
 
xmms = xmmsclient.XMMS("Sasdlkfjselect")
 
try:
	xmms.connect(os.getenv("XMMS_PATH"))
except IOError, detail:
	print "Connection failed:", detail
	sys.exit(1)
 
 
artist = collections.Match( field="artist", value="Pink" )
album = collections.Has(artist, "album")
title = collections.Match(field="title", value="%pink%")
  
result = xmms.coll_query_infos( artist, ["artist", "title" ])
result.wait()
print result.value()

print "---"
result = xmms.coll_query_infos( album, ["artist", "title" ])
result.wait()
print result.value()
 
print "---"
result = xmms.coll_query_infos( title, ["artist", "title" ])
result.wait()
print result.value()
 
print "---"
result = xmms.coll_query_infos( title | album, ["artist", "title" ])
result.wait()
print result.value()


Other ideas welcome!