Menu Docs

Página inicial do DocsDesenvolver aplicaçõesDrivers PythonPyMongo

Tipos personalizados

Nesta página

  • Visão geral
  • Codificar um tipo personalizado
  • Definir uma classe de codec de tipo
  • Adicionar codec ao registro de tipo
  • Obter uma referência de collection
  • Codificar um subtipo
  • Definir um codificador de contingência
  • Codificar tipos desconhecidos
  • Limitações
  • Documentação da API

Este guia explica como usar o PyMongo para codificar e decodificar tipos personalizados.

Talvez seja necessário definir um tipo personalizado se quiser armazenar um tipo de dados que o driver não entenda. Por exemplo, o tipo Decimal128 da biblioteca BSON é diferente do tipo Decimal integrado do Python. Tentar salvar uma instância do Decimal com PyMongo resulta em uma exceção do InvalidDocument , como mostrado no seguinte exemplo de código:

from decimal import Decimal
num = Decimal("45.321")
db["coll"].insert_one({"num": num})
Traceback (most recent call last):
...
bson.errors.InvalidDocument: cannot encode object: Decimal('45.321'), of
type: <class 'decimal.Decimal'>

As seções seguintes mostram como definir um tipo personalizado para este tipo do Decimal .

Para codificar um tipo personalizado, você deve primeiro definir um codec de tipo. Um codec de tipo descreve como uma instância de um tipo personalizado é convertida em e de um tipo que o módulo bson já pode codificar.

Quando você define um codec de tipo, sua classe deve herdar de uma das classes de base no módulo codec_options . A tabela a seguir descreve essas classes básicas e quando e como implementá-las:

Classe base
Quando usar
Membros a serem implementados
codec_options.TypeEncoder
Herde desta classe para definir um codec que codifica um tipo Python personalizado para um tipo BSON conhecido.
  • python_type atributo: o tipo de Python personalizado codificado por esse tipo de codec

  • transform_python() método: Função que transforma um valor de tipo personalizado em um tipo que o BSON pode codificar

codec_options.TypeDecoder
Herde desta classe para definir um codec que decodifica um tipo BSON especificado em um tipo Python personalizado.
  • bson_type atributo: o tipo BSON decodificado por esse codec de tipo

  • transform_bson() método: Função que transforma um valor de tipo BSON padrão no tipo personalizado

codec_options.TypeCodec
Herde desta classe para definir um codec que possa codificar e decodificar um tipo personalizado.
  • python_type atributo: o tipo de Python personalizado codificado por esse tipo de codec

  • bson_type atributo: o tipo BSON decodificado por esse codec de tipo

  • transform_bson() método: Função que transforma um valor de tipo BSON padrão no tipo personalizado

  • transform_python() método: Função que transforma um valor de tipo personalizado em um tipo que o BSON pode codificar

Como o tipo personalizado do exemplo Decimal pode ser convertido de e para uma instância do Decimal128 , você deve definir como codificar e decodificar esse tipo. Portanto, a classe de codec do tipo Decimal deve herdar da classe base TypeCodec :

from bson.decimal128 import Decimal128
from bson.codec_options import TypeCodec
class DecimalCodec(TypeCodec):
python_type = Decimal
bson_type = Decimal128
def transform_python(self, value):
return Decimal128(value)
def transform_bson(self, value):
return value.to_decimal()

Depois de definir um codec de tipo personalizado, você deve adicioná-lo ao registro de tipos do PyMongo, a lista de tipos que o driver pode codificar e decodificar. Para fazer isso, crie uma instância da classe TypeRegistry , passando uma instância da sua classe de codec do tipo dentro de uma lista. Se você criar vários codecs personalizados, poderá passá-los todos para o construtor TypeRegistry .

Os seguintes exemplos de código adicionam uma instância do codec do tipo DecimalCodec ao registro do tipo:

from bson.codec_options import TypeRegistry
decimal_codec = DecimalCodec()
type_registry = TypeRegistry([decimal_codec])

Observação

Uma vez instanciados, os registros são imutáveis e a única maneira de adicionar codecs a um registro é criar um novo.

Por fim, defina uma instância codec_options.CodecOptions , passando seu objeto TypeRegistry como um argumento de palavra-chave. Passe seu objeto CodecOptions para o método get_collection() para obter uma coleção que possa usar seu tipo personalizado:

from bson.codec_options import CodecOptions
codec_options = CodecOptions(type_registry=type_registry)
collection = db.get_collection("test", codec_options=codec_options)

Você pode então codificar e decodificar instâncias da classe Decimal :

import pprint
collection.insert_one({"num": Decimal("45.321")})
my_doc = collection.find_one()
pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'num': Decimal('45.321')}

Para ver como o MongoDB armazena uma instância do tipo personalizado, crie um novo objeto de coleção sem as opções de codec personalizado e use-o para recuperar o documento que contém o tipo personalizado. O exemplo seguinte mostra que o PyMongo armazena uma instância da classe Decimal como um valor Decimal128 :

import pprint
new_collection = db.get_collection("test")
pprint.pprint(new_collection.find_one())
{'_id': ObjectId('...'), 'num': Decimal128('45.321')}

Talvez você também precise codificar um ou mais tipos que herdam do seu tipo personalizado. Considere o seguinte subtipo da classe Decimal , que contém um método para retornar seu valor como um número inteiro:

class DecimalInt(Decimal):
def get_int(self):
return int(self)

Se você tentar salvar uma instância da classe DecimalInt sem primeiro registrar um codec de tipo para ela, o PyMongo chamará um erro:

collection.insert_one({"num": DecimalInt("45.321")})
Traceback (most recent call last):
...
bson.errors.InvalidDocument: cannot encode object: Decimal('45.321'),
of type: <class 'decimal.Decimal'>

Para codificar uma instância da classe DecimalInt , você deve definir um codec de tipo para a classe. Esse codec de tipo deve herdar do codec da classe pai, DecimalCodec, conforme mostrado no exemplo a seguir:

class DecimalIntCodec(DecimalCodec):
@property
def python_type(self):
# The Python type encoded by this type codec
return DecimalInt

Você pode então adicionar o codec do tipo sublcass ao tipo de registro e codificar instâncias do tipo personalizado:

import pprint
from bson.codec_options import CodecOptions
decimal_int_codec = DecimalIntCodec()
type_registry = TypeRegistry([decimal_codec, decimal_int_codec])
codec_options = CodecOptions(type_registry=type_registry)
collection = db.get_collection("test", codec_options=codec_options)
collection.insert_one({"num": DecimalInt("45.321")})
my_doc = collection.find_one()
pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'num': Decimal('45.321')}

Observação

O método transform_bson() da classe DecimalCodec resulta em esses valores sendo decodificados como Decimal, não DecimalInt.

Você também pode registrar um codificador de contingência, um chamável para codificar tipos não reconhecidos pelo BSON e para os quais nenhum codec de tipo foi registrado. O codificador fallback aceita um valor não codificado como parâmetro e retorna um valor codificado por BSON.

O seguinte codificador de contingência codifica o tipo Decimal do Python para Decimal128:

def fallback_encoder(value):
if isinstance(value, Decimal):
return Decimal128(value)
return value

Depois de declarar um codificador de contingência, execute as seguintes etapas:

  • Construa uma nova instância da classe TypeRegistry . Use o argumento de palavra-chave fallback_encoder para passar o codificador de contingência.

  • Construa uma nova instância da classe CodecOptions . Use o argumento da palavra-chave type_registry para passar na instância do TypeRegistry .

  • Chame o método get_collection() . Use o argumento da palavra-chave codec_options para passar na instância do CodecOptions .

O seguinte exemplo de código mostra este processo:

type_registry = TypeRegistry(fallback_encoder=fallback_encoder)
codec_options = CodecOptions(type_registry=type_registry)
collection = db.get_collection("test", codec_options=codec_options)

Você pode então usar esta referência a uma coleção para armazenar instâncias da classe Decimal :

import pprint
collection.insert_one({"num": Decimal("45.321")})
my_doc = collection.find_one()
pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'num': Decimal128('45.321')}

Observação

Os codificadores de fallback são invocados após as tentativas de codificar o valor fornecido com codificadores BSON padrão e qualquer codificador de tipo configurado falhar. Portanto, em um registro de tipo configurado com um codificador de tipo e um codificador de fallback que ambos têm como alvo o mesmo tipo personalizado, o comportamento especificado no codificador de tipo tem precedência.

Como os codificadores de contingência não precisam declarar os tipos que codificam de antetualmente, você pode usá-los nos casos em que um TypeEncoder não funcione. Por exemplo, você pode usar um codificador de contingência para salvar objetos arbitrários no MongoDB. Considere os seguintes tipos personalizados arbitrários:

class MyStringType(object):
def __init__(self, value):
self.__value = value
def __repr__(self):
return "MyStringType('%s')" % (self.__value,)
class MyNumberType(object):
def __init__(self, value):
self.__value = value
def __repr__(self):
return "MyNumberType(%s)" % (self.__value,)

Você pode definir um codificador de contingência que seleciona os objetos que recebe e os retorna como instâncias Binary com um subtipo personalizado. Esse subtipo personalizado permite definir uma classe TypeDecoder que identifica artefatos em pickles após a recuperação e os decodifica de forma transparente de volta para objetos Python:

import pickle
from bson.binary import Binary, USER_DEFINED_SUBTYPE
from bson.codec_options import TypeDecoder
def fallback_pickle_encoder(value):
return Binary(pickle.dumps(value), USER_DEFINED_SUBTYPE)
class PickledBinaryDecoder(TypeDecoder):
bson_type = Binary
def transform_bson(self, value):
if value.subtype == USER_DEFINED_SUBTYPE:
return pickle.loads(value)
return value

Você pode então adicionar o PickledBinaryDecoder às opções de codec de uma collection e usá-lo para codificar e decodificar seus tipos personalizados:

from bson.codec_options import CodecOptions,TypeRegistry
codec_options = CodecOptions(
type_registry=TypeRegistry(
[PickledBinaryDecoder()], fallback_encoder=fallback_pickle_encoder
)
)
collection = db.get_collection("test", codec_options=codec_options)
collection.insert_one(
{"_id": 1, "str": MyStringType("hello world"), "num": MyNumberType(2)}
)
my_doc = collection.find_one()
print(isinstance(my_doc["str"], MyStringType))
print(isinstance(my_doc["num"], MyNumberType))
True
True

Os codecs do tipo PyMongo e codificadores de contingência têm as seguintes limitações:

  • Você não pode personalizar o comportamento de codificação dos tipos de Python que o PyMongo já entende, como int e str. Se você tentar instanciar um registro de tipo com um ou mais codecs que agem em um tipo integrado, o PyMongo gerará um TypeError. Esta limitação também se aplica a todos os subtipos dos tipos padrão.

  • Você não pode encadear codificadores de tipo. Um valor de tipo personalizado, uma vez transformado pelo método transform_python() de um codec, deve resultar em um tipo que seja codificado por BSON por padrão, ou possa ser transformado pelo codificador de fallback em algo codificado por BSON. Não pode ser transformado uma segunda vez por um codec de tipo diferente.

  • O método Database.command() não aplica decodificadores de tipo personalizado ao decodificar o documento de resposta do comando.

  • A classe gridfs não aplica codificação ou decodificação de tipo personalizado a nenhum documento que recebe ou retorna.

Para obter mais informações sobre codificação e decodificação de tipos personalizados, consulte a seguinte documentação da API:

  • TypeCodec

  • TypeEncoder

  • TypeDecoder

  • TypeRegistry

  • CodecOptions

← Formatos de dados especializados