Advice for designing your own libraries

Advice for designing your own libraries

When designing your own library make sure to think of the following things. I will add new paragraphs to this article as I dicover new good practices.

Use standard python objects

Try to use standard python objects as much as possible. That way, your library becomes compatible with all the other python libaries.

For instance, when I created SAMpy : a library for reading and writing SAMCEF results, it returned dictonnaries, lists and pandas dataframes. Hence the results extracted from SAMCEF where compatible with all the scientific stack of python.

Limit the number of functionnalities

Following the same logic as before, the objects should do only one thing but do it well. Indeed, having a simple interface will reduce the complexity of your code and make it easier to use your library.

Again, with SAMpy, I decided to strictly limit the functionnalities to reading and writing SAMCEF files.

Define an exception class for your library

You should define your own exceptions in order to make it easier for your users to debug their code thanks to clearer messages that convey more meaning. That way, the user will know if the error comes from your library or something else.

Bonus if you group similar exceptions in a hierachy of inerited Exception classes.

Example : let's create a Exception related to the age of a person :

def check_age(age):
    if age < 0 and age > 130:
        raise ValueError

If the user inputed an invalid age, the ValueError exception would be thrown. That's fine but imagine you wan't to provide more feedback to your users that don't know the internal of your library. Let's now create a selfexplanatory Exception

class AgeInvalidError(ValueError):
    pass
def check_age(age):
    if age < 0 and age > 130:
        raise AgeInvalidError(age)

You can also add some helpful text to guide your users along the way:

class AgeInvalidError(ValueError):
    print("Age invalid, must be between 0 and 130")
    pass
def check_age(age):
    if age < 0 and age > 130:
        raise AgeInvalidError(age)

If you want to group all the logically linked exceptions, you can create a base class and inherit from it :

class BaseAgeInvalidError(ValueError):
    pass
class TooYoungError(BaseAgeInvalidError):
    pass
class TooOldError(BaseAgeInvalidError):
    pass


def check_age(age):
    if age < 0:
        raise TooYoungError(age)
    elif age > 130 :
        raise TooOldError(age) 

Structure your repository

You should have a file structure in your repository. It will help other contributers especially future contributers.

A nice directory structure for your project should look like this:

README.md
LICENSE
setup.py
requirements.txt
./MyPackage
./docs
./tests

Some prefer to use reStructured Text, I personnaly prefer Markdown

choosealicense.com will help you pick the license to use for your project.

For package and distribution management, create a setup.py file a the root of the directory The list of dependencies required to test, build and generate the doc are listed in a pip requirement file placed a the root of the directory and named requirements.txt

Put the documentation of your library in the docs directory.

Put your tests in the tests directory. Since your tests will need to import your library, I recommend modifying the path to resolve your package property.

In order to do so, you can create a context.py file located in the tests directory :

import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

import MyPackage

Then within your individual test files you can import your package like so :

from .context import MyPackage

Finally, your code will go into the MyPackage directory

Test your code

Once your library is in production, you have to guaranty some level of forward compatibility. Once your interface is defined, write some tests. In the future, when your code is modified, having those tests will make sure that the behaviour of your functions and objects won't be altered.

Document your code

Of course, you should have a documentation to go along with your library. Make sure to add a lot of commun examples as most users tend to learn from examples.

I recommend writing your documentation using Sphinx.