Docs Menu

Docs Homeアプリケーションの開発Python ドライバーPyMongo

カスタム タイプ

項目一覧

  • Overview
  • カスタム型のエンコード
  • タイプ コーデック クラスの定義
  • タイプ レジストリへのコーデックの追加
  • コレクション参照の取得
  • サブタイプのエンコード
  • Define a Fallback Encoder
  • 不明な型のエンコード
  • 制限
  • API ドキュメント

このガイドでは、PyMongo を使用してカスタム型をエンコードおよびデコードする方法について説明します。

ドライバーが理解しないデータ型を保存する場合は、カスタム型を定義する必要がある場合があります。 たとえば、BSON ライブラリの Decimal128型は Python の組み込みDecimal型とは異なります。 PyMongo でDecimalのインスタンスを保存しようとすると、次のコード例に示すように、 InvalidDocumentの例外が発生します。

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'>

次のセクションでは、このDecimal型のカスタムタイプを定義する方法を示します。

カスタム タイプをエンコードするには、まずタイプのコーデック を定義する必要があります。 タイプ コーデックは、カスタムタイプのインスタンスが、 bsonモジュールがすでにエンコードできるタイプに変換されたり、タイプから変換されたりする方法を示します。

型コーデックを定義する場合、クラスはcodec_optionsモジュール内の基本クラスの 1 つから継承する必要があります。 次の表では、これらの基本クラスと、それらを実装するタイミング、および方法について説明します。

基本クラス
使用ケース
実装するメンバー
codec_options.TypeEncoder
このクラスから継承し、カスタム Python 型を既知の BSON 型にエンコードするコーデックを定義します。
  • python_type 属性: この型コーデックによってエンコードされるカスタム Python 型

  • transform_python() メソッド: カスタム型の値を BSON がエンコードできる型に変換する関数

codec_options.TypeDecoder
このクラスから継承し、指定された BSON 型をカスタム Python 型にデコードするコーデックを定義します。
  • bson_type 属性: この型コーデックによってデコードされる BSON 型

  • transform_bson() メソッド: 標準的な BSON 型の値をカスタム型に変換する関数

codec_options.TypeCodec
このクラスから継承し、カスタム タイプをエンコードおよびデコードするコーデックの両方ができるコーデックを定義します。
  • python_type 属性: この型コーデックによってエンコードされるカスタム Python 型

  • bson_type 属性: この型コーデックによってデコードされる BSON 型

  • transform_bson() メソッド: 標準的な BSON 型の値をカスタム型に変換する関数

  • transform_python() メソッド: カスタム型の値を BSON がエンコードできる型に変換する関数

例のDecimalカスタムタイプはDecimal128インスタンスとの間で変換されるため、この型をエンコードおよびデコードする方法を定義する必要があります。 したがって、 Decimal型のコーデック クラスは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()

カスタム型コーデックを定義したら、それを PyMongo の型レジストリに追加する必要があります。これは、ドライバーがエンコードおよびデコードできる型のリストです。 そのためには、リスト内のタイプ コーデック クラスのインスタンスを渡して、 TypeRegistryクラスのインスタンスを作成します。 複数のカスタム コーデックを作成する場合は、それらすべてをTypeRegistryコンストラクターに渡すことができます。

次のコード例では、 DecimalCodec型コーデックのインスタンスを型レジストリに追加します。

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

注意

一度インスタンス化されると、レジストリは不変であり、レジストリにコーデックを追加する唯一の方法は新しいレジストリを作成することです。

最後に、 TypeRegistryオブジェクトをキーワード引数として渡して、 codec_options.CodecOptionsインスタンスを定義します。 CodecOptionsオブジェクトをget_collection()メソッドに渡して、カスタムタイプを使用できるコレクションを取得します。

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

次に、 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')}

MongoDB がカスタム型のインスタンスを保存する方法を確認するには、カスタマイズされたコーデック オプションを使用せずに新しいコレクション オブジェクトを作成し、それを使用してカスタム型を含むドキュメントを検索します。 次の例では、PyMongo がDecimalクラスのインスタンスをDecimal128値として保存することを示しています。

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

また、カスタム型から継承する 1 つ以上の 型 をエンコードする必要がある場合もあります。 値を整数として返すメソッドを含むDecimalクラスの次のサブタイプについて考えてみましょう。

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

最初にタイプ コーデックを登録せずにDecimalIntクラスのインスタンスを保存しようとすると、PyMongo はエラーを発生させます。

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'>

DecimalIntクラスのインスタンスをエンコードするには、クラスの型コーデックを定義する必要があります。 このタイプのコーデックは、次の例に示すように、親クラスのコーデックDecimalCodecから継承する必要があります。

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

次に、サブスクライブの型コーデックを型レジストリに追加し、カスタム型のインスタンスをエンコードできます。

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

注意

DecimalCodecクラスのtransform_bson()メソッドでは、これらの値はDecimalInt Decimalとしてデコードされます。

また、BSON によって認識されず、コーデックが登録されていないタイプをエンコードするための呼び出し可能なフォールバック エンコードを登録することもできます。 フォールバック エンコードは、エンコードできない値をパラメーターとして受け入れ、BSON でエンコード可能な値を返します。

次のフォールバック エンコードは Python のDecimal型をDecimal128にエンコードします。

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

フォールバック エンコードを宣言したら、次の手順を実行します。

  • TypeRegistryクラスの新しいインスタンスを構築します。 フォールバック エンコードでを渡すには、 fallback_encoderキーワード引数を使用します。

  • CodecOptionsクラスの新しいインスタンスを構築します。 type_registryキーワード引数を使用してTypeRegistryインスタンスに渡します。

  • get_collection()メソッドを呼び出します。 codec_optionsキーワード引数を使用してCodecOptionsインスタンスに渡します。

次のコード例は、このプロセスを示しています。

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

次に、このコレクションへの参照を使用して、 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')}

注意

フォールバック エンコードは、標準 BSON エンコードと構成されたタイプ エンコードを使用して指定された値をエンコードしようとすると呼び出さます。 そのため、両方が同じカスタム型を対象とする型エンコードとフォールバック エンコードで構成された型レジストリでは、型エンコードで指定された動作が優先されます。

フォールバック エンコードでは、エンコードする型を事前に宣言する必要がないため、 TypeEncoderが機能しない場合に使用できます。 たとえば、フォールバック エンコードを使用して任意のオブジェクトを MongoDB に保存できます。 次の任意のカスタム型について考えてみましょう。

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,)

受け取ったオブジェクトを選択し、カスタム サブタイプを持つBinaryインスタンスとして返すフォールバック エンコードを定義できます。 このカスタム サブタイプでは、検索時に選択された アーティファクトを識別し、それらを Python オブジェクトに透過的にデコードするTypeDecoderクラスを定義できます。

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

次に、コレクションのコーデック オプションにPickledBinaryDecoderを追加し、それを使用してカスタムタイプをエンコードおよびデコードできます。

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

PyMongo 型のコーデックとフォールバック エンコードには次の制限があります。

  • intstrなど、PyMongo がすでに理解している Python 型のエンコード動作をカスタマイズすることはできません。 組み込み型を操作する 1 つ以上のコーデックを使用して型レジストリをインスタンス化しようとすると、PyMongo はTypeErrorを発生させます。 この制限は、標準型のすべてのサブタイプにも適用されます。

  • タイプ エンコードを連鎖させることはできません。 コーデックのtransform_python()メソッドによって変換されるカスタム タイプ値は、デフォルトで BSON でエンコード可能な か、 フォールバック エンコード によって BSON でエンコード可能な状態に変換できるタイプになる必要があります。 異なるタイプのコーデックでは 2 回目の変換はできません

  • Database.command()メソッドは、コマンド応答ドキュメントのデコード中にカスタム型デコードを適用しません。

  • gridfsクラスは、受信または返すドキュメントに対してカスタム型のエンコードまたはデコードを適用しません。

カスタム型のエンコードとデコードの詳細については、次の API ドキュメントを参照してください。

  • TypeCodec

  • TypeEncoder

  • TypeDecoder

  • TypeRegistry

  • CodecOptions

← 特殊データ形式