mtpk-postgres 0.1.2__tar.gz → 0.1.4__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.
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/PKG-INFO +21 -1
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/README.md +20 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres/__init__.py +1 -1
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres/core_sync.py +51 -5
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres.egg-info/PKG-INFO +21 -1
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/pyproject.toml +1 -1
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/LICENSE +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres/async_adapter.py +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres/crud.py +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres/excepciones.py +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres/interface.py +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres/utils.py +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres.egg-info/SOURCES.txt +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres.egg-info/dependency_links.txt +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres.egg-info/requires.txt +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/mtpk_postgres.egg-info/top_level.txt +0 -0
- {mtpk_postgres-0.1.2 → mtpk_postgres-0.1.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mtpk_postgres
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Librería para sincronización estructural de bases de datos Postgres con modelos Python (adaptación de mtpk_mariadb)
|
|
5
5
|
Author-email: José Jesús Andrés Zambrana <jjandres@multiplika.es>
|
|
6
6
|
License: MIT
|
|
@@ -54,6 +54,26 @@ pip install -e .
|
|
|
54
54
|
|
|
55
55
|
## Historial
|
|
56
56
|
|
|
57
|
+
- **0.1.4**: corrige la causa de fondo del bug de la 0.1.3, que solo tapaba el síntoma en la columna generada
|
|
58
|
+
en sí. Había dos problemas relacionados: (1) `comparar_generar_alter` comparaba el tipo de columna por su
|
|
59
|
+
nombre "en crudo" (`col.tipo`), así que un modelo que usa alias como `INT`, `TINYINT` o `DATETIME` (en vez de
|
|
60
|
+
`INTEGER`/`SMALLINT`/`TIMESTAMP`, que es como se reconstruyen siempre desde Postgres) nunca coincidía con la
|
|
61
|
+
realidad y proponía un `ALTER COLUMN ... TYPE` en cada ejecución aunque el tipo no hubiera cambiado — inofensivo
|
|
62
|
+
la mayoría de las veces (Postgres permite "cambiar" una columna a su mismo tipo), pero rechazado de plano si otra
|
|
63
|
+
columna `GENERATED` depende de la columna "alterada"; ahora se compara por el tipo canónico
|
|
64
|
+
(`Columna._tipo_base()`). (2) `ManagerDB.aplicar_cambios()` — la ruta que se usa en la primera sincronización de
|
|
65
|
+
una base de datos nueva — creaba todas las tablas del modelo y a continuación las volvía a comparar todas,
|
|
66
|
+
incluidas las que acababa de crear en la misma llamada; una tabla recién creada coincide por definición con el
|
|
67
|
+
modelo que la creó, así que ahora se excluye de esa comparación (que solo tiene sentido sobre tablas
|
|
68
|
+
preexistentes). Esta segunda corrección evita esta clase entera de falsos positivos, no solo el caso ya visto.
|
|
69
|
+
- **0.1.3**: corrige un bug bloqueante en `comparar_generar_alter` — al crear una tabla nueva con una columna
|
|
70
|
+
`GENERATED ALWAYS AS (...) STORED` (p.ej. una columna de búsqueda `tsvector` calculada a partir de otra), la
|
|
71
|
+
siguiente pasada de comparación la trataba como una columna normal (la introspección no reconstruye la expresión
|
|
72
|
+
de generación) y proponía un `ALTER COLUMN` sobre ella justo después de crearla — algo que Postgres puede
|
|
73
|
+
rechazar cuando otras columnas dependen de la generada. Como `aplicar_cambios()` ejecuta todo en una única
|
|
74
|
+
transacción, ese fallo deshacía también la creación de las demás tablas. Ahora las columnas `generado` nunca se
|
|
75
|
+
comparan ni se alteran una vez existen (para cambiar su expresión hay que borrarlas y recrearlas a mano); de paso,
|
|
76
|
+
`Columna.to_sql()` ya respeta `not_null=True` en columnas generadas (antes se ignoraba).
|
|
57
77
|
- **0.1.2**: soporte nativo para columnas `VECTOR(n)` (pgvector) e índices con método (`USING gin/hnsw/...`,
|
|
58
78
|
`opclass`, `WITH (...)`) en `Index`. La introspección (`obtener_columnas_postgres`) ahora reconoce las columnas
|
|
59
79
|
`vector` por su `udt_name` y reconstruye su dimensión leyendo el catálogo (`pg_attribute`/`format_type`); sin esto,
|
|
@@ -34,6 +34,26 @@ pip install -e .
|
|
|
34
34
|
|
|
35
35
|
## Historial
|
|
36
36
|
|
|
37
|
+
- **0.1.4**: corrige la causa de fondo del bug de la 0.1.3, que solo tapaba el síntoma en la columna generada
|
|
38
|
+
en sí. Había dos problemas relacionados: (1) `comparar_generar_alter` comparaba el tipo de columna por su
|
|
39
|
+
nombre "en crudo" (`col.tipo`), así que un modelo que usa alias como `INT`, `TINYINT` o `DATETIME` (en vez de
|
|
40
|
+
`INTEGER`/`SMALLINT`/`TIMESTAMP`, que es como se reconstruyen siempre desde Postgres) nunca coincidía con la
|
|
41
|
+
realidad y proponía un `ALTER COLUMN ... TYPE` en cada ejecución aunque el tipo no hubiera cambiado — inofensivo
|
|
42
|
+
la mayoría de las veces (Postgres permite "cambiar" una columna a su mismo tipo), pero rechazado de plano si otra
|
|
43
|
+
columna `GENERATED` depende de la columna "alterada"; ahora se compara por el tipo canónico
|
|
44
|
+
(`Columna._tipo_base()`). (2) `ManagerDB.aplicar_cambios()` — la ruta que se usa en la primera sincronización de
|
|
45
|
+
una base de datos nueva — creaba todas las tablas del modelo y a continuación las volvía a comparar todas,
|
|
46
|
+
incluidas las que acababa de crear en la misma llamada; una tabla recién creada coincide por definición con el
|
|
47
|
+
modelo que la creó, así que ahora se excluye de esa comparación (que solo tiene sentido sobre tablas
|
|
48
|
+
preexistentes). Esta segunda corrección evita esta clase entera de falsos positivos, no solo el caso ya visto.
|
|
49
|
+
- **0.1.3**: corrige un bug bloqueante en `comparar_generar_alter` — al crear una tabla nueva con una columna
|
|
50
|
+
`GENERATED ALWAYS AS (...) STORED` (p.ej. una columna de búsqueda `tsvector` calculada a partir de otra), la
|
|
51
|
+
siguiente pasada de comparación la trataba como una columna normal (la introspección no reconstruye la expresión
|
|
52
|
+
de generación) y proponía un `ALTER COLUMN` sobre ella justo después de crearla — algo que Postgres puede
|
|
53
|
+
rechazar cuando otras columnas dependen de la generada. Como `aplicar_cambios()` ejecuta todo en una única
|
|
54
|
+
transacción, ese fallo deshacía también la creación de las demás tablas. Ahora las columnas `generado` nunca se
|
|
55
|
+
comparan ni se alteran una vez existen (para cambiar su expresión hay que borrarlas y recrearlas a mano); de paso,
|
|
56
|
+
`Columna.to_sql()` ya respeta `not_null=True` en columnas generadas (antes se ignoraba).
|
|
37
57
|
- **0.1.2**: soporte nativo para columnas `VECTOR(n)` (pgvector) e índices con método (`USING gin/hnsw/...`,
|
|
38
58
|
`opclass`, `WITH (...)`) en `Index`. La introspección (`obtener_columnas_postgres`) ahora reconoce las columnas
|
|
39
59
|
`vector` por su `udt_name` y reconstruye su dimensión leyendo el catálogo (`pg_attribute`/`format_type`); sin esto,
|
|
@@ -129,14 +129,25 @@ class Columna:
|
|
|
129
129
|
}
|
|
130
130
|
_SIN_SOPORTE = {"GEOMETRY", "POINT", "LINESTRING", "POLYGON"}
|
|
131
131
|
|
|
132
|
-
def
|
|
133
|
-
"""
|
|
132
|
+
def _tipo_base(self) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Nombre canónico del tipo en Postgres (sin longitud/precisión), p.ej. "INT" y "INTEGER"
|
|
135
|
+
devuelven ambos "INTEGER". Se usa tanto para generar SQL como para comparar el tipo del
|
|
136
|
+
modelo contra el tipo reconstruido desde la base de datos (obtener_columnas_postgres ya
|
|
137
|
+
siempre devuelve el nombre canónico) — comparar por el nombre "en crudo" haría que un
|
|
138
|
+
modelo que usa alias tipo "INT"/"TINYINT"/"DATETIME" nunca coincidiera con la realidad y
|
|
139
|
+
disparara un ALTER COLUMN de tipo en cada ejecución, aunque el tipo real no haya cambiado.
|
|
140
|
+
"""
|
|
134
141
|
tipo_upper = self.tipo.upper()
|
|
135
142
|
if tipo_upper in self._SIN_SOPORTE:
|
|
136
143
|
raise NotImplementedError(
|
|
137
144
|
f"El tipo '{tipo_upper}' requiere la extensión PostGIS y no está soportado."
|
|
138
145
|
)
|
|
139
|
-
|
|
146
|
+
return self._MAPA_TIPOS.get(tipo_upper, tipo_upper)
|
|
147
|
+
|
|
148
|
+
def _tipo_sql(self) -> str:
|
|
149
|
+
"""Traduce self.tipo (+ longitud/precision/escala) al tipo SQL de Postgres."""
|
|
150
|
+
base = self._tipo_base()
|
|
140
151
|
|
|
141
152
|
if base == "VECTOR":
|
|
142
153
|
if self.longitud is None:
|
|
@@ -166,6 +177,8 @@ class Columna:
|
|
|
166
177
|
|
|
167
178
|
if self.generado:
|
|
168
179
|
partes.append(f"{tipo_sql} GENERATED ALWAYS AS ({self.generado}) STORED")
|
|
180
|
+
if self.not_null:
|
|
181
|
+
partes.append("NOT NULL")
|
|
169
182
|
else:
|
|
170
183
|
partes.append(tipo_sql)
|
|
171
184
|
|
|
@@ -748,8 +761,14 @@ class Tabla:
|
|
|
748
761
|
return (val_real, val_def) in equivalentes or (val_def, val_real) in equivalentes or val_real == val_def
|
|
749
762
|
|
|
750
763
|
def tipos_iguales(col_real, col_def):
|
|
764
|
+
# Comparar por el tipo CANÓNICO (col._tipo_base()), no por el texto crudo de col.tipo:
|
|
765
|
+
# el modelo puede usar alias como "INT"/"TINYINT"/"DATETIME" que Postgres no distingue
|
|
766
|
+
# de "INTEGER"/"SMALLINT"/"TIMESTAMP" — comparando el texto crudo, esas columnas nunca
|
|
767
|
+
# coincidirían con la realidad y se propondría un ALTER COLUMN ... TYPE en cada
|
|
768
|
+
# ejecución aunque el tipo real no cambie (inofensivo casi siempre, pero Postgres lo
|
|
769
|
+
# rechaza si otra columna GENERATED depende de la columna "alterada").
|
|
751
770
|
return (
|
|
752
|
-
|
|
771
|
+
col_real._tipo_base() == col_def._tipo_base() and
|
|
753
772
|
col_real.longitud == col_def.longitud and
|
|
754
773
|
col_real.precision == col_def.precision and
|
|
755
774
|
(col_real.escala or None) == (col_def.escala or None)
|
|
@@ -771,6 +790,16 @@ class Tabla:
|
|
|
771
790
|
col_real = reales.get(nombre)
|
|
772
791
|
if not col_real:
|
|
773
792
|
col_alteraciones.append(f"ADD COLUMN {col_def.to_sql()}")
|
|
793
|
+
elif col_def.generado:
|
|
794
|
+
# Las columnas GENERATED ALWAYS AS (...) STORED no se comparan ni se alteran
|
|
795
|
+
# automáticamente una vez existen: la introspección no reconstruye la expresión
|
|
796
|
+
# de generación (obtener_columnas_postgres no la lee), así que cualquier columna
|
|
797
|
+
# "normal" reconstruida a partir de una generada se compararía con datos
|
|
798
|
+
# incompletos (p.ej. sin el NOT NULL que sí lleva la generada) y dispararía un
|
|
799
|
+
# ALTER espurio — que además Postgres puede rechazar de plano si otras columnas
|
|
800
|
+
# dependen de ella. Si la expresión cambia, hay que borrar y recrear la columna
|
|
801
|
+
# a mano.
|
|
802
|
+
continue
|
|
774
803
|
elif not iguales(col_real, col_def):
|
|
775
804
|
if not tipos_iguales(col_real, col_def):
|
|
776
805
|
tipo_sql = col_def._tipo_sql()
|
|
@@ -1195,6 +1224,21 @@ class ManagerDB(Database):
|
|
|
1195
1224
|
)
|
|
1196
1225
|
""")
|
|
1197
1226
|
|
|
1227
|
+
# Tablas que ya existían ANTES de esta llamada: solo tiene sentido comparar
|
|
1228
|
+
# (y potencialmente ALTERar) esas. Una tabla recién creada en esta misma llamada
|
|
1229
|
+
# coincide por definición con el modelo que la creó — compararla igual puede
|
|
1230
|
+
# disparar un ALTER falso-positivo (p.ej. no detecta que "INT" y "INTEGER" son el
|
|
1231
|
+
# mismo tipo) que además Postgres puede rechazar de plano si hay una columna
|
|
1232
|
+
# GENERATED que depende de la columna "alterada".
|
|
1233
|
+
tablas_preexistentes = set()
|
|
1234
|
+
for nombre_tabla in self.tablas:
|
|
1235
|
+
cursor.execute(
|
|
1236
|
+
"SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = %s",
|
|
1237
|
+
(nombre_tabla,)
|
|
1238
|
+
)
|
|
1239
|
+
if cursor.fetchone():
|
|
1240
|
+
tablas_preexistentes.add(nombre_tabla)
|
|
1241
|
+
|
|
1198
1242
|
# --- Crear tablas nuevas (+ índices + comentarios) ---
|
|
1199
1243
|
for nombre_tabla, tabla in self.tablas.items():
|
|
1200
1244
|
sentencias_creacion = [tabla.to_sql()] + tabla.to_sql_indices() + tabla.to_sql_comentarios()
|
|
@@ -1206,8 +1250,10 @@ class ManagerDB(Database):
|
|
|
1206
1250
|
if self.logger:
|
|
1207
1251
|
self.logger.info(f"Tabla \"{nombre_tabla}\": ejecutado CREATE IF NOT EXISTS.")
|
|
1208
1252
|
|
|
1209
|
-
# --- Aplicar ALTERs ---
|
|
1253
|
+
# --- Aplicar ALTERs (solo en tablas que ya existían antes de esta llamada) ---
|
|
1210
1254
|
for nombre_tabla, tabla in self.tablas.items():
|
|
1255
|
+
if nombre_tabla not in tablas_preexistentes:
|
|
1256
|
+
continue
|
|
1211
1257
|
alter_sqls = tabla.comparar_generar_alter(conexion, permitir_drop=permitir_drop)
|
|
1212
1258
|
for alter_sql in alter_sqls:
|
|
1213
1259
|
cursor.execute(alter_sql)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mtpk_postgres
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Librería para sincronización estructural de bases de datos Postgres con modelos Python (adaptación de mtpk_mariadb)
|
|
5
5
|
Author-email: José Jesús Andrés Zambrana <jjandres@multiplika.es>
|
|
6
6
|
License: MIT
|
|
@@ -54,6 +54,26 @@ pip install -e .
|
|
|
54
54
|
|
|
55
55
|
## Historial
|
|
56
56
|
|
|
57
|
+
- **0.1.4**: corrige la causa de fondo del bug de la 0.1.3, que solo tapaba el síntoma en la columna generada
|
|
58
|
+
en sí. Había dos problemas relacionados: (1) `comparar_generar_alter` comparaba el tipo de columna por su
|
|
59
|
+
nombre "en crudo" (`col.tipo`), así que un modelo que usa alias como `INT`, `TINYINT` o `DATETIME` (en vez de
|
|
60
|
+
`INTEGER`/`SMALLINT`/`TIMESTAMP`, que es como se reconstruyen siempre desde Postgres) nunca coincidía con la
|
|
61
|
+
realidad y proponía un `ALTER COLUMN ... TYPE` en cada ejecución aunque el tipo no hubiera cambiado — inofensivo
|
|
62
|
+
la mayoría de las veces (Postgres permite "cambiar" una columna a su mismo tipo), pero rechazado de plano si otra
|
|
63
|
+
columna `GENERATED` depende de la columna "alterada"; ahora se compara por el tipo canónico
|
|
64
|
+
(`Columna._tipo_base()`). (2) `ManagerDB.aplicar_cambios()` — la ruta que se usa en la primera sincronización de
|
|
65
|
+
una base de datos nueva — creaba todas las tablas del modelo y a continuación las volvía a comparar todas,
|
|
66
|
+
incluidas las que acababa de crear en la misma llamada; una tabla recién creada coincide por definición con el
|
|
67
|
+
modelo que la creó, así que ahora se excluye de esa comparación (que solo tiene sentido sobre tablas
|
|
68
|
+
preexistentes). Esta segunda corrección evita esta clase entera de falsos positivos, no solo el caso ya visto.
|
|
69
|
+
- **0.1.3**: corrige un bug bloqueante en `comparar_generar_alter` — al crear una tabla nueva con una columna
|
|
70
|
+
`GENERATED ALWAYS AS (...) STORED` (p.ej. una columna de búsqueda `tsvector` calculada a partir de otra), la
|
|
71
|
+
siguiente pasada de comparación la trataba como una columna normal (la introspección no reconstruye la expresión
|
|
72
|
+
de generación) y proponía un `ALTER COLUMN` sobre ella justo después de crearla — algo que Postgres puede
|
|
73
|
+
rechazar cuando otras columnas dependen de la generada. Como `aplicar_cambios()` ejecuta todo en una única
|
|
74
|
+
transacción, ese fallo deshacía también la creación de las demás tablas. Ahora las columnas `generado` nunca se
|
|
75
|
+
comparan ni se alteran una vez existen (para cambiar su expresión hay que borrarlas y recrearlas a mano); de paso,
|
|
76
|
+
`Columna.to_sql()` ya respeta `not_null=True` en columnas generadas (antes se ignoraba).
|
|
57
77
|
- **0.1.2**: soporte nativo para columnas `VECTOR(n)` (pgvector) e índices con método (`USING gin/hnsw/...`,
|
|
58
78
|
`opclass`, `WITH (...)`) en `Index`. La introspección (`obtener_columnas_postgres`) ahora reconoce las columnas
|
|
59
79
|
`vector` por su `udt_name` y reconstruye su dimensión leyendo el catálogo (`pg_attribute`/`format_type`); sin esto,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mtpk_postgres"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "Librería para sincronización estructural de bases de datos Postgres con modelos Python (adaptación de mtpk_mariadb)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|