Live to Win – Motorhead Covers and Pythonic Irrigation

The recent passing of Lemmy has caused me to reflect on on the career of one of the bands who made my growing up (and grown-up) years that much…well…louder.

Yes, I know that serious Python documentation should employ a sprinkling of Monty Python references but, let’s face it, what follows is more of a quick trawl through some basic Python constructs that I’ve found quite useful recently.
If I put them all here, at least I’ll know where to look when I need them again.

In any case, Michael Pailin made a guest appearance on the album Rock ‘n’ Roll so that’s probably enough of a link to safisfy the Monty Python criteria.

I find Python a really good language to code in…especially when the alternative is writing a Windows Batch Script. However, there is a “but”.
Python 3 is not backward compatible with Python 2. This can make life rather interesting on occasion.

It is possible to write code that is compatible with both versions of the language and there’s a useful article here on that topic.

The code I’ve written here has been tested on both Python 2 (2.7.6) and Python 3 (3.4.3).

One of the great things about Python is that there are a number of modules supplied as standard, which greatly simplify some common programming tasks.
What I’m going to run through here is :

  • Getting information about the environment
  • Handling runtime arguments with the argparse module
  • Reading config files with configparser
  • Writing information to log files with the logging module

Existential Questions

There are a number of questions that you’ll want to answer programatically, sooner rather than later…

Who am I

There’s a couple of ways to find out the user your connected as from inside Python.
You could simply use the os.getlogin() function…

import os
print( os.getlogin())

…but according to the official documentation [link] this is probably a better option…

import os
import pwd
print( pwd.getpwuid(os.getuid())[0])

Additionally, we may want to know the name of the Python program we’re currently in. The following script – called road_crew.py – should do the job :

import os
print(os.path.basename(__file__))

Running this we get :

road_crew.py

Where am I

Step forward the platform module, as seen here in this code (saved as hammersmith.py) :

import platform

def main() :
    # Get the name of the host machine
    machine_name = platform.node()
    # Get the OS and architecture
    os_type = platform.system()
    if platform.machine() == 'x86_64' :
        os_arch = '64-bit'
    else :
        os_arch = '32-bit'

    print('Running on '+machine_name+' which is running '+ os_type + ' ' + os_arch)

    # Now get more detailed OS information using the appropriate function...
    if os_type == 'Linux' :
        print(platform.linux_distribution())
    elif os_type == 'Windows' :
        print(platform.win32_ver())
    elif os_type == 'Mac' :
        #NOTE - I don't have a Mac handy so have no way of testing this statement
        print(platform.mac_ver())
    else :
        print("Sky high and 6000 miles away!")

if __name__ == '__main__' :
    main()

Running this on my Linux Mint machine produces :

Running on mike-TravelMate-B116-M which is running Linux 64-bit
('LinuxMint', '17.3', 'rosa')

As mentioned previously, you also may be quite keen to know the version of Python that your program is running on….

import sys

major = sys.version_info[0]
minor = sys.version_info[1]
micro = sys.version_info[2]

if major == 3 :
    print('Ace of Spades !')
else :
    print('Bomber !')

print('You are running Python ' + str(major) + '.' + str(minor) + '.' + str(micro))

On Python 3, this outputs…

Ace of Spades !
You are running Python 3.4.3

…whilst on Python 2…

Bomber !
You are running Python 2.7.6

When Am I

As for the current date and time, allow me to introduce another_perfect_day.py…

import time

today = time.strftime("%a %d %B %Y")
now = time.strftime("%H:%M:%S")

print("Today's date is " + today);
print("The time is now " + now);

…which gives us …

Today's date is Sun 06 March 2016
The time is now 19:15:19

Argument parsing

The argparse module makes handling arguments passed to the program fairly straightforward.
It allows you to provide a short or long switch for the argument, specify a default value, and even write some help text.
The program is called no_remorse.py and looks like this :

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-a", "--age", default = 40, help = "How old are you ? (defaults to 40 - nothing personal)")
args = vars(parser.parse_args())
age = args['age']
if int(age) > 39 :
    print('I remember when Motorhead had a number 1 album !')
else :
    print('Who are Motorhead ?')

The argparse gives us a couple of things. First of all, if we want to know more about the required parameters, we can simply invoke the help :

python no_remorse.py -h
usage: no_remorse.py [-h] [-a AGE]

optional arguments:
  -h, --help         show this help message and exit
  -a AGE, --age AGE  How old are you ? (defaults to 40 - nothing personal)

If we run it without specifying a value for age, it will pick up the default….

python no_remorse.py
I remember when Motorhead had a number 1 album !

…and if I’m tempted to lie about my age (explicitly, as opposed to by omission in the previous example)…

python no_remorse.py -a 39
Who are Motorhead ?

As well as using the single-letter switch for the parameter, we can use the long version …

python no_remorse.py --age 48
I remember when Motorhead had a number 1 album !

One other point to note, the program will not accept arguments passed by positon, either the long or short switch for the argument must be specified. Either that or Python comes with it’s own outrageous lie detector…

python no_remorse.py 25
usage: no_remorse.py [-h] [-a AGE]
no_remorse.py: error: unrecognized arguments: 25

Reading a config file

There are times when you need a program to run on multiple environments, each with slightly different details ( machine name, directory paths etc).
Rather than having to pass these details in each time you run the program, you can dump them all into a file for your program to read at runtime.
Usually, you’ll pass in an argument to point the program at the appropriate section of your config file. A config file will look something like this :

[DEV]
db_name = dev01

[TEST]
db_name = test01

[PROD]
db_name = prod

In this example, your program will probably accept an argument specifying which environment it needs to run against and then read the appropriate section of the config file to set variables to the appropriate values.

My working example is slightly different and is based on cover versions that Motorhead have done of other artists’ tracks, together with a couple of my favourite covers of Motorhead songs by other bands :

[MOTORHEAD]
Tammy Wynette = Stand By Your Man
The Kingsmen = Louie Louie

[METALLICA]
Motorhead = Overkill

[CORDUROY]
Motorhead = Motorhead

Now, you could spend a fair amount of time trying to figure out how to read this file and get the appropriate values…or you could just use the configparser module…

Conditional Import – making sure you find Configparser

The configparser module was renamed in Python3 so the import statement for it is different depending on which version of Python your using.
Fortunately, Python offers the ability to conditionally import modules as well as allowing you to alias them.
Therefore, this should solve your problem…

try:
    import configparser
except ImportError :
    import ConfigParser as configparser

So, if we’re running Python 3 the first import statement succeeds.
If we’re running Python2 we’ll get an ImportError, in which case we import the version 2 ConfigParser and alias it as configparser.
The alias means that we can refer to the module in the same way throughout the rest of the program without having to check which version we’ve actually imported.
As a result, our code should now run on either Python version :

try:
    import configparser
except ImportError :
    import ConfigParser as configparser

config = configparser.ConfigParser()
config.read('covers.cfg')

#Get a single value from the [CORDUROY] section of the config file
cover_artist = 'CORDUROY'
#Find the track they covered, originally recorded by Motorhead
# Pass the config section and the original artist ( the entry on the left-hand side of the "="
# in the config file
track = config.get(cover_artist, 'Motorhead')
# cover_artist and track are string objects so we can use the title method to initcap the output
print(cover_artist.title() + ' covered ' + track.title() + ' by Motorhead')

# Loop through all of the entries in the [MOTORHEAD] section of the config file
for original_artist in config.options('MOTORHEAD') :
    print('Motorhead covered ' + config.get('MOTORHEAD', original_artist) + ' by ' + original_artist.upper())

Run this and we get…

Corduroy covered Motorhead by Motorhead
Motorhead covered Stand By Your Man by TAMMY WYNETTE
Motorhead covered Louie Louie by THE KINGSMEN

Dead Men Tell No Tales

…but fortunately the Python logging module will let your programs sing like a canary.

As with the configparser, there’s no need to write lots of code to open and write to a file.
There are five levels of logging message supported :

  • DEBUG
  • INFO
  • WARNING – the default
  • ERROR
  • CRITICAL

There is a separate call to write each message type. The message itself can be formatted to include information such as a timestamp and the program from which the message was written. There’s a detailed how-to on logging here.

For now though, we want a simple program (logger.py) to write messages to a file wittily and originally titled logger.log…

import logging

logging.basicConfig(
    filename='logger.log',
    level=logging.INFO,
    format='%(asctime)s:%(filename)s:%(levelname)s:%(message)s'
)

logging.debug('No Remorse')
logging.info('Overnight Sensation')
logging.warn('March or Die')
logging.error('Bad Magic')

There’s no output to the screen when we run this program but if we check, there should now be a file called logger.log in the same directory which contains :

2016-03-06 19:19:59,375:logger.py:INFO:Overnight Sensation
2016-03-06 19:19:59,375:logger.py:WARNING:March or Die
2016-03-06 19:19:59,375:logger.py:ERROR:Bad Magic

As you can see, the type of message in the log depends on the logging member invoked to write the message.

If you want a more comprehensive/authoritative/coherent explanation of the features I’ve covered here, then have a look at the official Python documentation.
On the other hand, if you want to check out a rather unusual version of one of Motorhead’s signature tracks, this is definitely worth a look.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s