Refty
mongo-thingy
Python

:leaves: Powerful schema-less ODM for MongoDB and Python (sync + async)

Last updated Feb 1, 2026
69
Stars
12
Forks
9
Issues
0
Stars/day
Attention Score
27
Language breakdown
Python 100.0%
โ–ธ Files click to expand
README

[pymongo]: https://github.com/mongodb/mongo-python-driver [thingy]: https://github.com/Refty/thingy [mongomock]: https://github.com/mongomock/mongomock [montydb]: https://github.com/davidlatwe/montydb [motor]: https://github.com/mongodb/motor [mongomock-motor]: https://github.com/michaelkryukov/mongomock_motor

Mongo-Thingy

PyPI Supported Python Versions License Code style
Tests Tests Docs

**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.

What you'll get:

  • a simple and robust pure-Python code base, with 100% coverage and few
dependencies;
  • [PyMongo][pymongo] query language - no need to learn yet another one;
  • both sync and async support! choose what suits you best;
  • [Thingy][thingy] views - control what to show, and create fields based on
other fields;
  • swappable backend - wanna use SQLite behind the scenes? well, you can;
  • versioning (optional) - rollback to any point in any thingy history;
  • and more!

Compatibility

We support all Python and MongoDB versions supported by [PyMongo][pymongo], namely:

  • CPython 3.7+ and PyPy3.7+
  • MongoDB 3.6, 4.0, 4.2, 4.4, and 5.0.
As a backend, Mongo-Thingy supports the following libraries:
  • Synchronous:
* [PyMongo][pymongo] (default) * [Mongomock][mongomock] * [MontyDB][montydb]
  • Asynchronous:
* [Motor][motor] (default when Motor is installed) * [Motor][motor] with Tornado (default when Motor and Tornado are installed) * [Mongomock-Motor][mongomock-motor]

Install

pip install mongo-thingy

Examples

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_documents() 1 >>> User.find_one({"age": 42}) User({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 42})

In an AsyncIO (or Tornado) environment, use the asynchronous class instead:

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

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

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

To use another backend than the default ones, just pass its client class with `client_cls:

<pre><code class="lang-python">&gt;&gt;&gt; import mongomock &gt;&gt;&gt; connect(client_cls=mongomock.MongoClient)</code></pre>

Update a thingy

<pre><code class="lang-python">&gt;&gt;&gt; user.age 42 &gt;&gt;&gt; user.age = 1337 &gt;&gt;&gt; user.save() User({&#39;_id&#39;: ObjectId(...), &#39;name&#39;: &#39;Mr. Foo&#39;, &#39;age&#39;: 1337})</code></pre>

Thingy views power

Complete information with properties

<pre><code class="lang-python">&gt;&gt;&gt; class User(Thingy): ... @property ... def username(self): ... return &quot;&quot;.join(char for char in self.name if char.isalpha())

&gt;&gt;&gt; User.add_view(name=&quot;everything&quot;, defaults=True, include=&quot;username&quot;) &gt;&gt;&gt; user = User.find_one() &gt;&gt;&gt; user.view(&quot;everything&quot;) {&#39;_id&#39;: ObjectId(...), &#39;name&#39;: &#39;Mr. Foo&#39;, &#39;age&#39;: 1337, &#39;username&#39;: &#39;MrFoo&#39;}</code></pre>

Hide sensitive stuff

<pre><code class="lang-python">&gt;&gt;&gt; User.add_view(name=&quot;public&quot;, defaults=True, exclude=&quot;password&quot;) &gt;&gt;&gt; user.password = &quot;t0ps3cr3t&quot; &gt;&gt;&gt; user.view() {&#39;_id&#39;: ObjectId(...), &#39;name&#39;: &#39;Mr. Foo&#39;, &#39;age&#39;: 1337, &#39;password&#39;: &#39;t0ps3cr3t&#39;} &gt;&gt;&gt; user.view(&quot;public&quot;) {&#39;_id&#39;: ObjectId(...), &#39;name&#39;: &#39;Mr. Foo&#39;, &#39;age&#39;: 1337}</code></pre>

Only use certain fields/properties

<pre><code class="lang-python">&gt;&gt;&gt; User.add_view(name=&quot;credentials&quot;, include=[&quot;username&quot;, &quot;password&quot;]) &gt;&gt;&gt; user.view(&quot;credentials&quot;) {&#39;username&#39;: &#39;MrFoo&#39;, &#39;password&#39;: &#39;t0ps3cr3t&#39;}</code></pre>

Apply views on cursors

<pre><code class="lang-python">&gt;&gt;&gt; cursor = User.find() &gt;&gt;&gt; for credentials in cursor.view(&quot;credentials&quot;): ... print(credentials) {&#39;username&#39;: &#39;MrFoo&#39;, &#39;password&#39;: &#39;t0ps3cr3t&#39;} {&#39;username&#39;: &#39;MrsBar&#39;, &#39;password&#39;: &#39;123456789&#39;} ...</code></pre>

And if your cursor is already exhausted, you can still apply a view!

<pre><code class="lang-python">&gt;&gt;&gt; users = User.find().to_list(None) &gt;&gt;&gt; for credentials in users.view(&quot;credentials&quot;): ... print(credentials) {&#39;username&#39;: &#39;MrFoo&#39;, &#39;password&#39;: &#39;t0ps3cr3t&#39;} {&#39;username&#39;: &#39;MrsBar&#39;, &#39;password&#39;: &#39;123456789&#39;} ...</code></pre>

Versioning

<pre><code class="lang-python">&gt;&gt;&gt; from mongo_thingy.versioned import Versioned

&gt;&gt;&gt; class Article(Versioned, Thingy): ... pass

&gt;&gt;&gt; article = Article(c) &gt;&gt;&gt; article.version 0

&gt;&gt;&gt; article.save() Article({&#39;_id&#39;: ObjectId(&#39;...&#39;), &#39;content&#39;: &#39;Cogito ergo sum&#39;}) &gt;&gt;&gt; article.version 1

&gt;&gt;&gt; article.content = &quot;Sum ergo cogito&quot; &gt;&gt;&gt; article.save() Article({&#39;_id&#39;: ObjectId(&#39;...&#39;), &#39;content&#39;: &#39;Sum ergo cogito&#39;}) &gt;&gt;&gt; article.version 2

&gt;&gt;&gt; article.revert() Article({&#39;_id&#39;: ObjectId(&#39;...&#39;), &#39;content&#39;: &#39;Cogito ergo sum&#39;}) &gt;&gt;&gt; article.version 3</code></pre>

Database/collection "discovery"

Default behaviour

<pre><code class="lang-python">&gt;&gt;&gt; class AuthenticationGroup(Thingy): ... pass

&gt;&gt;&gt; connect(&quot;mongodb://localhost/&quot;) &gt;&gt;&gt; AuthenticationGroup.collection Collection(Database(MongoClient(host=[&#39;localhost:27017&#39;], ...), &#39;authentication&#39;), &#39;group&#39;)</code></pre>

Use mismatching names for Thingy class and database collection

You can either specify the collection name:

<pre><code class="lang-python">&gt;&gt;&gt; class Foo(Thingy): ... collection_name = &quot;bar&quot;</code></pre>

or the collection directly:

<pre><code class="lang-python">&gt;&gt;&gt; class Foo(Thingy): ... collection = db.bar</code></pre>

You can then check what collection is being used with:

<pre><code class="lang-python">&gt;&gt;&gt; Foo.collection Collection(Database(MongoClient(&#39;localhost&#39;, 27017), &#39;database&#39;), &#39;bar&#39;)</code></pre>

Indexes

Create an index

<pre><code class="lang-python">&gt;&gt;&gt; User.create_index(&quot;email&quot;, sparse=True, unique=True)</code></pre>

Add one or more indexes, create later

<pre><code class="lang-python">&gt;&gt;&gt; User.add_index(&quot;email&quot;, sparse=True, unique=True) &gt;&gt;&gt; User.add_index(&quot;username&quot;)

&gt;&gt;&gt; User.create_indexes()</code></pre>

Create all indexes of all thingies at once

<pre><code class="lang-python">&gt;&gt;&gt; from mongothingy import createindexes &gt;&gt;&gt; create_indexes()</code></pre>

Dealing with camelCase data

<pre><code class="lang-python">&gt;&gt;&gt; from mongo_thingy.camelcase import CamelCase

&gt;&gt;&gt; class SystemUser(CamelCase, Thingy): ... collection_name = &quot;systemUsers&quot;

&gt;&gt;&gt; user = SystemUser.find_one() &gt;&gt;&gt; user.view() {&#39;_id&#39;: ObjectId(...), &#39;firstName&#39;: &#39;John&#39;, &#39;lastName&#39;: &#39;Doe&#39;}

&gt;&gt;&gt; user.first_name &#39;John&#39; &gt;&gt;&gt; user.first_name = &quot;Jonny&quot; &gt;&gt;&gt; user.save() SystemUser({&#39;_id&#39;: ObjectId(...), firstName: &#39;Jonny&#39;, lastName: &#39;Doe&#39;})</code></pre>

Tests

To run the tests suite:

- make sure you have a MongoDB database running on localhost:27017 (you can spawn one with docker compose up -d); - install developers requirements with pip install -r requirements.txt; - run pytest`.

Sponsors

    Numberly         Refty    

ยฉ 2026 GitRepoTrend ยท Refty/mongo-thingy ยท Updated daily from GitHub