Mostrando entradas con la etiqueta importar. Mostrar todas las entradas
Mostrando entradas con la etiqueta importar. Mostrar todas las entradas

martes, 15 de abril de 2025

Python (XXVI) Sqlalchemy: Localizar e importar todas las clases a persistir. Modificar las tablas cuando la clase asociada cambia

1. Importar todas las clases a persistir con sqlalchemy

Para ello debemos tener todos los módulos que definen las clases dentro de una carpeta, en mi caso "models"

Creamos un módulo dentro de la carpeta "models" que recoja toda las clases y así solo tenemos que importar el array de clases a persistri.

El módulo se llama xmallmodels.py, y la variable que contendrá la clases es model_classes  y su código es:

import importlib
import pkgutil
import inspect
#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod
sys.path.append(str(path_root1))
path_root0 = Path(__file__).parents[0] # El mismo directorio
sys.path.append(str(path_root0))
from basicutils.xmdb import Base
import models
# ------Fin imprescindible

model_classes = []

for loader, module_name, is_pkg in pkgutil.iter_modules(models.__path__):
    module = importlib.import_module(f"models.{module_name}")
    
    for name, obj in inspect.getmembers(module, inspect.isclass):
        # Ensure it's defined in the current module and is a subclass of Base (but not Base itself)
        if obj.__module__ == module.__name__ and issubclass(obj, Base) and obj is not Base:
            model_classes.append(obj)

# Example: register or print them
for cls in model_classes:
    print(f"Found model class: {cls.__name__} from {cls.__module__}")


Por ejemplo en nuestro programa de arranque, que intenta vincular los modelos con la BD, basta con importar model_classes desde este módulo. El código de este módulo mnu_main.py es:

#!/home/eduard/MyPython/11.softprop-01/venv_softprop/bin/python3

#1. Imports
from sqlalchemy import Table
#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod
sys.path.append(str(path_root1))
path_root0 = Path(__file__).parents[0] # El mismo directorio
sys.path.append(str(path_root0))
from menus.mnu_fh import fh, app
#OJO: No eliminar las dependencias marcadas en gris (routes01mnu, routes02form, routes03grid,app) pues, sinó falla el programa
from menus import routes00comp, routes01mnu, routes02form, routes03grid # No eliminar !!!
from menus.mnu_fh import fh, app                         # No eliminar app !!!
from basicutils import xmdb
#--- Definición de tablas de la Base de datos postgres
from models.xmallmodels import model_classes
# ------Fin imprescindible

from basicutils import xmdb 

# Create database tables
xmdb.Base.metadata.create_all(bind=xmdb.engine)

# Execute the web server, but now not using fh.serve but uvicorm.run instead
#fh.serve()
if __name__ == "__main__":
	import uvicorn
	app.mount("/static", fh.StaticFiles(directory="/home/eduard/MyPython/11.softprop-01/static"), name="static")
	#uvicorn.run(app, host="0.0.0.0", port=5001, 
	#    ssl_keyfile="/home/eduard/MyPython/11.softprop-01/static/certs/wildcard2023Nginx.rsa", 
	#    ssl_certfile="/home/eduard/MyPython/11.softprop-01/static/certs/wildcard2023Nginx.crt")

	cert_path="/home/eduard/MyPython/11.softprop-01/static/certs/wildcard.municipio.es."
	#uvicorn.run(app, host="edu.tavernes.es", port=5001,	
	uvicorn.run(app, host="192.168.XXX.XXX", port=5001,	
		ssl_keyfile =cert_path+"key", 
		ssl_certfile=cert_path+"crt")


2. Modificar las tablas cuya clase asociada cambia

Para ello se buscan las clases que deriven de la clase Base, y si estan asociadas a una tabla (tienen el campo __tablename__) y se compara los atributos de la clase con las columnas de la tabla; y se añaden columnas  o se eliminan en base a los atributos de las clases.

Veamos el código del módulo xmdbupdate.py

''' Actualización de la estructura de tablas en función
     de las clases asociadas de SQLAlchemy.
'''

from sqlalchemy.sql import text
from sqlalchemy.inspection import inspect
from sqlalchemy.exc import ProgrammingError


#------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
path_root1 = Path(__file__).parents[1] # Damos un salto de directorio hacia arriba-> ximoutilsmod
sys.path.append(str(path_root1))
path_root0 = Path(__file__).parents[0] # El mismo directorio
sys.path.append(str(path_root0))
#OJO: No eliminar las dependencias marcadas en gris
#--- Definición de tablas de la Base de datos postgresfrom basicutils.xmdb import Base 
from basicutils import xmdb
from models.xmallmodels import model_classes
# ------Fin imprescindible

#-----------------------------------------------------------
# --- Configuration ---
session = xmdb.session_local()

def all_table_subclasses(cls):
    """Recursively find all subclasses of a class that has a table assigned."""
    subclasses = set()

    for subclass in cls.__subclasses__():
        print (subclass.__name__,getattr(subclass, '__tablename__', ''), getattr(subclass, '__abstract__', False))
        if len(getattr(subclass, '__tablename__', '')) > 0:
            subclasses.add(subclass)
        subclasses.update(all_table_subclasses(subclass))

    return subclasses



# --- Update function ---
def update_table_structure(cls):
    table_name = cls.__tablename__
    table_args = cls.__table_args__
    schema = table_args.get('schema') if isinstance(table_args, dict) else None
    inspector = inspect(xmdb.engine)

    if not inspector.has_table(table_name, schema=schema):
        print(f"Table '{schema}.{table_name}' does not exist. Creating it...")
        cls.__table__.create(xmdb.engine)
        return

    db_columns = {col["name"]: col for col in inspector.get_columns(table_name, schema=schema)}
    model_columns = {col.name: col for col in cls.__table__.columns}

    #Execute a transaction
    with xmdb.engine.begin() as conn:
        # Add missing columns
        for name, column in model_columns.items():
            if name not in db_columns:
                col_type = column.type.compile(xmdb.engine.dialect)
                nullable = "NULL" if column.nullable else "NOT NULL"
                alter = f'ALTER TABLE "{schema}"."{table_name}" ADD COLUMN "{name}" {col_type} {nullable};'
                print("Adding:", alter)
                conn.execute(text(alter))

        # Drop extra columns
        for name in db_columns:
            if name not in model_columns:
                alter = f'ALTER TABLE "{schema}"."{table_name}" DROP COLUMN "{name}";'
                print("Dropping:", alter)
                try:
                    conn.execute(text(alter))
                except ProgrammingError as e:
                    print(f"Could not drop column {name}: {e}")

# --- Run for all subclasses of Base for updating the tables---
def update_all_tables(base):
    for cls in all_table_subclasses(base):
        update_table_structure(cls)

if __name__ == "__main__":
	update_all_tables(xmdb.Base)
	session.close()
	xmdb.engine.dispose()
	




martes, 18 de junio de 2024

Python (V) . Importar módulos de otras carpetas. Entornos virtuales python: Crear, activar, desactivar, listar librerias instaladas. requirememts.txt

 0. Introducción


Para poder hacer referencia a módulos que están en otra carpeta, mirar a ver si dicha referencia está en sys.path

para ver lo que hay en el sys.path 

import sys
print(sys.path)

Para añadir una ruta al syspath 

sys.path.append('/home/ximo/miruta')

Para ver como obtenemos las rutas del fichero hasta la raiz del disco:

from pathlib import Path

for path in Path(__file__).parents:
    print(str(path))

Y obtenemos 

/home/ximo/MyPython/prova03_ref_library/lib02
/home/ximo/MyPython/prova03_ref_library
/home/ximo/MyPython
/home/ximo
/home
/


1. Solución 1: Añadir la carpeta raiz al sys.path. Si funciona por línea de comandos.

Supongamos que la librería raiz es root y esta tiene dos hijas lib01 y lib02

Supongamos que lib01 tiene dos hijas lib01_01 y lib01_02 y cada hija tiene un fichero python

Supongamos que lib02 tiene una hija lib02_01

prova03_ref_library
    lib01
        lib01_01
            proga.py
        lib01_02
            progb.py
    lib02
        lib02_01
            progc.py

desde progc.py queremos llamar a los otros progs, para ello se propone buscar la ruta de la carpeta padre del proyecto (a la carpeta prova03_ref_library)  y a partir de ahí hacer los imports como lib01e.carpeta_hijo.modulo. Por ejemplo lib01.lib01_01.proga

Veamos la ruta absuluta de la carpeta que contien a progc.py

/home/ximo/MyPython/prova03_ref_library/lib02/lb02_01

Observamos que hay 2 saltos a carpetas padres para pasar de la carpeta lib02_01 a la prov03_ref_library por tanto la ruta del padre se obtiene con 

Path(__file__).parents[2]

Por tanto si añadimos la expresión anterior al sys.path, ya podemos referenciar los módulos y carpetas que cuelgan de ella.

# progc.py

from pathlib import Path
import sys

path_root2 = Path(__file__).parents[2]
sys.path.append(str(path_root2))

import lib01.lib01_01.proga as proga
import lib01.lib01_02.progb as progb

# O también
#from lib01.lib01_01 import proga as proga
#from lib01.lib01_02 import progb as progb

Ahora no tenemos ningún problema para hacer estas importaciones

OJO: A veces falla si desde otro módulo importamos un módulo que importa otro. Si importamos desde la misma carpeta hay que hacer el "try-except"

# ------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
# ------Metemos tres niveles desde la caprta actual (o) hasta el abuelo (2)
for i in range(3):sys.path.append(str(Path(__file__).parents[i])) from basicutils import xmutils, xmexcel, xmpolish, xmsql, xmodoo try: # if in the same folder then needs a try and import directly from castillautils import xmcastilla except Exception as error: import xmcastilla # ------Fin imprescindible

2. Solución 2: Definir la carpeta raiz en el launch.json. NO FUNCIONA POR LINEA DE COMANDOS

Supongamos el ejemplo anterior

El nuevo launch.json será

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {"PYTHONPATH":"${workspaceRoot}"}, // modify PYTHONPATH
}
]
}

Y el fichero se simplificaría

import lib01.lib01_01.proga as proga
import lib01.lib01_02.progb as progb

# O también
#from lib01.lib01_01 import proga as proga
#from lib01.lib01_02 import progb as progb

3. Usar entorno virtual python en la configuración

Se puede crear un entorno virtual de python de varias maneras. Una es de la forma tracicional de situarse mendiante una ventana de comandos dentro de la carpeta del proyecto y ejecutar

#Opcion 1: Crearlo en una ruta relativa
python3 -m venv my-venv
#ACTIVARLO
source my-venv/bin/activate

#ACTIVARLO en WINDOWS
my-venv\Scripts\activate.bat

#---------------SI HAY PROBLEMAS DE POR COPIAR UN PROYECTO ANTERIOR
#---------------SE RECOMIENDA TRABAJAR EN RUTAS ABSOLUTAS 
python3 -m venv ~/MyPython/control-presencia/my-venv
#ACTIVARLO
source ~/MyPython/control-presencia/my-venv/bin/activate

o por contra desde vscode darle a Crtl-Shit-p y seleccionar Python: create environment

Una vez creado se puede crear una configuración (launch.json) dándole al botón triangular y dándole al vínculo "create a launch.json"


Y se copia este fichero, donde se especifica una ruta apython. Ojo en windows hay que indicar el nombre de ejecutable (${workspaceRoot}/.venv/bin/python.exe

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"python": "${workspaceRoot}/my-venv/bin/python", //Linux
"python": "${workspaceRoot}/my-venv/Scripts/python.exe", //Windows!!!
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {"PYTHONPATH":"${workspaceRoot}"}, // modify PYTHONPATH
}
]
}

 Para desactivar el virtual environment

deactivate

Para ver que entorno actual tenemos

echo $VIRTUAL_ENV

4. Ver las librerias instaladas en un entorno virtual. "requirements.txt"

Se puede listar sin detalles, con detalles o incluso crear el requirements.txt

#Primeramente hay qie activar el entorno virtual
venv/bin/activate.sh

#listar sin detalles
pip list

#listar con detalles
pip freeze


#crear requirements.txt
peep freeze > requirements.txt







lunes, 1 de febrero de 2021

Postgres: Importación de un fichero en una tabla. Solucuionar el error "invalid byte sequence for encoding "UTF8": 0xd1 0x41"

 Se va seguir lo expuesto en stackoverflow


Problema:

1. Se ha generado un fichero "txt" desde windows, llamado "file01.txt"

2. Se ha creado una tabla XIMO01 con un único campo c1 varchar de longitud 1000

3. Dentro de PgAdmin ejecutamos

COPY miesquema."XIMO01" (c1) FROM '/home/ximo/file01.txt'

y contesta

ERROR: invalid byte sequence for encoding "UTF8": 0xd1 0x41 CONTEXT: COPY EDU01, line 12 SQL state: 22021

Solución:

1. Pasamos el fichero con final de línea CRLF a LF :

 dos2unix -b file01.txt

2. Miramos que codificación tiene tiene el fichero

file file01.txt

y nos devuelve 

file01.txt: ISO-8859 text, with very long lines

hacemos la conversion a UTF-8 (ojo: el formato que acepta es ISO-8859-15 y no ISO-8859 !!!!)

iconv -f ISO-8859-15 -t UTF-8 < file01.txt > output01.txt

y ahora hacemos

COPY miesquema."XIMO01" (c1) FROM '/home/ximo/output01.txt'

y ya lo carga