This file is indexed.

/usr/share/doc/python-pymongo-doc/html/_sources/examples/custom_type.txt is in python-pymongo-doc 2.7.2-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
Custom Type Example
===================

This is an example of using a custom type with PyMongo. The example
here is a bit contrived, but shows how to use a
:class:`~pymongo.son_manipulator.SONManipulator` to manipulate
documents as they are saved or retrieved from MongoDB. More
specifically, it shows a couple different mechanisms for working with
custom datatypes in PyMongo.

Setup
-----

We'll start by getting a clean database to use for the example:

.. doctest::

  >>> from pymongo.mongo_client import MongoClient
  >>> client = MongoClient()
  >>> client.drop_database("custom_type_example")
  >>> db = client.custom_type_example

Since the purpose of the example is to demonstrate working with custom
types, we'll need a custom datatype to use. Here we define the aptly
named :class:`Custom` class, which has a single method, :meth:`x`:

.. doctest::

  >>> class Custom(object):
  ...   def __init__(self, x):
  ...     self.__x = x
  ...
  ...   def x(self):
  ...     return self.__x
  ...
  >>> foo = Custom(10)
  >>> foo.x()
  10

When we try to save an instance of :class:`Custom` with PyMongo, we'll
get an :class:`~bson.errors.InvalidDocument` exception:

.. doctest::

  >>> db.test.insert({"custom": Custom(5)})
  Traceback (most recent call last):
  InvalidDocument: cannot convert value of type <class 'Custom'> to bson

Manual Encoding
---------------

One way to work around this is to manipulate our data into something
we *can* save with PyMongo. To do so we define two methods,
:meth:`encode_custom` and :meth:`decode_custom`:

.. doctest::

  >>> def encode_custom(custom):
  ...   return {"_type": "custom", "x": custom.x()}
  ...
  >>> def decode_custom(document):
  ...   assert document["_type"] == "custom"
  ...   return Custom(document["x"])
  ...

We can now manually encode and decode :class:`Custom` instances and
use them with PyMongo:

.. doctest::

  >>> db.test.insert({"custom": encode_custom(Custom(5))})
  ObjectId('...')
  >>> db.test.find_one()
  {u'_id': ObjectId('...'), u'custom': {u'x': 5, u'_type': u'custom'}}
  >>> decode_custom(db.test.find_one()["custom"])
  <Custom object at ...>
  >>> decode_custom(db.test.find_one()["custom"]).x()
  5

Automatic Encoding and Decoding
-------------------------------

Needless to say, that was a little unwieldy. Let's make this a bit
more seamless by creating a new
:class:`~pymongo.son_manipulator.SONManipulator`.
:class:`~pymongo.son_manipulator.SONManipulator` instances allow you
to specify transformations to be applied automatically by PyMongo:

.. doctest::

  >>> from pymongo.son_manipulator import SONManipulator
  >>> class Transform(SONManipulator):
  ...   def transform_incoming(self, son, collection):
  ...     for (key, value) in son.items():
  ...       if isinstance(value, Custom):
  ...         son[key] = encode_custom(value)
  ...       elif isinstance(value, dict): # Make sure we recurse into sub-docs
  ...         son[key] = self.transform_incoming(value, collection)
  ...     return son
  ...
  ...   def transform_outgoing(self, son, collection):
  ...     for (key, value) in son.items():
  ...       if isinstance(value, dict):
  ...         if "_type" in value and value["_type"] == "custom":
  ...           son[key] = decode_custom(value)
  ...         else: # Again, make sure to recurse into sub-docs
  ...           son[key] = self.transform_outgoing(value, collection)
  ...     return son
  ...

Now we add our manipulator to the :class:`~pymongo.database.Database`:

.. doctest::

  >>> db.add_son_manipulator(Transform())

After doing so we can save and restore :class:`Custom` instances seamlessly:

.. doctest::

  >>> db.test.remove() # remove whatever has already been saved
  {...}
  >>> db.test.insert({"custom": Custom(5)})
  ObjectId('...')
  >>> db.test.find_one()
  {u'_id': ObjectId('...'), u'custom': <Custom object at ...>}
  >>> db.test.find_one()["custom"].x()
  5

If we get a new :class:`~pymongo.database.Database` instance we'll
clear out the :class:`~pymongo.son_manipulator.SONManipulator`
instance we added:

.. doctest::

  >>> db = client.custom_type_example

This allows us to see what was actually saved to the database:

.. doctest::

  >>> db.test.find_one()
  {u'_id': ObjectId('...'), u'custom': {u'x': 5, u'_type': u'custom'}}

which is the same format that we encode to with our
:meth:`encode_custom` method!

Binary Encoding
---------------

We can take this one step further by encoding to binary, using a user
defined subtype. This allows us to identify what to decode without
resorting to tricks like the ``_type`` field used above.

We'll start by defining the methods :meth:`to_binary` and
:meth:`from_binary`, which convert :class:`Custom` instances to and
from :class:`~bson.binary.Binary` instances:

.. note:: You could just pickle the instance and save that. What we do
   here is a little more lightweight.

.. doctest::

  >>> from bson.binary import Binary
  >>> def to_binary(custom):
  ...   return Binary(str(custom.x()), 128)
  ...
  >>> def from_binary(binary):
  ...   return Custom(int(binary))
  ...

Next we'll create another
:class:`~pymongo.son_manipulator.SONManipulator`, this time using the
methods we just defined:

.. doctest::

  >>> class TransformToBinary(SONManipulator):
  ...   def transform_incoming(self, son, collection):
  ...     for (key, value) in son.items():
  ...       if isinstance(value, Custom):
  ...         son[key] = to_binary(value)
  ...       elif isinstance(value, dict):
  ...         son[key] = self.transform_incoming(value, collection)
  ...     return son
  ...
  ...   def transform_outgoing(self, son, collection):
  ...     for (key, value) in son.items():
  ...       if isinstance(value, Binary) and value.subtype == 128:
  ...         son[key] = from_binary(value)
  ...       elif isinstance(value, dict):
  ...         son[key] = self.transform_outgoing(value, collection)
  ...     return son
  ...

Now we'll empty the :class:`~pymongo.database.Database` and add the
new manipulator:

.. doctest::

  >>> db.test.remove()
  {...}
  >>> db.add_son_manipulator(TransformToBinary())

After doing so we can save and restore :class:`Custom` instances
seamlessly:

.. doctest::

  >>> db.test.insert({"custom": Custom(5)})
  ObjectId('...')
  >>> db.test.find_one()
  {u'_id': ObjectId('...'), u'custom': <Custom object at ...>}
  >>> db.test.find_one()["custom"].x()
  5

We can see what's actually being saved to the database (and verify
that it is using a :class:`~bson.binary.Binary` instance) by
clearing out the manipulators and repeating our
:meth:`~pymongo.collection.Collection.find_one`:

.. doctest::

  >>> db = client.custom_type_example
  >>> db.test.find_one()
  {u'_id': ObjectId('...'), u'custom': Binary('5', 128)}