patera-migrate 0.2.0__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.
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "patera-migrate"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
requires-python = ">=3.13"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"patera",
|
|
7
|
+
"patera-database",
|
|
8
|
+
"pyway>=0.3.32",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[tool.uv.sources]
|
|
12
|
+
patera = { workspace = true }
|
|
13
|
+
patera-database = { workspace = true }
|
|
14
|
+
|
|
15
|
+
[tool.uv.build-backend]
|
|
16
|
+
module-name = "patera.migrate"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["uv_build>=0.10.9,<0.11.0"]
|
|
20
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyway implementation for Patera
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
from typing import Dict, Optional, cast
|
|
8
|
+
from patera import Patera
|
|
9
|
+
from patera.cli import CLIController, command
|
|
10
|
+
from patera.database.sql import SqlDatabase
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
14
|
+
|
|
15
|
+
from pyway.configfile import ConfigFile as PywayConfigFile
|
|
16
|
+
from pyway.info import Info as PywayInfo
|
|
17
|
+
from pyway.migrate import Migrate as PywayMigrate
|
|
18
|
+
from pyway.validate import Validate as PywayValidate
|
|
19
|
+
from pyway.checksum import Checksum as PywayChecksum
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _PateraPywayConfigs(BaseModel):
|
|
23
|
+
model_config = ConfigDict(extra="allow")
|
|
24
|
+
|
|
25
|
+
MIGRATE_CLI_NAME: Optional[str] = Field(
|
|
26
|
+
"migrate", description="Name of the cli command prefix"
|
|
27
|
+
)
|
|
28
|
+
MIGRATE_DATABASE_MIGRATION_DIR: Optional[str] = Field("migrations", description="")
|
|
29
|
+
MIGRATE_SQL_MIGRATION_PREFIX: Optional[str] = Field("V", description="")
|
|
30
|
+
MIGRATE_SQL_MIGRATION_SEPARATOR: Optional[str] = Field("__", description="")
|
|
31
|
+
MIGRATE_SQL_MIGRATION_SUFFIXES: Optional[str] = Field(".sql", description="")
|
|
32
|
+
MIGRATE_TABLE: Optional[str] = Field("pyway_migrations", description="")
|
|
33
|
+
MIGRATE_CONFIG_FILE: Optional[str] = Field(".pyway.conf", description="")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PywayCLIController(CLIController):
|
|
37
|
+
def __init__(self, app: Patera, extension: "Migrate"):
|
|
38
|
+
super().__init__(app)
|
|
39
|
+
self._ext = extension
|
|
40
|
+
|
|
41
|
+
def _get_configs_from_file(self) -> PywayConfigFile:
|
|
42
|
+
configs = {}
|
|
43
|
+
os.environ["PYWAY_DATABASE_MIGRATION_DIR"] = str(self._ext._migrations_path)
|
|
44
|
+
os.environ["PYWAY_CONFIG_FILE"] = str(self._ext._configs_path)
|
|
45
|
+
with open(str(self._ext._configs_path)) as config_file:
|
|
46
|
+
for line in config_file.readlines():
|
|
47
|
+
config, value = line.split(": ")
|
|
48
|
+
configs[config.strip()] = value.strip()
|
|
49
|
+
pyway_configs = PywayConfigFile(**configs)
|
|
50
|
+
return pyway_configs
|
|
51
|
+
|
|
52
|
+
def _clear_env_vars(self) -> None:
|
|
53
|
+
os.environ.pop("PYWAY_DATABASE_MIGRATION_DIR", None)
|
|
54
|
+
os.environ.pop("PYWAY_CONFIG_FILE", None)
|
|
55
|
+
|
|
56
|
+
@command("init", help="Initilizes Patera migration extension for database")
|
|
57
|
+
def init(self) -> None:
|
|
58
|
+
"""Initilizes pyway configs"""
|
|
59
|
+
self._ext.create_pyway_config()
|
|
60
|
+
|
|
61
|
+
@command("info", help="Provides information about current migrations and db status")
|
|
62
|
+
def info(self) -> None:
|
|
63
|
+
pyway_configs = self._get_configs_from_file()
|
|
64
|
+
try:
|
|
65
|
+
pyway_info = PywayInfo(pyway_configs)
|
|
66
|
+
print(pyway_info.run())
|
|
67
|
+
finally:
|
|
68
|
+
self._clear_env_vars()
|
|
69
|
+
|
|
70
|
+
@command(
|
|
71
|
+
"validate",
|
|
72
|
+
help="Validate helps you verify that the migrations applied to the database match the ones available locally.",
|
|
73
|
+
)
|
|
74
|
+
def validate(self) -> None:
|
|
75
|
+
pyway_configs = self._get_configs_from_file()
|
|
76
|
+
try:
|
|
77
|
+
pyway_validate = PywayValidate(pyway_configs)
|
|
78
|
+
print(pyway_validate.run())
|
|
79
|
+
finally:
|
|
80
|
+
self._clear_env_vars()
|
|
81
|
+
|
|
82
|
+
@command("migrate", help="Perform database migration")
|
|
83
|
+
def migrate(self) -> None:
|
|
84
|
+
pyway_configs = self._get_configs_from_file()
|
|
85
|
+
try:
|
|
86
|
+
pyway_migrate = PywayMigrate(pyway_configs)
|
|
87
|
+
print(pyway_migrate.run())
|
|
88
|
+
finally:
|
|
89
|
+
self._clear_env_vars()
|
|
90
|
+
|
|
91
|
+
@command(
|
|
92
|
+
"checksum",
|
|
93
|
+
help="Updates a checksum in the database. This is for advanced use only, as it could put the pyway database out of sync with reality. ",
|
|
94
|
+
)
|
|
95
|
+
def checksum(self, checksum_file: str) -> None:
|
|
96
|
+
pyway_configs = self._get_configs_from_file()
|
|
97
|
+
pyway_configs.checksum_file = checksum_file # type: ignore
|
|
98
|
+
try:
|
|
99
|
+
pyway_checksum = PywayChecksum(pyway_configs)
|
|
100
|
+
print(pyway_checksum.run())
|
|
101
|
+
finally:
|
|
102
|
+
self._clear_env_vars()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Migrate:
|
|
106
|
+
__db_name__: str
|
|
107
|
+
|
|
108
|
+
def __init__(self, app: Patera, db: SqlDatabase):
|
|
109
|
+
self._app = app
|
|
110
|
+
self._db = db
|
|
111
|
+
self.__db_name__ = db.__db_name__
|
|
112
|
+
self._configs = self._app.get_conf(self.configs_name, {})
|
|
113
|
+
self._configs = self._db.validate_configs(self._configs, _PateraPywayConfigs)
|
|
114
|
+
self._cli_controller = PywayCLIController(self._app, self)
|
|
115
|
+
self._cli_controller.set_ctrl_name(
|
|
116
|
+
cast(str, self._configs.get("MIGRATE_CLI_NAME"))
|
|
117
|
+
)
|
|
118
|
+
self._app.register_cli_controller(self._cli_controller)
|
|
119
|
+
self._migrations_path: Path = Path(self._app.root_path) / cast(
|
|
120
|
+
str, self._configs.get("MIGRATE_DATABASE_MIGRATION_DIR")
|
|
121
|
+
)
|
|
122
|
+
self._migrations_path.mkdir(exist_ok=True)
|
|
123
|
+
self._configs_path: Path = self._migrations_path / cast(
|
|
124
|
+
str, self._configs.get("MIGRATE_CONFIG_FILE")
|
|
125
|
+
)
|
|
126
|
+
# configs need to be initilized manually
|
|
127
|
+
# self.create_pyway_config()
|
|
128
|
+
|
|
129
|
+
def create_pyway_config(self) -> None:
|
|
130
|
+
if self._configs_path.exists():
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
db = self.parse_database_uri(self.database_uri)
|
|
134
|
+
|
|
135
|
+
lines = [
|
|
136
|
+
f"database_migration_dir: {self._configs.get('MIGRATE_DATABASE_MIGRATION_DIR')}",
|
|
137
|
+
f"database_table: {self._configs.get('MIGRATE_TABLE')}",
|
|
138
|
+
f"database_type: {db.get('type')}",
|
|
139
|
+
f"database_username: {db.get('username')}",
|
|
140
|
+
f"database_password: {db.get('password')}",
|
|
141
|
+
f"database_host: {db.get('host')}",
|
|
142
|
+
f"database_port: {db.get('port')}",
|
|
143
|
+
f"database_name: {db.get('database')}",
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
filtered = [line for line in lines if not line.endswith(": None")]
|
|
147
|
+
self._configs_path.write_text("\n".join(filtered) + "\n")
|
|
148
|
+
|
|
149
|
+
def parse_database_uri(self, uri: str) -> Dict[str, Optional[str]]:
|
|
150
|
+
parsed = urlparse(uri)
|
|
151
|
+
db_type = parsed.scheme.split("+")[0]
|
|
152
|
+
|
|
153
|
+
if db_type == "sqlite":
|
|
154
|
+
return {
|
|
155
|
+
"type": "sqlite",
|
|
156
|
+
"username": None,
|
|
157
|
+
"password": None,
|
|
158
|
+
"host": None,
|
|
159
|
+
"port": None,
|
|
160
|
+
"database": parsed.path.lstrip("/"),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"type": db_type,
|
|
165
|
+
"username": parsed.username,
|
|
166
|
+
"password": parsed.password,
|
|
167
|
+
"host": parsed.hostname,
|
|
168
|
+
"port": str(parsed.port) if parsed.port else None,
|
|
169
|
+
"database": parsed.path.lstrip("/") if parsed.path else None,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def database_uri(self) -> str:
|
|
174
|
+
return self._db.db_uri
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def configs_name(self) -> str:
|
|
178
|
+
return self.__class__.__name__
|