A collection of computer, gaming and general nerdy things.

Monday, September 29, 2014

str.format for fun and profit

One common thing I've used a few times is using str.format to create formatting metapatterns -- essentially a format pattern that becomes a format pattern when formatted. I'm not necessarily advocating for using this everywhere, but it's handy sometimes.

Let me provide two examples:

Arbitrary Padding

Part of a Python learning group gives homework, one of the "assignments" was to pad an input to an arbitrary amount with an arbitrary character. The "cannonical" example given was to pad a given number to five characters with zeroes. Formatting ahoy!

Keep in mind how closures work: When we call padder we're actually creating an "instance" of pad where the pattern is bound at the time of creation. The way this metapattern works is that {{}} will actually output {} when passed through formatting, so assuming defaults, the metapattern ends up looking like: {:0>5}.

In [8]:
def padder(padwith=0, direction='>', length=5):
    pattern = "{{:{}{}{}}}".format(padwith, direction, length)
    def pad(input):
        return pattern.format(input)
    return pad

p = padder()
g = padder(padwith='*', direction='^', length='7')

print(p(11))
print(g('g'))
00011
***g***

Generic repr

A more useful example is creating a generic __repr__ Mixin:

In [4]:
class ReprMixin:
    '''Provides a string representible form for objects.'''

    # Most models will have both an id and name fields
    # This can be overwritten if needed
    __repr_fields__ = ['id', 'name']

    def __repr__(self):
        fields =  {f:getattr(self, f, '<BLANK>') for f in self.__repr_fields__}
        # constructs a dictionary compatible pattern for formatting
        # {{{0}}} becomes {id} for example
        pattern = ['{0}={{{0}}}'.format(f) for f in self.__repr_fields__]
        pattern = ' '.join(pattern)
        pattern = pattern.format(**fields)
        return '<{} {}>'.format(self.__class__.__name__, pattern)

I've been using this in OpenWebAmp's (nee FlaskAmp) models for easy recognition of model instances in the command line shell. Basically, all of my main objects have a id and name fields, but some don't and I need more information from others. Creating a generic __repr__ method isn't very intuitive given how much attributes in objects can vary. Essentially what this does is look for __repr_fields__ in an object (using sane defaults if not present) and then iterates through that creating a list of patterns like ['id={id}', 'name={name}'] and then joins them into a single string, then formats it and then formats that into another pattern. What pops out the other end is something like this: <Artist id=1 name=The Foo Bars>.

But you can also override __repr_fields__ in a class to pull more or different information out of it.

In [7]:
class Artist(ReprMixin):
    def __init__(self, id, name):
        self.id = id
        self.name = name

class Member(ReprMixin):
    def __init__(self, id, name):
        self.id = id
        self.name = name

class Tag(ReprMixin):
    def __init__(self, id, name):
        self.id = id
        self.name = name

class MemberTaggedArtist(ReprMixin):
    __repr_fields__ = ['id', 'member', 'tag', 'artist']
    # pretend there's relational bindings here
    def __init__(self, id, member, tag, artist):
        self.id = id
        self.artist = artist
        self.member = member
        self.tag = tag

mta = MemberTaggedArtist(
    id=4, 
    member=Member(id=1, name='anr'), 
    tag=Tag(id=1, name='Rock'), 
    artist=Artist(id=1, name='The Foo Bars')
    )

repr(mta)
Out[7]:
'<MemberTaggedArtist id=4 member=<Member id=1 name=anr> tag=<Tag id=1 name=Rock> artist=<Artist id=1 name=The Foo Bars>>'

So metapatterns. They make your patterns a little unfun to read, but they're really useful. Don't stick 'em everywhere because they're awful to look at and terrible to debug if you screw something up.

Monday, September 15, 2014

Forgetting to Commit

So, I've made a grave mistake.

I forgot to commit. I wrote a bunch of code for FlaskAmp. Like a bunch. And didn't commit anything along the way. Whoops.

There's two ways I can see to correct this:

  1. Create a monster commit.
  2. Reconstruct each part of the code along the way.

Both of these are painful in their own right.

Monster Commit

There's nothing inherently wrong with a big commit. Sometimes when you make a change, you need to cascade changes through a code base. For example, changing the way a model is structured changes the way it's queried and the way it's serialized. There's nothing wrong with adding several files to a commit -- in fact, if you're not committing changes like this, you're probably doing it wrong.

Where there's a problem is what I did. Added many features, made many changes and didn't commit any of them. Whoops... Now, I'm faced with adding nine files that add:

  • Several new models.
  • Modifies all existing models.
  • Modifies existing queries.
  • Redefines all the serializers/Schemas.
  • Redefines how serializers/schemas are created.
  • Defines new marshmallow fields for the Serializers/Schema.
  • Adds a few new libraries.
  • Changes the information relayed in the API (including adding, changing and removing fields)
  • Changes internals of how information is added to the database.
  • Changes internal naming conventions.

Pretty much the local code for FlaskAmp looks completely different from the code in version control. I could just commit it all and be done. Vow to never let it happen again. Et cetera. But when something breaks in it...I can't go back and precisely locate the change I did that broke it because pretty much everything changed.

Reconstruct the Commits

This is likely the option I'll take. But it's going to be painful. It's going to involve rewriting a bunch of code -- hopefully better this time. I'll stash everything current in a development branch. Revert Master (also, I'm developing on FlaskAmp/Master...) to the state recorded on Github and begin making changes slowly. Annotating each commit thoughtfully (I recently read a suggestion to annotate with a line from the Zen of Python if appropriate).

If I go down this route, I'll suffer immediate repercussions for what I did instead of maybe, eventually down the road repercussions. It'll also allow me to write incremental tests as well. Did I mention I haven't been doing that as well? :(

For shame Alec. For shame.

Tuesday, September 9, 2014

ORMs: Fighting against or working with.

Quite a while ago I posted about struggling with a complex join using SQLAlchemy -- more specifically, the Flask extension for SQLAlchemy. My original surmise, "The problem is how I'm trying to proxy, not with the relations", was wrong. My entire problem lay with the relations. Here's the relevant bits from the models:

In []:
class Member(db.Model):
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.String(64))

class Artist(db.Model):
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.String(64))

class Tag(db.Model):
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.String(64))
    
class MemberTaggedArtists(db.Model):
    id = db.Column('id', db.Integer, primary_key=True)
    member_id = db.Column('member_id', db.Integer, db.ForiegnKey('members.id'))
    artist_id = db.Column('artist_id', db.Integer, db.ForiegnKey('artists.id'))
    tag_id = db.Column('tag_id', db.Integer, db.ForiegnKey('tags.id'))

    member = db.relationship(Member, backref='tagged_artists')
    artist = db.relationship(Artist, backref='member_tags')
    tag = db.relationship(Tag, backref='applied_tags')

And here's what I orignally wanted:

In []:
kurashu89.tagged_artists = {'Gorguts': ['Death Metal', 'Progressive Death Metal', 'Weird Metal']}
##Include just the tag and the number of times it was applied.
Gorguts.tags = {'Death Metal':1, 'Progressive Death Metal':1, 'Weird Metal':1}
##Include just the artist and the number of times they were tagged 
Death_Metal.applied_to = {'Gorguts':1} 

And then I forgot everything I knew about SQL. Well...not really. I knew how I'd do it in SQL. And I was hoping there'd be a magic bullet in SQLAlchemy that'd answer this for me. So, I fretted about it for months: I originally ran head first into this issue in April (actually, a little before when I began mocking this application up in my notebook and whiteboard). It wasn't until recently that I've been seriously digging in and writing a lot of code and learning more and more. I watched a talk earlier today that was a major break through: Flexing SQLAlchemy's Relational Power by Brandon Rhodes.

In the presentation he talks about having an "O" problem -- focusing too much on the Object portion of an ORM and ignoring the Relational Mapper portion. SQLAlchemy doesn't write queries for you. It makes them easier. I was hoping for a magic bullet, and in that I was too blind to my present knowledge.

SQL is Magic

Many programmers are familiar with several types of programming and application structures: Object-oriented, procedural and functional are the three that seem to get the most amount of love these days. But when dealing with data, few things will beat declarative. SQL is unusual because you don't tell it how to do something, you tell it what you want.

I'd argue few things are less understood than SQL when it comes to new programmers. Maybe Assembly. Or Perl.

But it's saying, "I know you're totally in the mindset of $LANGUAGE but you gotta get your data from the database so do a complete mental shift into SQL." People start to feel at home with PHP but when databases come into the pictures it's just...uh, what...I don't remember what I was doing.

I've been lucky that when I started learning programming, my dad was something of a Swiss army knife web developer/IT...thingperson (there was this period where he had the title SME but no one knew what it meant). He'd lead me on and give me just enough that I'd know where to look at piecing the rest of it together. I was forced to learn SQL. Maybe not well enough to be a DBA, but better than some. And I'll tell you what, "Thank you dad!" Knowing just enough SQL to scrap by and how to parse the Postgres documentation (seriously, read it some time, it's equal parts confusing and informative) has saved my butt more times than I can count.

Here's a small snippet from a PHP script thrown together to display a galleria.js website:

In []:
SELECT g.g_id, g.g_url, g.g_name,
(SELECT p.thumb FROM pictures p WHERE p.g_id = g.g_id ORDER BY p.p_id DESC LIMIT 1) AS cover,
(SELECT count(c.p_id) AS count FROM pictures c WHERE c.g_id = g.g_id) AS count
FROM galleries g
ORDER BY g.g_name ASC;

It's not the most beautiful or wonderful query ever written, but I'm not professing to be a genius either. It worked and it did exactly what I needed it to do: get a gallery's id, url and name as well as a URL to a picture inside the gallery and the number of pictures inside of it. Would I do it differently now? Yes, I'd join pictures and use count() as an aggregate function, I'd probably still retrieve the cover photo the same way because, frankly, I'm a little slack when it comes to joins.

But look at that. One second you're looking at PHP code, being like "Alright. Okay. ...really? I mean I guess." Then you hit this block of SQL. And you have to enter a different mindset. There's very little SQL has in common with PHP -- or frankly, many popular programming languages.

When I started really digging into Python and discovered SQLA, I was in love. Mike Bayer and his team really pulled out all the stops to create a wonderful toolbox. I remember thinking "I'll never write another query again!"

I was wrong: ORMs as Vietnam

ORMs have been infamously declared "The Vietnam of Computer Science". Is it a fair comparison? Honestly: I don't know. In 2006 I was 17 and barely managing to keep a D&D campaign site running, I wrote PHP and SQL and HTML and CSS together in one file. I wouldn't have known what an ORM was if I was beaten with one -- keep in mind this was still the day of PHP's mysql/pgsql functions, I think PDO had been introduced but my memory is hazy.

Eight years later, one and a half with using an ORM (poorly), I feel differently. I fundamentally agree wih Ted Neward: O/R mapping starts as something great but as you go on with half truths and misinformation, not really knowing what your goal is, things get really bad really quickly. I won't delve into extreme details, ORMs have been discussed to death in this context.

There's this great Mythos built up around ORMs. Mostly, they're half truths and lies of omission. But there's one told time and time again that fools even the wisest people I know: "You'll never write another query again!" Or as Jeff Atwood put it: "There are a lot of magic bullets out there, and no shortage of naive developers." I was looking for my magic bullet.

The Light

I can't profess to other ORMs, I've been tangentially exposed to Django's ORM and I'm aware that Rails and PHP have their own implementations. But SQLAlchemy is an absolute charm to work with. When you know what you're doing. Again, I'm not professing to be a genius. But watching Bradon Rhodes' presentation on flexing SQLA's relational power, I realized I was hiding from joins and queries and instead trying to tell SQLA "Just do it for me. Pleeeease." I'd been fighting against SQLA. SQLA is designed not to hide SQL from the developer. It's meant to say, "You're writing SQL but you're doing it with Python instead of inside Python." There's no hiding from SQL with SQLA -- it's even in the name.

I'd written a query in my project notebook, quite a while ago. Because I knew how to answer my question using pure SQL:

In []:
SELECT tags.name AS tag,  tags.id AS id, count(mta.member_id) AS count
FROM tags, membertaggedartists AS mta
WHERE tags.id = mta.tag_id AND mta.artist_id = :artist_id
GROUP BY id
ORDER BY count DESC, name ASC

It wasn't until I realized what I was not doing. Applying this knowledge to SQLA. I had also been lulled into complacency by Flask-SQLA's query object. Don't get me wrong, it's fantastic for simple queries and joins but for anything remotely complex all bets are off. Looking at what I wanted as output it's simply: The tag's id and name and a count of how many member attached it to a particular artist This translates almost 1:1 into SQLA's query model:

In []:
q = db.session.query(
    models.Tag.id.label('id'), 
    models.Tag.name.label('tag'), 
    db.func.count(models.MemberTaggedArtist.member_id).label('count')
    )
q = q.join(models.MemberTaggedArtist, models.Tag.id == models.MemberTaggedArtist.tag_id)
q = q.filter(models.MemberTaggedArtist.artist_id == artist.id)
q = q.group_by(models.MemberTaggedArtist.tag_id)
# order by tag count first, then tag name if two tags happen to have the same count
# there's probably a much better way to get this value without recalculating the result
q = q.order_by(db.desc(db.func.count(models.MemberTaggedArtist.member_id)))
q = q.order_by(models.Tag.name)

It's more verbose, but it's mostly because I'm going through my models module instead of just importing the models into the current namespace. A very nice thing about SQLA is you can view the query that it'll generate from this by print the statement (q in this context). What's it look like?

In []:
SELECT tags.id AS id, tags.name AS tag, count(membertaggedartists.member_id) AS count
FROM tags 
JOIN membertaggedartists ON tags.id = membertaggedartists.tag_id
WHERE membertaggedartists.artist_id = :artist_id_1 
GROUP BY membertaggedartists.tag_id 
ORDER BY count(membertaggedartists.member_id) DESC, tags.name

Exactly the same as mine (well, there's an explicit join instead of my implicit one).

When I stopped fighting SQLA and expecting it to be a magic bullet and realized, "Hey, I know SQL" my problems with this issue just...went away.

ORMs can be painful to use. Especially when they hide away important details and decide things for you. SQLA does very little of either (and will usually defer to the database it's connected to when it does). But when you work with a good one, and not against it, they are wonderful tools.

Tuesday, September 2, 2014

APIs V2: SQLAlchemy Boogaloo

Now that we have our skeleton API built and we can pull the metadata out of MP3 files, we can build an actual data driven API instead of processing everything by hand.

Tools needed

  • Flask
  • SQLAlchemy
  • MutagenX (Or Mutagen if you're running Python2)
  • Flask-Restful
  • Flask-Marshmallow
  • Flask-Script (working on replacing this with Click)
Why no TinyTag? I let TinyTag go after figuring out that the encoding error is centered there. Originally, I had not used Mutagen because it had given me issues. Turns out Mutagen supports Python2. There's a fork -- MutagenX -- that supports Python3. Problem solved.

Setup

Setting up the data driven API is pretty straight forward. However, I'm going to touch on a few more advanced things in here because simply doing an incremental development blog post would be terribly boring to me. The first slightly advanced thing is going to be modularizing the Flask app. Here's what my directory layout looks like right now:
FlaskAmp/
├── app
│   ├── config.py
│   ├── dev-sqlite.db
│   ├── factory.py
│   ├── __init__.py
│   ├── models.py
│   ├── serializers.py
│   └── utils.py
├── LICENSE
├── manager.py
├── README.md
└── requirements
It's a pretty standard layout. But it bears repeating. Under the app directory is where our action takes place. We'll go over each of these files.
In []:
#app/models.py
from flask.ext.sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Artist(db.Model):
    __tablename__ = "artists"
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.Unicode, unique=True)
    albums = db.relationship('Album', backref=db.backref('artist', uselist=False), order_by='Album.name')
    tracks = db.relationship('Track', backref=db.backref('artist', uselist=False), lazy='noload')

class Album(db.Model):
    __tablename__ = 'albums'
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.Unicode, unique=True)
    artist_id = db.Column('artist_id', db.Integer, db.ForeignKey('artists.id'))
    tracks = db.relationship(
        'Track', backref=db.backref('album', uselist=False),
        lazy='immediate', order_by='Track.position')
    
class Track(db.Model):
    __tablename__ = 'tracks'
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.Unicode, primary_key=True)
    length = db.Column('length', db.Integer)
    position = db.Column('position', db.Integer)
    location = db.Column('location', db.Unicode)
    stream = db.Column('stream', db.String)
    artist_id = db.Column('artist_id', db.Integer, db.ForeignKey('artists.id'))
    album_id = db.Column('album_id', db.Integer, db.ForeignKey('albums.id'))
This is a pretty standard setup for Flask-SQLAlchemy models. The only thing most people might not be familar with is the lazy='noload' on the relationship from Artist to Track. This tell SQLAlchemy to never load this data. Why? For now, we're not interested in loading it. Given that I'm already working on a much more indepth model structure, this'll get changed in the future.
In []:
#app/serializers.py
#this is pretty similar to before
from flask.ext.marshmallow import Marshmallow

ma = Marshmallow()

class ArtistSerializer(ma.Serializer):
    albums = ma.Nested('AlbumSerializer', exclude=('artist',), many=True)
    
    _links = ma.Hyperlinks({
        'self':ma.URL('artist', id='<id>'),
        'collection':ma.URL('artist')
        })
    
    class Meta:
        fields = ('id', 'name', 'albums', 'links')

class AlbumSerializer(ma.Serializer):
    artist = ma.Nested('ArtistSerializer', exclude=('albums',))
    tracks = ma.Nested('TrackSerializer', exclude=('albums', 'artist'), many=True)
    
    _links = ma.Hyperlinks({
        'self':ma.URL('album', id='<id>'),
        'collection':ma.URL('album')
        })
    
    class Meta:
        fields = ('id', 'name', 'artist', 'tracks', 'links')

class TrackSerializer(ma.Serializer):
        albums = ma.Nested('AlbumSerializer', exclude=('artist', 'tracks'), many=True)
        artist = ma.Nested('ArtistSerializer', exclude=('album'))
        length = ma.Method('convert_time')
        
        _links = ma.Hyperlinks({
            'self':ma.URL('track', id='<id>'),
            'collection':ma.URL('track')
            })
        
        def convert_time(self, track):
            seconds = track.length
            mins = seconds//60
            seconds -= (mins*60)
            
            return "{!s:2>0}:{!s:2>0}".format(mins, seconds)
        
        class Meta:
            fields = ('id', 'artist', 'album', 'name', 'position', 'length', 'links', 'stream')
The only thing I'll note here is the use of Meta.fields rather than additional and that we're only exposing the streaming id for tracks to the serializer (and thus through the API) instead of both it and the file location. Giving away information like a file structure just makes it easier for people with malicious intent to gain access to the system.
In []:
#app/config.py
import os

basedir = os.path.abspath(os.path.dirname(__file__))

class BaseConfig:
    SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    SQLALCHEMY_RECORD_QUERIES = False
    
    @staticmethod
    def init_app(app):
        pass

class DevConfig(BaseConfig):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV-DATABASE-URI') or 'sqlite:///{}'.format(os.path.join(basedir, 'dev-sqlite.db'))

configs = {
    'dev' : DevConfig,
    'default' : DevConfig
    }
In []:
#app/factory.py
from flask import Flask
from .config import configs

def create_app(env, exts=()):
    app = Flask(__name__)
    config = configs.get(env, 'default')
    app.config.from_object(config)
    config.init_app(app)
    
    for ext in exts:
        ext.init_app(app)
    
    return app
You'll see a lot of people turning to a factory for creating their application. However, they'll explicitly initialize their extensions one at a time. In this case, we pass in an environment and a list of extensions to initialize instead. I find that this is a little more flexible and doesn't rely on abusing scoping in Python.
In []:
#app/__init__.py
from flask import request, abort, send_file
from flask.ext.restful import Api, Resource
from .models import Artist, Album, Track
from .serializers import ArtistSerializer, AlbumSerializer, TrackSerializer
from .factory import create_app

app = create_app('dev', exts=[ma,db])
api = Api(app) # I've had strange things happen initializing the Restful API afterwards...

class SingleArtist(Resource):
    def get(self, id):
        artist = Artist.query.get_or_404(id)
        return {'artist' : ArtistSerializer(artist)}

class SingleAlbum(Resource):
    def get(self, id):
        album = Album.query.get_or_404(id)
        return {'album' : AlbumSerializer(artist)}

class SingleTrack(Resource):
    def get(self, id):
        track = Track.query.get_or_404(id)
        return {'track' : TrackSerializer(artist)}

class ListArtist(Resource):
    def get(self):
        page = request.args.get('page', default=1, type=int)
        limit = request.args.get('limit', default=10, type=int)
        
        q = Artist.query.order_by(Artist.name)
        q = q.paginate(page, limit, error_out=False)
        
        return {'artists' : ArtistSerializer(q.items, many=True)}

class ListAlbum(Resource):
    def get(self):
        page = request.args.get('page', default=1, type=int)
        limit = request.args.get('limit', default=10, type=int)
        
        q = Album.query.join(Artist).filter(Artist.id==Album.artist_id)
        q = q.order_by(Artist.name, Album.name)
        q = q.paginate(page, limit, error_out=False)
        
        return {'artists' : AlbumSerializer(q.items, many=True)}

class ListTrack(Resource):
    def get(self):
        page = request.args.get('page', default=1, type=int)
        limit = request.args.get('limit', default=10, type=int)
        
        q = Track.query.join(Artist, Album)
        q = q.filter(Artist.id==Album.artist_id, Album.id==Track.album_id)
        q = q.order_by(Artist.name, Album.name, Track.position)
        q = q.paginate(page, limit, error_out=False)
        
        return {'artists' : TrackSerializer(q.items, many=True)}

@app.route('/stream/<stream_id>/', methods=['GET'])
def stream(stream_id):
    track = Track.query.filter(Track.stream==stream_id).first()
    if not track:
        abort(404)
    
    return send_file(track.location)
    
api.add_resource(SingleArtist, '/artist/<int:id>/', endpoint='artist')
api.add_resource(ListArtist, '/artist/', endpoint='artists')

api.add_resource(SingleAlbum, '/album/<int:id>/', endpoint='album')
api.add_resource(ListAlbum, '/album/', endpoint='albums')

api.add_resource(SingleTrack, '/track/<int:id>/', endpoint='track')
api.add_resource(ListTrack, '/track/', endpoint='tracks')
There's a few differences here. I'll address a few:
  • Flask-SQLAlchemy provides a custom query object which locates our session for us. So, unlike regular SQLAlchemy, we don't have to directly use the session object. This can make querying feel a little odd if you're not used to it but ultimately, it acts the same.
  • When joining two models in SQLAlchemy, you need to explicitly filter on the join you'd like. There's multiple ways of doing this. I'll leave the others as an exercise to the reader.
  • Flask-SQLAlchemy includes a pagination helper object. It allows you to specify just a page and limit and paginate will return several helpful bits such as has_next, has_prev, next_num and prev_num among others.
  • abort allows you to redirect to an error page. Flask-SQLAlchemy implements a get_or_404 helper which does something similar.
  • send_file tells Flask to send a file response instead of something more typical.
This is all you need for your API to interact and stream. Except data. That's what manager.py is for.
In []:
#manager.py
from app import app, db, ma, models, serializers, utils
from flask.ext.script import Manager

manager = Manager(app)

@manager.option('-d', '--dir', dest='dir')
def add(dir):
    utils.store_directory(dir)

@manager.shell
def _shell_context():
    return dict(app=app, db=db, ma=ma, models=models, serials=serializers, utils=utils)

if __name__ == "__main__":
    manager.run()
Flask-Script is an incredibly useful extension that allows you to interact with your application on the command line. Consider this an extremely brief explaination.
  • Manager.option allows you to pass CLI arguments into your functions easily.
  • Manager.shell is a predetermined command that sets up a shell context for your application. All it requires is to know what to pass in.
The utils file is the only one I haven't covered yet. These are functions and things that will assist in the running of the application but are otherwise unconnected to anything else.
In []:
#app/utils.py
import os
from uuid import uuid4
from mutagenx import File
from .models import db, Album, Artist, Track

valid_types=('m4a', 'flac', 'mp3', 'ogg', 'oga')


def find(basedir, valid_types=valid_types):
    '''Utilize os.walk to only select out the files we'd like to potentially
    parse and yield them one at a time.'''
    basedir = os.path.abspath(basedir)
    for current, dirs, files in os.walk(basedir):
        if not files:
            continue
        for file in files:
            if file.endswith(valid_types):
                yield os.path.join(os.path.abspath(current), file)

def adaptor(track):
    return dict(
        artist=track['artist'][0],
        album=track['album'][0],
        position=int(track['tracknumber'][0].split('/')[0]),
        length=int(track.info.length),
        location=track.filename,
        name=track['title'][0],
        stream=str(uuid4())
        )

def adapt_track(track, adaptor=adaptor):

    info = adaptor(track)

    artist = Artist.query.filter(Artist.name == info['artist']).first()
    if not artist:
        artist = Artist(name=info['artist'])
        db.session.add(artist)
    info['artist'] = artist

    album = Album.query.filter(Album.name==info['album']).first()
    if not album:
        album = Album(name=info['album'], artist=artist)
        db.session.add(album)
    info['album'] = album

    track = Track.query.filter(Track.name==info['name'])
    track = track.filter(Track.album_id==album.id)
    track = track.first()
    if not track:
        track = Track(**info)
        db.session.add(track)

    return artist, album, track


def store_directory(basedir, valid_types=valid_types, adaptor=adaptor):
    for file in find(basedir, valid_types):
        file = File(file, easy=True)
        
        try:
            artist, album, track = adapt_track(file)
            print(
                " * Storing: {0.name} - {1.name} - {2.position:2>0} - {2.name}"
                "".format(artist, album, track))
            db.session.commit()
        except KeyError:
            pass
Now, to add information to the database, you'll simply do:
python manager.py add -d /home/justanr/Music/Acid Bath
And you'll get back something like this:
 * Storing: Acid Bath - When the Kite String Pops - 5 - Jezebel
 * Storing: Acid Bath - When the Kite String Pops - 11 - The Morticians Flame
 * Storing: Acid Bath - When the Kite String Pops - 14 - Cassie Eats Cockroaches
 * Storing: Acid Bath - When the Kite String Pops - 4 - Finger Paintings of the Insane
 * Storing: Acid Bath - When the Kite String Pops - 9 - Toubabo Koomi
 * Storing: Acid Bath - When the Kite String Pops - 13 - The Bones of Baby Dolls
 * Storing: Acid Bath - When the Kite String Pops - 6 - Scream of the Butterfly
 * Storing: Acid Bath - When the Kite String Pops - 10 - God Machine
 * Storing: Acid Bath - When the Kite String Pops - 2 - Tranquilized
 * Storing: Acid Bath - When the Kite String Pops - 8 - Dope Fiend
 * Storing: Acid Bath - When the Kite String Pops - 3 - Cheap Vodka
 * Storing: Acid Bath - When the Kite String Pops - 7 - Dr. Seuss Is Dead
 * Storing: Acid Bath - When the Kite String Pops - 12 - What Color Is Death
 * Storing: Acid Bath - When the Kite String Pops - 1 - The Blue
 * Storing: Acid Bath - Paegan Terrorism Tactics - 6 - Old Skin
 * Storing: Acid Bath - Paegan Terrorism Tactics - 10 - New Corpse
 * Storing: Acid Bath - Paegan Terrorism Tactics - 5 - Locust Spawning
 * Storing: Acid Bath - Paegan Terrorism Tactics - 12 - Ode of the Peagan
 * Storing: Acid Bath - Paegan Terrorism Tactics - 7 - New Death Sensation
 * Storing: Acid Bath - Paegan Terrorism Tactics - 11 - Dead Girl
 * Storing: Acid Bath - Paegan Terrorism Tactics - 4 - Diäb Soulé
 * Storing: Acid Bath - Paegan Terrorism Tactics - 3 - Graveflower
 * Storing: Acid Bath - Paegan Terrorism Tactics - 9 - 13 Fingers
 * Storing: Acid Bath - Paegan Terrorism Tactics - 2 - Bleed Me an Ocean
 * Storing: Acid Bath - Paegan Terrorism Tactics - 8 - Venus Blue
 * Storing: Acid Bath - Paegan Terrorism Tactics - 1 - Paegan Love Song
 * Storing: Acid Bath - Demos: 1993 - 1996 - 7 - Finger Paintings of the Insane
 * Storing: Acid Bath - Demos: 1993 - 1996 - 6 - Dope Fiend
 * Storing: Acid Bath - Demos: 1993 - 1996 - 8 - Jezebel
 * Storing: Acid Bath - Demos: 1993 - 1996 - 10 - Venus Blue
 * Storing: Acid Bath - Demos: 1993 - 1996 - 11 - Bleed Me an Ocean
 * Storing: Acid Bath - Demos: 1993 - 1996 - 3 - Scream of the Butterfly
 * Storing: Acid Bath - Demos: 1993 - 1996 - 9 - The Bones of Baby Dolls
 * Storing: Acid Bath - Demos: 1993 - 1996 - 2 - What Color Is Death
 * Storing: Acid Bath - Demos: 1993 - 1996 - 1 - Dr. Seuss Is Dead
 * Storing: Acid Bath - Demos: 1993 - 1996 - 5 - The Mortician's Flame
 * Storing: Acid Bath - Demos: 1993 - 1996 - 12 - Graveflower
 * Storing: Acid Bath - Demos: 1993 - 1996 - 4 - God Machine
Running the server is as simple as python manager.py runserver -p 5000 -h 0.0.0.0. From there navigating to 127.0.0.1:5000/artist/ will output something like this:
{
    "artists": [
        {
            "albums": [
                {
                    "id": 3,
                    "links": {
                        "collection": "/album/",
                        "self": "/album/3/"
                    },
                    "name": "Demos: 1993 - 1996",
                    "tracks": [
                        {
                            "id": 35,
                            "length": "05:39",
                            "links": {
                                "collection": "/track/",
                                "self": "/track/35/"
                            },
                            "name": "Dr. Seuss Is Dead",
                            "position": 1,
                            "stream": "5ef608d4-aca1-496d-a876-0778c2e6ea6b"
                        }
                    ]
                }
            ],
            "id": 1,
            "links": {
                "collection": "/artist/",
                "self": "/artist/1/"
            },
            "name": "Acid Bath"
        }
    ]
}
From there, navigating to 127.0.0.1:5000/stream/5ef608d4-aca1-496d-a876-0778c2e6ea6b/ will play the song.

From Here

There's many things wrong with the application. Flask is server static content, which is a job better served by a proper web server, such as Nginx. And we're just glossing over the issue of the application blowing up when it hits an object with only partial data. We're also storing objects one at a time instead of committing a batch of objects (say ten or fifteen at a time, or even a whole album or artist discography).
But this also lays the foundations of a much broader application. There's several features I intend on adding and building on to here until there's a full featured API.
But for now, feel free to tinker and change. If you develop something, submit me a pull request.