limber-timber 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- limber_timber-0.0.1/LICENSE +21 -0
- limber_timber-0.0.1/PKG-INFO +318 -0
- limber_timber-0.0.1/README.md +291 -0
- limber_timber-0.0.1/pyproject.toml +40 -0
- limber_timber-0.0.1/src/liti/__init__.py +0 -0
- limber_timber-0.0.1/src/liti/cli.py +158 -0
- limber_timber-0.0.1/src/liti/core/__init__.py +0 -0
- limber_timber-0.0.1/src/liti/core/backend/__init__.py +1 -0
- limber_timber-0.0.1/src/liti/core/backend/base.py +125 -0
- limber_timber-0.0.1/src/liti/core/backend/bigquery.py +781 -0
- limber_timber-0.0.1/src/liti/core/backend/memory.py +95 -0
- limber_timber-0.0.1/src/liti/core/base.py +118 -0
- limber_timber-0.0.1/src/liti/core/client/__init__.py +0 -0
- limber_timber-0.0.1/src/liti/core/client/bigquery.py +73 -0
- limber_timber-0.0.1/src/liti/core/function.py +66 -0
- limber_timber-0.0.1/src/liti/core/logger.py +72 -0
- limber_timber-0.0.1/src/liti/core/model/__init__.py +4 -0
- limber_timber-0.0.1/src/liti/core/model/v1/__init__.py +0 -0
- limber_timber-0.0.1/src/liti/core/model/v1/datatype.py +264 -0
- limber_timber-0.0.1/src/liti/core/model/v1/operation/__init__.py +1 -0
- limber_timber-0.0.1/src/liti/core/model/v1/operation/data/__init__.py +1 -0
- limber_timber-0.0.1/src/liti/core/model/v1/operation/data/base.py +22 -0
- limber_timber-0.0.1/src/liti/core/model/v1/operation/data/table.py +107 -0
- limber_timber-0.0.1/src/liti/core/model/v1/operation/ops/__init__.py +4 -0
- limber_timber-0.0.1/src/liti/core/model/v1/operation/ops/base.py +47 -0
- limber_timber-0.0.1/src/liti/core/model/v1/operation/ops/table.py +272 -0
- limber_timber-0.0.1/src/liti/core/model/v1/schema.py +274 -0
- limber_timber-0.0.1/src/liti/core/runner.py +147 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daniel Tashjian
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: limber-timber
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Database Migrations Made Easy
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Daniel Tashjian
|
|
7
|
+
Author-email: thewopple@gmail.com
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Framework :: Pydantic :: 2
|
|
11
|
+
Classifier: Framework :: Pytest
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Dist: devtools (==0.12.2)
|
|
20
|
+
Requires-Dist: google-cloud-bigquery (==3.34.0)
|
|
21
|
+
Requires-Dist: pydantic (==2.11.4)
|
|
22
|
+
Requires-Dist: pyyaml (==6.0.2)
|
|
23
|
+
Project-URL: Homepage, https://github.com/Wopple/limber-timber
|
|
24
|
+
Project-URL: Repository, https://github.com/Wopple/limber-timber
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
Limber Timber
|
|
28
|
+
=============
|
|
29
|
+
|
|
30
|
+
***Database Migrations Made Easy***
|
|
31
|
+
|
|
32
|
+
## Overview
|
|
33
|
+
|
|
34
|
+
This project is not ready for production use, consider it v0.0.0.
|
|
35
|
+
|
|
36
|
+
I am writing the migration system I always wanted but does not exist (yet).
|
|
37
|
+
|
|
38
|
+
## Notable Feature Goals
|
|
39
|
+
|
|
40
|
+
- Migrations specified in data, not SQL
|
|
41
|
+
- Down migrations automatically inferred from up migrations
|
|
42
|
+
- Yes, down migrations for drop table and drop column are automatically inferred
|
|
43
|
+
- Separation of database and metadata
|
|
44
|
+
- In-memory support
|
|
45
|
+
- Database adoption
|
|
46
|
+
- No checksums
|
|
47
|
+
- Manifest instead of numbered or timestamped migration filenames
|
|
48
|
+
- JSON schema for migration files
|
|
49
|
+
- Error recovery for backends that do not support DDL transactions
|
|
50
|
+
|
|
51
|
+
## Not Goals
|
|
52
|
+
|
|
53
|
+
- ORM
|
|
54
|
+
- Parsing SQL
|
|
55
|
+
- Specifying all kinds of migrations in pure data (e.g. DML migrations will use SQL)
|
|
56
|
+
- Preserving lost data
|
|
57
|
+
|
|
58
|
+
## Rationale
|
|
59
|
+
|
|
60
|
+
- It is cumbersome to iterate on migrations without robust down migrations
|
|
61
|
+
- Automatically inferred down migrations reduces developer burden
|
|
62
|
+
- Writing migrations in data is cleaner and not specific to a database
|
|
63
|
+
- Never parsing SQL reduces the complexity of the codebase
|
|
64
|
+
- A lightweight open source library makes it each to add missing features
|
|
65
|
+
- JSON schema allows IDEs to be configured for migration file validation and auto-completion
|
|
66
|
+
- Separation of database and metadata allows for more flexible metadata storage options
|
|
67
|
+
- In-memory database and metadata allow for application unit testing
|
|
68
|
+
- No checksums allows for modifying migration files without breaking the migrations
|
|
69
|
+
- Manifest files cause git merge conflicts when parallel development has collisions
|
|
70
|
+
|
|
71
|
+
## Roadmap
|
|
72
|
+
|
|
73
|
+
These are listed in rough priority order if you are interested in contributing.
|
|
74
|
+
|
|
75
|
+
- ✅ CLI
|
|
76
|
+
- ➡️ Publish to PyPI
|
|
77
|
+
- ➡️ Github Actions
|
|
78
|
+
- ➡️ Unit Tests
|
|
79
|
+
- ➡️ Release
|
|
80
|
+
- ✅ In-memory Database
|
|
81
|
+
- ✅ In-memory Metadata
|
|
82
|
+
- ➡️ Big Query Database
|
|
83
|
+
- ✅ Create Table
|
|
84
|
+
- ✅ Drop Table
|
|
85
|
+
- ✅ Rename Table
|
|
86
|
+
- ✅ Set Table Partition Expiration
|
|
87
|
+
- ✅ Set Table Clustering
|
|
88
|
+
- ✅ Add Column
|
|
89
|
+
- ✅ Drop Column
|
|
90
|
+
- ✅ Rename Column
|
|
91
|
+
- ➡️ Alter Column
|
|
92
|
+
- ➡️ Create View
|
|
93
|
+
- ➡️ Create Materialized View
|
|
94
|
+
- ➡️ Create Snapshot Table
|
|
95
|
+
- ➡️ Create Table Clone
|
|
96
|
+
- ➡️ Labels
|
|
97
|
+
- ➡️ Options
|
|
98
|
+
- ✅ Big Query Metadata
|
|
99
|
+
- ➡️ Database Adoption
|
|
100
|
+
- ➡️ JSON Schema
|
|
101
|
+
- ✅ Database Specific Validation
|
|
102
|
+
- ➡️ Templating
|
|
103
|
+
- ➡️ Expand Grouped Operations
|
|
104
|
+
- ➡️ Grouped Operation Application
|
|
105
|
+
- ➡️ Minimize Scan Output
|
|
106
|
+
- ➡️ Arbitrary DML SQL Migrations
|
|
107
|
+
- ➡️ File System Metadata
|
|
108
|
+
- ➡️ SQLite Database
|
|
109
|
+
- ➡️ SQLite Metadata
|
|
110
|
+
- ➡️ Postgres Database
|
|
111
|
+
- ➡️ Postgres Metadata
|
|
112
|
+
- ➡️ MySQL Database
|
|
113
|
+
- ➡️ MySQL Metadata
|
|
114
|
+
|
|
115
|
+
## Usage
|
|
116
|
+
|
|
117
|
+
### Create Migrations
|
|
118
|
+
|
|
119
|
+
1. Create your target manifest
|
|
120
|
+
|
|
121
|
+
> Note: All migration files can use any of these extensions:
|
|
122
|
+
> - `.json`
|
|
123
|
+
> - `.yaml`
|
|
124
|
+
> - `.yml`
|
|
125
|
+
|
|
126
|
+
Create a target directory with a manifest file named `manifest.yaml`.
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
# target_dir/manifest.yaml
|
|
130
|
+
version: 1
|
|
131
|
+
operation_files:
|
|
132
|
+
- path/to/create_user_table.yaml
|
|
133
|
+
- path/to/enrich_user_name.json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
2. Create your target migration operations
|
|
137
|
+
|
|
138
|
+
> Tip: Using a subdirectory for the operations files makes it easy to configure your IDE to apply the correct JSON schema.
|
|
139
|
+
|
|
140
|
+
Create the files listed in your manifest.
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
# target_dir/path/to/create_user_table.yaml
|
|
144
|
+
version: 1
|
|
145
|
+
operations:
|
|
146
|
+
- kind: create_table
|
|
147
|
+
data:
|
|
148
|
+
table:
|
|
149
|
+
name:
|
|
150
|
+
database: your_project
|
|
151
|
+
schema_name: your_dataset
|
|
152
|
+
table_name: users
|
|
153
|
+
columns:
|
|
154
|
+
- name: id
|
|
155
|
+
data_type: INT64
|
|
156
|
+
- name: name
|
|
157
|
+
data_type: STRING
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
# target_dir/path/to/enrich_user_name.yaml
|
|
162
|
+
version: 1
|
|
163
|
+
operations:
|
|
164
|
+
- kind: rename_column
|
|
165
|
+
data:
|
|
166
|
+
table_name:
|
|
167
|
+
database: your_project
|
|
168
|
+
schema_name: your_dataset
|
|
169
|
+
table_name: users
|
|
170
|
+
from_name: name
|
|
171
|
+
to_name: firstname
|
|
172
|
+
- kind: add_column
|
|
173
|
+
data:
|
|
174
|
+
table_name:
|
|
175
|
+
database: your_project
|
|
176
|
+
schema_name: your_dataset
|
|
177
|
+
table_name: users
|
|
178
|
+
column:
|
|
179
|
+
name: lastname
|
|
180
|
+
data_type: STRING
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
3. Check what migrations will run
|
|
184
|
+
|
|
185
|
+
```shell
|
|
186
|
+
poetry run liti migrate \
|
|
187
|
+
-t target_dir \
|
|
188
|
+
--db bigquery \
|
|
189
|
+
--meta bigquery \
|
|
190
|
+
--meta-table-name your_project.your_dataset._migrations
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
4. Run the migrations
|
|
194
|
+
|
|
195
|
+
```shell
|
|
196
|
+
poetry run liti migrate -w \
|
|
197
|
+
-t target_dir \
|
|
198
|
+
--db bigquery \
|
|
199
|
+
--meta bigquery \
|
|
200
|
+
--meta-table-name your_project.your_dataset._migrations
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Scan Database
|
|
204
|
+
|
|
205
|
+
You can also scan a schema / table which will print out the operations file that generates that schema / table.
|
|
206
|
+
|
|
207
|
+
```shell
|
|
208
|
+
# scan a schema
|
|
209
|
+
poetry run liti scan \
|
|
210
|
+
--db bigquery \
|
|
211
|
+
--scan-database your_project \
|
|
212
|
+
--scan-schema your_dataset
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```shell
|
|
216
|
+
# scan a table
|
|
217
|
+
poetry run liti scan \
|
|
218
|
+
--db bigquery \
|
|
219
|
+
--scan-database your_project \
|
|
220
|
+
--scan-schema your_dataset \
|
|
221
|
+
--scan-table your_table
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Learn
|
|
225
|
+
|
|
226
|
+
Being completely new to this project, you will have no idea where to start. Here. This is where you start. This is a
|
|
227
|
+
crash course on what Limber Timber is and how its put together.
|
|
228
|
+
|
|
229
|
+
### The Big Picture
|
|
230
|
+
|
|
231
|
+
Limber Timber uses the `Operation` to describe changes to a database. These operations are pure data. They can be
|
|
232
|
+
serialized to JSON or YAML, and can be deserialized from the same. Developers write JSON or YAML files to describe the
|
|
233
|
+
migrations for their application.
|
|
234
|
+
|
|
235
|
+
The `Operation` can be enhanced to become an `OperationOps`. This type brings behavior to the data. It allows you to:
|
|
236
|
+
- check if the operation has been applied to the database
|
|
237
|
+
- useful for recovery from a failure between applying an operation and writing it to the metadata
|
|
238
|
+
- apply the operation, i.e. the "up" migration
|
|
239
|
+
- produce the inverse `Operation` that will perform the "down" migration
|
|
240
|
+
|
|
241
|
+
Down migrations are inferred from the up migrations, so developers only ever have to write the up migrations.
|
|
242
|
+
|
|
243
|
+
### Migration Files
|
|
244
|
+
|
|
245
|
+
Migration files start with a manifest file. The manifest points to the operation files in the order they should be
|
|
246
|
+
applied. Each operation file contains a list of operations in the order they should be applied. In this way,
|
|
247
|
+
```
|
|
248
|
+
# file1
|
|
249
|
+
[op1, op2]
|
|
250
|
+
|
|
251
|
+
# file2
|
|
252
|
+
[op3]
|
|
253
|
+
```
|
|
254
|
+
is exactly the same as:
|
|
255
|
+
```
|
|
256
|
+
# file1
|
|
257
|
+
[op1]
|
|
258
|
+
|
|
259
|
+
# file2
|
|
260
|
+
[op2, op3]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The migrational unit is the `Operation`, not the file. Grouping operations into files can help for organization, but
|
|
264
|
+
having a single file with all operations or many files each with one operation are both valid. There are no checksums
|
|
265
|
+
and no need to specially name your files. You can also organize your migrations with sub-directories, just specify the
|
|
266
|
+
paths in the manifest.
|
|
267
|
+
|
|
268
|
+
One major benefit to this system is if parallel developers add operations, one will merge first, and then the other will
|
|
269
|
+
get a merge conflict. This is much better than having migrations applied out of order (or breaking) after the fact. You
|
|
270
|
+
learn right away about the conflict, and the developer is prompted to resolve it. This benefit assumes all developers
|
|
271
|
+
are using the same style for adding new migrations: either adding a new file to the manifest, or adding a new operation
|
|
272
|
+
to the most recent file.
|
|
273
|
+
|
|
274
|
+
### Python Modules
|
|
275
|
+
|
|
276
|
+
`liti.core.model`
|
|
277
|
+
|
|
278
|
+
This module stores all the data models. The models are versioned, though currently there is only the one version. The
|
|
279
|
+
hierarchy is roughly:
|
|
280
|
+
|
|
281
|
+
> `operation.ops` > `operation.data` > `schema` > `datatype`
|
|
282
|
+
|
|
283
|
+
`liti.core.model.v1.operation.data`
|
|
284
|
+
|
|
285
|
+
These are the pure data operations. They are (de)serialized between the operation files and metadata.
|
|
286
|
+
|
|
287
|
+
`liti.core.model.v1.operation.ops`
|
|
288
|
+
|
|
289
|
+
These are the wrappers that enhance operations with behavior. There is a 1:1 relationship.
|
|
290
|
+
|
|
291
|
+
`liti.core.model.v1.datatype`
|
|
292
|
+
|
|
293
|
+
These are descriptions of column types.
|
|
294
|
+
|
|
295
|
+
`liti.core.model.v1.schema`
|
|
296
|
+
|
|
297
|
+
These are descriptions of tables and related constructs.
|
|
298
|
+
|
|
299
|
+
`liti.core.backend`
|
|
300
|
+
|
|
301
|
+
Both the database and the metadata can support different backends. You can even use different backends together. The
|
|
302
|
+
backends deal in both the `liti` model and backend specific types adapting between the two.
|
|
303
|
+
|
|
304
|
+
`liti.core.client`
|
|
305
|
+
|
|
306
|
+
These are clients used by the backends. They solely deal in backend specific types with no dependencies on the `liti`
|
|
307
|
+
model.
|
|
308
|
+
|
|
309
|
+
`liti.core.base`
|
|
310
|
+
|
|
311
|
+
This module has base classes for applying default values and validating the data. They are implemented using the
|
|
312
|
+
Observer / Observable pattern so different backends can define their own behavior.
|
|
313
|
+
|
|
314
|
+
`liti.core.runner`
|
|
315
|
+
|
|
316
|
+
This module is for the runners associated with the various ways `liti` can be run. Main code will instantiate a runner
|
|
317
|
+
and run it.
|
|
318
|
+
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
Limber Timber
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
***Database Migrations Made Easy***
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
This project is not ready for production use, consider it v0.0.0.
|
|
9
|
+
|
|
10
|
+
I am writing the migration system I always wanted but does not exist (yet).
|
|
11
|
+
|
|
12
|
+
## Notable Feature Goals
|
|
13
|
+
|
|
14
|
+
- Migrations specified in data, not SQL
|
|
15
|
+
- Down migrations automatically inferred from up migrations
|
|
16
|
+
- Yes, down migrations for drop table and drop column are automatically inferred
|
|
17
|
+
- Separation of database and metadata
|
|
18
|
+
- In-memory support
|
|
19
|
+
- Database adoption
|
|
20
|
+
- No checksums
|
|
21
|
+
- Manifest instead of numbered or timestamped migration filenames
|
|
22
|
+
- JSON schema for migration files
|
|
23
|
+
- Error recovery for backends that do not support DDL transactions
|
|
24
|
+
|
|
25
|
+
## Not Goals
|
|
26
|
+
|
|
27
|
+
- ORM
|
|
28
|
+
- Parsing SQL
|
|
29
|
+
- Specifying all kinds of migrations in pure data (e.g. DML migrations will use SQL)
|
|
30
|
+
- Preserving lost data
|
|
31
|
+
|
|
32
|
+
## Rationale
|
|
33
|
+
|
|
34
|
+
- It is cumbersome to iterate on migrations without robust down migrations
|
|
35
|
+
- Automatically inferred down migrations reduces developer burden
|
|
36
|
+
- Writing migrations in data is cleaner and not specific to a database
|
|
37
|
+
- Never parsing SQL reduces the complexity of the codebase
|
|
38
|
+
- A lightweight open source library makes it each to add missing features
|
|
39
|
+
- JSON schema allows IDEs to be configured for migration file validation and auto-completion
|
|
40
|
+
- Separation of database and metadata allows for more flexible metadata storage options
|
|
41
|
+
- In-memory database and metadata allow for application unit testing
|
|
42
|
+
- No checksums allows for modifying migration files without breaking the migrations
|
|
43
|
+
- Manifest files cause git merge conflicts when parallel development has collisions
|
|
44
|
+
|
|
45
|
+
## Roadmap
|
|
46
|
+
|
|
47
|
+
These are listed in rough priority order if you are interested in contributing.
|
|
48
|
+
|
|
49
|
+
- ✅ CLI
|
|
50
|
+
- ➡️ Publish to PyPI
|
|
51
|
+
- ➡️ Github Actions
|
|
52
|
+
- ➡️ Unit Tests
|
|
53
|
+
- ➡️ Release
|
|
54
|
+
- ✅ In-memory Database
|
|
55
|
+
- ✅ In-memory Metadata
|
|
56
|
+
- ➡️ Big Query Database
|
|
57
|
+
- ✅ Create Table
|
|
58
|
+
- ✅ Drop Table
|
|
59
|
+
- ✅ Rename Table
|
|
60
|
+
- ✅ Set Table Partition Expiration
|
|
61
|
+
- ✅ Set Table Clustering
|
|
62
|
+
- ✅ Add Column
|
|
63
|
+
- ✅ Drop Column
|
|
64
|
+
- ✅ Rename Column
|
|
65
|
+
- ➡️ Alter Column
|
|
66
|
+
- ➡️ Create View
|
|
67
|
+
- ➡️ Create Materialized View
|
|
68
|
+
- ➡️ Create Snapshot Table
|
|
69
|
+
- ➡️ Create Table Clone
|
|
70
|
+
- ➡️ Labels
|
|
71
|
+
- ➡️ Options
|
|
72
|
+
- ✅ Big Query Metadata
|
|
73
|
+
- ➡️ Database Adoption
|
|
74
|
+
- ➡️ JSON Schema
|
|
75
|
+
- ✅ Database Specific Validation
|
|
76
|
+
- ➡️ Templating
|
|
77
|
+
- ➡️ Expand Grouped Operations
|
|
78
|
+
- ➡️ Grouped Operation Application
|
|
79
|
+
- ➡️ Minimize Scan Output
|
|
80
|
+
- ➡️ Arbitrary DML SQL Migrations
|
|
81
|
+
- ➡️ File System Metadata
|
|
82
|
+
- ➡️ SQLite Database
|
|
83
|
+
- ➡️ SQLite Metadata
|
|
84
|
+
- ➡️ Postgres Database
|
|
85
|
+
- ➡️ Postgres Metadata
|
|
86
|
+
- ➡️ MySQL Database
|
|
87
|
+
- ➡️ MySQL Metadata
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
### Create Migrations
|
|
92
|
+
|
|
93
|
+
1. Create your target manifest
|
|
94
|
+
|
|
95
|
+
> Note: All migration files can use any of these extensions:
|
|
96
|
+
> - `.json`
|
|
97
|
+
> - `.yaml`
|
|
98
|
+
> - `.yml`
|
|
99
|
+
|
|
100
|
+
Create a target directory with a manifest file named `manifest.yaml`.
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
# target_dir/manifest.yaml
|
|
104
|
+
version: 1
|
|
105
|
+
operation_files:
|
|
106
|
+
- path/to/create_user_table.yaml
|
|
107
|
+
- path/to/enrich_user_name.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
2. Create your target migration operations
|
|
111
|
+
|
|
112
|
+
> Tip: Using a subdirectory for the operations files makes it easy to configure your IDE to apply the correct JSON schema.
|
|
113
|
+
|
|
114
|
+
Create the files listed in your manifest.
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
# target_dir/path/to/create_user_table.yaml
|
|
118
|
+
version: 1
|
|
119
|
+
operations:
|
|
120
|
+
- kind: create_table
|
|
121
|
+
data:
|
|
122
|
+
table:
|
|
123
|
+
name:
|
|
124
|
+
database: your_project
|
|
125
|
+
schema_name: your_dataset
|
|
126
|
+
table_name: users
|
|
127
|
+
columns:
|
|
128
|
+
- name: id
|
|
129
|
+
data_type: INT64
|
|
130
|
+
- name: name
|
|
131
|
+
data_type: STRING
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
# target_dir/path/to/enrich_user_name.yaml
|
|
136
|
+
version: 1
|
|
137
|
+
operations:
|
|
138
|
+
- kind: rename_column
|
|
139
|
+
data:
|
|
140
|
+
table_name:
|
|
141
|
+
database: your_project
|
|
142
|
+
schema_name: your_dataset
|
|
143
|
+
table_name: users
|
|
144
|
+
from_name: name
|
|
145
|
+
to_name: firstname
|
|
146
|
+
- kind: add_column
|
|
147
|
+
data:
|
|
148
|
+
table_name:
|
|
149
|
+
database: your_project
|
|
150
|
+
schema_name: your_dataset
|
|
151
|
+
table_name: users
|
|
152
|
+
column:
|
|
153
|
+
name: lastname
|
|
154
|
+
data_type: STRING
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
3. Check what migrations will run
|
|
158
|
+
|
|
159
|
+
```shell
|
|
160
|
+
poetry run liti migrate \
|
|
161
|
+
-t target_dir \
|
|
162
|
+
--db bigquery \
|
|
163
|
+
--meta bigquery \
|
|
164
|
+
--meta-table-name your_project.your_dataset._migrations
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
4. Run the migrations
|
|
168
|
+
|
|
169
|
+
```shell
|
|
170
|
+
poetry run liti migrate -w \
|
|
171
|
+
-t target_dir \
|
|
172
|
+
--db bigquery \
|
|
173
|
+
--meta bigquery \
|
|
174
|
+
--meta-table-name your_project.your_dataset._migrations
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Scan Database
|
|
178
|
+
|
|
179
|
+
You can also scan a schema / table which will print out the operations file that generates that schema / table.
|
|
180
|
+
|
|
181
|
+
```shell
|
|
182
|
+
# scan a schema
|
|
183
|
+
poetry run liti scan \
|
|
184
|
+
--db bigquery \
|
|
185
|
+
--scan-database your_project \
|
|
186
|
+
--scan-schema your_dataset
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```shell
|
|
190
|
+
# scan a table
|
|
191
|
+
poetry run liti scan \
|
|
192
|
+
--db bigquery \
|
|
193
|
+
--scan-database your_project \
|
|
194
|
+
--scan-schema your_dataset \
|
|
195
|
+
--scan-table your_table
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Learn
|
|
199
|
+
|
|
200
|
+
Being completely new to this project, you will have no idea where to start. Here. This is where you start. This is a
|
|
201
|
+
crash course on what Limber Timber is and how its put together.
|
|
202
|
+
|
|
203
|
+
### The Big Picture
|
|
204
|
+
|
|
205
|
+
Limber Timber uses the `Operation` to describe changes to a database. These operations are pure data. They can be
|
|
206
|
+
serialized to JSON or YAML, and can be deserialized from the same. Developers write JSON or YAML files to describe the
|
|
207
|
+
migrations for their application.
|
|
208
|
+
|
|
209
|
+
The `Operation` can be enhanced to become an `OperationOps`. This type brings behavior to the data. It allows you to:
|
|
210
|
+
- check if the operation has been applied to the database
|
|
211
|
+
- useful for recovery from a failure between applying an operation and writing it to the metadata
|
|
212
|
+
- apply the operation, i.e. the "up" migration
|
|
213
|
+
- produce the inverse `Operation` that will perform the "down" migration
|
|
214
|
+
|
|
215
|
+
Down migrations are inferred from the up migrations, so developers only ever have to write the up migrations.
|
|
216
|
+
|
|
217
|
+
### Migration Files
|
|
218
|
+
|
|
219
|
+
Migration files start with a manifest file. The manifest points to the operation files in the order they should be
|
|
220
|
+
applied. Each operation file contains a list of operations in the order they should be applied. In this way,
|
|
221
|
+
```
|
|
222
|
+
# file1
|
|
223
|
+
[op1, op2]
|
|
224
|
+
|
|
225
|
+
# file2
|
|
226
|
+
[op3]
|
|
227
|
+
```
|
|
228
|
+
is exactly the same as:
|
|
229
|
+
```
|
|
230
|
+
# file1
|
|
231
|
+
[op1]
|
|
232
|
+
|
|
233
|
+
# file2
|
|
234
|
+
[op2, op3]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The migrational unit is the `Operation`, not the file. Grouping operations into files can help for organization, but
|
|
238
|
+
having a single file with all operations or many files each with one operation are both valid. There are no checksums
|
|
239
|
+
and no need to specially name your files. You can also organize your migrations with sub-directories, just specify the
|
|
240
|
+
paths in the manifest.
|
|
241
|
+
|
|
242
|
+
One major benefit to this system is if parallel developers add operations, one will merge first, and then the other will
|
|
243
|
+
get a merge conflict. This is much better than having migrations applied out of order (or breaking) after the fact. You
|
|
244
|
+
learn right away about the conflict, and the developer is prompted to resolve it. This benefit assumes all developers
|
|
245
|
+
are using the same style for adding new migrations: either adding a new file to the manifest, or adding a new operation
|
|
246
|
+
to the most recent file.
|
|
247
|
+
|
|
248
|
+
### Python Modules
|
|
249
|
+
|
|
250
|
+
`liti.core.model`
|
|
251
|
+
|
|
252
|
+
This module stores all the data models. The models are versioned, though currently there is only the one version. The
|
|
253
|
+
hierarchy is roughly:
|
|
254
|
+
|
|
255
|
+
> `operation.ops` > `operation.data` > `schema` > `datatype`
|
|
256
|
+
|
|
257
|
+
`liti.core.model.v1.operation.data`
|
|
258
|
+
|
|
259
|
+
These are the pure data operations. They are (de)serialized between the operation files and metadata.
|
|
260
|
+
|
|
261
|
+
`liti.core.model.v1.operation.ops`
|
|
262
|
+
|
|
263
|
+
These are the wrappers that enhance operations with behavior. There is a 1:1 relationship.
|
|
264
|
+
|
|
265
|
+
`liti.core.model.v1.datatype`
|
|
266
|
+
|
|
267
|
+
These are descriptions of column types.
|
|
268
|
+
|
|
269
|
+
`liti.core.model.v1.schema`
|
|
270
|
+
|
|
271
|
+
These are descriptions of tables and related constructs.
|
|
272
|
+
|
|
273
|
+
`liti.core.backend`
|
|
274
|
+
|
|
275
|
+
Both the database and the metadata can support different backends. You can even use different backends together. The
|
|
276
|
+
backends deal in both the `liti` model and backend specific types adapting between the two.
|
|
277
|
+
|
|
278
|
+
`liti.core.client`
|
|
279
|
+
|
|
280
|
+
These are clients used by the backends. They solely deal in backend specific types with no dependencies on the `liti`
|
|
281
|
+
model.
|
|
282
|
+
|
|
283
|
+
`liti.core.base`
|
|
284
|
+
|
|
285
|
+
This module has base classes for applying default values and validating the data. They are implemented using the
|
|
286
|
+
Observer / Observable pattern so different backends can define their own behavior.
|
|
287
|
+
|
|
288
|
+
`liti.core.runner`
|
|
289
|
+
|
|
290
|
+
This module is for the runners associated with the various ways `liti` can be run. Main code will instantiate a runner
|
|
291
|
+
and run it.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "limber-timber"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Database Migrations Made Easy"
|
|
5
|
+
authors = ["Daniel Tashjian <thewopple@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Programming Language :: Python :: 3",
|
|
9
|
+
"Operating System :: OS Independent",
|
|
10
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
11
|
+
"Framework :: Pytest",
|
|
12
|
+
"Framework :: Pydantic :: 2",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
packages = [
|
|
16
|
+
{ include = "liti", from = "src" },
|
|
17
|
+
]
|
|
18
|
+
homepage = "https://github.com/Wopple/limber-timber"
|
|
19
|
+
repository = "https://github.com/Wopple/limber-timber"
|
|
20
|
+
|
|
21
|
+
[tool.poetry.scripts]
|
|
22
|
+
liti = "liti.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.poetry.dependencies]
|
|
25
|
+
python = ">=3.10"
|
|
26
|
+
devtools = "0.12.2"
|
|
27
|
+
google-cloud-bigquery = "3.34.0"
|
|
28
|
+
pydantic = "2.11.4"
|
|
29
|
+
pyyaml = "6.0.2"
|
|
30
|
+
|
|
31
|
+
[tool.poetry.group.dev.dependencies]
|
|
32
|
+
pytest = "^8.3.5"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
pythonpath = "src"
|
|
36
|
+
testpaths = "tests"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["poetry-core"]
|
|
40
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|