Skip to content

Commit 6c7edda

Browse files
committed
New feature: Rollback the last migration
1 parent 1ecf3b0 commit 6c7edda

10 files changed

+180
-30
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## 0.5.2.
4+
5+
### Feature
6+
7+
- Rollback the last migration
8+
9+
### Documentation
10+
11+
- README.md
12+
- Modified sections
13+
- Getting Started
14+
- Rolling Back Migrations
15+
16+
317
## 0.5.1
418

519
### Documentation

README.md

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
# python-bigquery-migrations
22

3-
Python bigquery-migrations package is for creating and manipulating BigQuery databases easily.
3+
The `python-bigquery-migrations` package provides a streamlined way to create and manage BigQuery databases using intuitive CLI commands, such as the following:
4+
5+
```bash
6+
bigquery-migrations run
7+
```
8+
9+
**What are the benefits of using migrations?**
410

511
Migrations are like version control for your database, allowing you to define and share the application's datasets and table schema definitions.
612

7-
## Prerequisite
13+
## Getting Started
14+
15+
## 0. Prerequisite
816

917
- Google Cloud Project with enabled billing
1018
- Enabled Google Cloud BigQuery API
1119
- Google Cloud Service Account JSON file
1220

13-
## Getting Started
14-
15-
## Install
21+
## 1. Install
1622
```
1723
pip install bigquery-migrations
1824
```
1925

20-
## Create the project folder structure
26+
## 2. Create the project folder structure
2127

2228
Create two subdirectory:
2329
1. credentials
@@ -30,10 +36,45 @@ your-project-root-folder
3036
└── ...
3137
```
3238

33-
## Create the neccessary files in the folders
39+
## 3. Create the neccessary files in the folders
40+
41+
### Google Cloud Service Account JSON file
3442

3543
Put your Google Cloud Service Account JSON file in the credentials subdirectory. See more info in the [Authorize BigQuery Client section](#authorize-bigquery-client)
3644

45+
```
46+
your-project
47+
├── credentials
48+
│ ├── gcp-sa.json
49+
├── migrations
50+
└── ...
51+
```
52+
53+
You can use different folder name and file name but in that case you must specify them with command arguments, such as the following:
54+
55+
```bash
56+
bigquery-migrations run --gcp-sa-json-dir my-creds --gcp-sa-json-fname my-service-account.json
57+
```
58+
59+
|argument |description |
60+
|---------------------|-----------------------------------------------------------|
61+
|--gcp-sa-json-dir |Name of the service account JSON file directory (optional) |
62+
|--gcp-sa-json-fname |Name of the service account JSON file (optional) |
63+
64+
> **IMPORTANT!**
65+
> Never check the Google Cloud Service Account JSON file into version control. This file contains sensitive credentials that could compromise your Google Cloud account if exposed.
66+
67+
To prevent accidental commits, make sure to add the file to your .gitignore configuration. For example:
68+
69+
```bash
70+
# .gitignore
71+
gcp-sa.json
72+
```
73+
74+
By ignoring this file, you reduce the risk of unintentional leaks and maintain secure practices in your repository.
75+
76+
### Migrations
77+
3778
Create your own migrations and put them in the migrations directory. See the [Migration structure section](#migration-structure) and [Migration naming conventions section](#migration-naming-conventions) for more info.
3879

3980
```
@@ -45,12 +86,14 @@ your-project
4586
└── ...
4687
```
4788

48-
You can use different folder names but in that case you must specify them with command arguments:
89+
You can use different folder name but in that case you must specify it with a command argument:
90+
91+
```bash
92+
bigquery-migrations run --migrations-dir bq-migrations
93+
```
4994

5095
|argument |description |
5196
|---------------------|-----------------------------------------------------------|
52-
|--gcp-sa-json-dir |Name of the service account JSON file directory (optional) |
53-
|--gcp-sa-json-fname |Name of the service account JSON file (optional) |
5497
|--migrations-dir |Name of the migrations directory (optional) |
5598

5699

@@ -97,19 +140,31 @@ The migration_log.json file content should look like this:
97140

98141
## Rolling Back Migrations
99142

143+
### Rollback the last migration
144+
145+
To reverse the last migration, execute the `rollback` command and pass `last` with the `--migration-name` argument:
146+
147+
```bash
148+
bigquery-migrations rollback --migration-name last
149+
```
150+
151+
### Rollback a specific migration
152+
100153
To reverse a specific migration, execute the `rollback` command and pass the migration name with the `--migration-name` argument:
101154

102155
```bash
103156
bigquery-migrations rollback --migration-name 2024_12_10_121000_create_users_table
104157
```
105158

159+
### Rollback all migrations
160+
106161
To reverse all of your migrations, execute the `reset` command:
107162

108163
```bash
109164
bigquery-migrations reset
110165
```
111166

112-
### Authorize BigQuery Client
167+
## Authorize BigQuery Client
113168

114169
Put your service account JSON file in the credentials subdirectory in the root of your project.
115170

@@ -120,7 +175,7 @@ your-project
120175
...
121176
```
122177

123-
#### Creating a Service Account for Google BigQuery
178+
### Creating a Service Account for Google BigQuery
124179

125180
You can connect to BigQuery with a user account or a service account. A service account is a special kind of account designed to be used by applications or compute workloads, rather than a person. Service accounts don’t have passwords and use a unique email address for identification.
126181

justfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ migrations-list:
3838
migrations-run:
3939
python3 -m src.bigquery_migrations.migration_cli run
4040

41+
# Rollback migration
42+
migrations-rollback-last:
43+
python3 -m src.bigquery_migrations.migration_cli rollback --migration-name last
44+
4145
# Rollback migrations
42-
migrations-rollback:
46+
migrations-rollback-specified:
4347
python3 -m src.bigquery_migrations.migration_cli rollback --migration-name 2024_12_10_121000_create_users_table
4448

4549
# Rollback all migrations

src/bigquery_migrations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
__all__ = [
1111
"Migration"
1212
]
13-
__version__ = "0.5.1"
13+
__version__ = "0.5.2"

src/bigquery_migrations/migration_cli.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ def run_migrations(migrator: MigrationManager):
3232
except Exception as e:
3333
print(f"Error during migration: {e}")
3434

35+
def rollback_last_migration(migrator: MigrationManager):
36+
try:
37+
migrator.rollback_last()
38+
print("Rollback process completed successfully.")
39+
except Exception as e:
40+
print(f"Error during rollback: {e}")
41+
return
3542

3643
def rollback_migration(migrator: MigrationManager, migration_name: str):
3744
try:
@@ -53,27 +60,38 @@ def reset_migrations(migrator: MigrationManager):
5360
def main():
5461
parser = argparse.ArgumentParser(description="Perform BigQuery migrations")
5562
parser.add_argument(
56-
'command', choices=['list', 'run', 'rollback', 'reset'],
63+
'command',
64+
choices=['list', 'run', 'rollback', 'reset'],
65+
type=str,
5766
help="Choose the operation to perform: list, run, rollback, reset"
5867
)
5968
parser.add_argument(
6069
'--gcp-sa-json-dir',
70+
type=str,
71+
required=False,
6172
help="Name of the service account JSON file directory (optional)"
6273
)
6374
parser.add_argument(
6475
'--gcp-sa-json-fname',
76+
type=str,
77+
required=False,
6578
help="Name of the service account JSON file (optional)"
6679
)
6780
parser.add_argument(
6881
'--migrations-dir',
82+
type=str,
83+
required=False,
6984
help="Name of the migrations directory (optional)"
7085
)
7186
parser.add_argument(
7287
'--gcp-project-id',
88+
type=str,
89+
required=False,
7390
help="Specify the Google Cloud Project ID (optional)"
7491
)
7592
parser.add_argument(
7693
'--migration-name',
94+
type=str,
7795
help="Specify the name of the migration to rollback eg. 2024_01_02_120000_create_users_example (required for rollback command)"
7896
)
7997

@@ -93,7 +111,10 @@ def main():
93111
elif args.command == 'rollback':
94112
if not args.migration_name:
95113
parser.error("The --migration-name argument is required for the rollback command.")
96-
rollback_migration(migrator, args.migration_name)
114+
if args.migration_name == 'last':
115+
rollback_last_migration(migrator)
116+
else:
117+
rollback_migration(migrator, args.migration_name)
97118
elif args.command == 'reset':
98119
reset_migrations(migrator)
99120

src/bigquery_migrations/migration_manager.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ def rollback(self, migration_name: str) -> Tuple[str, Optional[str]]:
105105
migration_name = None
106106
finally:
107107
return migration_name, prev_migration
108+
109+
def rollback_last(self):
110+
"""Rollback the last applied migration"""
111+
last_migration, last_timestamp = self.get_last_migration()
112+
if not last_migration:
113+
print(Fore.CYAN + "No migrations have been applied yet.")
114+
return
115+
return self.rollback(last_migration)
116+
108117

109118
def reset(self) -> list[Optional[str]]:
110119
"""
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from google.cloud import bigquery
2+
from src.bigquery_migrations.migration import Migration
3+
4+
class CreateAnotherTableExample(Migration):
5+
"""
6+
See:
7+
https://github.com/googleapis/python-bigquery/tree/main/samples
8+
"""
9+
10+
def up(self):
11+
# TODO: Set table_id to the ID of the table to create.
12+
# table_id = "your_project.your_dataset.example_table"
13+
14+
# TODO: Define table schema
15+
'''
16+
schema = [
17+
bigquery.SchemaField("id", "INTEGER", mode="REQUIRED"),
18+
bigquery.SchemaField("name", "STRING", mode="REQUIRED"),
19+
bigquery.SchemaField("created_at", "TIMESTAMP", mode="NULLABLE"),
20+
]
21+
'''
22+
23+
# table = bigquery.Table(table_id, schema=schema)
24+
# table = self.client.create_table(table)
25+
class_name = self.__class__.__name__
26+
print(f"Class: {class_name}, Method: up")
27+
28+
def down(self):
29+
# TODO: Set table_id to the ID of the table to fetch.
30+
# table_id = "your_project.your_dataset.example_table"
31+
32+
# If the table does not exist, delete_table raises
33+
# google.api_core.exceptions.NotFound unless not_found_ok is True.
34+
# self.client.delete_table(table_id, not_found_ok=True)
35+
class_name = self.__class__.__name__
36+
print(f"Class: {class_name}, Method: down")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"last_migration": null, "timestamp": "2024-12-18T12:34:49.269255+00:00"}
1+
{"last_migration": null, "timestamp": "2024-12-23T16:12:39.282297+00:00"}

tests/unit/test_migration_dir_handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ def test_filename_list(self):
2323
expected_value = [
2424
"2024_01_01_120000_create_dataset_example",
2525
"2024_01_02_120000_create_table_example",
26-
"2024_01_03_120000_create_table_from_json_schema_example"
26+
"2024_01_03_120000_create_table_from_json_schema_example",
27+
"2024_01_04_120000_create_another_table_example"
2728
]
2829
current_value = under_test.filename_list()
2930
self.assertEqual(expected_value, current_value)

0 commit comments

Comments
 (0)