Implementare un ORM di base per PostgreSQL in Python

Un ORM (Object-Relational Mapper) consente di interagire con il database usando oggetti Python invece di scrivere query SQL manualmente. In questo articolo vedremo come creare un semplice ORM da zero per PostgreSQL usando la libreria psycopg2.

Requisiti

Assicurati di avere installato:

  • Python 3.7+
  • PostgreSQL
  • psycopg2
pip install psycopg2

1. Connessione al database

Partiamo creando una classe che gestisce la connessione al database.

import psycopg2

class Database:
    def __init__(self, dsn):
        self.dsn = dsn
        self.conn = psycopg2.connect(dsn)
        self.conn.autocommit = True

    def execute(self, query, params=None):
        with self.conn.cursor() as cur:
            cur.execute(query, params)

    def fetchall(self, query, params=None):
        with self.conn.cursor() as cur:
            cur.execute(query, params)
            return cur.fetchall()

    def fetchone(self, query, params=None):
        with self.conn.cursor() as cur:
            cur.execute(query, params)
            return cur.fetchone()

2. Definizione del modello base

Creiamo ora una classe base per i modelli, da cui erediteranno le tabelle.

class Model:
    db = None  # Da impostare esternamente

    @classmethod
    def create_table(cls):
        fields = []
        for name, type_ in cls.__annotations__.items():
            sql_type = 'SERIAL PRIMARY KEY' if name == 'id' else 'TEXT'
            fields.append(f"{name} {sql_type}")
        table = cls.__name__.lower()
        sql = f"CREATE TABLE IF NOT EXISTS {table} ({', '.join(fields)});"
        cls.db.execute(sql)

    def save(self):
        fields = [f for f in self.__annotations__ if f != 'id']
        values = [getattr(self, f) for f in fields]
        placeholders = ', '.join(['%s'] * len(values))
        sql = f"INSERT INTO {self.__class__.__name__.lower()} ({', '.join(fields)}) VALUES ({placeholders}) RETURNING id;"
        result = self.db.fetchone(sql, values)
        self.id = result[0]

    @classmethod
    def all(cls):
        sql = f"SELECT * FROM {cls.__name__.lower()};"
        rows = cls.db.fetchall(sql)
        instances = []
        for row in rows:
            obj = cls()
            for key, val in zip(cls.__annotations__.keys(), row):
                setattr(obj, key, val)
            instances.append(obj)
        return instances

3. Esempio di utilizzo

Ora possiamo definire un modello e usarlo per interagire con il database.

class User(Model):
    id: int
    name: str
    email: str

# Connessione al database
db = Database("dbname=test user=postgres password=secret")
Model.db = db

# Creazione tabella
User.create_table()

# Inserimento dati
u = User()
u.name = "Mario Rossi"
u.email = "mario@example.com"
u.save()

# Query
users = User.all()
for user in users:
    print(user.id, user.name, user.email)

Conclusione

Abbiamo costruito un ORM minimale che permette di mappare oggetti Python su una tabella PostgreSQL. Sebbene molto semplice, questo approccio dimostra i concetti fondamentali dietro gli ORM come SQLAlchemy o Django ORM. Si consiglia di estendere questa base con supporto per tipi di dati più precisi, vincoli, relazioni e validazioni.

Torna su