Migrazione da pymssql 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 FreeTDS.
  • Più cursori concorrenti per connessione.
  • Pooling di connessione integrato.
  • Supporto moderno per Python 3.10+.
  • Autenticazione nativa Microsoft Entra.
  • Oggetti riga con accesso agli attributi per impostazione predefinita.

Differenze principali

Feature pymssql mssql-python
Stile dei parametri format (%s, %d) qmark (?) e pyformat (%(name)s)
Biblioteca nativa FreeTDS DDBC (confezionato)
Pool di connessioni Esterno Predefinito
Cursori per connessione 1 Multiple
Versione minima di Python 3.6 3.10
callproc() Supportato Non implementato
as_dict cursore Extension Oggetti di riga (predefinito)
Copia in blocco conn.bulk_copy() cursor.bulkcopy()
Autocommit predefinito Off Off

Passaggi base della migrazione

I passaggi seguenti illustrano i cambiamenti più comuni necessari per migrare un'applicazione pymssql in mssql-python.

1. Aggiornare le importazioni

Sostituisci l'importazione pymssql con mssql_python:

Prima (pymssql):

import pymssql

Dopo (mssql-python):

import mssql_python

2. Aggiorna le chiamate di connessione

PymsSQL utilizza argomenti posizionali. Il driver mssql-python utilizza una stringa di connessione o argomenti per parole chiave:

Prima (pymssql, argomenti posizionali):

conn = pymssql.connect("<server>", "<user>", "<password>", "<database>")

Prima (pymssql, argomentazioni per parole chiave):

conn = pymssql.connect(
    host=r"<server>\<instance>",
    user="<login>",
    password="<password>",
    database="<database>"
)

Dopo (mssql-python, stringa di connessione):

conn = mssql_python.connect(
    "Server=<server>;"
    "Database=<database>;"
    "UID=<username>;"
    "PWD=<password>;"
    "Encrypt=yes;"
)

Dopo (mssql-python, consigliato da Microsoft Entra):

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

3. Aggiornare i marcatori dei parametri

pymssql utilizza i segnaposto di formato %s e %d. Il driver mssql-python utilizza ? (qmark) o %(name)s (pyformat):

Prima di (pymssql):

cursor.execute("SELECT * FROM Person.Person WHERE BusinessEntityID = %d AND FirstName = %s", (user_id, name))

Dopo (mssql-python, stile qmark):

user_id, name = 1, "Ken"
cursor.execute("SELECT * FROM Person.Person WHERE BusinessEntityID = ? AND FirstName = ?", (user_id, name))
print(cursor.fetchone())

cursor.execute(
    "SELECT * FROM Person.Person WHERE BusinessEntityID = %(id)s AND FirstName = %(name)s",
    {"id": user_id, "name": name}
)
print(cursor.fetchone())

4. Aggiorna esecuzioni

Aggiorna i segnaposto SQL da %s/%d a ? o :%(name)s

Prima (pymssql):

cursor.execute("CREATE TABLE #Persons (ID INT, Name NVARCHAR(50), Department NVARCHAR(50))")
cursor.executemany(
    "INSERT INTO #Persons VALUES (%d, %s, %s)",
    [(1, "John", "Sales"), (2, "Jane", "Marketing")]
)

Dopo (mssql-python, stile qmark):

cursor.execute("IF OBJECT_ID('#Persons') IS NOT NULL DROP TABLE #Persons")
cursor.execute("CREATE TABLE #Persons (ID INT, Name NVARCHAR(50), Department NVARCHAR(50))")
cursor.executemany(
    "INSERT INTO #Persons VALUES (?, ?, ?)",
    [(1, "John", "Sales"), (2, "Jane", "Marketing")]
)
cursor.execute("SELECT * FROM #Persons")
for row in cursor:
    print(row)

5. Usa gli attributi Row invece dei cursori as_dict

PymsSQL richiede as_dict=True di accedere alle colonne per nome. Il driver mssql-python restituisce Row oggetti che supportano sia l'accesso agli attributi che all'indice di default:

Prima (pymssql):

cursor = conn.cursor(as_dict=True)
cursor.execute("SELECT BusinessEntityID, FirstName FROM Person.Person WHERE FirstName = %s", ("John",))
for row in cursor:
    print("ID=%d, Name=%s" % (row["BusinessEntityID"], row["FirstName"]))

Dopo (mssql-python, accesso agli attributi per impostazione predefinita):

cursor = conn.cursor()
cursor.execute("SELECT BusinessEntityID, FirstName FROM Person.Person WHERE FirstName = ?", ("John",))
for row in cursor:
    print(f"ID={row.BusinessEntityID}, Name={row.FirstName}")
    # Index access also works: row[0], row[1]

Migrazione delle procedure memorizzate

Il driver mssql-python non implementa callproc(). Usa invece le istruzioni EXECUTE.

Usa EXECUTE per le stored procedure

PymsSQL supporta callproc(), ma il driver MSSQL-Python no. Usare EXECUTE invece:

Prima (pymssql):

cursor.callproc("uspGetEmployeeManagers", (5,))
for row in cursor:
    print(row)

Dopo (mssql-python):

cursor.execute("EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = ?", (5,))
for row in cursor:
    print(row)

Parametri di output

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

Prima di (pymssql):

cursor.callproc("GetProductCount", (category_id,))
count = cursor.fetchval()

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

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

Migrazione tramite copia in blocco

PymsSQL chiama bulk_copy() sulla connessione. Il driver mssql-python chiama bulkcopy() sul cursore con ulteriori opzioni:

Prima di (pymssql):

conn.bulk_copy("##BulkDemo", [(1, 2)] * 1000)
conn.commit()

Dopo (mssql-python):

cursor = conn.cursor()
cursor.execute("CREATE TABLE ##BulkDemo (Col1 INT, Col2 INT)")
conn.commit()
result = cursor.bulkcopy("##BulkDemo", [(1, 2)] * 1000)
print(f"Copied {result['rows_copied']} rows")
conn.commit()
cursor.execute("DROP TABLE ##BulkDemo")
conn.commit()

Il metodo mssql-python bulkcopy() supporta batch_size, timeout, column_mappings, keep_identity, check_constraintstable_lock, keep_nulls, , fire_triggers, , e use_internal_transaction. Vedi Copia in blocco per i dettagli.

Cursori multipli

PymsSQL permette un solo cursore attivo per connessione. Il driver mssql-python supporta più cursori concorrenti:

Prima (pymssql):

c1 = conn.cursor()
c1.execute("SELECT TOP 5 * FROM Person.Person")
c2 = conn.cursor()
c2.execute("SELECT TOP 5 * FROM Sales.SalesOrderHeader")
c1.fetchall()

Dopo (mssql-python):

c1 = conn.cursor()
c1.execute("SELECT TOP 5 BusinessEntityID, FirstName FROM Person.Person")
persons = c1.fetchall()

c2 = conn.cursor()
c2.execute("SELECT TOP 5 SalesOrderID FROM Sales.SalesOrderHeader")
orders = c2.fetchall()
print(f"Persons: {len(persons)}, Orders: {len(orders)}")

Pool di connessioni

pymssql non ha un supporto integrato per il pooling. Il driver mssql-python lo include automaticamente:

Prima (pymssql, pool esterno richiesto):

from dbutils.pooled_db import PooledDB
pool = PooledDB(pymssql, host="server", user="user", password="pwd", database="db")
conn = pool.connection()

Dopo mssql-python, il pooling è automatico:

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

Gestione degli errori

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

Prima (pymssql):

try:
    cursor.execute(query)
except pymssql.OperationalError as e:
    print(f"Operation failed: {e}")
except pymssql.InterfaceError as e:
    print(f"Interface error: {e}")

Dopo (mssql-python):

try:
    cursor = conn.cursor()
    cursor.execute("SELECT TOP 1 * FROM Production.Product")
    row = cursor.fetchone()
    print(row)
except mssql_python.OperationalError as e:
    print(f"Operation failed: {e}")
except mssql_python.InterfaceError as e:
    print(f"Interface error: {e}")

Esempio di migrazione completa

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

Precedente (pymssql)

Questa versione utilizza argomenti di connessione posizionali, as_dict=True, e marcatori di parametro %d:

import pymssql

def get_orders(customer_id: int):
    conn = pymssql.connect("<server>", "<user>", "<password>", "<database>")
    cursor = conn.cursor(as_dict=True)

    cursor.execute("""
        SELECT TOP 10 SalesOrderID, OrderDate, TotalDue
        FROM Sales.SalesOrderHeader
        WHERE CustomerID = %d
        ORDER BY OrderDate DESC
    """, (customer_id,))

    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)

I principali cambiamenti strutturali sono i marcatori dei parametri, lo stile di connessione e l'accesso alle righe:

import mssql_python

def get_orders(customer_id: int):
    conn = mssql_python.connect(
        "Server=<server>;"
        "Database=<database>;"
        "UID=<username>;"
        "PWD=<password>;"
    )
    cursor = conn.cursor()

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

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

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

I cambiamenti strutturali sono:

  1. import pymssqlimport mssql_python.
  2. Argomenti posizionali di connessione → stringa di connessione con parole chiave.
  3. %d marcatore parametro → ?.
  4. cursor(as_dict=True)cursor() con accesso agli attributi (row.SalesOrderID invece di row["SalesOrderID"]).

Checklist

  • [ ] Aggiorna le importazioni da pymssql a mssql_python.
  • [ ] Converti le chiamate di connessione da argomenti posizionali a stringhe di connessione.
  • [ ] Converti i marcatori di parametro %s/%d in ? o %(name)s.
  • [ ] Usa le istruzioni EXECUTE per chiamare le stored procedure.
  • [ ] Usa l'accesso agli attributi Row invece dei cursori as_dict=True.
  • [ ] Migra conn.bulk_copy() su cursor.bulkcopy().
  • [ ] Rimuovere la configurazione del pool di connessione esterna.
  • [ ] Rimuovere FreeTDS dai requisiti di dispiegamento.
  • [ ] Aggiornare i nomi delle classi di gestione delle eccezioni.
  • [ ] Testa tutte le query e le procedure memorizzate.