ORM plugin for using Cloud Spanner as a database backend for Django.
Using this library requires a Google Cloud Platform project with the Cloud Spanner API enabled. See the Spanner Python client documentation for details.
The version of django-google-spanner
must correspond to your version of Django. For example, django-google-spanner
2.2.x works with Django 2.2.y (Note: this is the only supported version at this time).
The minor release numbers of Django may not correspond to the minor release numbers of django-google-spanner
. Use the latest minor release of each.
To install from PyPI:
pip3 install django-google-spanner
To install from source:
git clone git@github.com:googleapis/python-spanner-django.git
cd python-spanner-django
pip3 install -e .
After installation, you'll have to update your Django settings.py
file as follows.
Add
django_spanner
as the very first entry in theINSTALLED_APPS
settings:INSTALLED_APPS = [ 'django_spanner', ... ]
Edit the
DATABASES
settings to point to an EXISTING database, as shown in the following example:DATABASES = { 'default': { 'ENGINE': 'django_spanner', 'PROJECT': '<GCP_project_id>', 'INSTANCE': '<instance_id>', 'NAME': '<database_name>', } }
In order to retrieve the Cloud Spanner credentials from a JSON file, the
credentials_uri
parameter can also be supplied in theOPTIONS
field:DATABASES = { 'default': { 'ENGINE': 'django_spanner', 'PROJECT': '<GCP_project_id>', 'INSTANCE': '<instance_id>', 'NAME': '<database_name>', 'OPTIONS': { 'credentials_uri': '<credentials_uri>', }, }, }
from google.cloud.spanner_dbapi import connect
connection = connect('<instance_id>', '<database_id>')
cursor = connection.cursor()
cursor.execute(
"SELECT *"
"FROM Singers"
"WHERE SingerId = 15"
)
results = cursor.fetchall()
Spanner doesn't have support for auto-generating primary key values. Therefore, django-google-spanner
monkey-patches AutoField
to generate a random UUID4. It generates a default using Field
's default
option which means AutoField
s will have a value when a model instance is created. For example:
>>> ExampleModel()
>>> ExampleModel.pk
4229421414948291880
To avoid hotspotting, these IDs are not monotonically increasing. This means that sorting models by ID isn't guaranteed to return them in the order in which they were created.
ForeignKey
constraints aren't created (#313)
Spanner does not support ON DELETE CASCADE
when creating foreign-key constraints, so this is not supported in django-google-spanner
.
Spanner does not support CHECK
constraints so one isn't created for PositiveIntegerField and CheckConstraint can't be used.
Spanner's support for Decimal types is limited to NUMERIC precision. Higher-precision values can be stored as strings instead.
Spanner does not support these functions.
This feature uses a column name that starts with an underscore (_order
) which Spanner doesn't allow.
Spanner does not support it and will throw an exception. For example:
>>> ExampleModel.objects.order_by('?')
...
django.db.utils.ProgrammingError: 400 Function not found: RANDOM ... FROM
example_model ORDER BY RANDOM() ASC
There are some limitations on schema changes to consider:
- No support for renaming tables and columns;
- A column's type can't be changed;
- A table's primary key can't be altered.
DurationField
arithmetic doesn't work with DateField
values (#253)
Spanner requires using different functions for arithmetic depending on the column type:
TIMESTAMP
columns (DateTimeField
) requireTIMESTAMP_ADD
orTIMESTAMP_SUB
DATE
columns (DateField
) requireDATE_ADD
orDATE_SUB
Django does not provide ways to determine which database function to use. DatabaseOperations.combine_duration_expression()
arbitrarily uses TIMESTAMP_ADD
and TIMESTAMP_SUB
. Therefore, if you use a DateField
in a DurationField
expression, you'll likely see an error such as:
"No matching signature for function TIMESTAMP\_ADD for argument types:
DATE, INTERVAL INT64 DATE\_TIME\_PART."
Spanner does not support this (#331) and will throw an error:
>>> ExampleModel.objects.update(integer=F('integer') / 2)
...
django.db.utils.ProgrammingError: 400 Value of type FLOAT64 cannot be
assigned to integer, which has type INT64 [at 1:46]\nUPDATE
example_model SET integer = (example_model.integer /...
Additions cannot include None
values. For example:
>>> Book.objects.annotate(adjusted_rating=F('rating') + None)
...
google.api_core.exceptions.InvalidArgument: 400 Operands of + cannot be literal
NULL ...