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-spannerTo install from source:
git clone [email protected]: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_spanneras the very first entry in theINSTALLED_APPSsettings:INSTALLED_APPS = [ 'django_spanner', ... ]
Edit the
DATABASESsettings 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_uriparameter can also be supplied in theOPTIONSfield: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 AutoFields 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:
TIMESTAMPcolumns (DateTimeField) requireTIMESTAMP_ADDorTIMESTAMP_SUBDATEcolumns (DateField) requireDATE_ADDorDATE_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 ...

