ORM plugin for using Cloud Spanner as a database backend for Django.
To use this library you'll need a Google Cloud Platform project with the Cloud Spanner API enabled. See the Cloud Spanner Python client docs for details.
Use the version of django-google-spanner that corresponds to your version
of Django. For example, django-google-spanner 2.2.x works with Django
2.2.y. (This is the only supported version at this time.)
The minor release number of Django doesn't correspond to the minor release
number 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 git@github.com:googleapis/python-spanner-django.git
cd python-spanner-django
pip3 install -e .After installation, you'll need to edit your Django
settings.py file:
Add
django_spanneras the very first entry in theINSTALLED_APPSsettingINSTALLED_APPS = [ 'django_spanner', ... ]
Edit the
DATABASESsetting to point to an EXISTING database
DATABASES = {
'default': {
'ENGINE': 'django_spanner',
'PROJECT': '<project_id>',
'INSTANCE': '<instance_id>',
'NAME': '<database_name>',
# Only include this if you need to specify where to retrieve the
# service account JSON for the credentials to connect to Cloud Spanner.
'OPTIONS': {
'credentials_uri': '<credentials_uri>',
},
},
}DATABASES = {
'default': {
'ENGINE': 'django_spanner',
'PROJECT': 'appdev-soda-spanner-staging', # Or the GCP project-id
'INSTANCE': 'django-dev1', # Or the Cloud Spanner instance
'NAME': 'db1', # Or the Cloud Spanner database to use
}
}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 = cur.fetchall()django-google-spanner always works in autocommit mode, which is
Django's default behavior even for backends that support manual transaction
management. Transactions cannot be controlled manually with calls like
django.db.transaction.atomic().
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.
Spanner doesn't support ON DELETE CASCADE when creating foreign-key
constraints so django-google-spanner doesn't support foreign key
constraints.
Spanner doesn't support CHECK constraints so one isn't created for
PositiveIntegerField
and CheckConstraint
can't be used.
Spanner doesn't support a NUMERIC data type that allows storing high precision decimal values without the possibility of data loss.
Spanner doesn't support these functions.
This feature uses a column name that starts with an underscore
(_order) which Spanner doesn't allow.
Spanner doesn't support it. For example:
>>> ExampleModel.objects.order_by('?')
...
django.db.utils.ProgrammingError: 400 Function not found: RANDOM ... FROM
example_model ORDER BY RANDOM() ASC
Spanner has some limitations on schema changes which you must respect:
- Renaming tables and columns isn't supported.
- A column's type can't be changed.
- A table's primary key can't be altered.
- Migrations aren't atomic since
django-google-spannerdoesn't support transactions.
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 doesn't provide a way to determine which database function to
use. DatabaseOperations.combine_duration_expression() arbitrary uses
TIMESTAMP_ADD and TIMESTAMP_SUB. Therefore, if you use a
DateField in a DurationField expression, you'll see an error
like: "No matching signature for function TIMESTAMP_ADD for argument
types: DATE, INTERVAL INT64 DATE_TIME_PART."
Spanner doesn't support this.
For example, if integer is IntegerField:
>>> 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 /...
For example:
>>> Book.objects.annotate(adjusted_rating=F('rating') + None)
...
google.api_core.exceptions.InvalidArgument: 400 Operands of + cannot be literal
NULL ...

