Conversores de tipos personalizados com mssql-python

Por padrão, o driver mssql-python converte tipos de dados Microsoft SQL para tipos Python apropriados (veja Mapeamentos de tipos de dados). O sistema de conversor de saída permite que você substitua esse comportamento para tipos SQL específicos, habilitando:

  • Transformações de tipos personalizadas (por exemplo, dinheiro para float em vez de decimal)
  • Integração com bibliotecas de terceiros (por exemplo, dados espaciais para objetos Shapely)
  • Formatação específica para negócios (por exemplo, datas em formatos personalizados de cadeia de caracteres)

Registrar conversores de saída

Use add_output_converter() para registrar 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 com que selecionam as colunas:

  • Uma chave de tipo Python é aplicável a cada tipo SQL que mapeia para esse tipo Python. Registrar um conversor em Decimal transforma 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 estreito, 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.

Use uma chave inteira quando uma chave de tipo Python for mais abrangente do que o desejado ou quando estiver portando código do pyodbc, que usa chaves inteiras. Use uma chave de tipo Python quando quiser 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 se registrou para o código inteiro ODBC SQL da coluna.

  2. O conversor registrado para o tipo Python em cursor.description.

  3. O conversor foi registrado para SQL_WVARCHAR, mas somente quando o tipo Python da coluna é str ou bytes.

Assinatura de função conversora

Quando você registra um conversor usando uma chave de tipo do Python, o tipo passado para add_output_converter() deve corresponder a um dos seguintes tipos do Python. A coluna Conversor recebe mostra o que o driver passa para sua função:

cursor.description type_code 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 cadeia de caracteres (chave str) recebem bytes bruto na codificação UTF-16LE, não cadeias de caracteres Python decodificadas. 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)

Gerenciar conversores

Use esses métodos para inspecionar ou remover conversores já registrados em uma conexão.

Obter conversor existente

Recupere a função conversora atualmente registrada para um tipo:

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

Remova um conversor

Desregistre um conversor para que o driver volte à conversão padrão para aquele tipo:

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

Limpe todos os conversores

Resete todos os conversores registrados para usar os mapeamentos de tipos padrão do Microsoft SQL:

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

Padrões comuns de conversores

Esses 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 padrão, o driver retorna os tipos DECIMAL/NUMÉRICO 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

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

Cuidado

Este exemplo converte TODOS os valores das strings (nvarchar, varchar, xml). A análise de JSON é feita em cada coluna de texto. Na prática, aplique a análise de JSON de forma seletiva, em vez de usá-la como um conversor genérico.

Formatação personalizada de data e hora

Converter datetime para formato de cadeia de caracteres 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

Cuidado

Quando você registra um conversor de saída, a função que você fornece é executada para cada valor correspondente no banco de dados.

  • Registre apenas conversores provenientes de código confiável.
  • Nunca aceite funções de conversor a partir da entrada do usuário.
  • Conversores maliciosos podem 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

Registrar um bytes conversor afeta TODAS as colunas binárias (varbinária, geográfica, geométrica). Use clear_output_converters() após consultas espaciais se também consultar dados binários não espaciais.

Exemplo: Formatação personalizada de linha

Crie um conversor de classe 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}")

Escopo e vida útil do conversor

  • Escopo de conexão: o driver registra conversores por conexão, não globalmente.
  • Persistente: Os conversores permanecem ativos até que você os remova ou feche a conexão.
  • Não herdado: Novas conexões não herdam conversores de outras conexõ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 driver chama conversores para cada valor do tipo registrado.
  • Mantenha as funções do conversor eficientes.
  • Para consultas de alto volume, considere se a conversão é necessária.
  • Analise o desempenho para verificar se os conversores afetam a taxa de transferência.
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