Conversores de tipos personalizados com mssql-python

Por defeito, o driver mssql-python converte tipos de dados Microsoft SQL em tipos Python apropriados (ver Mapeamentos de tipos de dados). O sistema de conversor de saída permite-lhe sobrepor este comportamento para tipos SQL específicos, possibilitando:

  • Transformações de tipos personalizadas (por exemplo, dinheiro para flutuar em vez de Decimal)
  • Integração com bibliotecas de terceiros (por exemplo, dados espaciais para objetos Shapely)
  • Formatação específica da empresa (por exemplo, datas em formatos personalizados de cadeias de caracteres)

Conversores de saída de registadores

Use add_output_converter() para registar uma função de conversor. A chave é ou um tipo Python que corresponde ao type_code em cursor.description, ou um código inteiro ODBC SQL, como 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

Escolha entre uma chave de tipo Python e uma chave de código de tipo SQL

As duas formas-chave diferem na precisão como selecionam as colunas:

  • Uma chave de tipo Python aplica-se a cada tipo SQL que corresponde a esse tipo Python. O registo de um conversor para Decimal transforma as colunas decimal, numeric, money e smallmoney.

  • Uma chave de código de tipo SQL inteiro corresponde ao código ODBC que a coluna reporta, que é mais estreita mas nem sempre é um único tipo SQL. SQL_NUMERIC transforma apenas colunas numéricas . SQL_DECIMAL transforma as colunas decimal, dinheiro e smallmoney , porque as três reportam esse mesmo código.

Usa uma chave inteira quando uma chave do tipo Python for mais ampla do que gostarias, ou quando estiveres a portar código do pyodbc, que usa chaves inteiras. Usa uma chave de tipo Python quando quiseres que um conversor cubra toda uma família de tipos 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')

Ordem de resolução do conversor

Para cada coluna, o driver seleciona no máximo um conversor, nesta ordem:

  1. O conversor foi registado para o código de tipo SQL ODBC inteiro da coluna.

  2. O conversor foi registado para o tipo Python em cursor.description.

  3. O conversor registou-se para SQL_WVARCHAR, mas apenas quando o tipo Python da coluna é str ou bytes.

Assinatura de função conversora

Quando regista um conversor com uma chave de tipo do Python, o tipo que passa a add_output_converter() tem de corresponder a um dos seguintes tipos do Python. A coluna O conversor recebe mostra o que o controlador passa à sua função:

cursor.description type_code O conversor recebe Tipos SQL da Microsoft
int int tinyint, smallint, int, bigint
float float real, float
decimal.Decimal Decimal decimal, numeric, money, smallmoney
str bytes (codificado em 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

Conversores do tipo string (str chave) recebem os dados bytes em bruto com codificação UTF-16LE, e não cadeias de caracteres de Python descodificadas. Todos os outros tipos recebem o objeto Python já convertido.

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)

Gerir conversores

Use estes métodos para inspecionar ou remover conversores já registados numa ligação.

Obter conversor existente

Recuperar a função conversora atualmente registada para um tipo:

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

Remova um conversor

Anular o registo de um conversor para que o controlador retome a conversão predefinida para esse tipo:

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

Limpar todos os conversores

Reiniciar todos os conversores registados para usar os mapeamentos de tipos predefinidos do Microsoft SQL:

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

Padrões comuns de conversores

Estes exemplos mostram as funções de conversor mais frequentemente necessárias.

Converta VARCHAR em maiúsculas

Conversores do tipo string recebem bytes brutos na codificação 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

Converter dinheiro em float

Por predefinição, o controlador devolve os tipos DECIMAL/NUMERIC como decimal.Decimal. Converter para 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'>

Analisar dados JSON

O Microsoft SQL Server pode armazenar JSON como texto. Análise automática para objetos 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

Atenção

Este exemplo converte TODOS os valores das cadeias (nvarchar, varchar, xml). A análise sintática JSON é tentada em todas as colunas de strings. Na prática, aplique a análise de JSON seletivamente, em vez de a usar como um conversor genérico.

Formatação personalizada de data e hora

Converter data e hora para o formato de cadeia 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

Considerações de segurança

Atenção

Quando regista um conversor de saída, a função que fornece executa-se em todos os valores correspondentes da base de dados.

  • Regista apenas conversores de código confiável.
  • Nunca aceite funções de conversor a partir de entradas do utilizador.
  • Conversores maliciosos podiam executar código arbitrário ou vazar dados.
# 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)

Exemplo: Integração de dados espaciais

Integre dados espaciais com a biblioteca 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

Registar um bytes conversor afeta TODAS as colunas binárias (varbinária, geografia, geometria). Utilize clear_output_converters() após consultas espaciais se também consultar dados binários não espaciais.

Exemplo: Formatação de linha personalizada

Crie um conversor de classes de dados:

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

Âmbito e tempo de vida do conversor

  • Escopo de conexão: O driver regista conversores por ligação, não globalmente.
  • Persistente: Os conversores permanecem ativos até os remover ou fechar a ligação.
  • Não herdadas: Novas ligações não herdam conversores de outras ligações.
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

Considerações sobre desempenho

  • O controlador invoca conversores para cada valor do tipo registado.
  • Mantenha as funções do conversor eficientes.
  • Para consultas de grande volume, considere se a conversão é necessária.
  • Desempenho do perfil se os conversores afetam o rendimento.
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