mtpk-postgres 0.1.3__tar.gz → 0.1.5__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.3 → mtpk_postgres-0.1.5}/PKG-INFO +19 -1
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/README.md +18 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres/__init__.py +1 -1
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres/async_adapter.py +9 -3
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres/core_sync.py +39 -5
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres.egg-info/PKG-INFO +19 -1
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/pyproject.toml +1 -1
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/LICENSE +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres/crud.py +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres/excepciones.py +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres/interface.py +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres/utils.py +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres.egg-info/SOURCES.txt +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres.egg-info/dependency_links.txt +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres.egg-info/requires.txt +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/mtpk_postgres.egg-info/top_level.txt +0 -0
- {mtpk_postgres-0.1.3 → mtpk_postgres-0.1.5}/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.5
|
|
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,24 @@ pip install -e .
|
|
|
54
54
|
|
|
55
55
|
## Historial
|
|
56
56
|
|
|
57
|
+
- **0.1.5**: corrige `AsyncCrudBase.insert()`/`insertar()` (y en general cualquier `AsyncDatabase._query_accion`
|
|
58
|
+
con `RETURNING`), que fallaban siempre con `KeyError: 0`. Las conexiones async se abren con
|
|
59
|
+
`row_factory=dict_row` para que las lecturas devuelvan diccionarios, pero `_query_accion` reutilizaba ese mismo
|
|
60
|
+
row_factory para leer el valor de `RETURNING id` como `fila[0]` — con `dict_row`, `fila` es un `dict` sin clave
|
|
61
|
+
`0`. Ahora ese cursor se abre explícitamente con `row_factory=tuple_row`. Sin este arreglo, cualquier alta de
|
|
62
|
+
registro (`insert`) fallaba siempre; la sincronización de esquema (crear/comparar tablas) no estaba afectada.
|
|
63
|
+
- **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
|
|
64
|
+
en sí. Había dos problemas relacionados: (1) `comparar_generar_alter` comparaba el tipo de columna por su
|
|
65
|
+
nombre "en crudo" (`col.tipo`), así que un modelo que usa alias como `INT`, `TINYINT` o `DATETIME` (en vez de
|
|
66
|
+
`INTEGER`/`SMALLINT`/`TIMESTAMP`, que es como se reconstruyen siempre desde Postgres) nunca coincidía con la
|
|
67
|
+
realidad y proponía un `ALTER COLUMN ... TYPE` en cada ejecución aunque el tipo no hubiera cambiado — inofensivo
|
|
68
|
+
la mayoría de las veces (Postgres permite "cambiar" una columna a su mismo tipo), pero rechazado de plano si otra
|
|
69
|
+
columna `GENERATED` depende de la columna "alterada"; ahora se compara por el tipo canónico
|
|
70
|
+
(`Columna._tipo_base()`). (2) `ManagerDB.aplicar_cambios()` — la ruta que se usa en la primera sincronización de
|
|
71
|
+
una base de datos nueva — creaba todas las tablas del modelo y a continuación las volvía a comparar todas,
|
|
72
|
+
incluidas las que acababa de crear en la misma llamada; una tabla recién creada coincide por definición con el
|
|
73
|
+
modelo que la creó, así que ahora se excluye de esa comparación (que solo tiene sentido sobre tablas
|
|
74
|
+
preexistentes). Esta segunda corrección evita esta clase entera de falsos positivos, no solo el caso ya visto.
|
|
57
75
|
- **0.1.3**: corrige un bug bloqueante en `comparar_generar_alter` — al crear una tabla nueva con una columna
|
|
58
76
|
`GENERATED ALWAYS AS (...) STORED` (p.ej. una columna de búsqueda `tsvector` calculada a partir de otra), la
|
|
59
77
|
siguiente pasada de comparación la trataba como una columna normal (la introspección no reconstruye la expresión
|
|
@@ -34,6 +34,24 @@ pip install -e .
|
|
|
34
34
|
|
|
35
35
|
## Historial
|
|
36
36
|
|
|
37
|
+
- **0.1.5**: corrige `AsyncCrudBase.insert()`/`insertar()` (y en general cualquier `AsyncDatabase._query_accion`
|
|
38
|
+
con `RETURNING`), que fallaban siempre con `KeyError: 0`. Las conexiones async se abren con
|
|
39
|
+
`row_factory=dict_row` para que las lecturas devuelvan diccionarios, pero `_query_accion` reutilizaba ese mismo
|
|
40
|
+
row_factory para leer el valor de `RETURNING id` como `fila[0]` — con `dict_row`, `fila` es un `dict` sin clave
|
|
41
|
+
`0`. Ahora ese cursor se abre explícitamente con `row_factory=tuple_row`. Sin este arreglo, cualquier alta de
|
|
42
|
+
registro (`insert`) fallaba siempre; la sincronización de esquema (crear/comparar tablas) no estaba afectada.
|
|
43
|
+
- **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
|
|
44
|
+
en sí. Había dos problemas relacionados: (1) `comparar_generar_alter` comparaba el tipo de columna por su
|
|
45
|
+
nombre "en crudo" (`col.tipo`), así que un modelo que usa alias como `INT`, `TINYINT` o `DATETIME` (en vez de
|
|
46
|
+
`INTEGER`/`SMALLINT`/`TIMESTAMP`, que es como se reconstruyen siempre desde Postgres) nunca coincidía con la
|
|
47
|
+
realidad y proponía un `ALTER COLUMN ... TYPE` en cada ejecución aunque el tipo no hubiera cambiado — inofensivo
|
|
48
|
+
la mayoría de las veces (Postgres permite "cambiar" una columna a su mismo tipo), pero rechazado de plano si otra
|
|
49
|
+
columna `GENERATED` depende de la columna "alterada"; ahora se compara por el tipo canónico
|
|
50
|
+
(`Columna._tipo_base()`). (2) `ManagerDB.aplicar_cambios()` — la ruta que se usa en la primera sincronización de
|
|
51
|
+
una base de datos nueva — creaba todas las tablas del modelo y a continuación las volvía a comparar todas,
|
|
52
|
+
incluidas las que acababa de crear en la misma llamada; una tabla recién creada coincide por definición con el
|
|
53
|
+
modelo que la creó, así que ahora se excluye de esa comparación (que solo tiene sentido sobre tablas
|
|
54
|
+
preexistentes). Esta segunda corrección evita esta clase entera de falsos positivos, no solo el caso ya visto.
|
|
37
55
|
- **0.1.3**: corrige un bug bloqueante en `comparar_generar_alter` — al crear una tabla nueva con una columna
|
|
38
56
|
`GENERATED ALWAYS AS (...) STORED` (p.ej. una columna de búsqueda `tsvector` calculada a partir de otra), la
|
|
39
57
|
siguiente pasada de comparación la trataba como una columna normal (la introspección no reconstruye la expresión
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# Esta librería es un wrapper (envolvente) del conector psycopg para manejo de Postgres con funciones asíncronas.
|
|
10
10
|
|
|
11
11
|
import psycopg
|
|
12
|
-
from psycopg.rows import dict_row
|
|
12
|
+
from psycopg.rows import dict_row, tuple_row
|
|
13
13
|
from typing import Optional, Union, List, Dict
|
|
14
14
|
from .core_sync import Tabla, SQLLiteral
|
|
15
15
|
from logging import Logger
|
|
@@ -261,9 +261,15 @@ class AsyncDatabase:
|
|
|
261
261
|
INSERT/UPDATE/DELETE/etc. Postgres no tiene `lastrowid`: si la sentencia incluye una
|
|
262
262
|
cláusula RETURNING, el primer valor de la primera fila devuelta se guarda en
|
|
263
263
|
`self.ultimo_insert_id`. Devuelve el número de filas afectadas.
|
|
264
|
+
|
|
265
|
+
El cursor se abre explícitamente con `row_factory=tuple_row`: tanto `conexion` (si viene
|
|
266
|
+
de `transaccion()`) como las conexiones de `_PseudoPool` se crean con `row_factory=dict_row`
|
|
267
|
+
para que `_query_select` devuelva dicts, pero aquí necesitamos `fila[0]` (primera columna
|
|
268
|
+
del RETURNING) — con el row_factory heredado de la conexión, `fila` sería un dict y
|
|
269
|
+
`fila[0]` lanzaría KeyError: 0 en vez de darnos el id.
|
|
264
270
|
"""
|
|
265
271
|
if conexion is not None:
|
|
266
|
-
async with conexion.cursor() as cursor:
|
|
272
|
+
async with conexion.cursor(row_factory=tuple_row) as cursor:
|
|
267
273
|
await cursor.execute(sql, params)
|
|
268
274
|
filas = cursor.rowcount
|
|
269
275
|
if cursor.description:
|
|
@@ -277,7 +283,7 @@ class AsyncDatabase:
|
|
|
277
283
|
|
|
278
284
|
async with self.pool.acquire() as cx:
|
|
279
285
|
try:
|
|
280
|
-
async with cx.cursor() as cursor:
|
|
286
|
+
async with cx.cursor(row_factory=tuple_row) as cursor:
|
|
281
287
|
await cursor.execute(sql, params)
|
|
282
288
|
filas = cursor.rowcount
|
|
283
289
|
if cursor.description:
|
|
@@ -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:
|
|
@@ -750,8 +761,14 @@ class Tabla:
|
|
|
750
761
|
return (val_real, val_def) in equivalentes or (val_def, val_real) in equivalentes or val_real == val_def
|
|
751
762
|
|
|
752
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").
|
|
753
770
|
return (
|
|
754
|
-
|
|
771
|
+
col_real._tipo_base() == col_def._tipo_base() and
|
|
755
772
|
col_real.longitud == col_def.longitud and
|
|
756
773
|
col_real.precision == col_def.precision and
|
|
757
774
|
(col_real.escala or None) == (col_def.escala or None)
|
|
@@ -1207,6 +1224,21 @@ class ManagerDB(Database):
|
|
|
1207
1224
|
)
|
|
1208
1225
|
""")
|
|
1209
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
|
+
|
|
1210
1242
|
# --- Crear tablas nuevas (+ índices + comentarios) ---
|
|
1211
1243
|
for nombre_tabla, tabla in self.tablas.items():
|
|
1212
1244
|
sentencias_creacion = [tabla.to_sql()] + tabla.to_sql_indices() + tabla.to_sql_comentarios()
|
|
@@ -1218,8 +1250,10 @@ class ManagerDB(Database):
|
|
|
1218
1250
|
if self.logger:
|
|
1219
1251
|
self.logger.info(f"Tabla \"{nombre_tabla}\": ejecutado CREATE IF NOT EXISTS.")
|
|
1220
1252
|
|
|
1221
|
-
# --- Aplicar ALTERs ---
|
|
1253
|
+
# --- Aplicar ALTERs (solo en tablas que ya existían antes de esta llamada) ---
|
|
1222
1254
|
for nombre_tabla, tabla in self.tablas.items():
|
|
1255
|
+
if nombre_tabla not in tablas_preexistentes:
|
|
1256
|
+
continue
|
|
1223
1257
|
alter_sqls = tabla.comparar_generar_alter(conexion, permitir_drop=permitir_drop)
|
|
1224
1258
|
for alter_sql in alter_sqls:
|
|
1225
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.5
|
|
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,24 @@ pip install -e .
|
|
|
54
54
|
|
|
55
55
|
## Historial
|
|
56
56
|
|
|
57
|
+
- **0.1.5**: corrige `AsyncCrudBase.insert()`/`insertar()` (y en general cualquier `AsyncDatabase._query_accion`
|
|
58
|
+
con `RETURNING`), que fallaban siempre con `KeyError: 0`. Las conexiones async se abren con
|
|
59
|
+
`row_factory=dict_row` para que las lecturas devuelvan diccionarios, pero `_query_accion` reutilizaba ese mismo
|
|
60
|
+
row_factory para leer el valor de `RETURNING id` como `fila[0]` — con `dict_row`, `fila` es un `dict` sin clave
|
|
61
|
+
`0`. Ahora ese cursor se abre explícitamente con `row_factory=tuple_row`. Sin este arreglo, cualquier alta de
|
|
62
|
+
registro (`insert`) fallaba siempre; la sincronización de esquema (crear/comparar tablas) no estaba afectada.
|
|
63
|
+
- **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
|
|
64
|
+
en sí. Había dos problemas relacionados: (1) `comparar_generar_alter` comparaba el tipo de columna por su
|
|
65
|
+
nombre "en crudo" (`col.tipo`), así que un modelo que usa alias como `INT`, `TINYINT` o `DATETIME` (en vez de
|
|
66
|
+
`INTEGER`/`SMALLINT`/`TIMESTAMP`, que es como se reconstruyen siempre desde Postgres) nunca coincidía con la
|
|
67
|
+
realidad y proponía un `ALTER COLUMN ... TYPE` en cada ejecución aunque el tipo no hubiera cambiado — inofensivo
|
|
68
|
+
la mayoría de las veces (Postgres permite "cambiar" una columna a su mismo tipo), pero rechazado de plano si otra
|
|
69
|
+
columna `GENERATED` depende de la columna "alterada"; ahora se compara por el tipo canónico
|
|
70
|
+
(`Columna._tipo_base()`). (2) `ManagerDB.aplicar_cambios()` — la ruta que se usa en la primera sincronización de
|
|
71
|
+
una base de datos nueva — creaba todas las tablas del modelo y a continuación las volvía a comparar todas,
|
|
72
|
+
incluidas las que acababa de crear en la misma llamada; una tabla recién creada coincide por definición con el
|
|
73
|
+
modelo que la creó, así que ahora se excluye de esa comparación (que solo tiene sentido sobre tablas
|
|
74
|
+
preexistentes). Esta segunda corrección evita esta clase entera de falsos positivos, no solo el caso ya visto.
|
|
57
75
|
- **0.1.3**: corrige un bug bloqueante en `comparar_generar_alter` — al crear una tabla nueva con una columna
|
|
58
76
|
`GENERATED ALWAYS AS (...) STORED` (p.ej. una columna de búsqueda `tsvector` calculada a partir de otra), la
|
|
59
77
|
siguiente pasada de comparación la trataba como una columna normal (la introspección no reconstruye la expresión
|
|
@@ -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.5"
|
|
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
|