Decorators: A neat way to modify functions

Published: 03/04/2012 by Andrew Kember (Updated on 04/04/2012) with tags: Programming.

In Python, decorators are a construction that helps reduce boilerplate code, enhance the maintainability of our code and address the separation of concerns. A decorator is a statement just above the function it decorates. Here's a simple example so you can understand what I mean:

def heading1(func):
    """Define the decorating function - this wraps the return value of the 
    string in html heading tags. Don't actually write html like this - it is a
    silly idea.

    """
    def wrapper(*args, **kwargs):
        return "<h1>" + func(*args, **kwargs) + "</h1>"
    return wrapper


# Use the decorator here, before the function definition.
@heading1
def subject():
    return "Decorators: A neat way to modify functions"
    
print subject()
# Output is "<h1>Decorators: A neat way to modify functions</h1>"

That seems very neat, but this is a simple way of writing something complicated, so there are some kinks to iron out. The first is that in a generally applicable decorator, the decorating function must be able to accept an arbitrary argument list (meaning any arrangement of arguments and keyword arguments). The issue with this is that the resulting decorated function takes on the signature of the decorating function, so introspection of the function tells you that it accepts all positional and keyword arguments (*args, **kwargs).

The second problem is that the decorating function does not have the same attributes as the decorated function. E.g. __name__, __doc__, __module__, __dict__ and in Python 3, __annotations__.

The solution to these problems is provided in the form of the decorator package. This does all the leg-work of copying attributes as well as making signature-preserving decorators. As an added bonus, it removes the need to nest the wrapper function.

Here is the same trivial example, written using the decorator module:

import decorator

@decorator.decorator
def heading1(func, *args, **kwargs):
    """This was the wrapper function for the decorator. It now takes the 
    caller function as the first argument. The defining of the decorator 
    itself is handled by... the decorator!
    
    """
    return "<h1>" + func(*args, **kwargs) + "</h1>"

   
# Use the decorator here, before the function definition.
@heading1
def subject():
    return "Decorators: A neat way to modify functions"
    
print subject()
# Output is "<h1>Decorators: A neat way to modify functions</h1>"

There are plenty more resources for managing and learning about decorators. If you want to avoid installing the decorators package, you can do some of the hard work with functools.wraps. There's more about the decorators package and some good examples in the package documentation. StackOverflow is always a good place to look for examples (good and bad). The Python docs have a page full of decorator examples.

Thanks to saurik over on Hacker News for pointing out that I should really be using decorator.decorator as a decorator.

This is a series of articles about features of Python and styles of programming that you wouldn't necessarily come across if you learnt Python in the course of your work. Some of the articles are about metaprogramming techniques, some are about design patterns, but all of these techniques are only available if you know that they're there. Here's the rest of the series: Python on the job - what did I miss?


Ubuntu home server: Notifications by email

Published: 16/03/2012 by Andrew Kember (Updated on 16/03/2012) with tags: Solutions, Sysadmin.

TL;DR

Install and configure postfix and dovecot; Install certificates for secure authentication; Set up email aliases; Done.

This walkthrough tells you how to provide an email service to daemons on a home server so that it can send emails to a server admin's Gmail account.

Key: Actions look like this, results look like this and commands you enter on a terminal look like this. Replace [my_username] with your login on this server e.g. andrew. Replace [external_FQDN] with the domain name that you use to access your server from outside your local network. (FQDN is Fully Qualified Domain Name.) Replace [gmail address] with your normal email address. This should work just as well for non-gmail addresses, but it's a useful distinction to show we'll be sending mail outside our local network.

Pre-requisites:

  • Computer running Ubuntu (This was done on 10.04, but it's fairly standard stuff)
  • Domain name and DNS provider who can make this work - e.g. dyn.com

sudo aptitude install postfix

Postfix installs
Postfix starts its configuration gui

Select defaults for:

  • General type of mail configuration
  • System mail name

sudo dpkg-reconfigure postfix
Postfix starts its configuration gui

Select the following options:

  • General type of mail configuration: Internet Site
  • System mail name: [external_FQDN]
  • Root and postmaster mail recipient: [my_username]
  • Other destinations to accept mail for (blank for none): localhost.[external_FQDN], localhost
  • Force synchronous updates on mail queue?: No
  • Local networks: 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 [internal CIDR block, e.g. 192.168.0.0/24]
  • Mailbox size limit (bytes): 0
  • Local address extension character: +
  • Internet protocols to use: all

Configure Postfix for SMTP-AUTH using Dovecot SASL

sudo postconf -e 'smtpd_sasl_type = dovecot'
sudo postconf -e 'smtpd_sasl_path = private/auth-client'
sudo postconf -e 'smtpd_sasl_local_domain ='
sudo postconf -e 'smtpd_sasl_security_options = noanonymous'
sudo postconf -e 'broken_sasl_auth_clients = yes'
sudo postconf -e 'smtpd_sasl_auth_enable = yes'
sudo postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination'
sudo postconf -e 'inet_interfaces = all'

Postfix is configured silently - there is no output from these commands unless there's a problem.

Generate the keys for the Certificate Signing Request (CSR)

openssl genrsa -des3 -out server.key 1024
Enter passphrase when prompted
server.key file is created in your current working directory.

Now create the insecure key (no passphrase):
openssl rsa -in server.key -out server.key.insecure
Enter passphrase when prompted
server.key.insecure file is created in your current working directory.

Name the key files appropriately:
mv server.key server.key.secure
mv server.key.insecure server.key

server.key.secure and server.key files are present in your current working directory.

Create the CSR using the insecure key:
openssl req -new -key server.key -out server.csr
In the next step, you'll fill in some details. The only important option is the Common Name, which should be the FQDN of the server. This is slightly different to the advice on Wikipedia which indicates that the CN(Common Name) is used as part of the DN(Distinguished Name).
Fill in some details about: Country Name; State or Province Name; Locality Name; Organization Name; Organizational Unit Name; Common Name; Email address.
When prompted for the following optional attributes, leave them blank: A challenge password; An optional company name.
server.csr file is create in your current working directory.

Create a self-signed certificate and install it

Note that this certificate will be valid from now until an end date determined by the number after the -days option.
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
sudo cp server.crt /etc/ssl/certs
sudo cp server.key /etc/ssl/private

Configure Postfix to provide TLS encryption for incoming and outgoing mail

sudo postconf -e 'smtpd_tls_auth_only = no'
sudo postconf -e 'smtp_tls_security_level = may'
sudo postconf -e 'smtpd_tls_security_level = may'
sudo postconf -e 'smtp_tls_note_starttls_offer = yes'
sudo postconf -e 'smtpd_tls_key_file = /etc/ssl/private/server.key'
sudo postconf -e 'smtpd_tls_cert_file = /etc/ssl/certs/server.crt'
sudo postconf -e 'smtpd_tls_loglevel = 1'
sudo postconf -e 'smtpd_tls_received_header = yes'
sudo postconf -e 'smtpd_tls_session_cache_timeout = 3600s'
sudo postconf -e 'tls_random_source = dev:/dev/urandom'
sudo postconf -e 'myhostname = [external_FQDN]'

Now restart postfix:
sudo /etc/init.d/postfix restart
Postfix should restart with no errors

Configuring SASL

sudo apt-get install dovecot-common
Dovecot will install.

Edit /etc/dovecot/dovecot.conf as root (e.g. sudoedit /etc/dovecot/dovecot.conf)
On line 1116, or thereabouts, uncomment the socket listen option and modify the section so it looks like this:

 socket listen {
    #master {
      # Master socket provides access to userdb information. It's typically
      # used to give Dovecot's local delivery agent access to userdb so it
      # can find mailbox locations.
      #path = /var/run/dovecot/auth-master
      #mode = 0600
      # Default user/group is the one who started dovecot-auth (root)
      #user = 
      #group = 
    #}
    client {
      # The client socket is generally safe to export to everyone. Typical use
      # is to export it to your SMTP server so it can do SMTP AUTH lookups
      # using it.
      path = /var/spool/postfix/private/auth-client
      mode = 0660
      user = postfix
      group = postfix
    }
  }

Now restart Dovecot
sudo /etc/init.d/dovecot restart

Setting up Aliases

Edit /etc/aliases as root (e.g. sudoedit /etc/aliases) to add your gmail address. Once you've finished, it should look like this:

    # See man 5 aliases for format
    postmaster:    root
    root:          [gmail address]
    [my_username]: [gmail address]

Testing

Lets see if we can connect to our postfix instance with telnet.
telnet localhost 25
... results in the following:

    Trying ::1...
    Connected to localhost.
    Escape character is '^]'.
    220 [external_FQDN] ESMTP Postfix (Ubuntu)

Type the following command into the telnet session:
ehlo [external_FQDN]
The output should include the following lines (and probably a bunch of others):

    250-[external_FQDN]
    250-STARTTLS
    250-AUTH PLAIN
    250-AUTH=PLAIN
    250-8BITMIME

Let's follow that up by sending an email directly from the telnet session.
Type the following commands into the telnet session:
mail from: root@localhost
rcpt to: [my_username]@localhost
data
Subject: My first mail on Postfix

Hello,
Are you there, Charlie Bear?
regards,
Me
. (Type the .[dot] in a new Line and press Enter )
quit

Postfix will acknowledge each command with a message ending in 'Ok' (except when you type the message contents). The output should look a bit like this:

    250 2.0.0 Ok: queued as 402DA9FCD4
    quit
    221 2.0.0 Bye
    Connection closed by foreign host.

Wait for it... Okay - now check your email. If all has gone well, you've got an email from yourself sitting in your inbox.

These instructions were pieced together from Postfix: Ubuntu server guide and Certificates: Ubuntu server guide.


Old hymns made new

Published: 16/03/2012 by Andrew Kember (Updated on 16/03/2012) with tags: Life.

Sansa clip volume problem

Published: 16/03/2012 by Andrew Kember (Updated on 16/03/2012) with tags: Solutions.

TL;DR

To increase the maximum volume of your Sansa Clip, set the Region option to "Rest of World".

Continue Reading…

Awesome feature of dropbox - instant sync

Published: 16/03/2012 by Andrew Kember (Updated on 16/03/2012) with tags: Tools.

TL;DR

Dropbox can save bandwidth and time by only uploading files it's not seen before.

Continue Reading…

Using regular expressions with the Find command

Published: 16/03/2012 by Andrew Kember (Updated on 16/03/2012) with tags: Sysadmin.

TL;DR

find . -regextype posix-extended -regex '.*\.(xsd|java)'

Continue Reading…


For more articles, browse the articles section.