{"id":25277082,"url":"https://github.com/refty/mongo-thingy","last_synced_at":"2025-04-04T21:06:10.256Z","repository":{"id":39415342,"uuid":"88879534","full_name":"Refty/mongo-thingy","owner":"Refty","description":":leaves: Powerful schema-less ODM for MongoDB and Python (sync + async)","archived":false,"fork":false,"pushed_at":"2024-12-11T18:22:39.000Z","size":167,"stargazers_count":70,"open_issues_count":7,"forks_count":12,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-02T12:58:00.548Z","etag":null,"topics":["mongodb","mongodb-odm","mongodb-orm","odm","orm","pymongo","python","python-library","python3"],"latest_commit_sha":null,"homepage":"https://mongo-thingy.readthedocs.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Refty.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-20T15:10:09.000Z","updated_at":"2025-03-30T14:36:58.000Z","dependencies_parsed_at":"2024-02-03T08:44:41.502Z","dependency_job_id":"67ed9914-0c62-46f6-82cf-d6aa61f54b38","html_url":"https://github.com/Refty/mongo-thingy","commit_stats":{"total_commits":200,"total_committers":4,"mean_commits":50.0,"dds":"0.17500000000000004","last_synced_commit":"cc535ed69022f4793634e65df7edf8d48591757f"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Refty%2Fmongo-thingy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Refty%2Fmongo-thingy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Refty%2Fmongo-thingy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Refty%2Fmongo-thingy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Refty","download_url":"https://codeload.github.com/Refty/mongo-thingy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247249524,"owners_count":20908212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mongodb","mongodb-odm","mongodb-orm","odm","orm","pymongo","python","python-library","python3"],"created_at":"2025-02-12T16:41:00.809Z","updated_at":"2025-04-04T21:06:10.235Z","avatar_url":"https://github.com/Refty.png","language":"Python","readme":"[pymongo]: https://github.com/mongodb/mongo-python-driver\n[thingy]: https://github.com/Refty/thingy\n[mongomock]: https://github.com/mongomock/mongomock\n[montydb]: https://github.com/davidlatwe/montydb\n[motor]: https://github.com/mongodb/motor\n[mongomock-motor]: https://github.com/michaelkryukov/mongomock_motor\n\n![Mongo-Thingy](https://socialify.git.ci/Refty/mongo-thingy/image?font=Bitter\u0026language=1\u0026logo=data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%2520viewBox%253D%25220%25200%25201024%25201024%2522%253E%253Cdefs%253E%253Cstyle%253E.a%257Bfill%253A%252300684a%253B%257D%253C%252Fstyle%253E%253C%252Fdefs%253E%253Ctitle%253EMongoDB%253C%252Ftitle%253E%253Cpath%2520class%253D%2522a%2522%2520d%253D%2522M600.5%252C118.86C557.8%252C68.41%252C521%252C17.18%252C513.51%252C6.54a1.92%252C1.92%252C0%252C0%252C0-2.77%252C0c-7.51%252C10.64-44.29%252C61.87-87%252C112.32C57.19%252C584.3%252C481.48%252C898.4%252C481.48%252C898.4l3.56%252C2.37C488.2%252C949.24%252C496.11%252C1019%252C496.11%252C1019h31.63s7.91-69.36%252C11.08-118.23l3.55-2.76C542.77%252C898%252C967.06%252C584.3%252C600.5%252C118.86ZM511.93%252C891.31s-19-16.16-24.12-24.44v-.78l22.93-506.83a1.19%252C1.19%252C0%252C0%252C1%252C2.37%252C0l22.94%252C506.83v.78C530.91%252C875.15%252C511.93%252C891.31%252C511.93%252C891.31Z%2522%252F%253E%253C%252Fsvg%253E\u0026owner=1\u0026pattern=Charlie%20Brown\u0026theme=Light)\n\n\u003cdiv align=\"center\"\u003e\n    \u003ca href=\"https://pypi.org/project/mongo-thingy\"\u003e\u003cimg src=\"https://img.shields.io/pypi/v/mongo-thingy.svg\" alt=\"PyPI\"\u003e\u003c/a\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/pyversions/mongo-thingy\" alt=\"Supported Python Versions\"\u003e\n    \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/refty/mongo-thingy\" alt=\"License\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/ambv/black\"\u003e\u003cimg src=\"https://img.shields.io/badge/code%20style-black-black\" alt=\"Code style\"\u003e\u003c/a\u003e\n    \u003cbr/\u003e\n    \u003ca href=\"https://github.com/Refty/mongo-thingy/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/Refty/mongo-thingy/tests.yml?branch=master\" alt=\"Tests\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://coveralls.io/github/Refty/mongo-thingy\"\u003e\u003cimg src=\"https://img.shields.io/coveralls/Refty/mongo-thingy.svg\" alt=\"Tests\"\u003e\u003c/a\u003e\n    \u003ca href=\"http://mongo-thingy.readthedocs.io\"\u003e\u003cimg src=\"https://readthedocs.org/projects/mongo-thingy/badge\" alt=\"Docs\"\u003e\u003c/a\u003e\n    \u003cbr /\u003e\u003cbr /\u003e\n\u003c/div\u003e\n\n**_Mongo-Thingy_ is the most idiomatic and friendly-yet-powerful way to use\nMongoDB with Python.**\n\nIt is an _\"Object-Document Mapper\"_ that gives you full advantage of MongoDB\nschema-less design by **not** asking you to define schemas in your code.\n\nWhat you'll get:\n\n- a simple and robust pure-Python code base, with 100% coverage and few\n  dependencies;\n- [PyMongo][pymongo] query language - no need to learn yet another one;\n- both sync and async support! choose what suits you best;\n- [Thingy][thingy] views - control what to show, and create fields based on\n  other fields;\n- swappable backend - wanna use SQLite behind the scenes? well, you can;\n- versioning *(optional)* - rollback to any point in any thingy history;\n- and more!\n\n# Compatibility\n\nWe support all Python and MongoDB versions supported by [PyMongo][pymongo],\nnamely:\n\n- CPython 3.7+ and PyPy3.7+\n- MongoDB 3.6, 4.0, 4.2, 4.4, and 5.0.\n\nAs a backend, Mongo-Thingy supports the following libraries:\n\n- Synchronous:\n\n  * [PyMongo][pymongo] (default)\n  * [Mongomock][mongomock]\n  * [MontyDB][montydb]\n\n- Asynchronous:\n\n  * [Motor][motor] (default when Motor is installed)\n  * [Motor][motor] with Tornado (default when Motor and Tornado are installed)\n  * [Mongomock-Motor][mongomock-motor]\n\n# Install\n\n```sh\npip install mongo-thingy\n```\n\n# Examples\n\n## First steps\n\n### Connect, insert and find thingies\n\n```python\n\u003e\u003e\u003e from mongo_thingy import connect, Thingy\n\u003e\u003e\u003e connect(\"mongodb://localhost/test\")\n\n\u003e\u003e\u003e class User(Thingy):\n...     pass\n\n\u003e\u003e\u003e user = User({\"name\": \"Mr. Foo\", \"age\": 42}).save()\n\u003e\u003e\u003e User.count_documents()\n1\n\u003e\u003e\u003e User.find_one({\"age\": 42})\nUser({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 42})\n```\n\nIn an AsyncIO (or Tornado) environment, use the asynchronous class instead:\n\n```python\n\u003e\u003e\u003e from mongo_thingy import connect, AsyncThingy\n\u003e\u003e\u003e connect(\"mongodb://localhost/test\")\n\n\u003e\u003e\u003e class User(AsyncThingy):\n...     pass\n\n\u003e\u003e\u003e user = await User({\"name\": \"Mr. Foo\", \"age\": 42}).save()\n\u003e\u003e\u003e await User.count_documents()\n1\n\u003e\u003e\u003e await User.find_one({\"age\": 42})\nUser({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 42})\n```\n\nTo use another backend than the default ones, just pass its client class with\n``client_cls``:\n\n```python\n\u003e\u003e\u003e import mongomock\n\u003e\u003e\u003e connect(client_cls=mongomock.MongoClient)\n```\n\n### Update a thingy\n\n```python\n\u003e\u003e\u003e user.age\n42\n\u003e\u003e\u003e user.age = 1337\n\u003e\u003e\u003e user.save()\nUser({'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337})\n```\n\n## Thingy views power\n\n### Complete information with properties\n\n```python\n\u003e\u003e\u003e class User(Thingy):\n...     @property\n...     def username(self):\n...         return \"\".join(char for char in self.name if char.isalpha())\n\n\u003e\u003e\u003e User.add_view(name=\"everything\", defaults=True, include=\"username\")\n\u003e\u003e\u003e user = User.find_one()\n\u003e\u003e\u003e user.view(\"everything\")\n{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337, 'username': 'MrFoo'}\n```\n\n### Hide sensitive stuff\n\n```python\n\u003e\u003e\u003e User.add_view(name=\"public\", defaults=True, exclude=\"password\")\n\u003e\u003e\u003e user.password = \"t0ps3cr3t\"\n\u003e\u003e\u003e user.view()\n{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337, 'password': 't0ps3cr3t'}\n\u003e\u003e\u003e user.view(\"public\")\n{'_id': ObjectId(...), 'name': 'Mr. Foo', 'age': 1337}\n```\n\n### Only use certain fields/properties\n\n```python\n\u003e\u003e\u003e User.add_view(name=\"credentials\", include=[\"username\", \"password\"])\n\u003e\u003e\u003e user.view(\"credentials\")\n{'username': 'MrFoo', 'password': 't0ps3cr3t'}\n```\n\n### Apply views on cursors\n\n```python\n\u003e\u003e\u003e cursor = User.find()\n\u003e\u003e\u003e for credentials in cursor.view(\"credentials\"):\n...     print(credentials)\n{'username': 'MrFoo', 'password': 't0ps3cr3t'}\n{'username': 'MrsBar', 'password': '123456789'}\n...\n```\n\nAnd if your cursor is already exhausted, you can still apply a view!\n\n```python\n\u003e\u003e\u003e users = User.find().to_list(None)\n\u003e\u003e\u003e for credentials in users.view(\"credentials\"):\n...     print(credentials)\n{'username': 'MrFoo', 'password': 't0ps3cr3t'}\n{'username': 'MrsBar', 'password': '123456789'}\n...\n```\n\n## Versioning\n\n```python\n\u003e\u003e\u003e from mongo_thingy.versioned import Versioned\n\n\u003e\u003e\u003e class Article(Versioned, Thingy):\n...     pass\n\n\u003e\u003e\u003e article = Article(content=\"Cogito ergo sum\")\n\u003e\u003e\u003e article.version\n0\n\n\u003e\u003e\u003e article.save()\nArticle({'_id': ObjectId('...'), 'content': 'Cogito ergo sum'})\n\u003e\u003e\u003e article.version\n1\n\n\u003e\u003e\u003e article.content = \"Sum ergo cogito\"\n\u003e\u003e\u003e article.save()\nArticle({'_id': ObjectId('...'), 'content': 'Sum ergo cogito'})\n\u003e\u003e\u003e article.version\n2\n\n\u003e\u003e\u003e article.revert()\nArticle({'_id': ObjectId('...'), 'content': 'Cogito ergo sum'})\n\u003e\u003e\u003e article.version\n3\n```\n\n## Database/collection \"discovery\"\n\n### Default behaviour\n\n```python\n\u003e\u003e\u003e class AuthenticationGroup(Thingy):\n...     pass\n\n\u003e\u003e\u003e connect(\"mongodb://localhost/\")\n\u003e\u003e\u003e AuthenticationGroup.collection\nCollection(Database(MongoClient(host=['localhost:27017'], ...), 'authentication'), 'group')\n```\n\n### Use mismatching names for Thingy class and database collection\n\nYou can either specify the collection name:\n\n```python\n\u003e\u003e\u003e class Foo(Thingy):\n...   collection_name = \"bar\"\n```\n\nor the collection directly:\n\n```python\n\u003e\u003e\u003e class Foo(Thingy):\n...   collection = db.bar\n```\n\nYou can then check what collection is being used with:\n\n```python\n\u003e\u003e\u003e Foo.collection\nCollection(Database(MongoClient('localhost', 27017), 'database'), 'bar')\n```\n\n## Indexes\n\n### Create an index\n\n```python\n\u003e\u003e\u003e User.create_index(\"email\", sparse=True, unique=True)\n```\n\n### Add one or more indexes, create later\n\n```python\n\u003e\u003e\u003e User.add_index(\"email\", sparse=True, unique=True)\n\u003e\u003e\u003e User.add_index(\"username\")\n\n\u003e\u003e\u003e User.create_indexes()\n```\n\n### Create all indexes of all thingies at once\n\n```python\n\u003e\u003e\u003e from mongo_thingy import create_indexes\n\u003e\u003e\u003e create_indexes()\n```\n\n## Dealing with camelCase data\n\n```python\n\u003e\u003e\u003e from mongo_thingy.camelcase import CamelCase\n\n\u003e\u003e\u003e class SystemUser(CamelCase, Thingy):\n...     collection_name = \"systemUsers\"\n\n\u003e\u003e\u003e user = SystemUser.find_one()\n\u003e\u003e\u003e user.view()\n{'_id': ObjectId(...), 'firstName': 'John', 'lastName': 'Doe'}\n\n\u003e\u003e\u003e user.first_name\n'John'\n\u003e\u003e\u003e user.first_name = \"Jonny\"\n\u003e\u003e\u003e user.save()\nSystemUser({'_id': ObjectId(...), firstName: 'Jonny', lastName: 'Doe'})\n```\n\n# Tests\n\nTo run the tests suite:\n\n  - make sure you have a MongoDB database running on `localhost:27017` (you can\n    spawn one with `docker compose up -d`);\n  - install developers requirements with `pip install -r requirements.txt`;\n  - run `pytest`.\n\n# Sponsors\n\n\u003cdiv align=\"center\"\u003e\n    \u0026nbsp;\u0026nbsp;\u0026nbsp;\n    \u003ca href=\"https://numberly.com/\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/Refty/mongo-thingy/master/img/numberly.png\" alt=\"Numberly\"\u003e\u003c/a\u003e\n    \u0026nbsp;\u0026nbsp;\u0026nbsp;\n    \u0026nbsp;\u0026nbsp;\u0026nbsp;\n    \u003ca href=\"https://refty.co/\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/Refty/mongo-thingy/master/img/refty.png\" alt=\"Refty\"\u003e\u003c/a\u003e\n    \u0026nbsp;\u0026nbsp;\u0026nbsp;\n\u003c/div\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frefty%2Fmongo-thingy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frefty%2Fmongo-thingy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frefty%2Fmongo-thingy/lists"}