Página inicial do Docs → Desenvolver aplicações → Drivers Python → PyMongo
Tipos personalizados
Nesta página
Visão geral
Este guia explica como usar o PyMongo para codificar e decodificar tipos personalizados.
Codificar um tipo personalizado
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
.
Definir uma classe de codec de tipo
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. |
|
codec_options.TypeDecoder | Herde desta classe para definir um codec que decodifica um tipo BSON especificado em um tipo Python personalizado. |
|
codec_options.TypeCodec | Herde desta classe para definir um codec que possa codificar e decodificar um tipo personalizado. |
|
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()
Adicionar codec ao registro de tipo
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.
Obter uma referência de collection
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')}
Codificar um subtipo
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): 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
.
Definir um codificador de contingência
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-chavefallback_encoder
para passar o codificador de contingência.Construa uma nova instância da classe
CodecOptions
. Use o argumento da palavra-chavetype_registry
para passar na instância doTypeRegistry
.Chame o método
get_collection()
. Use o argumento da palavra-chavecodec_options
para passar na instância doCodecOptions
.
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.
Codificar tipos desconhecidos
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
Limitações
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
estr
. Se você tentar instanciar um registro de tipo com um ou mais codecs que agem em um tipo integrado, o PyMongo gerará umTypeError
. 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.
Documentação da API
Para obter mais informações sobre codificação e decodificação de tipos personalizados, consulte a seguinte documentação da API: