SqlAlchemy Intro (3): Metadata.


*Disclaimer: Hoy es lunes, y mañana será martes. "Deal with it!"

TLDR: Diferentes maneras de crear tablas en una base de datos. Mi intención es utilizar la metodologia ORM. Las tablas se crean extendiendo una clase Base.

Siguiendo con la guía de introducción a SQLAlchemy.

Componentes de la base de datos: Metadata

Los cimientos de las interacciones de SQLAlchemy con la base de datos son un conjuto de objetos Python que representan conceptos de la base de datos, el conjunto de estos objetos se denomina "database metadata". Algunos de estos objetos son; tablas, columnas...

Una base de datos puede tener diferentes conjutos de metadatos, pero habitualmente si usamos tablas relacionadas entre ellas es mejor usar un solo objeto de metadatos.

>>> from sqlalchemy import MetaData
>>> metadata = MetaData()

Estructura básica: "Table".

Las tablas ("Table"), son la unidad básica del conjunto de metadatos. Hay dos formas de crear una tabla reflejarla o declararla.

Declarar tablas.

Declarar una tabla significa definir una tabla para añadirla al conjunto de metadatos. Hay tres formas de declarar:

Usando Core.

Creamos la tabla como una instancia de la clase "Table".

>>> from sqlalchemy import Table, Column, Integer, String
>>> from sqlalchemy import ForeignKey
>>> user_table = Table(
...     "user_account",
...     metadata,
...     Column('id', Integer, primary_key=True),
...     Column('name', String(30)),
...     Column('fullname', String)
... )
>>> address_table = Table(
...     "address",
...     metadata,
...     Column('id', Integer, primary_key=True),
...     Column('user_id', ForeignKey('user_account.id'), nullable=False),
...     Column('email_address', String, nullable=False)
... )

De esta forma las tabla se agregan a la metadata. "Column" es una clase que representa una columna de la tabla. "Integer" o "String" los tipos de datos que contendrá la columna ("Column and Data Types").

También se puden crear relaciones y restricciones entre tablas. En la tabla denominada "user_account" se crea una clave primaria con el atributo de la columna primary_key=True. Como contraparte, en la tabla "address" se crea una columna denominada "user_id" que mediante la función ForeignKey()hace referencia a la clave primaria antes mencionada. Al utilizar ForeignKey() no es necesario definir el tipo de dato de la columna, ya que heredará el tipo de dato de la clave primaria.

Además del tipo de dato y de la restricción de clave externa, también se pueden crear restricciones de obligatoriedad. Al indicar nullable=False restingimos la columna a NO estar vacía.

Para crear las tablas en la base de datos utilizaremos la función "MetaData.create_all()".

metadata.create_all(engine)

Usando ORM.

Para crear tablas usando la metodología ORM utilizaremos una clase base, y a partir de esa clase crearemos otras clases que representarán las diferentes tablas.

Para crear la clase base utilizaremos la función "declarative_base()".

from sqlalchemy.orm import declarative_base
Base = declarative_base()

Esta función además de crear una clase base, también crea un registro ("registry"), y éste crea la metadata.

Una vez definida la clae Base, podemos definir las tablas.

>>> from sqlalchemy.orm import relationship
>>> class User(Base):
...     __tablename__ = 'user_account'
...
...     id = Column(Integer, primary_key=True)
...     name = Column(String(30))
...     fullname = Column(String)
...
...     addresses = relationship("Address", back_populates="user")
...
...     def __repr__(self):
...        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

>>> class Address(Base):
...     __tablename__ = 'address'
...
...     id = Column(Integer, primary_key=True)
...     email_address = Column(String, nullable=False)
...     user_id = Column(Integer, ForeignKey('user_account.id'))
...
...     user = relationship("User", back_populates="addresses")
...
...     def __repr__(self):
...         return f"Address(id={self.id!r}, email_address={self.email_address!r})"

Estas clases creadas también incluyen objetos tipo tabla como los creados en la sección del uso de la metodologia "Core". Se puede acceder a éstos objetos con el atributo .__table__. Este objeto se crea graicas al atributo de la clase Base denominado .__tablename__.

En este ejemplo también se ha utilizado la restricción de clave primaria y clave externa. Para crear esta relación se utiliza la función "relationship()".

Para crear las tablas en la base de datos utilizaremos "Metadata.create_all()".

Base.metadata.create_all(engine)

Usando un método híbrido.

En el método híbrido crearemos la tabla como si utilizaramos el método "Core", y posteriormente la integraremos en la clase "Base" del método "ORM".

class User(Base):
    __table__ = user_table

     addresses = relationship("Address", back_populates="user")

     def __repr__(self):
        return f"User({self.name!r}, {self.fullname!r})"

class Address(Base):
    __table__ = address_table

     user = relationship("User", back_populates="addresses")

     def __repr__(self):
         return f"Address({self.email_address!r})"

En este caso en vez de utilizar el atributo .__tablename__ para crear el objeto "table", directamente usamos el atributo .__table__ y le asignamos el objeto creador mediate el método "Core".

Reflejar tablas.

Este método de creación de tablas no me acaba de quedar del todo claro. Parece que en vez de crear una tabla en la metadata, lo que hace es copiar la estructura de una tabla creada previamente. No creo que vaya a usar este sistema, para más información aquí y aquí.