Convertitori di tipi personalizzati con mssql-python

Di default, il driver mssql-python converte i tipi di dati Microsoft SQL in tipi Python appropriati (vedi Mappaggi dei tipi dati). Il sistema di convertitore di output ti permette di sovrascrivere questo comportamento per tipi SQL specifici, abilitando:

  • Trasformazioni di tipo personalizzate (ad esempio, da money a float invece che a Decimal)
  • Integrazione con librerie di terze parti (ad esempio, dati spaziali verso oggetti Shapely)
  • Formattazione specifica per l'azienda (ad esempio, date a formati di stringhe personalizzate)

Convertitori per l'uscita del registro

Usalo add_output_converter() per registrare una funzione di convertimento. La chiave è un tipo di Python che corrisponde a type_code in cursor.description, oppure un codice intero del tipo SQL ODBC come mssql_python.SQL_DECIMAL:

import mssql_python
from datetime import datetime

def my_datetime_converter(value):
    """Convert datetime to a custom display string."""
    if value is None:
        return None
    return value.strftime("%B %d, %Y at %I:%M %p")

conn = mssql_python.connect(connection_string)
conn.add_output_converter(datetime, my_datetime_converter)

cursor = conn.cursor()
cursor.execute("SELECT CAST('2026-07-16T10:30:00' AS DATETIME2)")
print(cursor.fetchval())  # July 16, 2026 at 10:30 AM

Scegli tra una chiave di tipo Python e una chiave di codice di tipo SQL

Le due forme chiave differiscono per come selezionano con precisione le colonne:

  • Una chiave di tipo Python si applica a ogni tipo SQL che si mappa a quel tipo Python. La registrazione di un convertitore per Decimal trasforma le colonne decimal, numeric, money e smallmoney.

  • Una chiave di codice di tipo SQL intero corrisponde al codice ODBC che la colonna riporta, che è più ristretto ma non sempre è un singolo tipo SQL. SQL_NUMERIC trasforma solo colonne numeriche . SQL_DECIMAL Trasforma le colonne decimale, denaro e smallmoney , perché tutte e tre riportano lo stesso codice.

Usa una chiave intera quando una chiave di tipo Python è più ampia di quanto desideri, oppure quando porti codice da pyodbc, che usa chiavi intere. Usa una chiave di tipo Python quando vuoi che un convertitore copra un'intera famiglia di tipi SQL.

import mssql_python

def tag_decimal(value):
    return f"D:{value}"

conn.add_output_converter(mssql_python.SQL_DECIMAL, tag_decimal)

cursor.execute("""
    SELECT CAST(12.34 AS decimal(10,2)),
           CAST(56.78 AS numeric(10,2)),
           CAST(90.12 AS money)
""")
print(cursor.fetchone())  # ('D:12.34', Decimal('56.78'), 'D:90.1200')

Ordine della risoluzione del convertitore

Per ogni colonna, il driver seleziona al massimo un convertitore, in quest'ordine:

  1. Il convertitore registrava il codice intero ODBC SQL della colonna.

  2. È il convertitore registrato per il tipo Python in cursor.description.

  3. Il convertitore registrava per SQL_WVARCHAR, ma solo quando il tipo Python della colonna è str o bytes.

Firma della funzione convertitore

Quando registri un convertitore con una chiave di tipo Python, il tipo che passi a add_output_converter() deve corrispondere a uno dei seguenti tipi Python. La colonna ricevuto dal convertitore mostra quali valori il driver passa alla tua funzione:

cursor.description type_code Il convertitore riceve Tipi SQL di Microsoft
int int tinyint, smallint, intbigint
float float real, float
decimal.Decimal Decimal decimal, numeric, moneysmallmoney
str bytes (con codifica UTF-16LE) char, varchar, nchar, nvarchar, xml
datetime.datetime datetime datetime, datetime2, smalldatetime
datetime.date date date
bytes bytes varbinary, binary, geographygeometry
bool bool bit

Note

I convertitori di tipo stringa (strchiavi) ricevono il grezzo bytes nella codifica UTF-16LE, non le stringhe Python decodificate. Tutti gli altri tipi ricevono l'oggetto Python già convertito.

from decimal import Decimal

def decimal_converter(value: Decimal | None) -> float | None:
    if value is None:
        return None
    # value is a Decimal object for numeric/money types
    return float(value)

Gestire i convertitori

Usa questi metodi per ispezionare o rimuovere convertitori già registrati su una connessione.

Ottieni il convertitore esistente

Recupera la funzione convertitore attualmente registrata per un tipo:

converter = conn.get_output_converter(str)
if converter:
    print(f"Converter registered: {converter}")
else:
    print("Using default conversion")

Rimuovere un convertitore

Sblocca un convertitore in modo che il driver torni alla conversione predefinita per quel tipo:

conn.remove_output_converter(str)
# str columns now use default conversion

Cancella tutti i convertitori

Resetta tutti i convertitori registrati per utilizzare le mappature di tipo predefinite di Microsoft SQL:

conn.clear_output_converters()
# All types now use default conversion

Modelli di convertitore comuni

Questi esempi mostrano le funzioni di convertitore più frequentemente necessarie.

Converti VARCHAR in maiuscolo

I convertitori di tipo stringa ricevono byte grezzi nella codifica UTF-16LE:

def uppercase_converter(value):
    if value is None:
        return None
    return value.decode('utf-16-le').upper()

conn.add_output_converter(str, uppercase_converter)

cursor = conn.cursor()
cursor.execute("SELECT 'hello world'")
print(cursor.fetchval())  # HELLO WORLD

Converti il denaro in float

Di default, il driver restituisce i tipi DECIMAL/NUMERIC come decimal.Decimal. Converti in float:

from decimal import Decimal

def money_to_float(value):
    if value is None:
        return None
    return float(value)

conn.add_output_converter(Decimal, money_to_float)

cursor = conn.cursor()
cursor.execute("SELECT CAST(19.99 AS MONEY)")
result = cursor.fetchval()
print(type(result))  # <class 'float'>

Analizza i dati JSON

Microsoft SQL Server può memorizzare JSON come testo. Conversione automatica in oggetti Python:

import json

def json_converter(value):
    if value is None:
        return None
    text = value.decode('utf-16-le')
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        return text  # Return as string if not valid JSON

conn.add_output_converter(str, json_converter)

cursor = conn.cursor()
cursor.execute("SELECT '{\"name\": \"Widget\", \"price\": 19.99}'")
data = cursor.fetchval()
print(data['name'])  # Widget

Attenzione

Questo esempio converte TUTTI i valori delle stringhe (nvarchar, varchar, xml). Si tenta l'analisi JSON su ogni colonna di stringhe. In pratica, applica l'analisi JSON in modo selettivo invece che come convertitore a copertura.

Formattazione personalizzata con data-orario

Converti data e ora in formato stringa ISO:

from datetime import datetime

def datetime_to_iso(value):
    if value is None:
        return None
    return value.isoformat()

conn.add_output_converter(datetime, datetime_to_iso)

cursor = conn.cursor()
cursor.execute("SELECT TOP 1 ModifiedDate FROM Production.Product")
print(cursor.fetchval())  # 2014-02-08T10:01:36.827000

Considerazioni relative alla sicurezza

Attenzione

Quando registri un convertitore di output, la funzione che fornisci viene eseguita su ogni valore corrispondente del database.

  • Registra solo i convertitori da codice affidabile.
  • Non accettare mai le funzioni del convertitore tramite input dell'utente.
  • I convertitori dannosi potrebbero eseguire codice arbitrario o far fuggire dati.
# DANGEROUS - never do this
def unsafe_example(user_converter_code):
    converter_func = eval(user_converter_code)  # Security risk!
    conn.add_output_converter(str, converter_func)

Esempio: Integrazione dei dati spaziali

Integra i dati spaziali con la libreria Shapely:

from shapely import wkb

def geometry_converter(value):
    """Convert WKB binary to Shapely geometry object."""
    if value is None:
        return None
    return wkb.loads(value)

conn.add_output_converter(bytes, geometry_converter)

# Query must use STAsBinary() to get standard WKB format
cursor = conn.cursor()
cursor.execute(
    "SELECT SpatialLocation.STAsBinary() FROM Person.Address "
    "WHERE SpatialLocation IS NOT NULL AND City = 'Seattle'"
)
for row in cursor:
    shape = row[0]
    print(f"Point: ({shape.x:.4f}, {shape.y:.4f})")

Note

La registrazione di un bytes convertitore riguarda TUTTE le colonne binarie (varbinaria, geografia, geometria). Usa clear_output_converters() dopo le query spaziali se interroghi anche dati binari non spaziali.

Esempio: Formattazione personalizzata delle righe

Crea un convertitore di classi dati:

from dataclasses import dataclass

@dataclass
class Product:
    id: int
    name: str
    price: float

def fetch_products_as_dataclass(cursor):
    """Convert rows to Product dataclass instances."""
    cursor.execute(
        "SELECT ProductID, Name, ListPrice FROM Production.Product "
        "WHERE ListPrice > 0"
    )
    results = []
    for row in cursor:
        results.append(Product(
            id=row.ProductID,
            name=row.Name,
            price=float(row.ListPrice)
        ))
    return results

products = fetch_products_as_dataclass(cursor)
for p in products[:5]:
    print(f"{p.name}: ${p.price:.2f}")

Scopo del convertitore e durata del convertitore

  • Con ambito di connessione: Il driver registra i convertitori per ogni connessione, non a livello globale.
  • Persistente: I convertitori rimangono attivi finché non li rimuovi o chiudi la connessione.
  • Non ereditato: le nuove connessioni non ereditano convertitori da altre connessioni.
conn1 = mssql_python.connect(connection_string)
conn1.add_output_converter(str, uppercase_converter)

conn2 = mssql_python.connect(connection_string)
# conn2 does NOT have the converter registered

Considerazioni sulle prestazioni

  • Il driver chiama i convertitori per ogni valore del tipo registrato.
  • Mantieni le funzioni del convertitore efficienti.
  • Per le query ad alto volume, valuta se la conversione sia necessaria.
  • Analizza le prestazioni se i convertitori influiscono sulla velocità effettiva.
import time

def slow_converter(value):
    time.sleep(1)  # Avoid time consuming routines like this - adds 1 second for each value processed!
    return str(value) if value else None