Migra da pyodbc a mssql-python

Il driver mssql-python è il driver Python di prima parte di Microsoft per Microsoft SQL. Se preferisci un'opzione driver mantenuta da Microsoft, offre:

  • Nessuna dipendenza da driver ODBC esterni.
  • Pooling di connessione integrato.
  • Supporto moderno per Python 3.10+.
  • Autenticazione nativa Microsoft Entra.

Differenze principali

Feature pyodbc mssql-python
Stile dei parametri qmark (?) qmark (?) e pyformat (%(name)s)
Driver ODBC è richiesto No
Pool di connessioni Esterno Predefinito
Versione minima di Python 3.6 3.10
callproc() Supportato Non implementato
Autocommit predefinito Off Off

Passaggi base della migrazione

I passaggi seguenti coprono le modifiche chiave per migrare un'applicazione pyodbc su mssql-python.

1. Aggiornare le importazioni

Sostituisci l'importazione pyodbc con mssql_python:

Prima (pyodbc):

import pyodbc

Dopo (mssql-python):

import mssql_python

2. Aggiornare stringhe di connessione

Rimuovi la DRIVER= parola chiave e aggiorna il metodo di autenticazione:

In precedenza (pyodbc, richiede il driver ODBC):

conn = pyodbc.connect(
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=localhost;"
    "DATABASE=AdventureWorks2022;"
    "Trusted_Connection=yes;"
)

Dopo (mssql-python, nessun driver necessario, usando l'autenticazione di Microsoft Entra):

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes;"
)

3. Mantieni le tue query così come sono

Il driver mssql-python supporta entrambi gli stili di parametri ? (qmark) e %(name)s (pyformat). Le tue query esistenti ? funzionano senza cambiamenti:

Prima (pyodbc):

cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))

Dopo (mssql-python, la stessa query):

cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Color = ?", (1, "Red"))

4. Mantieni executemany così com'è

Le chiamate esistenti executemany con tuple e ? marker funzionano senza modifiche:

Prima (pyodbc):

cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)

Dopo (mssql-python, stesso codice):

cursor.execute("DROP TABLE IF EXISTS #MigrateDemo")
cursor.execute("CREATE TABLE #MigrateDemo (ID INT, Name NVARCHAR(50))")
data = [(1, "Alice"), (2, "Bob"), (3, "Carol")]
cursor.executemany("INSERT INTO #MigrateDemo (ID, Name) VALUES (?, ?)", data)

Migrazione delle procedure memorizzate

Il driver mssql-python non implementa callproc(). Le sezioni seguenti mostrano come usarlo EXECUTE invece.

Usa EXECUTE per le stored procedure

Il driver pyodbc supporta callproc(), ma il driver mssql-python no. Usare EXECUTE invece:

Prima (pyodbc):

cursor.callproc("dbo.uspGetEmployeeManagers", (5,))
results = cursor.fetchall()

Dopo (mssql-python):

cursor.execute(
    "EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = %(id)s",
    {"id": 5}
)
results = cursor.fetchall()
print(f"Got {len(results)} rows")

Parametri di output

Usa variabili T-SQL per catturare i valori di output invece di affidarti ai callproc() parametri di output:

Prima (pyodbc, usando callproc):

params = (category_id, pyodbc.SQL_INTEGER)
cursor.callproc("dbo.GetProductCount", params)
count = params[1].value

Dopo (mssql-python, usando variabili T-SQL):

cursor.execute(
    """
    DECLARE @count INT;
    SELECT @count = COUNT(*) FROM Production.Product
    WHERE ProductSubcategoryID = %(cat_id)s;
    SELECT @count AS ProductCount;
    """,
    {"cat_id": 1}
)
product_count = cursor.fetchval()
print(f"Product count: {product_count}")

Migrazioni specifiche per funzionalità

Le sezioni seguenti trattano specifiche caratteristiche pyodbc e i loro equivalenti mssql-python.

Stringhe di connessione

Parola chiave pyodbc parola chiave mssql-python Notes
DRIVER={...} Non necessario Il driver ODBC è integrato internamente.
SERVER= Server= Nessuna modifica del comportamento.
DATABASE= Database= Nessuna modifica del comportamento.
Trusted_Connection= Trusted_Connection= Nessuna modifica del comportamento.
UID= / PWD= UID= / PWD= Nessuna modifica del comportamento.
Authentication= Authentication= Accetta gli stessi valori.

Autocommit

Il comportamento dell'autocommit è identico in entrambi i driver:

pyodbc:

conn.autocommit = True
pyodbc.connect(connection_string, autocommit=True)

MSSQL-Python:

conn.autocommit = True

Inserimenti in blocco

Per velocizzare i batch di grandi dimensioni INSERT, gli utenti di pyodbc impostano fast_executemany = True. Il driver mssql-python ottimizza executemany già per i batch parametrizzati, quindi inserti moderati non hanno bisogno di flag speciale. Per carichi di dati elevati, preferisci bulkcopy(), che trasmette le righe tramite il protocollo di copia in massa ed è molto più veloce rispetto all'emissione di singole INSERT sentenzioni. Per il flusso di lavoro completo, vedi Usa copia in massa.

pyodbc:

cursor.fast_executemany = True
cursor.executemany(query, data)

Dopo (mssql-python), batch di dimensioni moderate con executemany:

cursor.execute("DROP TABLE IF EXISTS #BulkTarget")
cursor.execute("CREATE TABLE #BulkTarget (ID INT, Name NVARCHAR(50))")
data = [(i, f"Item {i}") for i in range(100)]
cursor.executemany("INSERT INTO #BulkTarget (ID, Name) VALUES (?, ?)", data)
conn.commit()

Dopo (mssql-python), carichi grandi con bulkcopy (preferibile):

cursor.execute("IF OBJECT_ID('##BulkTarget') IS NOT NULL DROP TABLE ##BulkTarget")
cursor.execute("CREATE TABLE ##BulkTarget (ID INT, Name NVARCHAR(50))")
conn.commit()  # Commit DDL before bulkcopy
data = [(i, f"Item {i}") for i in range(100)]
result = cursor.bulkcopy("##BulkTarget", data)
print(f"Bulk copied {result['rows_copied']} rows")
cursor.execute("DROP TABLE ##BulkTarget")
conn.commit()

Factory di righe

Il driver mssql-python restituisce Row oggetti che supportano l'accesso agli attributi di default, senza richiedere una custom row factory:

PYODBC (fabbrica di file personalizzate):

def namedtuple_row_factory(cursor):
    from collections import namedtuple
    columns = [col[0] for col in cursor.description]
    Row = namedtuple("Row", columns)
    return Row

mssql-python (accesso agli attributi di default):

cursor.execute("SELECT Name, ListPrice FROM Production.Product")
row = cursor.fetchone()
print(row.Name)   # Attribute access works directly
print(row[0])     # Index access also works

Gestione degli errori

Il driver mssql-python utilizza la stessa gerarchia delle eccezioni di pyodbc, quindi la maggior parte dei gestori di eccezioni richiede solo un cambio di nome del modulo.

Gerarchia delle eccezioni

I nomi delle classi di eccezione corrispondono direttamente da un driver all'altro:

pyodbc:

try:
    cursor.execute(query)
except pyodbc.Error as e:
    pass
except pyodbc.DatabaseError as e:
    pass
except pyodbc.OperationalError as e:
    pass

MSSQL-Python:

try:
    cursor.execute("SELECT TOP 1 * FROM Production.Product")
    print(cursor.fetchone())
except mssql_python.Error as e:
    pass
except mssql_python.DatabaseError as e:
    pass
except mssql_python.OperationalError as e:
    pass

Dettagli dell'errore

Entrambi i driver espongono i dettagli dell'errore tramite gli argomenti dell'eccezione:

pyodbc:

try:
    cursor.execute(query)
except pyodbc.Error as e:
    sqlstate = e.args[0]
    message = e.args[1]

MSSQL-Python:

try:
    cursor.execute("SELECT TOP 1 * FROM NonExistentTable_XYZ")
except mssql_python.Error as e:
    # Error message contains SQLSTATE and details
    print(str(e))

Pool di connessioni

Il driver mssql-python include di default il pool di connessioni, quindi non sono più necessarie librerie di pooling esterne.

Rimuovere il pool esterno

Se hai usato il pool esterno con pyodbc, il driver mssql-python lo ha integrato:

Prima (pool esterno di pyodbc):

from dbutils.pooled_db import PooledDB

pool = PooledDB(pyodbc, 5, driver="{ODBC Driver 18 for SQL Server}",
                server="your_server", database="your_database",
                uid="your_username", pwd="your_password")
conn = pool.connection()

Dopo (pooling integrato di mssql-python):

conn = mssql_python.connect(connection_string)
conn.close()

Configura il pool

Sostituisci la dimensione predefinita del pool e il timeout con mssql_python.pooling():

import mssql_python

mssql_python.pooling()

Esempio di migrazione completa

Quanto segue mostra la stessa funzione scritta con pyodbc e poi riscritta con mssql-python.

Prima (pyodbc)

Questa versione utilizza la stringa di connessione pyodbc con una DRIVER parola chiave:

import pyodbc
from datetime import date

def get_orders(customer_id: int, start_date: date):
    conn = pyodbc.connect(
        "DRIVER={ODBC Driver 18 for SQL Server};"
        "SERVER=localhost;"
        "DATABASE=AdventureWorks2022;"
        "Trusted_Connection=yes;"
    )
    cursor = conn.cursor()

    cursor.execute("""
        SELECT SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = ? AND OrderDate >= ?
        ORDER BY OrderDate DESC
    """, (customer_id, start_date))

    orders = []
    for row in cursor:
        orders.append({
            "id": row.SalesOrderID,
            "date": row.OrderDate,
            "total": row.TotalDue
        })

    cursor.close()
    conn.close()
    return orders

Dopo (mssql-python)

Questa versione elimina la DRIVER parola chiave. Tutte le query, i parametri e i modelli di accesso alle righe rimangono identici:

import mssql_python
from datetime import date

def get_orders(customer_id: int, start_date: date):
    conn = mssql_python.connect(
        "Server=localhost;"
        "Database=AdventureWorks2022;"
        "Trusted_Connection=yes;"
    )
    cursor = conn.cursor()

    cursor.execute("""
        SELECT SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = ? AND OrderDate >= ?
        ORDER BY OrderDate DESC
    """, (customer_id, start_date))

    orders = []
    for row in cursor:
        orders.append({
            "id": row.SalesOrderID,
            "date": row.OrderDate,
            "total": row.TotalDue
        })

    cursor.close()
    conn.close()
    return orders

Le uniche modifiche sono l'istruzione import e la stringa di connessione (non è necessaria la parola chiave DRIVER). Ogni query, parametro, pattern di fetch e accesso alla riga rimane identico.

Verifica della migrazione

Prima di completare la migrazione, esegui le stesse query su entrambi i driver e confronta i risultati per confermare il comportamento equivalente.

Verifica il comportamento equivalente

Usa una funzione di confronto che esegue la stessa query su entrambi i driver e afferma che i risultati corrispondono:

import pyodbc
import mssql_python

def compare_results(pyodbc_conn_str: str, mssql_conn_str: str, query: str):
    """Compare results from both drivers."""
    # pyodbc query
    pyodbc_conn = pyodbc.connect(pyodbc_conn_str)
    pyodbc_cursor = pyodbc_conn.cursor()
    pyodbc_cursor.execute(query)
    pyodbc_results = pyodbc_cursor.fetchall()
    pyodbc_conn.close()
    
    # mssql-python query
    mssql_conn = mssql_python.connect(mssql_conn_str)
    mssql_cursor = mssql_conn.cursor()
    mssql_cursor.execute(query)
    mssql_results = mssql_cursor.fetchall()
    mssql_conn.close()
    
    # Compare
    assert len(pyodbc_results) == len(mssql_results)
    for p_row, m_row in zip(pyodbc_results, mssql_results):
        assert tuple(p_row) == tuple(m_row)
    
    print(f"Results match: {len(pyodbc_results)} rows")

Checklist

  • [ ] Aggiorna le importazioni da pyodbc a mssql_python.
  • [ ] Rimuovi DRIVER= dalle stringhe di connessione.
  • [ ] Conserva le query dei parametri esistenti ? (funzionano as-is).
  • [ ] Usa le istruzioni EXECUTE per chiamare le stored procedure.
  • [ ] Rimuovere la configurazione del pool di connessione esterna.
  • [ ] Aggiornare i nomi delle classi di gestione delle eccezioni.
  • [ ] Testa tutte le query e le procedure memorizzate.
  • [ ] Verifica la gestione dei tipi di dati (specialmente decimali e date).
  • [ ] Rimuovere il driver ODBC dai requisiti di implementazione.