Skip to main content

Generate gap-less sequences of integer values.

Project description

The problem

On PostgreSQL, SERIAL columns aren’t guaranteed to be sequential.

If a transaction inserts a row and then is rolled back, the sequence counter isn’t rolled back for performance reasons, creating a gap in the sequence.

Django’s default, implicit primary keys are backed by SERIAL columns. Usually they’re sequential but this problem can create unexpected gaps.

This is a problem for some use cases such as accounting.

The solution

django-sequences provides just one function, get_next_value, which is designed to be used as follows:

from django.db import transaction

from sequences import get_next_value

from invoices.models import Invoice

with transaction.atomic():
    Invoice.objects.create(number=get_next_value('invoice_numbers'))

The guarantees of django-sequences only apply if you call get_next_value and save its return value to the database within the same transaction!

Installation

Install django-sequences:

$ pip install django-sequences

Add it to the list of applications in your project’s settings:

INSTALLED_APPS += ['sequences.apps.SequencesConfig']

Run migrations:

$ django-admin migrate

API

get_next_value generates a gap-less sequence of integer values:

>>> get_next_value()
1
>>> get_next_value()
2
>>> get_next_value()
3

It supports multiple independent sequences:

>>> get_next_value('cases')
1
>>> get_next_value('cases')
2
>>> get_next_value('invoices')
1
>>> get_next_value('invoices')
2

The first value defaults to 1. It can be customized:

>>> get_next_value('customers', initial_value=1000)  # pro growth hacking

The initial_value parameter only matters when get_next_value is called for the first time for a given sequence — assuming the corresponding database transaction gets committed; as discussed above, if the transaction is rolled back, the generated value isn’t consumed. It’s also possible to initialize a sequence in a data migration and not use initial_value in actual code.

Database transactions that call get_next_value for a given sequence are serialized. In other words, when you call get_next_value in a database transaction, other callers which attempt to get a value from the same sequence will block until the transaction completes, either with a commit or a rollback. You should keep such transactions short to minimize the impact on performance.

Passing nowait=True will cause get_next_value to raise an exception instead of blocking. This will rarely be useful. Also it doesn’t work for the first call. (Arguably this is a bug. Patches welcome.)

Calls to get_next_value for different sequences don’t interact with one another.

To sum up, the complete signature of get_next_value is:

get_next_value(sequence_name='default', initial_value=1, nowait=False)

Under the hood, it relies on PostgreSQL’s transactional integrity to guarantee that each value will be returned exactly once.

Contributing

You can run tests with:

$ make test

If you’d like to contribute, please open an issue or a pull request on GitHub!

Other databases

INTEGER PRIMARY KEY AUTOINCREMENT fields on SQLite don’t have this problem.

The author doesn’t know if this problem can happens on MySQL or Oracle. If it does, then the current implementation of django-sequences should work. If you test this, please open an issue on GitHub to report your findings. Note that MySQL won’t support the nowait parameter.

Changelog

1.0

  • Initial stable release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-sequences-1.0.1.tar.gz (5.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_sequences-1.0.1-py2.py3-none-any.whl (10.9 kB view details)

Uploaded Python 2Python 3

File details

Details for the file django-sequences-1.0.1.tar.gz.

File metadata

File hashes

Hashes for django-sequences-1.0.1.tar.gz
Algorithm Hash digest
SHA256 97c99fb6bfe6ba843608aa67ea924cb1bd5e094732ef16c6b8cb8f51f3aff021
MD5 ff892bc17e4bd873cc16ae2b47c13ca0
BLAKE2b-256 3eec7e40c757fc65a84f4b61301265a17fe94706c1ef72d6376c8e957097c596

See more details on using hashes here.

File details

Details for the file django_sequences-1.0.1-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for django_sequences-1.0.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 39c14f61ae25b0593d576515175df118ac3004749f0173cf70e493d35dc9fdc4
MD5 c8abf6f99d009c774ff542246ca947ea
BLAKE2b-256 56df3a162850c31ae18a36c3454920abede05a2263b41d2e0e1744e2509de80f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page