Atum — Import strings as Erlang-like atoms in Python.

Posted on December 14, 2018 in software

Past few months I have been experimenting with a bunch of libraries/ frameworks that help with building networked applications - often in the form of API servers and clients. Many use JSON or another serialization on wire and utilize strings to pass around static information. Others use strings to configure the application through code.

An example could be passing methods=['GET', 'POST'] to Flask's @app.route(...) decorator. The strings 'GET' and 'POST' are usually repeated in many places and are prone to typos such as 'GRT' instead of 'GET' that may go unnoticed until the code fails during testing or worse - while being used. ( Many frameworks raise exceptions on attempting to run code with such typos since the relevant validation is run when the program is loaded. However, this may not be possible in every case.)

Erlang's Atoms come to mind when I think of this, to quote the Erlang Documentation:

3.3 Atom

An atom is a literal, a constant with name. An atom is to be enclosed in single quotes (’) if it does not begin with a lower-case letter or if it contains other characters than alphanumeric characters, underscore (_), or @.

Examples:

hello
phone_number
'Monday'
'phone number'

http://erlang.org/doc/reference_manual/data_types.htm

Python has consistently surprised me with things it can do, if you ask it. This seemed like a trivial matter for Python. Having found nothing that did just this, I decided to give it a try - if it works, great - if not, I'll still have learned something useful about my tools.

Presenting Atum:

# atum.py

import sys as _sys

# intern() is a builtin in Python 2.
if _sys.version_info > (3, 0):
    intern = _sys.intern


class Atum(object):
    def __getattr__(self, item):
        if item.startswith('__'):
            return self.__getattribute__(item)
        return intern(item)

    def __getitem__(self, item):
        return item


_sys.modules[__name__] = Atum()

Not much going in there… this code above is all you need to import a string created from thin air, with the same name as its value.

How to use Atum:

from atum import (
    q, quit, exit,
    echo, direct, bcast,
)
while True:
    command = input('> ')
    cmd = command.lower().strip().split(' ')[0]
    if cmd in [q, quit, exit]:
        break
    elif cmd == echo:
        pass
    elif cmd == direct:
        pass
    elif cmd == bcast:
        pass

I've found atum improves readability for scripts, though I am yet to try using it in a large project.

There are a few benefits to spending the extra moment to import the atoms you will use in your application:

  • introduces vocabulary at the top of the module
  • cleaner code - easier to read
  • interned strings - skip allocation at runtime
  • works across network since atums are python strings
  • autocomplete where supported, make less errors while composing
  • catch more errors at linting step
  • "one" 'less' "RSI" 'trigger'

You can install atum with:

$ pip install atum

Atum is available at:

  • PyPI: https://pypi.org/project/atum/
  • Github: https://github.com/hiway/atum