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

Add migration object for stronger typing · 7dracoder/python-spanner-orm@e73e243 · GitHub

Commit e73e243

Browse files
committed
Add migration object for stronger typing
Fixing the TODO from google#55. In the process, I noticed that the migration skeleton didn't work properly, so I fixed the imports and the return value there.
1 parent 58543a9 commit e73e243

9 files changed

Lines changed: 176 additions & 123 deletions

File tree

‎spanner_orm/__init__.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from spanner_orm import model
2424
from spanner_orm import relationship
2525
from spanner_orm.admin import api as admin_api
26+
from spanner_orm.admin import update
2627

2728
# add NullHandler to root-module logger so that individual modules
2829
# won't have to.
@@ -62,3 +63,13 @@
6263

6364
transactional_read = decorator.transactional_read
6465
transactional_write = decorator.transactional_write
66+
67+
CreateTable = update.CreateTable
68+
DropTable = update.DropTable
69+
AddColumn = update.AddColumn
70+
DropColumn = update.DropColumn
71+
AlterColumn = update.AlterColumn
72+
CreateIndex = update.CreateIndex
73+
DropIndex = update.DropIndex
74+
NoUpdate = update.NoUpdate
75+
model_creation_ddl = update.model_creation_ddl

‎spanner_orm/admin/migration.py‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# python3
2+
# Copyright 2019 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Holds information about a specific migration."""
16+
17+
from __future__ import annotations
18+
19+
from typing import Callable, Optional
20+
21+
from spanner_orm.admin import update
22+
23+
24+
def no_update_callable() -> update.SchemaUpdate:
25+
return update.NoUpdate()
26+
27+
28+
class Migration:
29+
"""Holds information about a specific migration."""
30+
31+
def __init__(self,
32+
migration_id: str,
33+
prev_migration_id: Optional[str],
34+
upgrade: Optional[Callable[[], update.SchemaUpdate]] = None,
35+
downgrade: Optional[Callable[[], update.SchemaUpdate]] = None):
36+
self._id = migration_id
37+
self._prev = prev_migration_id
38+
self._upgrade = upgrade or no_update_callable
39+
self._downgrade = downgrade or no_update_callable
40+
41+
@property
42+
def migration_id(self) -> str:
43+
return self._id
44+
45+
@property
46+
def prev_migration_id(self) -> Optional[str]:
47+
return self._prev
48+
49+
@property
50+
def upgrade(self) -> Optional[Callable[[], update.SchemaUpdate]]:
51+
return self._upgrade
52+
53+
@property
54+
def downgrade(self) -> Optional[Callable[[], update.SchemaUpdate]]:
55+
return self._downgrade

‎spanner_orm/admin/migration.skel‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ Migration ID: $migration_id
44
Created: $current_date
55
"""
66

7+
import spanner_orm
8+
79
migration_id = $migration_id
810
prev_migration_id = $prev_migration_id
911

10-
from spanner_orm.admin import update
11-
1212
# Returns a SchemaUpdate object that tells what should be changed
13-
def upgrade():
14-
pass
13+
def upgrade() -> spanner_orm.NoUpdate:
14+
return spanner_orm.NoUpdate()
1515

1616
# Returns a SchemaUpdate object that tells how to roll back the changes
17-
def downgrade():
18-
pass
17+
def downgrade() -> spanner_orm.NoUpdate:
18+
return spanner_orm.NoUpdate()

‎spanner_orm/admin/migration_executor.py‎

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818

1919
import datetime
2020
import logging
21-
from typing import Any, Dict, Optional
21+
from typing import Iterable, List, Dict, Optional
2222

2323
from spanner_orm import api
2424
from spanner_orm import error
2525
from spanner_orm.admin import api as admin_api
2626
from spanner_orm.admin import metadata
27+
from spanner_orm.admin import migration
2728
from spanner_orm.admin import migration_manager
2829
from spanner_orm.admin import migration_status
2930
from spanner_orm.admin import update
@@ -54,8 +55,7 @@ def migrated(self, migration_id: str) -> bool:
5455
return True
5556
return self._migration_status().get(migration_id, False)
5657

57-
# TODO(dbrandao): make a Migration object so this is no longer Any
58-
def migrations(self) -> Any:
58+
def migrations(self) -> List[migration.Migration]:
5959
return self._manager.migrations
6060

6161
def migrate(self, target_migration: Optional[str] = None) -> None:
@@ -74,16 +74,16 @@ def migrate(self, target_migration: Optional[str] = None) -> None:
7474
# Filter to unmigrated migrations
7575
migrations = self._filter_migrations(self.migrations(), False,
7676
target_migration)
77-
for migration in migrations:
78-
_logger.info('Processing migration %s', migration.migration_id)
79-
schema_update = migration.upgrade()
77+
for migration_ in migrations:
78+
_logger.info('Processing migration %s', migration_.migration_id)
79+
schema_update = migration_.upgrade()
8080
if not isinstance(schema_update, update.SchemaUpdate):
8181
raise error.SpannerError(
8282
'Migration {} did not return a SchemaUpdate'.format(
83-
migration.migration_id))
83+
migration_.migration_id))
8484
schema_update.execute()
8585

86-
self._update_status(migration.migration_id, True)
86+
self._update_status(migration_.migration_id, True)
8787
self._hangup()
8888

8989
def rollback(self, target_migration: str) -> None:
@@ -105,16 +105,16 @@ def rollback(self, target_migration: str) -> None:
105105
# Filter to migrated migrations from most recently applied
106106
migrations = self._filter_migrations(
107107
reversed(self.migrations()), True, target_migration)
108-
for migration in migrations:
109-
_logger.info('Processing migration %s', migration.migration_id)
110-
schema_update = migration.downgrade()
108+
for migration_ in migrations:
109+
_logger.info('Processing migration %s', migration_.migration_id)
110+
schema_update = migration_.downgrade()
111111
if not isinstance(schema_update, update.SchemaUpdate):
112112
raise error.SpannerError(
113113
'Migration {} did not return a SchemaUpdate'.format(
114-
migration.migration_id))
114+
migration_.migration_id))
115115
schema_update.execute()
116116

117-
self._update_status(migration.migration_id, False)
117+
self._update_status(migration_.migration_id, False)
118118
self._hangup()
119119

120120
def _connect(self) -> None:
@@ -133,8 +133,9 @@ def _hangup(self) -> None:
133133
admin_api.SpannerAdminApi.hangup()
134134
api.SpannerApi.hangup()
135135

136-
def _filter_migrations(self, migrations: Any, migrated: bool,
137-
last_migration: Optional[str]) -> Any:
136+
def _filter_migrations(
137+
self, migrations: Iterable[migration.Migration], migrated: bool,
138+
last_migration: Optional[str]) -> List[migration.Migration]:
138139
"""Filters the list of migrations according to the desired conditions.
139140
140141
Args:
@@ -147,11 +148,11 @@ def _filter_migrations(self, migrations: Any, migrated: bool,
147148
"""
148149
filtered = []
149150
last_migration_found = False
150-
for migration in migrations:
151-
if self.migrated(migration.migration_id) == migrated:
152-
filtered.append(migration)
151+
for migration_ in migrations:
152+
if self.migrated(migration_.migration_id) == migrated:
153+
filtered.append(migration_)
153154

154-
if last_migration and migration.migration_id == last_migration:
155+
if last_migration and migration_.migration_id == last_migration:
155156
last_migration_found = True
156157
break
157158

@@ -169,8 +170,8 @@ def _migration_status(self) -> Dict[str, bool]:
169170
if not model_from_db:
170171
update.CreateTable(migration_status.MigrationStatus).execute()
171172
self._migration_status_map = {
172-
migration.id: migration.migrated
173-
for migration in migration_status.MigrationStatus.all()
173+
migration_.id: migration_.migrated
174+
for migration_ in migration_status.MigrationStatus.all()
174175
}
175176

176177
return self._migration_status_map
@@ -198,9 +199,9 @@ def _validate_migrations(self) -> None:
198199
'First migration {} depends on unmigrated migration {}'.format(
199200
first.migration_id, first.prev_migration_id))
200201

201-
for migration in migrations:
202-
if (self.migrated(migration.migration_id) and
203-
not self.migrated(migration.prev_migration_id)):
202+
for migration_ in migrations:
203+
if (self.migrated(migration_.migration_id) and
204+
not self.migrated(migration_.prev_migration_id)):
204205
raise error.SpannerError(
205206
'Migrated migration {} depends on an unmigrated migration'.format(
206-
migration.migration_id))
207+
migration_.migration_id))

‎spanner_orm/admin/migration_manager.py‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616

1717
from __future__ import annotations
1818

19-
2019
import datetime
2120
import importlib
2221
import os
2322
import re
2423
import string
25-
from typing import Any, Iterable, List, Optional
24+
from typing import Iterable, List, Optional
2625
import uuid
2726

2827
from spanner_orm import error
28+
from spanner_orm.admin import migration
2929

3030

3131
class MigrationManager:
@@ -63,25 +63,30 @@ def generate(self, migration_name: str) -> str:
6363
return filepath
6464

6565
@property
66-
def migrations(self) -> Any:
66+
def migrations(self) -> List[migration.Migration]:
6767
"""Loads and orders all migrations in the base dir."""
6868
if self._migrations is None:
6969
unordered_migrations = self._all_migrations()
7070
self._migrations = self._order_migrations(unordered_migrations)
7171
return self._migrations
7272

73-
def _migration_from_file(self, filename: str) -> Any:
73+
def _migration_from_file(self, filename: str) -> migration.Migration:
7474
"""Loads a single migration from the given filename in the base dir."""
7575
module_name = re.sub(r'\W', '_', filename)
7676
path = os.path.join(self.basedir, filename)
7777
spec = importlib.util.spec_from_file_location(module_name, path)
7878
module = importlib.util.module_from_spec(spec)
7979
spec.loader.exec_module(module)
80-
if not hasattr(module, 'migration_id'):
80+
try:
81+
result = migration.Migration(module.migration_id,
82+
module.prev_migration_id,
83+
getattr(module, 'upgrade', None),
84+
getattr(module, 'downgrade', None))
85+
except AttributeError:
8186
raise error.SpannerError('{} has no migration id'.format(path))
82-
return module
87+
return result
8388

84-
def _all_migrations(self) -> List[Any]:
89+
def _all_migrations(self) -> List[migration.Migration]:
8590
"""Loads all migrations from the base dir."""
8691
migrations = []
8792
for filename in os.listdir(self.basedir):
@@ -90,16 +95,17 @@ def _all_migrations(self) -> List[Any]:
9095
migrations.append(self._migration_from_file(filename))
9196
return migrations
9297

93-
def _order_migrations(self, migrations: Iterable[Any]) -> List[Any]:
98+
def _order_migrations(self, migrations: Iterable[migration.Migration]
99+
) -> List[migration.Migration]:
94100
"""Returns list of migrations in the order they have to be applied."""
95101
if not migrations:
96102
return []
97103

98-
id_map = {migration.migration_id: migration for migration in migrations}
104+
id_map = {migration_.migration_id: migration_ for migration_ in migrations}
99105
start_migration = None
100-
for migration_id, migration in id_map.items():
101-
if migration.prev_migration_id and migration.prev_migration_id in id_map:
102-
current = id_map[migration.prev_migration_id]
106+
for migration_id, migration_ in id_map.items():
107+
if migration_.prev_migration_id and migration_.prev_migration_id in id_map:
108+
current = id_map[migration_.prev_migration_id]
103109
if hasattr(current, 'next'):
104110
raise error.SpannerError(
105111
'{name} has unclear successor migration'.format(

‎spanner_orm/tests/migrations/test_1_4a7a7dee0718.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@
1818
Created: 2019-02-27 18:52
1919
"""
2020

21+
import spanner_orm
22+
2123
migration_id = '4a7a7dee0718'
2224
prev_migration_id = None
2325

2426

2527
# Returns a SchemaUpdate object that tells what should be changed
26-
def upgrade():
27-
pass
28+
def upgrade() -> spanner_orm.NoUpdate:
29+
return spanner_orm.NoUpdate()
2830

2931

3032
# Returns a SchemaUpdate object that tells how to roll back the changes
31-
def downgrade():
32-
pass
33+
def downgrade() -> spanner_orm.NoUpdate:
34+
return spanner_orm.NoUpdate()

‎spanner_orm/tests/migrations/test_2_5c078bbb4d43.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@
1818
Created: 2019-02-27 18:52
1919
"""
2020

21+
import spanner_orm
22+
2123
migration_id = '5c078bbb4d43'
2224
prev_migration_id = '4a7a7dee0718'
2325

2426

2527
# Returns a SchemaUpdate object that tells what should be changed
26-
def upgrade():
27-
pass
28+
def upgrade() -> spanner_orm.NoUpdate:
29+
return spanner_orm.NoUpdate()
2830

2931

3032
# Returns a SchemaUpdate object that tells how to roll back the changes
31-
def downgrade():
32-
pass
33+
def downgrade() -> spanner_orm.NoUpdate:
34+
return spanner_orm.NoUpdate()

‎spanner_orm/tests/migrations/test_3_eceb25f170dd.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@
1818
Created: 2019-02-27 18:52
1919
"""
2020

21+
import spanner_orm
22+
2123
migration_id = 'eceb25f170dd'
2224
prev_migration_id = '5c078bbb4d43'
2325

2426

2527
# Returns a SchemaUpdate object that tells what should be changed
26-
def upgrade():
27-
pass
28+
def upgrade() -> spanner_orm.NoUpdate:
29+
return spanner_orm.NoUpdate()
2830

2931

3032
# Returns a SchemaUpdate object that tells how to roll back the changes
31-
def downgrade():
32-
pass
33+
def downgrade() -> spanner_orm.NoUpdate:
34+
return spanner_orm.NoUpdate()

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL