Niestandardowe konwertery typów w mssql-python

Domyślnie sterownik mssql-python konwertuje typy danych Microsoft SQL na odpowiednie typy Python (patrz mapowania typów danych). System konwerterów wyjściowych pozwala nadpisać to zachowanie dla konkretnych typów SQL, umożliwiając:

  • Niestandardowe transformacje typów (na przykład money to float zamiast Decimal)
  • Integracja z bibliotekami firm trzecich (na przykład dane przestrzenne do obiektów Shapely)
  • Formatowanie specyficzne dla firmy (na przykład daty do niestandardowych formatów ciągów znaków)

Zarejestruj przetworniki wyjściowe

Użyj add_output_converter() do rejestracji funkcji konwertera. Kluczem jest albo typ w języku Python, który odpowiada elementowi cursor.description w type_code, albo całkowitoliczbowy kod typu SQL ODBC, taki jak 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

Wybierz między kluczem typu Python a kodem SQL

Dwie kluczowe formy różnią się tym, jak precyzyjnie wybierają kolumny:

  • Klucz typu Python jest stosowany do każdego typu SQL mapującego się na ten typ Python. Zarejestrowanie konwertera dla Decimal przekształca kolumny decimal, numeric, money i smallmoney.

  • Klucz całkowitoliczbowego kodu typu SQL odpowiada kodowi typu ODBC zgłaszanemu przez kolumnę, który jest bardziej zawężony, ale nie zawsze stanowi pojedynczy typ SQL. SQL_NUMERIC przekształca tylko kolumny liczbowe . SQL_DECIMAL przekształca kolumny decimal, money i smallmoney, ponieważ wszystkie trzy zwracają ten sam kod.

Używaj klucza całkowitoliczbowego, gdy klucz typu Python jest szerszy niż chcesz, albo gdy przenosisz kod z pyodbc, który używa kluczy całkowitoliczbowych. Używaj klucza typu Python, gdy chcesz, aby jeden konwerter pokrył całą rodzinę typów 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')

Kolejność rozdzielczości konwertera

Dla każdej kolumny sterownik wybiera co najwyżej jeden konwerter, w następującej kolejności:

  1. Konwerter zarejestrowany dla całkowitoliczbowego kodu typu SQL ODBC kolumny.

  2. Konwerter zarejestrowany dla typu Python w cursor.description.

  3. Konwerter został zarejestrowany dla SQL_WVARCHAR, ale tylko wtedy, gdy typ języka Python dla kolumny to str lub bytes.

Sygnatura funkcji konwertera

Gdy rejestrujesz konwerter przy użyciu klucza typu Python, typ, który przekazujesz do add_output_converter(), musi być zgodny z jednym z poniższych typów języka Python. Kolumna Konwerter odbiera pokazuje, co sterownik przekazuje twojej funkcji:

cursor.description type_code Konwerter odbiera Typy Microsoft SQL
int int tinyint, smallint, int, bigint
float float real, float
decimal.Decimal Decimal decimal, numeric, money, smallmoney
str bytes (zakodowane w UTF-16LE) char, varchar, , nchar, nvarcharxml
datetime.datetime datetime datetime, datetime2, smalldatetime
datetime.date date date
bytes bytes varbinary, binary, geography, geometry
bool bool bit

Note

Konwertery typu string (strklucz) odbierają surowe bytes dane w kodowaniu UTF-16LE, a nie zdekodowane ciągi Python. Wszystkie pozostałe typy otrzymują już przekonwertowany obiekt Python.

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)

Zarządzanie konwerterami

Użyj tych metod do inspekcji lub usunięcia przetworników już zarejestrowanych na połączeniu.

Pobierz istniejący konwerter

Pobierz funkcję konwertera zarejestrowaną obecnie dla typu:

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

Usuń konwerter

Odrejestruj konwerter, aby sterownik wrócił do domyślnej konwersji dla tego typu:

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

Wyczyść wszystkie konwertery

Zresetuj wszystkie zarejestrowane konwertery, aby korzystały z domyślnych mapowań typów Microsoft SQL:

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

Typowe wzory konwerterów

Te przykłady pokazują najczęściej potrzebne funkcje konwertera.

Przekonwertuj VARCHAR na wielką literę

Konwertery typu string otrzymują surowe bajty w kodowaniu 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

Przelicz pieniądze na wolne

Domyślnie sterownik zwraca typy DECIMAL/NUMERIC jako decimal.Decimal. Przelicz na 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'>

Przetwarzaj dane JSON

Microsoft SQL Server może przechowywać JSON jako tekst. Automatyczne parsowanie do obiektów 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

Ten przykład konwertuje WSZYSTKIE wartości ciągu (nvarchar, varchar, xml). Próba parsowania JSON jest wykonywana na każdej kolumnie ciągu znaków. W praktyce należy stosować parsowanie JSON selektywnie, a nie jako konwerter blanket.

Niestandardowe formatowanie czasu i daty

Przekonwertowanie czasu daty do formatu ISO string:

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

Zagadnienia dotyczące zabezpieczeń

Caution

Gdy rejestrujesz konwerter wyjściowy, funkcja, którą dostarczasz, działa na każdej odpowiadającej wartości bazy danych.

  • Rejestruj tylko konwertery z zaufanego kodu.
  • Nigdy nie akceptuj funkcji konwertera z danych wejściowych od użytkownika.
  • Złośliwe konwertery mogą wykonywać dowolny kod lub ujawniać dane.
# 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)

Przykład: Integracja danych przestrzennych

Integruj dane przestrzenne z biblioteką 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

Rejestracja konwertera bytes wpływa na WSZYSTKIE kolumny binarne (warbinarne, geograficzne, geometryczne). Używaj clear_output_converters() po zapytaniach przestrzennych, jeśli wykonujesz również zapytania o nieprzestrzenne dane binarne.

Przykład: Niestandardowe formatowanie wierszy

Stwórz konwerter klas danych:

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

Zakres konwertera i czas eksploatacji

  • Zakres połączeniowy: Sterownik rejestruje konwertery na każde połączenie, a nie globalnie.
  • Trwałe: Konwertery pozostają aktywne, dopóki je nie usuniesz lub nie zamkniesz połączenia.
  • Nie dziedziczone: Nowe połączenia nie dziedziczą konwerterów z innych połączeń.
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

Zagadnienia dotyczące wydajności

  • Sterownik wywołuje konwertery dla każdej wartości zarejestrowanego typu.
  • Utrzymuj efektywność funkcji konwerterów.
  • W przypadku zapytań o dużej liczbie zapytań rozważ, czy konwersja jest konieczna.
  • Przeprowadź profilowanie wydajności, jeśli konwertery wpływają na przepustowość.
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