Convertisseurs de types personnalisés avec mssql-python

Par défaut, le pilote mssql-python convertit les types de données SQL de Microsoft en types Python correspondants (voir Correspondance des types de données). Le système de conversion de sortie vous permet de remplacer ce comportement pour des types SQL spécifiques, en permettant :

  • Transformations de types personnalisées (par exemple, de money en float au lieu de Decimal)
  • Intégration avec des bibliothèques tierces (par exemple, données spatiales vers des objets Shapely)
  • Formatage spécifique à l’entreprise (par exemple, conversion des dates en formats de chaîne personnalisés)

Enregistrer des convertisseurs de sortie

Utilisez add_output_converter() pour enregistrer une fonction de convertisseur. La clé est soit un type Python correspondant à type_code dans cursor.description, soit un code entier ODBC SQL tel que 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

Choisissez entre une clé de type Python et une clé de code de type SQL

Les deux formes clés diffèrent dans la précision de la sélection des colonnes :

  • Une clé de type Python s’applique à chaque type SQL qui correspond à ce type Python. L’enregistrement d’un convertisseur pour Decimal transforme les colonnes decimal, numeric, money et smallmoney.

  • Une clé de code de type SQL entier correspond au code ODBC que la colonne rapporte, qui est plus étroit mais n’est pas toujours un seul type SQL. SQL_NUMERIC ne transforme que les colonnes numériques . SQL_DECIMAL transforme les colonnes decimal, money et smallmoney, car toutes les trois renvoient ce même code.

Utilisez une clé d'entières lorsqu'une clé de type Python est plus large que ce que vous souhaitez, ou lorsque vous portez du code depuis pyodbc, qui utilise des clés d'entiers. Utilisez une clé de type Python lorsque vous voulez qu’un convertisseur couvre toute une famille de types 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')

Ordre de résolution du convertisseur

Pour chaque colonne, le pilote sélectionne au maximum un convertisseur, dans cet ordre :

  1. Le convertisseur s’est enregistré pour le code entier type ODBC SQL de la colonne.

  2. Le convertisseur a été enregistré pour le type Python dans cursor.description.

  3. Le convertisseur s'est enregistré pour SQL_WVARCHAR, mais seulement lorsque le type Python de la colonne est str ou bytes.

Signature d’une fonction de conversion

Lorsque vous enregistrez un convertisseur avec une clé de type Python, le type que vous passez à add_output_converter() doit correspondre à l’un des types Python suivants. La colonne Valeur reçue par le convertisseur indique ce que le pilote transmet à votre fonction :

cursor.description type_code Le convertisseur reçoit Types SQL de Microsoft
int int tinyint, smallint, int, bigint
float float real, float
decimal.Decimal Decimal decimal, numeric, money, smallmoney
str bytes (encodé en UTF-16LE) char, varchar, nchar, nvarchar, xml
datetime.datetime datetime datetime, datetime2, smalldatetime
datetime.date date date
bytes bytes varbinary, binary, geography, geometry
bool bool bit

Note

Les convertisseurs de type chaîne (strkey) reçoivent des chaînes brutes bytes en codage UTF-16LE, et non des chaînes Python décodées. Tous les autres types reçoivent l’objet Python déjà converti.

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)

Gérer les convertisseurs

Utilisez ces méthodes pour inspecter ou retirer les convertisseurs déjà enregistrés sur une connexion.

Obtenir le convertisseur existant

Récupérer la fonction de convertisseur actuellement enregistrée pour un type :

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

Retirez un convertisseur

Désenregistrer un convertisseur pour que le pilote revienne à la conversion par défaut pour ce type :

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

Effacer tous les convertisseurs

Réinitialisez tous les convertisseurs enregistrés pour utiliser les correspondances de type par défaut de Microsoft SQL :

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

Patrons de convertisseur courants

Ces exemples montrent les fonctions de convertisseur les plus fréquemment nécessaires.

Convertir VARCHAR en majuscules

Les convertisseurs de type chaîne reçoivent des octets bruts en codage 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

Convertir de money en float

Par défaut, le pilote retourne les types DÉCIMAUX/NUMÉRIQUES comme decimal.Decimal. Convertir en 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'>

Analyser les données JSON

Microsoft SQL Server peut stocker du JSON sous forme de texte. Analyse automatique en objets 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

Caution

Cet exemple convertit TOUTES les valeurs de chaînes (nvarchar, varchar, xml). L’analyse JSON est tentée sur chaque colonne de chaînes. En pratique, appliquez l’analyse JSON de manière sélective plutôt que comme un convertisseur global.

Formatage personnalisé de la date et de l’heure

Convertir la date et l’heure au format de chaîne 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

Considérations relatives à la sécurité

Caution

Lorsque vous enregistrez un convertisseur de sortie, la fonction que vous fournissez s’exécute sur chaque valeur correspondante de la base de données.

  • N’enregistrez que des convertisseurs provenant d’un code fiable.
  • N’acceptez jamais de fonctions de conversion provenant des entrées utilisateur.
  • Les convertisseurs malveillants pouvaient exécuter un code arbitraire ou faire fuir des données.
# 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)

Exemple : Intégration des données spatiales

Intégrez les données spatiales avec la bibliothèque 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

L’enregistrement d’un bytes convertisseur affecte TOUTES les colonnes binaires (varbinaire, géographie, géométrie). Utilisez clear_output_converters() après des requêtes spatiales si vous effectuez également des requêtes sur des données binaires non spatiales.

Exemple : formatage de lignes personnalisé

Créez un convertisseur de classes de données :

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}")

Portée du convertisseur et durée de vie

  • Étendu à la connexion : le pilote enregistre les convertisseurs par connexion, pas globalement.
  • Persistant : Les convertisseurs restent actifs jusqu’à ce que vous les retiriez ou fermiez la connexion.
  • Non hérités : Les nouvelles connexions n’héritent pas des convertisseurs d’autres connexions.
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

Considérations relatives aux performances

  • Le conducteur appelle des convertisseurs pour chaque valeur du type enregistré.
  • Gardez les fonctions du convertisseur efficaces.
  • Pour les requêtes à grand volume, considérez si la conversion est nécessaire.
  • Analysez les performances si les convertisseurs ont une incidence sur le débit.
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