Mongo-Thingy is the most idiomatic and friendly-yet-powerful way to use MongoDB with Python.

It is an “Object-Document Mapper” that gives you full advantage of MongoDB schema-less design by not asking you to define schemas in your code, but with all the powerful features you would expect from such a library.

Mongo-Thingy has:

  • a simple and robust code base, with 100% coverage and few dependencies;
  • PyMongo query language - no need to learn yet another one;
  • Thingy views - control what to show, and create fields based on other fields;
  • versioning (optional) - rollback to any point in any thingy history;
  • and more!



Mongo-Thingy is pure-Python.

It supports all Python and MongoDB versions supported by PyMongo, namely:

  • CPython 2.7, 3.4+, PyPy, and PyPy3
  • MongoDB 2.6, 3.0, 3.2, 3.4, 3.6, 4.0, and 4.2


$ pip install mongo-thingy


First steps

Connect, insert and find thingies

>>> from mongo_thingy import connect, Thingy
>>> connect("mongodb://localhost/test")

>>> class User(Thingy):
...     pass

>>> user = User({"name": "Mr. Foo", "age": 42}).save()
>>> User.count()
>>> User.find_one({"age": 42})
User({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 42})

Update a thingy

>>> user.age
>>> user.age = 1337
User({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337})

Thingy views power

Complete information with properties

>>> class User(Thingy):
...     @property
...     def username(self):
...         return "".join(char for char in if char.isalpha())

>>> User.add_view(name="everything", defaults=True, include="username")
>>> user = User.find_one()
>>> user.view("everything")
{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337, 'username': 'MrFoo'}

Hide sensitive stuff

>>> User.add_view(name="public", defaults=True, exclude="password")
>>> user.password = "t0ps3cr3t"
>>> user.view()
{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337, 'password': 't0ps3cr3t'}
>>> user.view("public")
{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337}

Only use certain fields/properties

>>> User.add_view(name="credentials", include=["username", "password"])
>>> user.view("credentials")
{'username': 'MrFoo', 'password': 't0ps3cr3t'}

Apply views on cursors

>>> for credentials in User.find().view("credentials"):
...     print(credentials)
{'username': 'MrFoo', 'password': 't0ps3cr3t'}
{'username': 'MrsBar', 'password': '123456789'}


>>> from mongo_thingy.versioned import Versioned

>>> class Article(Versioned, Thingy):
...     pass

>>> article = Article(content="Cogito ergo sum")
>>> article.version

Article({'_id': ObjectId('...'), 'content': 'Cogito ergo sum'})
>>> article.version

>>> article.content = "Sum ergo cogito"
Article({'_id': ObjectId('...'), 'content': 'Sum ergo cogito'})
>>> article.version

>>> article.revert()
Article({'_id': ObjectId('...'), 'content': 'Cogito ergo sum'})
>>> article.version

Database/collection “discovery”

Default behaviour

>>> class AuthenticationGroup(Thingy):
...     pass

>>> connect("mongodb://localhost/")
>>> AuthenticationGroup.collection
Collection(Database(MongoClient(host=['localhost:27017'], ...), 'authentication'), 'group')

Use mismatching names for Thingy class and database collection

You can either specify the collection name:

>>> class Foo(Thingy):
...   collection_name = "bar"

or the collection directly:

>>> class Foo(Thingy):
...   collection =

You can then check what collection is being used with:

>>> Foo.collection
Collection(Database(MongoClient('localhost', 27017), 'database'), 'bar')


Create an index

>>> User.create_index("email", sparse=True, unique=True)

Add one or more indexes, create later

>>> User.add_index("email", sparse=True, unique=True)
>>> User.add_index("username")

>>> User.create_indexes()

Create all indexes of all thingies at once

>>> from mongo_thingy import create_indexes
>>> create_indexes()


To run Mongo-Thingy tests:

  • make sure you have a MongoDB database running on localhost:27017;
  • install developers requirements with pip install -r requirements.txt;
  • run pytest.