FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Merge branch 'google:main' into main · google/python-spanner-orm@b06c91c · GitHub

Commit b06c91c

Browse files
authored
Merge branch 'google:main' into main
2 parents 4948110 + 2b6a9d8 commit b06c91c

3 files changed

Lines changed: 167 additions & 22 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
[![.github/workflows/test.yaml](https://github.com/google/python-spanner-orm/actions/workflows/test.yaml/badge.svg)](https://github.com/google/python-spanner-orm/actions/workflows/test.yaml)
2+
13
# Google Cloud Spanner ORM
24

35
This is a lightweight ORM written in Python and built on top of Cloud Spanner.

‎spanner_orm/admin/update.py‎

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@ def execute(self) -> None:
5555
self.validate()
5656
api.spanner_admin_api().update_schema(self.ddl())
5757

58-
@abc.abstractmethod
5958
def validate(self) -> None:
60-
raise NotImplementedError
59+
pass # TODO(dseomn): Remove this method.
6160

6261

6362
class CreateTable(SchemaUpdate):
@@ -137,25 +136,6 @@ def __init__(self, table_name: str):
137136
def ddl(self) -> str:
138137
return 'DROP TABLE {}'.format(self._table)
139138

140-
def validate(self) -> None:
141-
existing_model = metadata.SpannerMetadata.model(self._table)
142-
if not existing_model:
143-
raise error.SpannerError('Table {} does not exist'.format(self._table))
144-
145-
# Model indexes include the primary index
146-
if len(existing_model.indexes) > 1:
147-
raise error.SpannerError('Table {} has a secondary index'.format(
148-
self._table))
149-
150-
self._validate_not_interleaved(existing_model)
151-
152-
def _validate_not_interleaved(self,
153-
existing_model: Type[model.Model]) -> None:
154-
for model_ in metadata.SpannerMetadata.models().values():
155-
if model_.interleaved == existing_model:
156-
raise error.SpannerError('Table {} has interleaved table {}'.format(
157-
self._table, model_.table))
158-
159139

160140
class AddColumn(SchemaUpdate):
161141
"""Update for adding a column to an existing table.

‎spanner_orm/tests/migrations_emulator_test.py‎

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,23 @@
1515
import datetime
1616
import logging
1717
import os
18+
import textwrap
19+
from typing import Iterable, Type
1820
import unittest
1921

22+
from absl.testing import absltest
23+
from absl.testing import parameterized
2024
import spanner_orm
25+
from spanner_orm.admin import metadata
2126
from spanner_orm.tests import models
2227
from spanner_orm.testlib.spanner_emulator import testlib as spanner_emulator_testlib
2328

2429
from google.api_core import exceptions as google_api_exceptions
2530

2631

2732
class MigrationsEmulatorTest(spanner_emulator_testlib.TestCase):
33+
"""Basic tests using generic migrations."""
34+
2835
TEST_MIGRATIONS_DIR = os.path.join(
2936
os.path.dirname(os.path.abspath(__file__)),
3037
'migrations_for_emulator_test',
@@ -73,6 +80,162 @@ def test_key(self):
7380
}).save()
7481

7582

83+
class SpecificMigrationsEmulatorTest(
84+
parameterized.TestCase,
85+
spanner_emulator_testlib.TestCase,
86+
):
87+
"""Tests of specific migrations."""
88+
89+
def setUp(self):
90+
super().setUp()
91+
self._migrations_dir = self.create_tempdir()
92+
self._migration_index = None
93+
94+
def _append_migrations(self, *migrations: str) -> None:
95+
"""Appends migrations to the sequence of migrations in self._migrations_dir.
96+
97+
Args:
98+
*migrations: Each string is the python code to define a single upgrade()
99+
function. Leading indentation is stripped and migration boilerplate is
100+
added.
101+
"""
102+
for migration in migrations:
103+
if self._migration_index is None:
104+
prev_migration_id = None
105+
self._migration_index = 0
106+
else:
107+
prev_migration_id = str(self._migration_index)
108+
self._migration_index += 1
109+
migration_id = str(self._migration_index)
110+
self._migrations_dir.create_file(
111+
f'migration_{migration_id}.py',
112+
'\n'.join((
113+
'import spanner_orm',
114+
f'migration_id = {migration_id!r}',
115+
f'prev_migration_id = {prev_migration_id!r}',
116+
textwrap.dedent(migration),
117+
'def downgrade(): raise NotImplementedError()',
118+
)),
119+
)
120+
121+
def test_drop_interleaved_table(self):
122+
self._append_migrations(
123+
"""
124+
class _Parent(spanner_orm.Model):
125+
__table__ = 'Parent'
126+
parent_key = spanner_orm.Field(
127+
spanner_orm.String, primary_key=True)
128+
129+
def upgrade():
130+
return spanner_orm.CreateTable(_Parent)
131+
""",
132+
"""
133+
class _Parent(spanner_orm.Model):
134+
__table__ = 'Parent'
135+
parent_key = spanner_orm.Field(
136+
spanner_orm.String, primary_key=True)
137+
138+
class _Child(spanner_orm.Model):
139+
__table__ = 'Child'
140+
__interleaved__ = _Parent
141+
parent_key = spanner_orm.Field(
142+
spanner_orm.String, primary_key=True)
143+
child_key = spanner_orm.Field(
144+
spanner_orm.String, primary_key=True)
145+
146+
def upgrade():
147+
return spanner_orm.CreateTable(_Child)
148+
""",
149+
"""
150+
def upgrade():
151+
return spanner_orm.DropTable('Child')
152+
""",
153+
)
154+
self.run_orm_migrations(self._migrations_dir)
155+
self.assertCountEqual(
156+
('Parent',),
157+
metadata.SpannerMetadata.tables().keys() - {'spanner_orm_migrations'},
158+
)
159+
160+
@parameterized.named_parameters(
161+
dict(
162+
testcase_name='does_not_exist',
163+
create_migrations=(),
164+
error_class=google_api_exceptions.NotFound,
165+
),
166+
dict(
167+
testcase_name='has_secondary_index',
168+
create_migrations=(
169+
"""
170+
class _TableToDrop(spanner_orm.Model):
171+
__table__ = 'TableToDrop'
172+
key = spanner_orm.Field(
173+
spanner_orm.String, primary_key=True)
174+
value = spanner_orm.Field(spanner_orm.String)
175+
176+
def upgrade():
177+
return spanner_orm.CreateTable(_TableToDrop)
178+
""",
179+
"""
180+
def upgrade():
181+
return spanner_orm.CreateIndex(
182+
table_name='TableToDrop',
183+
index_name='value_index',
184+
columns=['value'],
185+
)
186+
""",
187+
),
188+
error_class=google_api_exceptions.FailedPrecondition,
189+
),
190+
dict(
191+
testcase_name='has_interleaved_child',
192+
create_migrations=(
193+
"""
194+
class _TableToDrop(spanner_orm.Model):
195+
__table__ = 'TableToDrop'
196+
parent_key = spanner_orm.Field(
197+
spanner_orm.String, primary_key=True)
198+
199+
def upgrade():
200+
return spanner_orm.CreateTable(_TableToDrop)
201+
""",
202+
"""
203+
class _TableToDrop(spanner_orm.Model):
204+
__table__ = 'TableToDrop'
205+
parent_key = spanner_orm.Field(
206+
spanner_orm.String, primary_key=True)
207+
208+
class _Child(spanner_orm.Model):
209+
__table__ = 'Child'
210+
__interleaved__ = _TableToDrop
211+
parent_key = spanner_orm.Field(
212+
spanner_orm.String, primary_key=True)
213+
child_key = spanner_orm.Field(
214+
spanner_orm.String, primary_key=True)
215+
216+
def upgrade():
217+
return spanner_orm.CreateTable(_Child)
218+
""",
219+
),
220+
error_class=google_api_exceptions.FailedPrecondition,
221+
),
222+
)
223+
def test_drop_table_error(
224+
self,
225+
*,
226+
create_migrations: Iterable[str],
227+
error_class: Type[Exception],
228+
):
229+
self._append_migrations(*create_migrations)
230+
self.run_orm_migrations(self._migrations_dir)
231+
self._append_migrations("""
232+
def upgrade():
233+
return spanner_orm.DropTable('TableToDrop')
234+
""")
235+
with self.assertRaises(error_class):
236+
self.run_orm_migrations(self._migrations_dir)
237+
238+
76239
if __name__ == '__main__':
77240
logging.basicConfig()
78-
unittest.main()
241+
absltest.main()

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL