Anpassade typkonverterare med mssql-python

Som standard konverterar mssql-python-drivrutinen Microsoft SQL-datatyper till lämpliga Python-typer (se Datatypmappningar). Outputkonvertersystemet låter dig åsidosätta detta beteende för specifika SQL-typer, vilket aktiverar:

  • Anpassade typomvandlingar (till exempel money till float i stället för Decimal)
  • Integration med tredjepartsbibliotek (till exempel rumslig data till Shapely-objekt)
  • Affärsspecifik formatering (till exempel datum till anpassade strängformat)

Omvandlare för registerutgångar

Använd add_output_converter() för att registrera en omvandlarfunktion. Nyckeln är antingen en Python-typ som matchar type_code i cursor.description, eller en heltalskod ODBC SQL-typ såsom 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

Välj mellan en Python-typnyckel och en SQL-typkodsnyckel

De två nyckelformerna skiljer sig åt i hur de exakt väljer kolumner:

  • En Python-typnyckel gäller för varje SQL-typ som mappas till den Python-typen. Att registrera en omvandlare för Decimal omvandlar decimal-, numerisk-, penning- och småmyntskolumner .

  • En heltals-SQL-typkodnyckel matchar ODBC-typkoden som kolumnen rapporterar, som är smalare men inte alltid en enda SQL-typ. SQL_NUMERIC transformerar endast numeriska kolumner. SQL_DECIMAL Omvandlar decimal-, money- och smallmoney-kolumnerna , eftersom alla tre rapporterar samma kod.

Använd en heltalsnyckel när en Python-typnyckel är mer generell än du vill, eller när du porterar kod från pyodbc, som använder heltalsnycklar. Använd en Python-typnyckel när du vill att en konverterare ska täcka en hel familj av SQL-typer.

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')

Upplösningsordning för omvandlare

För varje kolumn väljer drivrutinen högst en omvandlare, i denna ordning:

  1. Omvandlaren registrerade kolumnens heltalskod för ODBC SQL-typ.

  2. Konverteraren som är registrerad för Python-typen i cursor.description.

  3. Konverteraren registrerades för SQL_WVARCHAR, men endast när kolumnens Python-typ är str eller bytes.

Omvandlarfunktionssignatur

När du registrerar en konverterare med en Python-typnyckel måste den typ du skickar till add_output_converter() matcha en av följande Python-typer. Kolumnen Konverteraren tar emot visar vad drivrutinen skickar till din funktion:

cursor.description type_code Omvandlaren tar emot Microsoft SQL-typer
int int tinyint, smallint, int, bigint
float float real, float
decimal.Decimal Decimal decimal, numeric, money, smallmoney
str bytes (kodad i 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

Strängtypomvandlare (strnyckel) får rå bytes i UTF-16LE-kodning, inte avkodade Python-strängar. Alla andra typer får det redan konverterade Python-objektet.

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)

Hantera omvandlare

Använd dessa metoder för att inspektera eller ta bort konverterare som redan är registrerade på en anslutning.

Skaffa befintlig omvandlare

Hämta omvandlarfunktionen som för närvarande är registrerad för en typ:

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

Ta bort en omvandlare

Avregistrera en omvandlare så att drivrutinen återgår till standardkonvertering för den typen:

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

Rensa alla omvandlare

Återställ alla registrerade omvandlare till att använda Microsoft SQL:s standardtypmappningar:

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

Vanliga omvandlarmönster

Dessa exempel visar de mest frekvent behövda omvandlarfunktionerna.

Omvandla VARCHAR till versaler

Strängtypomvandlare tar emot råa bytes i UTF-16LE-kodning:

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

Konvertera pengar till flytande medel

Som standard returnerar drivrutinen typerna DECIMAL/NUMERIC som decimal.Decimal. Konvertera till 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'>

Parse JSON-data

Microsoft SQL Server kan lagra JSON som text. Automatiskt parsa till Python-objekt:

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

Detta exempel konverterar ALLA strängvärden (nvarchar, varchar, xml). JSON-parsning försöks på varje strängkolumn. I praktiken, tillämpa JSON-parsning selektivt istället för som en blanket-omvandlare.

Anpassad formatering av datum och tid

Konvertera datetime till ISO-strängformat:

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

Säkerhetsfrågor

Caution

När du registrerar en utgångsomvandlare körs funktionen du tillhandahåller på varje matchande databasvärde.

  • Registrera endast konverterare från betrodd kod.
  • Acceptera aldrig omvandlarfunktioner från användarens inmatning.
  • Illvilliga konverterare kan köra godtycklig kod eller läcka data.
# 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)

Exempel: Integration av rumsliga data

Integrera rumslig data med Shapely-biblioteket:

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

Att registrera en bytes omvandlare påverkar ALLA binära kolumner (varbinära, geografi, geometri). Använd clear_output_converters() efter rumsliga frågor om du också frågar efter icke-rumsliga binärdata.

Exempel: Anpassad radformatering

Skapa en dataklassomvandlare:

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

Omvandlarens omfattning och livslängd

  • Anslutningsbegränsad: Drivrutinen registrerar konverterare per anslutning, inte globalt.
  • Beständig: Omvandlare förblir aktiva tills du tar bort dem eller stänger anslutningen.
  • Inte ärvda: Nya anslutningar ärver inte omvandlare från andra anslutningar.
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

Prestandaöverväganden

  • Drivrutinen anropar konverterare för varje värde av den registrerade typen.
  • Håll omvandlarfunktionerna effektiva.
  • För frågor med hög volym, överväg om konvertering är nödvändig.
  • Profilera prestandan om omvandlare påverkar genomströmningen.
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