Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

403
Vistas
SQLAlchemy con enlaces múltiples: elija dinámicamente enlazar para consultar

Tengo 4 bases de datos diferentes, una para cada uno de mis clientes (clínicas médicas), las cuales tienen exactamente la misma estructura.

En mi aplicación, tengo modelos como Patient , Doctor , Appointment , etc.

Tomemos uno de ellos como ejemplo:

 class Patient(db.Model): __tablename__ = "patients" id = Column(Integer, primary_key=True) first_name = Column(String, index=True) last_name = Column(String, index=True) date_of_birth = Column(Date, index=True)

Descubrí que con la ayuda de enlaces puedo crear diferentes bases de datos y asociar cada modelo a un enlace diferente. Así que tengo esta configuración:

 app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://user:pass@localhost/main' app.config['SQLALCHEMY_BINDS'] = { 'clinic1':'mysql://user:pass@localhost/clinic1', 'clinic2':'mysql://user:pass@localhost/clinic2', 'clinic3':'mysql://user:pass@localhost/clinic3', 'clinic4':'mysql://user:pass@localhost/clinic4' }

Ahora estoy tratando de lograr dos cosas:

  1. Quiero que cuando cree tablas usando db.create_all() cree la tabla de patients en las 4 bases de datos (clinic1->clinic4)
  2. Quiero poder elegir un enlace específico de forma dinámica (en tiempo de ejecución), de modo que cualquier consulta como Patient.query.filter().count() se ejecute en la base de datos de enlace elegida

Idealmente, se comportaría así:

 with DbContext(bind='client1'): patients_count = Patient.query.filter().count() print(patients_count) # outside of the `with` context we are back to the default bind

Sin embargo, haciendo esto:

 patients_count = Patient.query.filter().count()

sin especificar un enlace, generará un error (ya que la tabla de patients no existe en el enlace predeterminado)

¡Cualquier ejemplo de código que pueda guiar cómo se puede hacer esto sería muy apreciado!

PD: Puede ser que sugiera no usar diferentes bases de datos y, en su lugar, usar una con diferentes columnas/tablas, pero siga mi ejemplo e intente explicar cómo se puede hacer esto usando este patrón de múltiples bases de datos idénticas.
¡Gracias!

over 4 years ago · Santiago Trujillo
1 Respuestas
Responde la pregunta

0

1. Crear tablas en todos los enlaces

Observación: db.create_all() llama a self.get_tables_for_bind() .

Solución: anule SQLAlchemy get_tables_for_bind() para admitir '__all__' .

 class MySQLAlchemy(SQLAlchemy): def get_tables_for_bind(self, bind=None): result = [] for table in self.Model.metadata.tables.values(): # if table.info.get('bind_key') == bind: if table.info.get('bind_key') == bind or (bind is not None and table.info.get('bind_key') == '__all__'): result.append(table) return result

Uso:

 # db = SQLAlchemy(app) # Replace this db = MySQLAlchemy(app) # with this db.create_all()

2. Elija un enlace específico dinámicamente

Observación: SignallingSession get_bind() es responsable de determinar el enlace.

Solución:

  1. SignallingSession get_bind() para obtener la clave de vinculación de algún contexto.
  2. Anule SQLAlchemy create_session() para usar nuestra clase de sesión personalizada.
  3. Apoye el contexto para elegir un enlace específico en db para accesibilidad.
  4. Fuerce la especificación de un contexto para las tablas con '__all__' como clave de vinculación, anulando SQLAlchemy get_binds() para restaurar el motor predeterminado.
 class MySignallingSession(SignallingSession): def __init__(self, db, *args, **kwargs): super().__init__(db, *args, **kwargs) self.db = db def get_bind(self, mapper=None, clause=None): if mapper is not None: info = getattr(mapper.persist_selectable, 'info', {}) if info.get('bind_key') == '__all__': info['bind_key'] = self.db.context_bind_key try: return super().get_bind(mapper=mapper, clause=clause) finally: info['bind_key'] = '__all__' return super().get_bind(mapper=mapper, clause=clause) class MySQLAlchemy(SQLAlchemy): context_bind_key = None @contextmanager def context(self, bind=None): _context_bind_key = self.context_bind_key try: self.context_bind_key = bind yield finally: self.context_bind_key = _context_bind_key def create_session(self, options): return orm.sessionmaker(class_=MySignallingSession, db=self, **options) def get_binds(self, app=None): binds = super().get_binds(app=app) # Restore default engine for table.info.get('bind_key') == '__all__' app = self.get_app(app) engine = self.get_engine(app, None) tables = self.get_tables_for_bind('__all__') binds.update(dict((table, engine) for table in tables)) return binds def get_tables_for_bind(self, bind=None): result = [] for table in self.Model.metadata.tables.values(): if table.info.get('bind_key') == bind or (bind is not None and table.info.get('bind_key') == '__all__'): result.append(table) return result

Uso:

 class Patient(db.Model): __tablename__ = "patients" __bind_key__ = "__all__" # Add this

Caso de prueba:

 with db.context(bind='clinic1'): db.session.add(Patient()) db.session.flush() # Flush in 'clinic1' with db.context(bind='clinic2'): patients_count = Patient.query.filter().count() print(patients_count) # 0 in 'clinic2' patients_count = Patient.query.filter().count() print(patients_count) # 1 in 'clinic1'

Acerca de las claves externas que hacen referencia al enlace predeterminado

Tienes que especificar el schema .

Limitaciones:

  • mysql:
    • Los enlaces deben estar en la misma instancia de MySQL. De lo contrario, tiene que ser una columna simple.
    • El objeto externo en el enlace predeterminado ya debe estar comprometido.
      De lo contrario, al insertar un objeto que hace referencia a él, obtendrá este error de bloqueo:

      MySQLdb._exceptions.OperationalError: (1205, 'Se excedió el tiempo de espera de bloqueo; intente reiniciar la transacción')

  • SQLite: las claves externas en las bases de datos no se aplican.

Uso:

 # app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://user:pass@localhost/main' class PatientType(db.Model): __tablename__ = "patient_types" __table_args__ = {"schema": "main"} # Add this, based on database name id = Column(Integer, primary_key=True) # ... class Patient(db.Model): __tablename__ = "patients" __bind_key__ = "__all__" id = Column(Integer, primary_key=True) # ... # patient_type_id = Column(Integer, ForeignKey("patient_types.id")) # Replace this patient_type_id = Column(Integer, ForeignKey("main.patient_types.id")) # with this patient_type = relationship("PatientType")

Caso de prueba:

 patient_type = PatientType.query.first() if not patient_type: patient_type = PatientType() db.session.add(patient_type) db.session.commit() # Commit to reference from other binds with db.context(bind='clinic1'): db.session.add(Patient(patient_type=patient_type)) db.session.flush() # Flush in 'clinic1' with db.context(bind='clinic2'): patients_count = Patient.query.filter().count() print(patients_count) # 0 in 'clinic2' patients_count = Patient.query.filter().count() print(patients_count) # 1 in 'clinic1'
over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda