Metadata-Version: 2.4
Name: mtpk_postgres
Version: 0.1.6
Summary: Librería para sincronización estructural de bases de datos Postgres con modelos Python (adaptación de mtpk_mariadb)
Author-email: José Jesús Andrés Zambrana <jjandres@multiplika.es>
License: MIT
Project-URL: Homepage, https://github.com/jjandres/mtpk_postgres
Project-URL: Repository, https://github.com/jjandres/mtpk_postgres
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Database
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: psycopg[binary,pool]
Requires-Dist: pydantic
Requires-Dist: bcrypt
Dynamic: license-file

# mtpk_postgres

Adaptación a Postgres (vía [psycopg 3](https://www.psycopg.org/psycopg3/)) de `mtpk_mariadb`: define tus tablas con
dataclasses (`Tabla`, `Columna`, `ForeignKey`, `Index`), sincroniza la estructura de la base de datos automáticamente
(CREATE/ALTER TABLE, índices, claves foráneas) y usa `AsyncCrudBase` para CRUD asíncrono.

No modifica ni depende de `mtpk_mariadb`; es un paquete independiente. Ver `docs/mtpk-postgres-adaptacion.md` en el
repo para el detalle de las diferencias frente a la versión MariaDB.

## Instalación

```
pip install -e .
```

## Notas de la adaptación

- Identificadores citados con `"comillas dobles"` en vez de backticks.
- `auto_increment=True` genera `GENERATED ALWAYS AS IDENTITY` en vez de `AUTO_INCREMENT`; como Postgres no tiene
  `lastrowid`, las inserciones vía `AsyncCrudBase.insert()`/`insertar()` añaden `RETURNING id`.
- `Tabla.to_sql()` ya no incluye índices ni comentarios inline (Postgres no lo permite dentro de `CREATE TABLE`):
  usa `Tabla.to_sql_indices()` y `Tabla.to_sql_comentarios()` para las sentencias `CREATE INDEX` y `COMMENT ON`
  correspondientes. `ManagerDB` ya las ejecuta automáticamente al crear tablas.
- `ENUM`/`SET` se traducen a `TEXT` + `CHECK`; al reconstruir la estructura desde la base de datos ambos se
  reportan como `ENUM` (Postgres no permite distinguirlos después del hecho).
- A diferencia de MariaDB, el DDL en Postgres es transaccional: los métodos de `ManagerDB` confirman (`COMMIT`)
  explícitamente tras crear tablas, triggers y procedimientos.
- Columnas `VECTOR(n)` (extensión [pgvector](https://github.com/pgvector/pgvector), hay que crearla antes con
  `CREATE EXTENSION IF NOT EXISTS vector;`): `Columna(tipo="VECTOR", longitud=n)`. Es una extensión propia de este
  paquete, no existe en `mtpk-mariadb`.
- Índices con método (`GIN`, `GIST`, `HNSW`/`IVFFlat` de pgvector, etc.): `Index(columnas=[...], metodo="hnsw",
  opclass="vector_cosine_ops", with_opciones="m = 16, ef_construction = 64")`. `metodo` es incompatible con `unico`
  (Postgres no permite índices UNIQUE con esos métodos).

## Historial

- **0.1.5**: corrige `AsyncCrudBase.insert()`/`insertar()` (y en general cualquier `AsyncDatabase._query_accion`
  con `RETURNING`), que fallaban siempre con `KeyError: 0`. Las conexiones async se abren con
  `row_factory=dict_row` para que las lecturas devuelvan diccionarios, pero `_query_accion` reutilizaba ese mismo
  row_factory para leer el valor de `RETURNING id` como `fila[0]` — con `dict_row`, `fila` es un `dict` sin clave
  `0`. Ahora ese cursor se abre explícitamente con `row_factory=tuple_row`. Sin este arreglo, cualquier alta de
  registro (`insert`) fallaba siempre; la sincronización de esquema (crear/comparar tablas) no estaba afectada.
- **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
  en sí. Había dos problemas relacionados: (1) `comparar_generar_alter` comparaba el tipo de columna por su
  nombre "en crudo" (`col.tipo`), así que un modelo que usa alias como `INT`, `TINYINT` o `DATETIME` (en vez de
  `INTEGER`/`SMALLINT`/`TIMESTAMP`, que es como se reconstruyen siempre desde Postgres) nunca coincidía con la
  realidad y proponía un `ALTER COLUMN ... TYPE` en cada ejecución aunque el tipo no hubiera cambiado — inofensivo
  la mayoría de las veces (Postgres permite "cambiar" una columna a su mismo tipo), pero rechazado de plano si otra
  columna `GENERATED` depende de la columna "alterada"; ahora se compara por el tipo canónico
  (`Columna._tipo_base()`). (2) `ManagerDB.aplicar_cambios()` — la ruta que se usa en la primera sincronización de
  una base de datos nueva — creaba todas las tablas del modelo y a continuación las volvía a comparar todas,
  incluidas las que acababa de crear en la misma llamada; una tabla recién creada coincide por definición con el
  modelo que la creó, así que ahora se excluye de esa comparación (que solo tiene sentido sobre tablas
  preexistentes). Esta segunda corrección evita esta clase entera de falsos positivos, no solo el caso ya visto.
- **0.1.3**: corrige un bug bloqueante en `comparar_generar_alter` — al crear una tabla nueva con una columna
  `GENERATED ALWAYS AS (...) STORED` (p.ej. una columna de búsqueda `tsvector` calculada a partir de otra), la
  siguiente pasada de comparación la trataba como una columna normal (la introspección no reconstruye la expresión
  de generación) y proponía un `ALTER COLUMN` sobre ella justo después de crearla — algo que Postgres puede
  rechazar cuando otras columnas dependen de la generada. Como `aplicar_cambios()` ejecuta todo en una única
  transacción, ese fallo deshacía también la creación de las demás tablas. Ahora las columnas `generado` nunca se
  comparan ni se alteran una vez existen (para cambiar su expresión hay que borrarlas y recrearlas a mano); de paso,
  `Columna.to_sql()` ya respeta `not_null=True` en columnas generadas (antes se ignoraba).
- **0.1.2**: soporte nativo para columnas `VECTOR(n)` (pgvector) e índices con método (`USING gin/hnsw/...`,
  `opclass`, `WITH (...)`) en `Index`. La introspección (`obtener_columnas_postgres`) ahora reconoce las columnas
  `vector` por su `udt_name` y reconstruye su dimensión leyendo el catálogo (`pg_attribute`/`format_type`); sin esto,
  `data_type` llegaba como `'USER-DEFINED'` y `comparar_generar_alter` proponía un `ALTER COLUMN` en cada ejecución
  aunque la columna no hubiera cambiado.
- **0.1.1**: corrige un bug bloqueante — `Tabla` no enlazaba `indice.tabla`/`fk.tabla`/`columna.tabla` cuando
  `columnas`/`indices`/`foreign_keys` se pasaban directamente por el constructor (solo lo hacían `add_index()` y
  compañía). Como `Index.to_sql()` necesita esa referencia para generar el `CREATE INDEX`, cualquier tabla definida
  de la forma normal y con al menos un índice fallaba con `ValueError` al sincronizar el esquema. Se añadió
  `Tabla.__post_init__` para enlazarlos siempre.
- **0.1.0**: primera versión publicada.
