Docs Home → アプリケーションの開発 → Python ドライバー → PyMongo
カスタム タイプ
項目一覧
Overview
このガイドでは、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 型にエンコードするコーデックを定義します。 |
|
codec_options.TypeDecoder | このクラスから継承し、指定された BSON 型をカスタム Python 型にデコードするコーデックを定義します。 |
|
codec_options.TypeCodec | このクラスから継承し、カスタム タイプをエンコードおよびデコードするコーデックの両方ができるコーデックを定義します。 |
|
例の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): 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
としてデコードされます。
Define a Fallback Encoder
また、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 型のコーデックとフォールバック エンコードには次の制限があります。
int
やstr
など、PyMongo がすでに理解している Python 型のエンコード動作をカスタマイズすることはできません。 組み込み型を操作する 1 つ以上のコーデックを使用して型レジストリをインスタンス化しようとすると、PyMongo はTypeError
を発生させます。 この制限は、標準型のすべてのサブタイプにも適用されます。タイプ エンコードを連鎖させることはできません。 コーデックの
transform_python()
メソッドによって変換されるカスタム タイプ値は、デフォルトで BSON でエンコード可能な か、 フォールバック エンコード によって BSON でエンコード可能な状態に変換できるタイプになる必要があります。 異なるタイプのコーデックでは 2 回目の変換はできません。Database.command()
メソッドは、コマンド応答ドキュメントのデコード中にカスタム型デコードを適用しません。gridfs
クラスは、受信または返すドキュメントに対してカスタム型のエンコードまたはデコードを適用しません。
API ドキュメント
カスタム型のエンコードとデコードの詳細については、次の API ドキュメントを参照してください。