Sonntag, 4. September 2011

Cassandra und Python Teil 2 - der Objectmapper

Im ersten Teil des Artikels zur Nutzung von Cassandra mit Python wurde das pycassa-Modul beschrieben.

pycassa bringt zusätzlich auch noch einen Objectmapper mit, mit dessen Hilfe man Pythonklassen auf Column Families mappen kann. Als Beispiel Keyspace dient, wie im ersten Teil auch, "pydemo" und als Column Family wieder "pykontakt". Alle Beispiel werden in der Python-Shell ausgeführt.

Als erstes muss man natürlich pycassa wieder importieren:

>>> import pycassa

Danach legen wir die Klasse an, auf die später gemappt werden soll:

>>> class CFMap(object):
...        name = pycassa.String(default='nobody')
...        alter = pycassa.IntString(default=0)

Wie man sieht, muss man Datentypen aus pycassa / Cassandra zuordnen. Der Default wird immer dann eingesetzt, wenn man selber keine weiteren Vorgaben macht.

Jetzt wird einer Verbindung zum laufenden Cassandra-Server hergestellt sowie der Keyspace "pydemo" und Column Family "pykontakt" genutzt:

>>> pool = pycassa.connect('pydemo')
>>> cf = pycassa.ColumnFamily(pool,'pykontakt',
         autopack_values=False, autopack_names=False)

Wichtig sind die beiden Festlegungen von "autopack_values" und "autopack_names" auf "False" (der Default ist "True"), da im Falle von Mappings die automatische Konvertierung des Datentyps durch den Mapper erfolgt.

Jetzt kann man das eigentliche Mapping vornehmen:

>>> CFMap.objects = pycassa.ColumnFamilyMap(CFMap,cf)

Das Mapping kann man jetzt für neue Objekte nutzen:

>>> c = CFMap()
>>> c.key = 'User100'
>>> c.name = 'Susi'
>>> c.alter = 25

Das Objekt kann nun gespeichert werden:

>>> CFMap.objects.insert(c)
1315076828600806

und natürlich auch abgefragt:

>>> CFMap.objects.get(c.key).name
'Susi'
>>> CFMap.objects.get(c.key).alter
25

Die Defaultwerte werden immer dann eingesetzt, wenn man einer Spalte keinen Wert zuweist:

>>> c2 = CFMap()
>>> c2.key = 'User101'
>>> c2.name = 'Rainer'
>>> CFMap.objects.insert(c2)
1315077378894450
>>> CFMap.objects.get(c2.key).name
'Rainer'
>>> CFMap.objects.get(c2.key).alter
0

Über den Befehl multiget können mehrere Schlüssel auf einmal abgefragt werden, wobei das Ergebnis wie Dictionary abgefragt werden kann:

>>> erg = CFMap.objects.multiget([c2.key,c.key])
>>> erg
OrderedDict([('User101', <__main__.CFMap object at 0x7fe4bfa4ed90>), ('User100', <__main__.CFMap object at 0x7fe4bfa4edd0>)])
>>> erg['User100']
<__main__.CFMap object at 0x7fe4bfa4edd0>
>>> erg['User100'].name
'Susi'
>>> erg['User101'].alter
0

Über den Mapper könne auch Objekte gelöscht werden:

>>> CFMap.objects.remove(c2)
1315078335744352

Eine vollständige Übersicht über den Pycassa-Mapper findet man in der API-Referenz.

Hinweis: Alle Beispiele sind unter Python 2.6 mit Ubuntu 10.04, pycassa 1.1.1 und Cassandra 0.8.2 getestet.

Cassandra und Python

Ergänzend zum Cassandra-Artikel in FreiesMagazin 9/2011 werden hier noch zusätzliche Infos zur Nutzung von Cassandra mit Python gegeben.

Für alle, die den Artikel noch nicht gelesen haben sollten: Cassandra ist die Datenbank hinter Facebook (ist also für "großes" geschaffen), Open Source (in der Obhut der Apache Foundation unter Apache Lizenz) und hat ein interessantes, weil strukturiertes aber trotzdem flexibles Datenmodell.

Und natürlich gibt es auch ein Python-Modul namens "pycassa", mit dem man auf Cassandra Datenbanken zugreifen kann.

Installation

Die Installation ist mittels easy_install schnell erledigt:

sudo easy_install thrift
sudo easy_install pycassa

pycassa braucht das thrift-Modul, löst es als Abhängigkeit aber nicht selber auf, so dass man es selber händisch installieren muss.

Jetzt kann man via Python auf Cassandra zugreifen. Dazu muss man das Modul wie üblich importieren:

>>> import pycassa

Keyspace und Column Family anlegen

Um mit pycassa ein Verbindung zum Server herstellen, ein Keyspace anzulegen usw. benötigt man den "system_manager":

#Verbindung zum Server herstellen
>>> sys = pycassa.system_manager.SystemManager('localhost:9160')
#Keyspace "pydemo" anlegen, Replikation "SimpleStrategy" (=1)
>>> sys.create_keyspace('pydemo',1)
#Column Family "pykontakt" anlegen
>>> sys.create_column_family('pydemo','pykontakt')

Die gezeigten Befehle sind die "Minimalversion". Für eine vollständige Übersicht sollte man einen Blick in die zugehörige API-Dokumentation werfen.

Mit der Datenbank arbeiten

Jetzt kann man mit der Column Family arbeiten.

#Verbindung zur Datenbank und Keyspace "pydemo" herstellen
>>> pool = pycassa.connect('pydemo')
#Column Family "pykonakt" auswählen
>>> cf = pycassa.ColumnFamily(pool,'pykontakt')
#einige Spalten mit Wert eingeben
>>> cf.insert('User_1',{'name':'Susi'})
>>> cf.insert('User_1',{'alter':'25'})
#es ist auch möglich, mehrere Spalten auf einmal zu schreiben
>>> cf.insert('User_2',{'name':'Otto','alter':30})
>>> cf.insert('User_3',{'name':'Rainer','alter':'25'})

Es wurden also drei Schlüssel, "User_1", User_2", "User_3", angelegt und jeweils ein Wert in die Spaten "name" und "alter" geschrieben.

Die Werte können natürlich auch abgefragt werden:

#User_1 abfragen
>>> cf.get('User_1')
OrderedDict([('alter', '25'), ('name', 'Susi')])
#umgekehrte Reihenfolge
>>> cf.get('User_1',column_reversed=True)
OrderedDict([('name', 'Susi'), ('alter', '25')])
#nur eine Spalte abfragen
>>> cf.get('User_1',columns=['name'])
OrderedDict([('name', 'Susi')])
#Zeitstempel mit Abfragen
>>> cf.get('User_1',include_timestamp=True)
OrderedDict([('alter', ('25', 1312827347304141)), ('name', ('Susi', 1312827336484155))])

Wie man sieht ist das Ergebnis einer Abfrage vom Datentype OrderedDict, es kann darauf also wie auf ein Dictionary zugegriffen werden.

Im direktem Vergleich zum Cassandra-CLI übernimmt pycasssa dankenswerterweise die Konvertierung des Datentyps, d.h. beim Schreiben in die Datenbank wird alles automatisch nach "ByteType" konvertiert und beim Lesen wieder zurück nach UTF-8 usw.

Spalten indizieren

Cassandra unterstützt auch Abfragen über einen Index. Dazu muss die entsprechende Spalte aber zuerst explizit indiziert werden, was wieder über den weiter oben erwähnten SystemManager erfolgen kann:

>>> sys.create_index('pydemo','pykontakt',
'alter',pycassa.system_manager.BYTES_TYPE,index_name='alter_idx')

Hiermit wird im Keyspace "pydemo" aus der Column Family "pykontakt" die Spalte "alter" indiziert. pycassa.system_manager.BYTES_TYPE legt den Typ der Spalte fest, in diesem Fall BYTES_TYPE, den Defaulttyp. Die Angabe des Indexnames ist optional.

Mit einer so indizierten Spalte kann eine entsprechende Abfrage gestartet werden. Im folgenden wird nach Einträgen gesucht, bei denen das Alter gleich 25 ist.

#Suchausdruck festlegen
>>> alter_expr = pycassa.index.create_index_expression('alter','25')
>>> clause = pycassa.index.create_index_clause([alter_expr])
#Abfrage durchführen
>>> for key,user in cf.get_indexed_slices(clause):
...     print 'Schlüssel: %s' %key
...     print 'Name: %s, Alter: %s' %(user['name'],user['alter'])
...
Schlüssel: User_3
Name: Rainer, Alter: 25
Schlüssel: User_1
Name: Susi, Alter: 25

Natürlich ist es auch möglichen, Spalten oder ganze Schlüssel zu löschen:

#Spalte "alter" für "User_3" löschen
>>> cf.remove('User_3',['alter'])
1312829216114192
>>> cf.get('User_3')
OrderedDict([('name', 'Rainer')])
#Schüssel "User_3" komplett löschen
>>> cf.remove('User_3')
1312829235495355
>>> cf.get('User_3')
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python2.6/dist-packages/pycassa-1.1.1-py2.6.egg/pycassa/columnfamily.py", line 409, in get
raise NotFoundException()
pycassa.cassandra.c08.ttypes.NotFoundException: NotFoundException()

weiterführende Hinweise

Wie oben bereits erwähnt bietet pycassa wesentlich mehr Möglichkeiten als hier gezeigt. Für Interessierte sei ein Blick in die Dokumentation oder das ebenfalls online verfügbare, etwas ausführlichere, englischsprachige Tutorial (an dem sich dieses Tutorial hier auch orientiert) empfohlen.

Im zweiten Teil dieses Blogeintrags wird der ebenfalls in pycasse enthaltene Mapper gezeigt.

Hinweis: Alle Beispiele sind unter Python 2.6 mit Ubuntu 10.04, pycassa 1.1.1 und Cassandra 0.8.2 getestet.

Freitag, 26. August 2011

Unity & GNOME2 & ich

Das Thema ist ja immer noch einer Dauerbrenner, auch wenn GNOME 3 und dessen GUI zur Zeit etwas immer im Rampenlicht seht.

Ich selber nutzte zwei Laptops in etwa zu gleichen Teilen, was die Zeit davor angeht. Der eine läuft unter Ubuntu 10.4 LTS mit GNOME2, der anderen unter Ubuntu 11.4 mit Unity. Beides Standardinstallationen ohne Anpassungen. Lucid nehme ich primär zum Programmieren (weil der Server, auf dem die Software später landet, auch unter 10.4 läuft), Natty für private Sachen wie Fotos etc. Und natürlich beide Laptops für Internet, Chat u.ä.

Zum eigentlichen Punkt: Ich selber habe überhaupt keinen Probleme, zwischen Unity und GNOME2  hin- und her zu wechseln. Ich kann auch nicht behaupten, dass das eine oder das andere "besser" oder "effektiver" wäre. Bei Unity kann ich vielleicht etwas schneller zwischen geöffneten Programmen wechseln, weil ich die entsprechenden Tastenkombinationen inzwischen kenne. Das ist aber (für mich) ein marginaler Vorteil.

Klar, Design und Usebility sind Geschmackssache. Aber so gruselig ist Unity definitiv nicht, dass man "Gnome for life" sagen muss... Klar, es ist anders und neu, aber wer nicht offen für neues und anderes ist, bekommt so wie so irgendwann ein Problem.

Jedenfalls sehe ich der Sache gelassen entgegen und freue mich schon auf Oneric mit Unity. Es sollen ja ein paar Sachen geändert und verbessert worden sein. Mal sehen, ob der Flamewar "Unity vs. GNOME" mit dem Release von Ubuntu 11.10 wieder neu aufflammt. Vielleicht auch eher nicht, weil das GNOME-Projekt mit GNOME 3 ja auch einen "progressiven Steilpaß nach ganz vorne" geschlagen hat ;-)

Und wenn's wirklich nicht passt: Das aktuelle Xfce / Xubuntu gefällt mir auch gut... Die sind ja wenigsten Konservativ beim Desktop-Design. ;-)

Donnerstag, 25. August 2011

Earth Crisis "Neutralize The Threat" - Rezension

 Letzten Monat, Juli 2011, ist das neue Album "Neutralize The Thread" von Earth Crisis erschienen, der insgesamt neunte Longplayer (und gleichzeitig siebtes Studioalbum) der Band.

Zu Earth Crisis an sich muss man nicht viel sagen - eine der bekanntesten und einflussreichsten Hardcorebands der Neunziger. Soundmäßig war Earth Crisis immer etwas metalllastig - und ist es auch auf dem neuen Album immer noch.

Im Gegensatz zum letzten Album "To The Death" ist das neue Album weniger brachial. Am besten lässt sich "Neutralize The Thread" wohl als "die alten Earth Crisis mit zeitgemäßem Sound" bezeichnen. Und das klingt sehr gut!

Auf allen zehn Songs geben die Jungs richtig Gas und zeigen, dass sie immer noch zur 1. Riege im Hardcore / Metallcore gehören. Tightes Zusammenspiel mit harten Riffs, gepaart mit prägnantem Drumming und Karl Buechners Gesang - so kennt man Earth Crisis und deshalb mögen wir Earth Crisis.

Nach etwas mehr als 30 min ist die CD dann zu Ende. Nun gut, Hardcore Bands waren noch nie für Alben mit epischer Länge bekannt. Dabei ist die CD aber so abwechslungsreich und gut, dass man sie auch ohne weiteres direkt nochmal hören kann.

Meine Anspieltipps sind dabei der recht kurze Opener "Raise" und der Titeltrack "Neutralize The Thread".

Fazit: Wer Earth Crisis mag braucht die CD auf jeden Fall, wer metalllastigen Hardcore mag, der braucht die CD auch, da sie definitiv zu den "must have" Releases aus diesem Genre gehört.

Montag, 15. August 2011

Bottle: Dateinamen für dynamisch generierte PDFs übertragen

Ich habe diverse Python-basierte Webapplikation für das Firmenintranet programmiert, die PDFs (mittels ReportLab) generieren. Die PDFs schreiben ich dabei in ein StringIO-Objekt anstatt die Datei temporär auf Platte zu legen. Funktioniert auch alles soweit gut.

Das einzige "Problem" war, dass der Name des PDF nicht übertragen wurde, d.h. wenn man die Datei im Browser öffnet und dann speichern will schlagen Evince, Adobe Reader & Co. als Dateiname immer "pdf.pdf" (oder so ähnlich) vor. Nun gut, nicht wirklich tragisch, aber ein bisschen doof ist das trotzdem.

Die Lösung für das Problem ist dabei aber denkbar einfach - man muss nur von Hand den passende Eintrag im Response-Header setzen. Im Falle von Bottle, meinem aktuell favorisierten Webframework, sieht das so aus:


from bottle import response, ...
...
#Buffer-Objekt anlegen
buf = cStringIO.StringIO()
#hier kommt der Pythoncode zur PDF Generierung
...
#Senden vorbereiten
response.headers['Content-Type'] = 'application/pdf'
response.headers['Content-Disposition'] = 'attachment;filename={0}'.format(pdf_filename)
#und Senden
return  buf

Wie gesagt, sehr einfach. "pdf_filename" enthält dabei den eigentlichen Dateinamen. Funktioniert natürlich auch genau so mit allen anderen Dateitypen.

Freitag, 5. August 2011

Skatepark Dornumersiel

Letzthin waren wir (Familie) im Urlaub in Norddeutschland. Da ich wusste, dass in Dornumersiel ein Skatepark ist, habe ich direkt mal mein Skateboard und meine Skateschuhe eingepackt.

Und - es hat sich gelohnt! Der Skatepark ist zwar nicht groß, aber echt klasse! Klasse in so fern, als das die Rampen einfach zu fahren sind, so dass auch Einsteiger, Gelegenheitsskater und alte Leute (die letzten beiden Punkte treffen auch mich zu ;-) ) Spaß haben können.

Der Park besteht aus einer zentralen Funbox, die auf einer Seite aus einer relativ flachen Pyramide und auf der anderen Seite aus einer Rampe und einer Bank besteht. Weiterhin gibt es einer Quarterpipe mit zwei Corners, eine kombinierte Quarterpipe mit Bank (ideal als Anlauf für die Funbox!), einer Grindbox mit "Auffahrt" und einem etwas zu hohen Rail.

Grindbox, kombinierte Quarter + Bank

Ollie über die Funbox, Quarterpipe im Hintergrund

Wie gesagt, es hat Spaß gemacht, auch wenn meine Skateschuhe dabei das zeitliche gesegnet haben...

Also, wer mal in der Nähe ist und sein Board dabei hat, der sollte im Skatepark Dornumersiel mal eine Runde fahren gehen. BMX-tauglich ist der Park übrigens auch.

Dienstag, 26. Juli 2011

Meine alten Skateschuhe - R.I.P.

Ich bin immer noch in tiefer Trauer... meine Skateschuhe sind nach 15 Jahren (oder vielleicht mehr?) kaputt gegangen. Trotzdem sie von der Markenfirma DC Shoes waren...

Nun gut - 15 Jahre halten die wenigsten Schuhe. Und Skateschuhe erst recht nicht. Wer sich jetzt wundert - die letzten 12-13 Jahre bin ich ziemlich wenig gefahren. Und mein Griptape ist ziemlich abgenutzt. ;-)

Anbei noch ein letztes Foto. Vielen von euch werden den Schuh wahrscheinlich gar nicht kennen. Wenn mich meine Erinnerung nicht täuscht waren das die mit als erstes erhältlichen DC Schuhe in Deutschland. Das Modell müsste das damalige Guy Mariana Signature Model sein. Kann mich aber auch täuschen...



Jedenfalls: R.I.P.

Ubuntu - Installation auf USB-Stick

Vor dem Urlaub bestand für mich die Notwendigkeit, Ubuntu (meine favorisierte Linux-Distribution) auf einen USB-Stick zu installieren, da ich nur ein geliehenes Netbook mit hatte, auf das ich kein Ubuntu fix installieren durfte (wollte).

Also musste eine USB-Stick her. Gekauft habe ich einen 16GB Stick von Lexar, wobei ich vorab nicht speziell auf die Schreib- / Lesegeschwindigkeit geachtet habe, sondern nur darauf, dass es eine Marken-Stick ist. Die Installation sollte "echt" sein, also kein Live-System von USB, sondern richtig, wie man auf eine HD halt auch installiert.

Üblicherweise nutzte ich GNOME bzw. seit Natty 11.04 Unity, für den Stick habe ich dann aber Xubuntu gewählt. Grund: 1) wollte ich es nochmal ausprobieren und 2) vielleicht etwas performanter vom Stick als K|Ubuntu. Also flux das Alternate-ISO von Xubuntu 11.04 heruntergeladen, gebrannt, Stick eingesteckt und die CD gestartet.

Die Installation an sich läuft wie auf jedes andere Laufwerk auch. Man sollte halt nur als Ziel den Stick wählen (wenn man eine HD hat wird der Stick i.d.R. als /dev/sdb eingebunden - bei mir jedenfalls) und diesen dann von Hand Partitionieren. Hat man bereits eine Linux-Installation auf der regulären Festplatte, dann will der Installer wahrscheinlich die vorhandene Swap-Partition nutzen. Da man den Stick aber "stand-alone" nutzen will, muss man diese dann händisch abwählen und Swap auf dem Stick anlegen (oder weg lassen, je nach eigenem Geschmack). Außerdem sollte man tunlichst darauf achten, dass der Bootloader ebenfalls auf den Stick geschrieben wird und man sich nicht aus versehen den Bootloader auf der HD abschießt. Der Rest der Installation läuft dann wieder wie gewohnt.

Ein Punkt ist noch anzumerken: Der Bootloader GRUB2 erkannt alle Laufwerke auf dem aktuellen Rechner und fügt diese dem Bootmenü hinzu. Somit stimmt das Bootmenü natürlich nicht, wenn man den USB-Stick auf einem anderen Rechner nutzt. Natürlich kann man Ubuntu auf dem Stick darüber booten, aber halt nicht die anderen Betriebssysteme, welche auf der jeweiligen lokalen HD sind. Wer dies unbedingt braucht muss halt jedes mal GRUB2 aktualisieren und neu booten. Von Ubuntu aus kann man später natürlich auf den Inhalt der lokalen Laufwerke zugreifen.

Nach der Installation ist der Stick bereit und man kann Ubuntu so auf jedem Rechner nutzen, der von einem USB-Stick booten kann. Der Performance fand ich ok, heißt der Bootvorgang ist zügig genug und auch das Arbeiten läuft relativ flüssig. Lediglich wenn man viel I/O hat, d.h. mehrere Programme gleichzeitig startet oder ein Update von Paketen macht wird es ein bisschen langsamer - aber immer noch deutlich schneller als ein Live-System von CD oder DVD.

Python CGI und Templates - die Eleminierung von überflüssigen print-Statements

Zugegebener Maßen ist es vielleicht nicht mehr ganz auf der Höhe der Zeit, über Python und CGI-Programmierung zu schreiben. Schließlich ist WSGI der Stand der Technik und absolut die 1. Wahl bei der Webprogrammierung mit Python.

Nun ist aber auch so, dass das Urgestein CGI noch lange nicht am Ende ist. Das hat wohl mehrere Gründe: So unterstützt quasi jeder Webserver CGI mit Bordmitteln, während WSGI-Unterstützung - so fern überhaupt vorhanden - in der Regel nachträglich installiert und konfiguriert werden muss. Ein weiterer Punkt ist, dass inzwischen zwar relativ viele Anbieter von Homepage-Paketen / Webspace Python unterstützen - aber eben nur für CGI-Skripte. WSGI ist hier die absolute Ausnahme und wirklich rar.

Daher ist es auch nicht so verwunderlich, dass in Python Support-Foren auch immer wieder Fragen zu Thema „Python + CGI“ auftauchen.

Nun, weiter schwierig ist das Thema wirklich nicht. Eine Standardinstallation von Python enthält bereits ein Modul für CGI, Tutorials gibt es haufenweise im Web.

Eine Sache findet man jedoch recht selten: Die Nutzung von Standard-Templates (im Python String-Modul enthalten) in Kombination mit CGI. In vielen CGI Tutorials findet man eher ellenlange print Statements. Gut, zum einen könnte es daran liegen, dass das Templates erst seit Python 2.4 an Bord sind, viele Tutorials sind aber (wahrscheinlich) älter. Durch die Verwendung von Templates wird aber die Programmlogik von der Darstellung / Ausgabe getrennt, was das Programm an sich zum einen wesentlich übersichtlicher macht und zum anderen die Codepflege vereinfacht.

Das Vorgehen dabei ist eigentlich simpel: Man erstellt eine Template-Datei, was nichts anderes als eine „normale“ HTML-Datei ist, die aber die Platzhalter für die spätere Ersetzung enthält. Das CGI-Skript lädt dann diese Datei, generiert ein Dict, welches die Werte für die Substitution enthält und lässt das Template dann am Ende ausgeben.

Das folgende minimalistische Beispiel verdeutlicht dies. Es werden drei Dateien erstellt: die HTML-Datei namens „index.html“, welche das Eingabeformular enthält, das Template „ausgabe.html“ und das CGI-Skript „ausgabe.cgi“.

index.html:
<html>
<head>
<title>Python CGI und Templates</title>
</head>
<body>
<form action="cgi-bin/ausgabe.cgi">
<p>Bitte Namen eingeben: <input type="text" name="name"></p>
<p><input type="submit"></p>
</form>
</body>
</html>

ausgabe.html:
<html>
<head>
<title>Hallo CGI-Welt!</title>
</head>
<body>
<p>Hallo $name!</p>
</body>

ausgabe.cgi:
#!/usr/bin/env python

import cgi
from string import Template

form = cgi.FieldStorage()
name = form['name'].value
ersetzung = {'name': name}
temp = Template(open('ausgabe.html').read())

print 'Content-type: text/html'
print
print temp.substitute(ersetzung)

Letztendlich also nicht anderes als ein reguläres Template, nur halt in einem CGI angewendet.

Natürlich könnte man auch ein „echtes“ Template-Modul wie z.B. Jinja2, Mako oder Genshi einsetzen, sofern man selber Python-Module installieren kann bzw. darf. Für einfache Seiten reichen die Templates aus dem String-Modul aber alle Mal.

Zum Abschluss dann noch der obligatorische Hinweis, dass man dieses Beispiel natürlich nicht 1:1 für seine Anwendung übernehmen darf, da keinerlei Prüfung etc. des Eingabewerts „name“ erfolgt, was in realen Anwendungen eine Sicherheitslücke darstellen würde.

Montag, 25. Juli 2011

Pulley "The long and the short of it" - Rezension

Nach zwei Jahren Pause ist vor Ende Juni 2011 die neue EP von Pulley erschienen, Titel "The long and the short of it".

Pulley gehört zu meinen Lieblingsbands - schöner Westcoast-Punkrock, melodisch und straight.

Leider hat die neue EP nur drei Tracks und somit eine Gesamtspielzeit von etwas weniger als 9 Minuten... Nicht viel, aber besser als gar nichts.

Im Vergleich zu den Vorgängeralben und EPs finde ich "The long and the short of it" etwas rauer - klingt aber immer noch nach Pulley. Sprich, wer Pulley mag, dem wird auch diese EP mögen. Und wer Pulley noch nicht kennt, aber Westcoast-Punkrock mag, der sollte sich diesen Release mal anhören. Mir gefällt's jedenfalls.