Overview
このガイドでは、PyMongo を使用してカスタム型をエンコードおよびデコードする方法について説明します。
デフォルトのタイプ コーデック
PyMongoには、任意で使用できるいくつかの組み込みコーデックが含まれています。これらの組み込みコーデックは一般的なデータ型を自動的に処理します。例、ドライバーは Python の decimal.Decimal 型をBSON Decimal128 値にエンコードおよびデコードできるようにする DecimalEncoder および DecimalDecoder クラスを提供します。
DecimalEncoderクラスはPython decimal.Decimal 値をBSON Decimal128 値に変換します。DecimalDecoderクラスはBSON Decimal128 値をPython decimal.Decimal 値に変換します。
次のコードでは、DecimalEncoderクラスを使用して小数の 1.0 をエンコードします。
opts = CodecOptions(type_registry=TypeRegistry([DecimalEncoder()])) bson.encode({"d": decimal.Decimal('1.0')}, codec_options=opts)
次のコードでは、DecimalDecoderクラスを使用してBSONデータをデコードします。
opts = CodecOptions(type_registry=TypeRegistry([DecimalDecoder()])) bson.decode(data, codec_options=opts)
カスタム型のエンコード
ドライバーがネイティブに直列化できないデータ型を保存する場合は、カスタム型を定義する必要があるかもしれません。例、次のコード例に示すように、 PyMongoで Enum のインスタンスを保存しようとすると、InvalidDocument の例外が発生します。対応するコードを表示するには、Synchronous タブまたは Asynchronousタブを選択します。
from enum import Enum class Status(Enum): ACTIVE = "active" INACTIVE = "inactive" status = Status.ACTIVE db["coll"].insert_one({"status": status})
Traceback (most recent call last): ... bson.errors.InvalidDocument: Invalid document {'status': <Status.ACTIVE: 'active'>, '_id': ObjectId('68bb6144862a5cfb94a9fd48')} | cannot encode object: <Status.ACTIVE: 'active'>, of type: <enum 'Status'>
from enum import Enum class Status(Enum): ACTIVE = "active" INACTIVE = "inactive" status = Status.ACTIVE await db["coll"].insert_one({"status": status})
Traceback (most recent call last): ... bson.errors.InvalidDocument: Invalid document {'status': <Status.ACTIVE: 'active'>, '_id': ObjectId('68bb6144862a5cfb94a9fd48')} | cannot encode object: <Status.ACTIVE: 'active'>, of type: <enum 'Status'>
次のセクションでは、このEnum型のカスタムタイプを定義する方法を示します。
タイプ コーデック クラスの定義
カスタム タイプをエンコードするには、まずタイプのコーデック を定義する必要があります。 タイプ コーデックは、カスタムタイプのインスタンスが、 bsonモジュールがすでにエンコードできるタイプに変換されたり、タイプから変換されたりする方法を示します。
型コーデックを定義する場合、クラスはcodec_optionsモジュール内の基本クラスの 1 つから継承する必要があります。 次の表では、これらの基本クラスと、それらを実装するタイミング、および方法について説明します。
基本クラス | 使用ケース | 実装するメンバー |
|---|---|---|
| このクラスから継承し、カスタム Python 型を既知の BSON 型にエンコードするコーデックを定義します。 |
|
| このクラスから継承し、指定された BSON 型をカスタム Python 型にデコードするコーデックを定義します。 |
|
| このクラスから継承し、カスタム タイプをエンコードおよびデコードするコーデックの両方ができるコーデックを定義します。 |
|
例のEnumCodecカスタムタイプはstrインスタンスとの間で変換されるため、この型をエンコードおよびデコードする方法を定義する必要があります。 したがって、 Enum型のコーデック クラスはTypeCodec基本クラスから継承する必要があります。
from bson.codec_options import TypeCodec class EnumCodec(TypeCodec): python_type = Status bson_type = str def transform_python(self, value): return value.value def transform_bson(self, value): try: return Status(value) except ValueError: return value
タイプ レジストリへのコーデックの追加
カスタム型コーデックを定義したら、それを PyMongo の型レジストリに追加する必要があります。これは、ドライバーがエンコードおよびデコードできる型のリストです。 そのためには、リスト内のタイプ コーデック クラスのインスタンスを渡して、 TypeRegistryクラスのインスタンスを作成します。 複数のカスタム コーデックを作成する場合は、それらすべてをTypeRegistryコンストラクターに渡すことができます。
次のコード例では、 EnumCodec型コーデックのインスタンスを型レジストリに追加します。
from bson.codec_options import TypeRegistry enum_codec = EnumCodec() type_registry = TypeRegistry([enum_codec])
注意
一度インスタンス化されると、レジストリは不変であり、レジストリにコーデックを追加する唯一の方法は新しいレジストリを作成することです。
コレクション参照の取得
最後に、 TypeRegistryオブジェクトをキーワード引数として渡して、 codec_options.CodecOptionsインスタンスを定義します。 CodecOptionsオブジェクトをget_collection()メソッドに渡して、カスタムタイプを使用できるコレクションを取得します。
from bson.codec_options import CodecOptions codec_options = CodecOptions(type_registry=type_registry) collection = database.get_collection("test", codec_options=codec_options)
次に、Statusクラスのインスタンスをエンコードおよびデコードできます。対応するコードを表示するには、Synchronous タブまたは Asynchronousタブを選択します。
import pprint collection.insert_one({"status": Status.ACTIVE}) my_doc = collection.find_one({"status": "active"}) pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'status': <Status.ACTIVE: 'active'>}
import pprint await collection.insert_one({"status": Status.ACTIVE}) my_doc = await collection.find_one({"status": "active"}) pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'status': <Status.ACTIVE: 'active'>}
MongoDB がカスタム型のインスタンスを保存する方法を確認するには、カスタマイズされたコーデック オプションを使用せずに新しいコレクションオブジェクトを作成し、それを使用してカスタム型を含むドキュメントを検索します。次の例では、 PyMongo がStatusクラスのインスタンスを str 値として保存することを示しています。対応するコードを表示するには、Synchronous タブまたは Asynchronousタブを選択します。
import pprint new_collection = database.get_collection("test") pprint.pprint(new_collection.find_one())
{'_id': ObjectId('...'), 'status': 'active'}
import pprint new_collection = database.get_collection("test") pprint.pprint(await new_collection.find_one())
{'_id': ObjectId('...'), 'status': 'active'}
サブタイプのエンコード
また、カスタム型から継承する 1 つ以上の 型 をエンコードする必要がある場合もあります。ステータスがアクティブな状態を表しているかどうかを確認するメソッドが含まれる Status列挙クラスの次のサブタイプを検討してみましょう。
class ExtendedStatus(Enum): ACTIVE = "active" INACTIVE = "inactive" PENDING = "pending" def is_active(self): return self == ExtendedStatus.ACTIVE
最初にタイプ コーデックを登録せずに ExtendedStatusクラスのインスタンスを保存しようとすると、 PyMongo はエラーを発生させます。対応するコードを表示するには、Synchronous タブまたは Asynchronousタブを選択します。
collection.insert_one({"status": ExtendedStatus.ACTIVE})
Traceback (most recent call last): ... bson.errors.InvalidDocument: Invalid document {'status': <ExtendedStatus.ACTIVE: 'active'>, '_id': ObjectId('...')} | cannot encode object: <ExtendedStatus.ACTIVE: 'active'>, of type: <enum 'ExtendedStatus'>
await collection.insert_one({"status": ExtendedStatus.ACTIVE})
Traceback (most recent call last): ... bson.errors.InvalidDocument: Invalid document {'status': <ExtendedStatus.ACTIVE: 'active'>, '_id': ObjectId('...')} | cannot encode object: <ExtendedStatus.ACTIVE: 'active'>, of type: <enum 'ExtendedStatus'>
ExtendedStatusクラスのインスタンスをエンコードするには、クラスの型コーデックを定義する必要があります。 このタイプのコーデックは、次の例に示すように、親クラスのコーデックEnumCodecから継承する必要があります。
class ExtendedStatusCodec(EnumCodec): def python_type(self): # The Python type encoded by this type codec return ExtendedStatus
次に、サブクラスの型コーデックを型レジストリに追加し、カスタム型のインスタンスをエンコードできます。対応するコードを表示するには、Synchronous タブまたは Asynchronousタブを選択します。
import pprint from bson.codec_options import CodecOptions extended_status_codec = ExtendedStatusCodec() type_registry = TypeRegistry([enum_codec, extended_status_codec]) codec_options = CodecOptions(type_registry=type_registry) collection = database.get_collection("test", codec_options=codec_options) collection.insert_one({"status": ExtendedStatus.ACTIVE}) my_doc = collection.find_one() pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'status': <Status.ACTIVE: 'active'>}
import pprint from bson.codec_options import CodecOptions extended_status_codec = ExtendedStatusCodec() type_registry = TypeRegistry([enum_codec, extended_status_codec]) codec_options = CodecOptions(type_registry=type_registry) collection = database.get_collection("test", codec_options=codec_options) await collection.insert_one({"status": ExtendedStatus.ACTIVE}) my_doc = await collection.find_one() pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'status': <Status.ACTIVE: 'active'>}
注意
EnumCodecクラスのtransform_bson()メソッドでは、これらの値はExtendedStatus Statusとしてデコードされます。
Define a Fallback Encoder
また、BSON によって認識されず、コーデックが登録されていないタイプをエンコードするための呼び出し可能なフォールバック エンコードを登録することもできます。 フォールバック エンコードは、エンコードできない値をパラメーターとして受け入れ、BSON でエンコード可能な値を返します。
次のフォールバック エンコードは Python のEnum型をstrにエンコードします。
from enum import Enum def fallback_encoder(value): if isinstance(value, Enum): return value.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)
次に、この参照をコレクションに使用して、Statusクラスのインスタンスを保存できます。対応するコードを表示するには、Synchronous タブまたは Asynchronousタブを選択します。
import pprint collection.insert_one({"status": Status.ACTIVE}) my_doc = collection.find_one() pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'status': 'active'}
import pprint await collection.insert_one({"status": Status.ACTIVE}) my_doc = await collection.find_one() pprint.pprint(my_doc)
{'_id': ObjectId('...'), 'status': 'active'}
注意
フォールバック エンコードは、標準 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,)
列値インスタンスを string 値に変換して列挙インスタンスを処理するフォールバック エンコードを定義できます。または、ストレージ用に他のオブジェクトを選択します。次の例は、さまざまなタイプのカスタム オブジェクトの処理方法を示しています。
import pickle from enum import Enum def fallback_pickle_encoder(value): if isinstance(value, Enum): return value.value return pickle.dumps(value).decode('latin-1') class PickledStringDecoder(TypeDecoder): bson_type = str def transform_bson(self, value): try: # Try to unpickle the string value return pickle.loads(value.encode('latin-1')) except: # If unpickling fails, return the original string return value
次に、型レジストリでフォールバック エンコードを使用してカスタム型をエンコードおよびデコードできます。対応するコードを表示するには、Synchronous タブまたは Asynchronousタブを選択します。
from bson.codec_options import CodecOptions,TypeRegistry codec_options = CodecOptions( type_registry=TypeRegistry( 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
from bson.codec_options import CodecOptions,TypeRegistry codec_options = CodecOptions( type_registry=TypeRegistry( fallback_encoder=fallback_pickle_encoder ) ) collection = db.get_collection("test", codec_options=codec_options) await collection.insert_one( {"_id": 1, "str": MyStringType("hello world"), "num": MyNumberType(2)} ) my_doc = await 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 ドキュメントを参照してください。