Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

A Python library for carefully refactoring critical paths by testing in production (inspired by GitHub’s Scientist) with support for Python 2.7, 3.3+

Why?

Some blocks of code are more critical than the rest. Laboratory helps us refactor important code paths with confidence by testing in production and verifying the results.

By running experiments, Laboratory helps you establish a feedback loop which you can use to make improvements to unproven code until the error rate is nil.

All non-trivial software has bugs, and bugs in code lead to bugs in data. This means only production is production, and so testing in production is the only way to ensure your code actually works.

I recommend reading Jesse Toth’s blog post on using a similar techniques within Github that inspired this library, as well as Charity Majors on why you should test in production.

Getting started

See: Installation

With Laboratory you conduct an experiment with your known-good code as the control block and a new code branch as a candidate. Laboratory will:

  • Execute the new and the old code in a randomised order

  • Compare the return values

  • Record timing information about old & new code

  • Catch (but record!) exceptions in the new code

  • Publish all of this information

Let’s imagine you’re refactoring some authorisation code. Your existing code is working, but it’s a fragile pile of spaghetti that is becoming hard to maintain. You want to refactor, but this is important code and you simply can’t afford to get this wrong or else you risk exposing user data. Considering the state of the original code, this could be difficult to pull off, but Laboratory is here to help.

Laboratory helps us verify the correctness of our implementation even with the cornucopia of factors that make production a unique environment (bad or legacy data, heavy load, etc.)

Let’s set up an experiment to run our old (control) and new (candidate) code:

import laboratory

# set up the experiment and define control and candidate functions
experiment = laboratory.Experiment()
experiment.control(authorise_control, args=[user], kwargs={'action': action})
experiment.candidate(authorise_candidate, args=[user], kwargs={'action': action})

# conduct the experiment and return the control value
authorised = experiment.conduct()

Note that the Experiment class can also be used as a decorator if the control and candidate functions take the same arguments.

def authorise_candidate(user, action=None):
    return True

@Experiment.decorator(candidate=authorise_candidate)
def authorise_control(user, action=None):
    return True

An experiment will always return the value of the control block.

Adding context

A lot of the time there’s going to be extra context around an experiment that’s useful to use in publishing or when verifying results. There are a couple ways to set this.

# The first is experiment-wide context, which will be set on every Observation an experiment makes
experiment = laboratory.Experiment(name='Authorisation experiment', context={'action': action})

# Context can also be set on an Observation-specific basis
experiment.control(control_func, context={'strategy': 1})
experiment.candidate(cand_func, context={'strategy': 2})

Context can be retrieved using the get_context method on Experiment and Observation instances.

class Experiment(laboratory.Experiment):
    def publish(self, result):
        self.get_context()
        result.control.get_context()
        result.candidates[0].get_context()

Controlling comparison

Not all data is created equal. By default laboratory compares using ==, but sometimes you may need to tweak this to suit your needs. It’s easy enough — subclass Experiment and implement the compare(control, observation) method.

class MyExperiment(Experiment):
    def compare(self, control, observation):
        return control.value['id'] == observation.value['id']

Raise on mismatch

The Experiment class accepts a raise_on_mismatch argument which you can set to True if you want Laboratory to raise an exception when the comparison returns false. This may be useful in testing, for example.

Publishing results

This data is useless unless we can do something with it. Laboratory makes no assumptions about how to do this — it’s entirely for you to implement to suit your needs. For example, timing data can be sent to graphite, and mismatches can be placed in a capped collection in redis for debugging later.

The publish method is passed a Result instance, with control and candidate data is available in Result.control and Result.candidates respectively.

class MyExperiment(laboratory.Experiment):
    def publish(self, result):
        statsd.timing('MyExperiment.control', result.control.duration)
        for o in result.candidates:
            statsd.timing('MyExperiment.%s' % o.name, o.duration)

Installation

Installing from pypi is recommended

$ pip install laboratory

You can also install a tagged version from Github

$ pip install https://github.com/joealcorn/laboratory/archive/v0.4.2.tar.gz

Or the latest development version

$ pip install git+https://github.com/joealcorn/laboratory.git

Maintenance

Laboratory is actively maintained by Joe Alcorn (Github, Twitter)

Metadata

Release files for laboratory 1.0.dev0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for laboratory 1.0.dev0
File Size Uploaded
laboratory-1.0.dev0.tar.gz 7.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for laboratory 1.0.dev0
File Interpreter ABI Platform
laboratory-1.0.dev0-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 18.9 kB

Release files / laboratory-1.0.dev0.tar.gz

Download URL laboratory-1.0.dev0.tar.gz
Size 7.4 kB
Tags Source
SHA-256 checksum
How to use checksums
112eaaab14131c3d121fb0b4ae247c6f01881b67a8631f4e991a5f82b8d3b0fc
BLAKE2b-256 checksum
How to use checksums
1ee5c776da48759d03abb110d3fb21a6f228eaa0f328cd47f5773db25420b4fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release files / laboratory-1.0.dev0-py2.py3-none-any.whl

Download URL laboratory-1.0.dev0-py2.py3-none-any.whl
Size 11.6 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
4b7ac2808e1156df294a16bf9a398bb064b870e0b2741b5a6c6c551bc3e44490
BLAKE2b-256 checksum
How to use checksums
1c7ea247370a703b3789fedfebbaacdd6cb8f478ccf81b8eb2b57ffe50841790
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release history Release notifications | RSS feed

1.0.2

2 release files

1.0.1

2 release files

1.0

2 release files

This release

1.0.dev0 This release

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

0

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page