Docs Menu
Docs Home
/ /

タイプ コーデックによるデータのエンコード

このガイドでは、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 つから継承する必要があります。 次の表では、これらの基本クラスと、それらを実装するタイミング、および方法について説明します。

基本クラス
使用ケース
実装するメンバー

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 がエンコードできる型に変換する関数

例の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):
@property
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としてデコードされます。

また、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 型のコーデックとフォールバック エンコードには次の制限があります。

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

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

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

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

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

  • TypeCodec

  • TypeEncoder

  • TypeDecoder

  • TypeRegistry

  • CodecOptions

  • Decimal128

戻る

直列化

項目一覧