Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cb14250

Browse filesBrowse files
committed
django: add more connection related methods
1 parent 8bcc32b commit cb14250
Copy full SHA for cb14250

File tree

Expand file treeCollapse file tree

4 files changed

+142
-32
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+142
-32
lines changed

‎spanner-django/spanner/django/base.py

Copy file name to clipboardExpand all lines: spanner-django/spanner/django/base.py
+53-26Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
# limitations under the License.
1414

1515
from django.db.backends.base.base import BaseDatabaseWrapper
16+
from django.utils.functional import cached_property
17+
from django.utils.asyncio import async_unsafe
1618

1719
from .parse_utils import (
18-
resolve_project_id,
19-
parse_spanner_url
20+
extract_connection_params
2021
)
2122

23+
from google.cloud import spanner_v1 as spanner
24+
2225

2326
class DatabaseWrapper(BaseDatabaseWrapper):
2427
vendor = 'spanner'
@@ -95,29 +98,53 @@ class DatabaseWrapper(BaseDatabaseWrapper):
9598
'iendswith': 'ENDS_WITH(%s, %s)',
9699
}
97100

101+
98102
def get_connection_params(self):
99-
# We'll expect settings in the form of either:
100-
# {
101-
# "NAME": "spanner",
102-
# "INSTANCE": "instance",
103-
# "AUTOCOMMIT": True or False,
104-
# "READ_ONLY": True or False,
105-
# "PROJECT_ID": "<project_id>",
106-
# }
107-
#
108-
# OR
109-
# {
110-
# "SPANNER_URL": "cloudspanner:[//host[:port]]/project/<project_id>/instances/<instance-id>/databases/<database-name>?property-name=property-value
111-
# }
112-
settings_dict = self.settings_dict
113-
114-
if settings_dict['SPANNER_URL']:
115-
return parse_spanner_url(settings_dict['SPANNER_URL'])
103+
return extract_connection_params(self.settings_dict)
104+
105+
106+
@async_unsafe
107+
def get_new_connection(self, conn_params):
108+
kwargs = dict(
109+
project=conn_params.get('project_id'),
110+
user_agent='spanner-django/v1',
111+
)
112+
113+
credentials_uri = conn_params.get('credentials_uri')
114+
client = None
115+
116+
if credentials_uri:
117+
client = spanner.Client.from_service_account_json(credentials_uri, **kwargs)
116118
else:
117-
return dict(
118-
auto_commit=settings_dict['AUTO_COMMIT'],
119-
database=settings_dict['NAME'] or 'spanner',
120-
instance=settings_dict['INSTANCE'],
121-
project_id=resolve_project_id(settings_dict['PROJECT_ID']),
122-
read_only=settings_dict['READ_ONLY'],
123-
)
119+
client = spanner.Client(**kwargs)
120+
121+
return client
122+
123+
124+
@async_unsafe
125+
def create_cursor(self, name=None):
126+
raise Exception('unimplemented')
127+
128+
129+
def disable_constraint_checking(self):
130+
raise Exception('unimplemented')
131+
132+
133+
def enable_constraint_checking(self):
134+
raise Exception('unimplemented')
135+
136+
137+
def check_constraints(self, table_names=None):
138+
raise Exception('unimplemented')
139+
140+
141+
def is_usable(self):
142+
raise Exception('unimplemented')
143+
144+
@cached_property
145+
def display_name(self):
146+
return 'Spanner'
147+
148+
@cached_property
149+
def data_type_check_constraints(self):
150+
raise Exception('unimplemented')
+50Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import subprocess
16+
from django.db.backends.base.client import BaseDatabaseClient
17+
18+
from .parse_utils import (
19+
extract_connection_params
20+
)
21+
22+
23+
def DatabaseClient(BaseDatabaseClient):
24+
executable_name = 'spanner'
25+
26+
@classmethod
27+
def settings_to_cmd_args(cls, settings_dict):
28+
args = [cls.executable_name]
29+
settings = extract_connection_params(self.settings_dict)
30+
project_id = settings['project_id']
31+
if project_id:
32+
args += ['--project_id=%s' % project_id]
33+
instance = settings['instance']
34+
if instance:
35+
args += ['--instance=%s' % instance]
36+
db_name = settings['db_name']
37+
if db_name:
38+
args += ['--db_name=%s' % db_name]
39+
auto_commit = settings['auto_commit']
40+
if auto_commit:
41+
args += ['--auto_commit=%s' % auto_commit]
42+
read_only = settings['read_only']
43+
if read_only:
44+
args += ['--read_only=%s' % read_only]
45+
46+
return args
47+
48+
def runshell(self);
49+
args = DatabaseClient.settings_to_cmd_args(self.connection.settings_dict)
50+
subprocess.run(args, check=True)

‎spanner-django/spanner/django/parse_utils.py

Copy file name to clipboardExpand all lines: spanner-django/spanner/django/parse_utils.py
+34-6Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
import re
1616
from urllib.parse import urlparse
1717

18+
from django.core.exceptions import ImproperlyConfigured
1819

1920
def resolve_project_id(project_id):
2021
if project_id == 'DEFAULT_PROJECT_ID' or not project_id:
2122
# Resolve it from the environment.
22-
raise Exception(
23+
raise ImproperlyConfigured(
2324
'Cannot yet resolve the project_id from the environment')
2425
else:
2526
return project_id
@@ -31,14 +32,14 @@ def parse_spanner_url(spanner_url):
3132
# Examples:
3233
# cloudspanner://spanner.googleapis.com/projects/test-project-012345/instances/test-instance/databases/dev-db
3334
if not spanner_url:
34-
raise Exception('expecting a non-blank spanner_url')
35+
raise ImproperlyConfigured('expecting a non-blank spanner_url')
3536

3637
o = urlparse(spanner_url or '')
3738
if o.scheme == '':
38-
raise Exception('expecting cloudspanner as the scheme')
39+
raise ImproperlyConfigured('expecting cloudspanner as the scheme')
3940

4041
if o.scheme != 'cloudspanner':
41-
raise Exception('invalid scheme {got}, expected {want}'.format(
42+
raise ImproperlyConfigured('invalid scheme {got}, expected {want}'.format(
4243
got=o.scheme, want='cloudspanner'))
4344

4445
properties = parse_properties(o.query)
@@ -83,17 +84,44 @@ def parse_properties(kv_pairs, sep=';'):
8384
def parse_projectid_instance_dbname(url_path):
8485
matches = rePROJECTID_INSTANCE_DBNAME.findall(url_path)
8586
if len(matches) != 1:
86-
raise Exception('%s does not match pattern %s' % (
87+
raise ImproperlyConfigured('%s does not match pattern %s' % (
8788
url_path, rePROJECTID_INSTANCE_DBNAME.pattern))
8889

8990
head = matches[0]
9091
g, w = len(head), rePROJECTID_INSTANCE_DBNAME.groups
9192
if g != w:
92-
raise Exception('unmatched groups, got %d want %d' % (g, w))
93+
raise ImproperlyConfigured('unmatched groups, got %d want %d' % (g, w))
9394

9495
project_id, instance, database = head
9596
return dict(
9697
project_id=project_id,
9798
instance=instance,
9899
database=database,
99100
)
101+
102+
103+
def extract_connection_params(settings_dict):
104+
# We'll expect settings in the form of either:
105+
# {
106+
# "NAME": "spanner",
107+
# "INSTANCE": "instance",
108+
# "AUTOCOMMIT": True or False,
109+
# "READ_ONLY": True or False,
110+
# "PROJECT_ID": "<project_id>",
111+
# }
112+
#
113+
# OR
114+
# {
115+
# "SPANNER_URL": "cloudspanner:[//host[:port]]/project/<project_id>/instances/<instance-id>/databases/<database-name>?property-name=property-value
116+
# }
117+
118+
if settings_dict['SPANNER_URL']:
119+
return parse_spanner_url(settings_dict['SPANNER_URL'])
120+
else:
121+
return dict(
122+
auto_commit=settings_dict['AUTO_COMMIT'],
123+
database=settings_dict['NAME'] or 'spanner',
124+
instance=settings_dict['INSTANCE'],
125+
project_id=resolve_project_id(settings_dict['PROJECT_ID']),
126+
read_only=settings_dict['READ_ONLY'],
127+
)

‎spanner-django/tests/spanner/django/test_parse_utils.py

Copy file name to clipboardExpand all lines: spanner-django/tests/spanner/django/test_parse_utils.py
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
from unittest import TestCase
1616

17+
from django.core.exceptions import ImproperlyConfigured
18+
1719
from spanner.django.parse_utils import parse_spanner_url
1820

1921

@@ -24,6 +26,7 @@ def test_no_args(self):
2426
got = parse_spanner_url(url)
2527
self.assertEqual(got, None)
2628

29+
self.assertTrue(isinstance(exc.exception, ImproperlyConfigured))
2730
self.assertEqual(*exc.exception.args, 'expecting a non-blank spanner_url')
2831

2932

@@ -34,6 +37,7 @@ def test_no_host(self):
3437
got = parse_spanner_url(url)
3538
self.assertEqual(got, None)
3639

40+
self.assertTrue(isinstance(exc.exception, ImproperlyConfigured))
3741
self.assertEqual(*exc.exception.args, 'expecting cloudspanner as the scheme')
3842

3943
def test_invalid_scheme(self):
@@ -43,6 +47,7 @@ def test_invalid_scheme(self):
4347
got = parse_spanner_url(url)
4448
self.assertEqual(got, None)
4549

50+
self.assertTrue(isinstance(exc.exception, ImproperlyConfigured))
4651
self.assertEqual(*exc.exception.args, 'invalid scheme foo, expected cloudspanner')
4752

4853

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.