{"id":26578818,"url":"https://github.com/pprunty/motormongo","last_synced_at":"2026-03-05T04:03:48.313Z","repository":{"id":209015572,"uuid":"723017514","full_name":"pprunty/motormongo","owner":"pprunty","description":"🤖 An Object Document Mapper (O.D.M) for MongoDB built on-top of Motor's asynchronous library for asynchronous CRUD operations. Built for use in asynchronous backend applications using Python.","archived":false,"fork":false,"pushed_at":"2024-06-08T13:49:49.000Z","size":737,"stargazers_count":18,"open_issues_count":5,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-27T18:37:42.836Z","etag":null,"topics":["asynchronous","asyncio","backend","fastapi","high-performance","mongo","mongodb","mongoengine","motor","motormongo","nosql","odm","orm","pymongo","python","scalable","tornado"],"latest_commit_sha":null,"homepage":"https://motormongo.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/pprunty.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":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-11-24T13:35:15.000Z","updated_at":"2025-11-23T00:15:26.000Z","dependencies_parsed_at":"2023-11-24T15:26:53.166Z","dependency_job_id":"3f1d2b81-e9fc-44db-bfd9-2f9aa2302f83","html_url":"https://github.com/pprunty/motormongo","commit_stats":null,"previous_names":["pprunty/motormongo"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/pprunty/motormongo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fmotormongo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fmotormongo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fmotormongo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fmotormongo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pprunty","download_url":"https://codeload.github.com/pprunty/motormongo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pprunty%2Fmotormongo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30109078,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T03:40:26.266Z","status":"ssl_error","status_checked_at":"2026-03-05T03:39:15.902Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["asynchronous","asyncio","backend","fastapi","high-performance","mongo","mongodb","mongoengine","motor","motormongo","nosql","odm","orm","pymongo","python","scalable","tornado"],"created_at":"2025-03-23T05:17:08.893Z","updated_at":"2026-03-05T04:03:48.296Z","avatar_url":"https://github.com/pprunty.png","language":"Python","readme":"![motormongo.png](motormongo.png)\n\n| Description             | Badge                                                                                                                                                   |\n|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|\n| **PyPI - Version**      | [![PyPI - Version](https://img.shields.io/pypi/v/motormongo)](https://pypi.org/project/motormongo/)                                                     |\n| **Downloads**           | [![Downloads](https://static.pepy.tech/badge/motormongo/month)](https://pepy.tech/project/motormongo)                                                   |\n| **PyPI License**        | [![PyPI License](https://img.shields.io/pypi/l/motormongo.svg)](https://pypi.org/project/motormongo/)                                                   |\n| **GitHub Contributors** | [![GitHub Contributors](https://img.shields.io/github/contributors/pprunty/motormongo.svg)](https://github.com/pprunty/motormongo/graphs/contributors)  |\n| **Code Coverage**       | [![codecov](https://codecov.io/gh/pprunty/motormongo/graph/badge.svg?token=XSNQ1ZBWIF)](https://codecov.io/gh/pprunty/motormongo)                       |\n| **Code Style**          | ![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)                                                                        |\n| **Documentation**       | [![Documentation Status](https://readthedocs.org/projects/motormongo/badge/?version=latest)](https://motormongo.readthedocs.io/en/latest/?badge=latest) |\n\n[//]: # ([![PyPI - Downloads]\u0026#40;https://img.shields.io/pypi/dm/motormongo\u0026#41;]\u0026#40;https://pypi.org/project/motormongo/\u0026#41;)\n\nAuthor: [Patrick Prunty](https://pprunty.github.io/pprunty/).\n\n`motormongo` is an Object Document Mapper (ODM) for [MongoDB](https://www.mongodb.com) built on top\nof [`motor`](https://github.com/mongodb/motor), the MongoDB\nrecommended asynchronous Python driver for MongoDB Python applications, designed to work with Tornado or\nasyncio and enable non-blocking access to MongoDB.\n\nAsynchronous operations in a backend system, built using [FastAPI](https://github.com/tiangolo/fastapi) for\nexample, enhances performance and scalability by enabling non-blocking, concurrent handling of multiple I/O requests,\nleading to more efficient use of server resources, by forcing the CPU usage on the backend server's main thread to be\nmaximized across concurrent requests. For more low-level details on the advantages of asynchronous `motormongo` over\nexisting MongoDB ODMs, such as `mongoengine` [see here](#why-use-motormongo).\n\nThe interface for instantiating Document classes follows similar logic\nto [`mongoengine`](https://github.com/MongoEngine/mongoengine), enabling ease-of-transition and\nmigration from `mongoengine` to the asynchronous `motormongo`.\n\nCheck out the documentation site [here](https://motormongo.readthedocs.io).\n\n1. [Installation](#installation)\n2. [Quickstart](#quickstart)\n3. [Why use motormongo?](#why-use-motormongo)\n4. [motormongo Fields](#motormongo-fields)\n5. [CRUD classmethods](#class-methods)\n6. [CRUD instance methods](#instance-methods)\n7. [Aggregation Operations](#aggregation)\n8. [Polymorphism and Inheritance](#polymorphism-and-inheritance)\n9. [Pooling Options and Configuration](#pooling-options-configuration)\n10. [FastAPI integration](#fastapi-integration)\n11. [License](#license)\n\n## Installation\n\nTo install `motormongo`, you can use `pip` inside your virtual environment:\n\n```shell\npython -m pip install motormongo\n```\n\nAlternatively, to install `motormongo` into your `poetry` environment:\n\n```shell\npoetry add motormongo\n```\n\n## Quickstart\n\n### Step 1. Create a motormongo client:\n\n```python\nimport asyncio\nfrom motormongo import DataBase\n\n\nasync def init_db():\n    # This 'connect' method needs to be called inside of an async function\n    await DataBase.connect(uri=\"\u003cmongo_uri\u003e\", db=\"\u003cmongo_database\u003e\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(init_db())\n```\n\nor, in a FastAPI application:\n\n```python\nfrom fastapi import FastAPI\nfrom motormongo import DataBase\n\napp = FastAPI()\n\n\n@app.on_event(\"startup\")\nasync def startup_db_client():\n    await DataBase.connect(uri=\"\u003cmongo_uri\u003e\", db=\"\u003cmongo_database\u003e\")\n\n\n@app.on_event(\"shutdown\")\nasync def shutdown_db_client():\n    await DataBase.close()\n```\n\nThe `mongo_uri` should look something like this:\n\n```text\nmongodb+srv://\u003cusername\u003e:\u003cpassword\u003e@\u003ccluster\u003e.mongodb.net\n```\n\nand `database` should be the name of an existing MongoDB database in your MongoDB instance.\n\nFor details on how to set up a local or cloud MongoDB database instance,\nsee [here](https://www.mongodb.com/cloud/atlas/lp/try4?utm_source=google\u0026utm_campaign=search_gs_pl_evergreen_atlas_general_prosp-brand_gic-null_emea-ie_ps-all_desktop_eng_lead\u0026utm_term=using%20mongodb\u0026utm_medium=cpc_paid_search\u0026utm_ad=p\u0026utm_ad_campaign_id=9510384711\u0026adgroup=150907565274\u0026cq_cmp=9510384711\u0026gad_source=1\u0026gclid=Cj0KCQiAyeWrBhDDARIsAGP1mWQ6B0kPYX9Tqmzku-4r-uUzOGL1PKDgSTlfpYeZ0I6HE3C-dgh1xF4aArHqEALw_wcB).\n\nYou can also specify and pass `pooling_options` to the Motor on the `DataBase.connect()` method, like so:\n\n```python\nimport asyncio\nfrom motormongo import DataBase\n\n# Example pooling options\npooling_options = {\n    'maxPoolSize': 50,\n    'minPoolSize': 10,\n    'maxIdleTimeMS': 30000,\n    'waitQueueTimeoutMS': 5000,\n    'connectTimeoutMS': 10000,\n    'socketTimeoutMS': 20000\n}\n\n\nasync def init_db():\n    # This 'connect' method needs to be called inside of an async function\n    await DataBase.connect(uri=\"\u003cmongo_uri\u003e\", db=\"\u003cmongo_database\u003e\", **pooling_options)\n\n\nif __name__ == \"__main__\":\n    asyncio.run(init_db())\n```\n\nSee [Pooling Options Configuration](#pooling-options-configuration) section for more details.\n\n### Step 2. Define a motormongo Document:\n\nDefine a `motormongo` `User` document:\n\n```python\nimport re\nimport bcrypt\nfrom motormongo import Document, BinaryField, StringField\n\n\ndef hash_password(password: str) -\u003e bytes:\n    # Example hashing function\n    return bcrypt.hashpw(password.encode('utf-8'), salt=bcrypt.gensalt())\n\n\nclass User(Document):\n    username = StringField(help_text=\"The username for the user\", min_length=3, max_length=50)\n    email = StringField(help_text=\"The email for the user\", regex=re.compile(r'^\\S+@\\S+\\.\\S+$'))  # Simple email regex\n    password = BinaryField(help_text=\"The hashed password for the user\", hash_function=hash_password)\n\n    def verify_password(self, password: str) -\u003e bool:\n        \"\"\" Utility function which can be used to validate user's salted password later...\n        \n        ex.     user = await User.find_one({\"_id\": request.user_id})\n                is_authenticated = user.verify_password(request.password)\n        \"\"\"\n        return bcrypt.checkpw(password.encode(\"utf-8\"), self.password)\n\n    class Meta:\n        collection = \"users\"  # \u003c If not provided, will default to class name (ex. User-\u003euser, UserDetails-\u003euser_details)\n        created_at_timestamp = True  # \u003c Provide a DateTimeField for document creation\n        updated_at_timestamp = True  # \u003c Provide a DateTimeField for document updates\n```\n\n### Step 3: Create a MongoDB document using the User class\n\n```python\nimport bcrypt\n\nawait User.insert_one(\n    {\n        \"username\": \"johndoe\",\n        \"email\": \"johndoe@portmarnock.ie\",\n        \"password\": \"password123\"\n        # \u003c hash_functon will hash the string literal password and store binary field in the database\n    }\n)\n```\n\n### Step 4: Validate user was created in your MongoDB collection\n\nYou can do this by using [MongoDB compass](https://www.mongodb.com/products/tools/compass) GUI, or alternatively, add a\nquery to find all documents in the user\ncollection after doing the insert in step 3:\n\n```python\nusers = User.find_many({})\nif users:\n    print(\"User collection contains the following documents:\")\n    for user in users:\n        print(user.to_dict())\nelse:\n    print(\"User collection failed to update! Check your MongoDB connection details and try again!\")\n```\n\n### Step 5: Put all the code above into one file and run it\n\n```shell\npython main.py\n```\n\nor in a FastAPI application:\n\n```shell\nuvicorn main:app --reload\n```\n\nPlease refer to [FastAPI Documentation](https://fastapi.tiangolo.com/tutorial/) for more details on how to get setup\nwith FastAPI.\n\n## Congratulations 🎉\n\nYou've successfully created your first `motormongo` Object Document Mapper class. 🥳\n\nThe subsequent sections detail the datatype fields provided by `motormongo`, as well as the CRUD\noperations available on the classmethods and object instance methods of a `motormongo` document.\n\nIf you wish to get straight into how to integrate `motormongo` with your `FastAPI` application, skip ahead to the\n[FastAPI Integration](#fastapi-integration) section.\n\n## Why use motormongo?\n\nIntegrating `motormongo`, an asynchronous Object-Document Mapper (ODM) for MongoDB, into a backend built with an\nasynchronous web framework like `FastAPI`, enhances the system's ability to handle I/O-bound operations efficiently.\nUsing the `await` keyword with `motormongo` operations allows the event loop to manage concurrent requests effectively,\nfreeing up the main thread to handle other tasks while waiting for database operations to complete.\n\n### Understanding the Efficiency of Asynchronous Operations\n\n#### Traditional Approach with `pymongo` and `mongoengine` (Synchronous)\n\nTypically, a web server handling multiple requests that involve fetching documents from MongoDB would face bottlenecks\nwith synchronous database operations:\n\n```python\nfrom fastapi import FastAPI, HTTPException\nfrom mongoengine import connect, Document, StringField\nfrom fastapi.encoders import jsonable_encoder\n\nconnect(db=\"testdb\", host=\"mongodb://localhost:27017/\", alias=\"default\")\n\n\nclass User(Document):\n    name = StringField(required=True)\n\n\napp = FastAPI()\n\n\n@app.get(\"/get_data/\")\nasync def get_data(name: str):\n    user = User.objects(name=name).first()\n    if user:\n        return jsonable_encoder(user.to_mongo().to_dict())\n    else:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n```\n\nIn this setup, operations such as `User.objects(name=name).first()` block the thread until completion, hindering the\nserver's ability to process other requests concurrently, leading to inefficient resource use and potential performance\nbottlenecks.\n\n#### Asynchronous Approach with `motor` and `motormongo`\n\nSwitching to an asynchronous ODM like `motormongo` allows FastAPI to handle database operations without blocking:\n\n```python\nfrom fastapi import FastAPI\nfrom motormongo import DataBase, Document, StringField\n\n\nclass User(Document):\n    name = StringField(required=True)\n\n\napp = FastAPI()\n\n\n@app.on_event(\"startup\")\nasync def startup_db_client():\n    await DataBase.connect(uri=\"mongodb://localhost:27017/\", db=\"testdb\")\n\n\n@app.get(\"/get_data/\")\nasync def get_data(name: str):\n    user = await User.find_one({'name': name})\n    if user:\n        return user.to_dict()\n    else:\n        return {\"message\": \"User not found\"}\n```\n\nUsing `await` with `motormongo` operations such as `User.find_one()` allows the application to perform non-blocking\ndatabase operations. This asynchronous model is particularly advantageous for I/O-bound applications, allowing the\nserver to handle multiple requests efficiently by utilizing Python's `asyncio`.\n\n### Maximizing Performance with Multiple FastAPI Workers\n\nTo further enhance the performance of FastAPI applications utilizing `motormongo`, deploying multiple worker processes\ncan significantly increase the application's ability to handle high volumes of concurrent requests:\n\n- **Scalability**: Deploying FastAPI with multiple workers enables the application to scale across multiple CPU cores,\n  offering better handling of concurrent requests by running multiple instances of the application, each in its own\n  process.\n- **Resource Utilization**: More workers mean that the application can utilize more system resources, effectively\n  distributing the load and preventing any single worker from becoming a bottleneck.\n- **Deployment Strategy**: Use an ASGI server like `uvicorn` with the `--workers` option to specify the number of worker\n  processes. For example, `uvicorn app:app --workers 4` would run the application with four worker processes.\n\nBy leveraging `motormongo` with FastAPI, developers can build backend systems capable of handling asynchronous I/O-bound\noperations efficiently. This setup not only improves the application's responsiveness and throughput by utilizing the\nasynchronous capabilities of Python's `asyncio` but also maximizes performance through the strategic deployment of\nmultiple workers. Together, these strategies enable the creation of highly scalable, efficient, and modern web\napplications.\n\n## motormongo Fields\n\n`motormongo` supports a variety of field types to accurately define the schema of your MongoDB documents. Each field\ntype is designed to handle specific data types and validations:\n\n- [BinaryField](#binaryfield): Stores binary data, useful for storing encoded or hashed data like passwords.\n- [BooleanField](#booleanfield): Stores boolean values (`True` or `False`).\n- [DateTimeField](#datetimefield): Manages date and time, with options for automatically setting current date/time on\n  creation or update.\n- [EmbeddedDocumentField](#embeddeddocumentfield): For fields that should contain values from a predefined enumeration.\n- [EnumField](#enumfield): For fields that should contain values from a predefined enumeration.\n- [FloatField](#floatfield): Handles floating-point numbers, with options to specify minimum and maximum values.\n- [GeoJSONField](#geojsonfield): Manages geographical data in GeoJSON format, with an option to return data as JSON.\n- [IntegerField](#integerfield): Manages integer data, allowing specifications for minimum and maximum values.\n- [ListField](#listfield): Handles lists of items, which can be of any type.\n- [ReferenceField](#referencefield): Creates a reference to another document.\n- [StringField](#stringfield): Handles string data with options for minimum and maximum length, and regex validation.\n\nAll fields have the following default parameters:\n\n* **`default`**: Specifies the default value for the field if no value is provided. This parameter can be a static value\n  or a callable object. The callable object is useful for dynamic values like generating timestamps or unique\n  identifiers.\n* **`required`**: A boolean indicating whether the field is mandatory. If set to True, the document cannot be saved\n  without providing a value for this field.\n* **`unique`**: A boolean specifying if the field value should be unique across the collection. This is crucial for\n  fields like email addresses or usernames.\n\n[//]: # (* **`null`**: add details)\n\n### BinaryField\n\nThe `BinaryField` is designed for storing binary data within a database. It offers capabilities for encoding, hashing,\nand decoding data, making it versatile for handling various types of binary data, including but not limited to encrypted\nor hashed content.\n\n**Parameters:**\n\n- `hash_function`: (Optional) A callable that hashes input data. The function should have a type annotation to indicate\n  whether it expects a `str` or `bytes` as input. This annotation is crucial as it dictates whether the `BinaryField`\n  should encode the string before hashing. If the annotation indicates `str`, the field will pass the string directly to\n  the `hash_function`. If `bytes`, the `BinaryField` will encode the string (using the provided `encode` function or\n  default UTF-8 encoding) before hashing.\n- `return_decoded`: (Optional) A boolean indicating whether to decode binary data when it is retrieved from the\n  database. If set to `True`, the stored binary data will be decoded back into a string using the provided `decode`\n  function or default UTF-8 decoding. This is useful for data that was encoded but not hashed, as hashed data cannot be\n  meaningfully decoded.\n- `encode`: (Optional) A function to encode a string to bytes before storage. If not provided, the class defaults to\n  UTF-8 encoding. This function is used when the input data is a string and needs to be stored as binary data, or before\n  hashing if the `hash_function` expects `bytes`.\n- `decode`: (Optional) A function to decode bytes back to a string when data is retrieved from the database. This\n  parameter is only relevant if `return_decoded` is `True`. If not provided, the class defaults to UTF-8 decoding.\n\n**Important**: For the `hash_function` to work correctly with the `BinaryField`, it must include type annotations for\nits parameters. This enables the `BinaryField` to determine the correct processing strategy (i.e., whether to encode the\nstring before hashing).\n\n### Example Usage:\n\n```python\nfrom motormongo import Document, BinaryField, StringField\nimport bcrypt\n\n\n# Hash function with type annotation indicating it expects a 'str'\ndef hash_password(password: str) -\u003e bytes:\n    return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())\n\n\nclass User(Document):\n    username = StringField(min_length=3, max_length=50)\n    # Note: 'hash_function' requires a type annotation\n    password = BinaryField(hash_function=hash_password, return_decoded=False)\n\n    def verify_password(self, password: str) -\u003e bool:\n        # Verifies if the provided password matches the stored hash\n        return bcrypt.checkpw(password.encode(\"utf-8\"), self.password)\n\n\n# Creating a user instance with a hashed password\nuser = User(username=\"johndoe\", password=\"secret\")\ninserted_user = await user.save()\n\n# Authentication checks\nis_authenticated = inserted_user.verify_password(\"wrongpassword\")  # Expected to return False\nis_authenticated = inserted_user.verify_password(\"secret\")  # Expected to return True\n```\n\n### BooleanField\n\nThe `BooleanField` is used for storing boolean values (True or False). It ensures that the data stored in this field is\nstrictly boolean.\n\n**Parameters:**\n\n- There are no specific parameters unique to BooleanField other than those inherited from the base Field class.\n\n```python\nfrom motormongo import Document, BooleanField, StringField\n\n\nclass Product(Document):\n    name = StringField(min_length=1, max_length=100)\n    is_available = BooleanField(default=False)\n\n\n# Create a product indicating its availability\nproduct = Product(name=\"Gadget\", is_available=True)\nawait product.save()\n```\n\n### DateTimeField\n\nThe `DateTimeField` handles date and time values, with options to automatically update these values on document creation\nor modification.\n\n**Parameters:**\n\n- `auto_now`: Automatically update the field to the current datetime when the document is saved.\n- `auto_now_add`: Automatically set the field to the current datetime when the document is created.\n- `datetime_formats`: List of string formats to parse datetime strings.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, DateTimeField\n\n\nclass Event(Document):\n    start_time = DateTimeField(auto_now_add=True)\n\n\n# Create an event with the current start time\nevent = Event()\nawait event.save()\n```\n\n### EmbeddedDocumentField\n\nThe `EmbeddedDocumentField` is used for embedding documents within a document, supporting nested document structures.\nThis field allows you to include complex data structures as part of your document.\n\n**Parameters:**\n\n- `document_type`: The class of the embedded document, which must be a subclass of `EmbeddedDocument`, `BaseModel` from\n  Pydantic, or `dict` representation of the `EmbeddedDocument`.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, EmbeddedDocument, EmbeddedDocumentField, StringField\nfrom pydantic import BaseModel\n\n\nclass Address(EmbeddedDocument):\n    street = StringField()\n    city = StringField()\n\n\nclass User(Document):\n    name = StringField()\n    address = EmbeddedDocumentField(document_type=Address)\n\n\nclass PydanticAddress(BaseModel):\n    street: str\n    city: str\n\n\n# Create a user with an embedded address document\nuser = User(name=\"John Doe\", address={\"street\": \"123 Elm St\", \"city\": \"Springfield\"})\n# user = User(name=\"John Doe\", address=Address(street=\"123 Elm St\", city=\"Springfield\")) # Also works\n# user = User(name=\"John Doe\", address=PydanticAddress(street=\"123 Elm St\", city=\"Springfield\")) # Also works\nawait user.save()\n```\n\n### EnumField\n\nThe `EnumField` is designed to store enumerated values, allowing for validation against a predefined set of options.\n\n**Parameters:**\n\n- `enum`: The enumeration class that defines valid values for the field.\n\n**Example Usage:**\n\n```python\nimport enum\nfrom motormongo import Document, EnumField\n\n\nclass UserStatus(enum.Enum):\n    ACTIVE = 'active'\n    INACTIVE = 'inactive'\n    BANNED = 'banned'\n\n\nclass User(Document):\n    status = EnumField(enum=UserStatus)\n\n\n# Create a user and set their status using the EnumField\nuser = User(status=UserStatus.ACTIVE)\n# user = User(status=\"active\") # Also works\nawait user.save()\n```\n\n### FloatField\n\nThe `FloatField` handles floating-point numbers, with options to specify minimum and maximum values.\n\n**Parameters:**\n\n- `min_value`: (Optional) The minimum allowable value.\n- `max_value`: (Optional) The maximum allowable value.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, FloatField\n\n\nclass Measurement(Document):\n    temperature = FloatField(min_value=-273.15)  # Absolute zero constraint\n\n\n# Record a temperature measurement\nmeasurement = Measurement(temperature=25.5)\nawait measurement.save()\n```\n\n### GeoJSONField\n\nThe `GeoJSONField` is designed for storing geographical coordinates in GeoJSON format.\n\n**Parameters:**\n\n- `return_as_list`: (Optional) If `True`, returns the coordinates as a [longitude, latitude] list instead of a GeoJSON\n  object.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, GeoJSONField\n\n\nclass Location(Document):\n    point = GeoJSONField()\n\n\n# Create a location point\nlocation = Location(point={\"type\": \"Point\", \"coordinates\": [-73.856077, 40.848447]})  # Could also use \n# location = Location(point=[-73.856077, 40.848447]) # This would also work\nawait location.save()\n```\n\n### IntegerField\n\nThe `IntegerField` is used for storing integer values, with optional validation for minimum and maximum values.\n\n**Parameters:**\n\n- `min_value`: (Optional) The minimum allowable value.\n- `max_value`: (Optional) The maximum allowable value.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, IntegerField\n\n\nclass Product(Document):\n    quantity = IntegerField(min_value=0)\n\n\n# Create a product with quantity validation\nproduct = Product(quantity=10)\nawait product.save()\n```\n\n### ListField\n\nThe `ListField` is used for storing a list of items, optionally validating the type of items in the list.\n\n**Parameters:**\n\n- `field`: (Optional) A `Field` instance specifying the type of items in the list.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, ListField, StringField\n\n\nclass ShoppingList(Document):\n    items = ListField(field=StringField())\n\n\n# Create a shopping list with string items\nshopping_list = ShoppingList(items=[\"Milk\", \"Eggs\", \"Bread\"])\nawait shopping_list.save()\n```\n\n### ReferenceField\n\nThe `ReferenceField` is used to create a reference to another document, typically for creating relationships between\ncollections.\n\n**Parameters:**\n\n- `document`: The class of the document to which the field references.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, ReferenceField, StringField\n\n\nclass User(Document):\n    name = StringField()\n\n\nclass Post(Document):\n    author = ReferenceField(document=User)\n\n\n# Create a user and a post referencing the user\nuser = User(name=\"John Doe\")\nawait user.save()\npost = Post(author=user)\n```\n\nTo fetch the referenced document, you must await the coroutine returned by accessing the reference field. This operation\nasynchronously retrieves the related document instance from the database.\n\n```python\n# Assuming `post` is an instance of the Post document with a reference to a User\n# Fetch the user referenced by the post's author field\nreferenced_user = await post.author\nif referenced_user:\n    print(\"Referenced User:\",\n          referenced_user.to_dict())  # Should print, {'_id': '65d8bf2dad3fa2e9169d2f94', 'name': 'John Doe'}\nelse:\n    print(\"User not found or failed to fetch.\")\n```\n\nThis example demonstrates how to access and asynchronously fetch the document referenced by a `ReferenceField`. The\nawait\nkeyword is crucial because the operation is asynchronous, involving a database query to retrieve the referenced\ndocument.\n\n**Note:** Ensure that the fetching operation is performed within an asynchronous context, such as an async function. The\nReferenceField provides a powerful way to manage relationships between documents, enabling complex data models with\ninterconnected documents.\n\n### StringField\n\nThe `StringField` is used for storing string data in a document. It supports validation for minimum and maximum length\nand can enforce a specific regex pattern.\n\n**Parameters:**\n\n- `min_length`: (Optional) The minimum length of the string.\n- `max_length`: (Optional) The maximum length of the string.\n- `regex`: (Optional) A regex pattern that the string must match.\n\n**Example Usage:**\n\n```python\nfrom motormongo import Document, StringField\n\n\nclass UserProfile(Document):\n    username = StringField(min_length=3, max_length=50)\n    bio = StringField(max_length=200, regex=r'^[A-Za-z0-9 ]*$')  # Alphanumeric and space only\n\n\n# Create a user profile with validation\nprofile = UserProfile(username=\"user123\", bio=\"I love coding.\")\nawait profile.save()\n```\n\n## Class methods\n\n## Operations\n\nThe following class methods are supported by `motormongo`'s `Document` class:\n\n| CRUD Type | Operation                                                                                                                          |\n|-----------|------------------------------------------------------------------------------------------------------------------------------------|\n| Create    | [`insert_one(document: dict, **kwargs) -\u003e Document`](#insert_one)                                                                  |\n| Create    | [`insert_many(documents: List[dict]) -\u003e Tuple[List[Document], Any]`](#insert_many)                                                 |\n| Read      | [`find_one(query: dict, **kwargs) -\u003e Document`](#find_one)                                                                         |\n| Read      | [`find_many(query: dict, limit: int = None, return_as_list: bool = True **kwargs) -\u003e List[Document]`](#find_many)                  |\n| Update    | [`update_one(query: dict, update_fields: dict) -\u003e Document`](#update_one)                                                          |\n| Update    | [`update_many(query: dict, update_fields: dict) -\u003e Tuple[List[Document], int]`](#update_many)                                      |\n| Delete    | [`delete_one(query: dict, **kwargs) -\u003e bool`](#delete_one)                                                                         |\n| Delete    | [`delete_many(query: dict, **kwargs) -\u003e int`](#delete_many)                                                                        |\n| Mixed     | [`find_one_or_create(query: dict, defaults: dict) -\u003e Tuple[Document, bool]`](#find_one_or_create)                                  |\n| Mixed     | [`find_one_and_replace(query: dict, replacement: dict) -\u003e Document`](#find_one_and_replace)                                        |\n| Mixed     | [`find_one_and_delete(query: dict) -\u003e Document`](#find_one_and_delete)                                                             |\n| Mixed     | [`find_one_and_update_empty_fields(query: dict, update_fields: dict) -\u003e Tuple[Document, bool]`](#find_one_and_update_empty_fields) |\n\nAll examples below assume `User` is a subclass of `motormongo` provided Document class.\n\n### Create\n\n#### \u003ca name=\"insert_one\"\u003e\u003c/a\u003e `insert_one(document: dict, **kwargs) -\u003e Document`\n\nInserts a single document into the database.\n\n```python\nuser = await User.insert_one({\n    \"name\": \"John\",\n    \"age\": 24,\n    \"alive\": True\n})\n```\n\nAlternatively, using `**kwargs`:\n\n```python\nuser = await User.insert_one(\n    name=\"John\",\n    age=24,\n    alive=True)\n```\n\nAnd similarly, with a dictionary:\n\n```python\nuser_document = {\n    \"name\": \"John\",\n    \"age\": 24,\n    \"alive\": True\n}\nuser = await User.insert_one(**user_document)\n```\n\n#### \u003ca name=\"insert_many\"\u003e\u003c/a\u003e `insert_many(List[document]) -\u003e tuple[List['Document'], Any]`\n\n```python\nusers, user_ids = await User.insert_many(\n    [\n        {\n            \"name\": \"John\",\n            \"age\": 24,\n            \"alive\": True\n        },\n        {\n            \"name\": \"Mary\",\n            \"age\": 2,\n            \"alive\": False\n        }\n    ]\n)\n```\n\nor\n\n```python\ndocs_to_insert = [{\"name\": \"Alice\", \"age\": 30}, {\"name\": \"Bob\", \"age\": 25}]\ninserted_docs, inserted_ids = await User.insert_many(docs_to_insert)\n```\n\n### Read\n\n#### \u003ca name=\"find_one\"\u003e\u003c/a\u003e `find_one(query, **kwargs) -\u003e Document`\n\n```python\nuser = await User.find_one(\n    {\n        \"_id\": \"655fc281c440f677fa1e117e\"\n    }\n)\n```\n\nAlternatively, using `**kwargs`:\n\n```python\nuser = await User.find_one(_id=\"655fc281c440f677fa1e117e\")\n```\n\n**Note:** The `_id` string datatype here is automatically converted to a BSON ObjectID, however, `motormongo` handles\nthe\nscenario when a\nBSON ObjectId is passed as the `_id` datatype:\n\n```python\nfrom bson import ObjectId\n\nuser = await User.find_one(\n    {\n        \"_id\": ObjectId(\"655fc281c440f677fa1e117e\")\n    }\n)\n```\n\n#### \u003ca name=\"find_many\"\u003e\u003c/a\u003e `find_many(query, limit, **kwargs) -\u003e List[Document]`\n\n```python\nusers = await User.find_many(age={\"$gt\": 40}, alive=False, limit=20)\n```\n\nor\n\n```python\nquery = {\"age\": {\"$gt\": 40}, \"alive\": False}\nusers = await User.find_many(**query, limit=20)\n```\n\n### Update\n\n#### \u003ca name=\"update_one\"\u003e\u003c/a\u003e `update_one(query, updated_fields) -\u003e Document`\n\n```python\nupdated_user = await User.update_one(\n    {\n        \"_id\": \"655fc281c440f677fa1e117e\"\n    },\n    {\n        \"name\": \"new_name\",\n        \"age\": 30\n    }\n)\n```\n\nor\n\n```python\nquery_criteria = {\"name\": \"old_name\"}\nupdate_data = {\"name\": \"updated_name\"}\nupdated_user = await User.update_one(query_criteria, update_data)\n```\n\n#### \u003ca name=\"update_many\"\u003e\u003c/a\u003e `update_many(qeury, fields) -\u003e Tuple[List[Any], int]`\n\n```python\nupdated_users, modified_count = await User.update_many({'age': {'$gt': 40}}, {'category': 'senior'})\n```\n\nanother example:\n\n```python\nupdated_users, modified_count = await User.update_many({'name': 'John Doe'}, {'$inc': {'age': 1}})\n```\n\n### Delete\n\n#### \u003ca name=\"delete_one\"\u003e\u003c/a\u003e `delete_one(query, **kwargs) -\u003e bool`\n\n```python\ndeleted = await User.delete_one({'_id': '507f191e810c19729de860ea'})\n```\n\nAlternatively, using `**kwargs`:\n\n```python\ndeleted = await User.delete_one(name='John Doe')\n```\n\n#### \u003ca name=\"delete_many\"\u003e\u003c/a\u003e `delete_many(query, **kwargs) -\u003e int`\n\n```python\ndeleted_count = await User.delete_many({'age': {'$gt': 40}})\n```\n\nAnother example:\n\n```python\n# Delete all users with a specific status\ndeleted_count = await User.delete_many({'status': 'inactive'})\n```\n\nAlternatively, using `**kwargs`:\n\n```python\ndeleted_count = await User.delete_many(status='inactive')\n```\n\n### Mixed\n\n#### \u003ca name=\"find_one_or_create\"\u003e\u003c/a\u003e `find_one_or_create(query, defaults) -\u003e Tuple['Document', bool]`\n\n```python\nuser, created = await User.find_one_or_create({'username': 'johndoe'}, defaults={'age': 30})\n```\n\n#### \u003ca name=\"find_one_and_replace\"\u003e\u003c/a\u003e `find_one_and_replace(query, replacement) -\u003e Document`\n\n```python\nreplaced_user = await User.find_one_and_replace({'username': 'johndoe'}, {'username': 'johndoe', 'age': 35})\n```\n\n#### \u003ca name=\"find_one_and_delete\"\u003e\u003c/a\u003e `find_one_and_delete(query) -\u003e Document`\n\n```python\ndeleted_user = await User.find_one_and_delete({'username': 'johndoe'})\n```\n\n#### \u003ca name=\"find_one_and_update_empty_fields\"\u003e\u003c/a\u003e `find_one_and_update_empty_fields(query, update_fields) -\u003e Tuple['Document', bool]`\n\n```python\nupdated_user, updated = await User.find_one_and_update_empty_fields(\n    {'username': 'johndoe'},\n    {'email': 'johndoe@example.com', 'age': 30}\n)\n```\n\n## Instance methods\n\n`motormongo` also supports the manipulation of fields on the [object instance](). This allows\nusers to programmatically achieve the same operations listed above through the object instance\nitself.\n\n### Operations\n\nThe following are object instance methods are supported by `motormongo`'s `Document` class:\n\n| CRUD Type | Operation                     |\n|-----------|-------------------------------|\n| Create    | [`save() -\u003e None`](#save)     |\n| Delete    | [`delete() -\u003e None`](#delete) |\n\n**Note:** All update operations can be manipulated on the fields in the Document class object itself.\n\n#### \u003ca name=\"save\"\u003e\u003c/a\u003e `user.save() -\u003e None`\n\n```python\n# Find user by MongoDB _id\nuser = await User.find_one(\n    {\n        \"_id\": \"655fc281c440f677fa1e117e\"\n    }\n)\n# If there age is greater than 80, make them dead\nif user.age \u003e 80:\n    user.alive = False\n# Persist update on User instance in MongoDB mongo\nuser.save()\n```\n\nIn this example, `User.find_one()` returns an instance of `User`. If the age field\nis greater than 80, the alive field is set to false. The instance of the document in the MongoDB\ndatabase is then updated by calling the `.save()` method on the `User` object instance.\n\n### Delete\n\n#### \u003ca name=\"delete\"\u003e\u003c/a\u003e `user.delete() -\u003e None`\n\n```python\n# Find all users where the user is not alive\nusers = await User.find_many(\n    {\n        \"alive\": False\n    }\n)\n# Recursively delete all User instances in the users list who are not alive\nfor user in users:\n    user.delete()\n```\n\n### Aggregation\n\nThe `aggregate` class method is designed to perform aggregation operations on the documents within the collection. It\nallows the execution of a sequence of data aggregation operations defined by the `pipeline` parameter. This method can\nreturn the results either as a list of documents or as a cursor, based on the `return_as_list` flag.\n\n**Parameters:**\n\n- `pipeline`: A list of dictionaries defining the aggregation operations to be performed on the collection.\n- `return_as_list` (optional): A boolean flag that determines the format of the returned results. If set to `True`, the\n  method returns a list of documents. If `False` (default), it returns a cursor.\n\n**Returns:**\n\n- If `return_as_list` is `True`, returns a list of documents resulting from the aggregation pipeline.\n- If `return_as_list` is `False`, returns a Cursor to iterate over the results.\n\n**Raises:**\n\n- `ValueError`: If an error occurs during the execution of the pipeline.\n\n**Example Usage:**\n\n```python\nfrom yourmodule import YourDocumentClass\n\n# Connect to the database (Assuming the database connection is already set up)\n# Define an aggregation pipeline\npipeline = [\n    {\"$match\": {\"status\": \"active\"}},\n    {\"$project\": {\"_id\": 0, \"username\": 1, \"status\": 1}},\n    {\"$sort\": {\"username\": 1}}\n]\n\n# Execute the aggregation without returning a list\ncursor = await YourDocumentClass.aggregate(pipeline)\nasync for doc in cursor:\n    print(doc)\n\n# Execute the aggregation and return results as a list\ndocs_list = await YourDocumentClass.aggregate(pipeline, return_as_list=True)\nprint(docs_list)\n```\n\n## Polymorphism and Inheritance\n\nThis part of the documentation provides an overview of implementing and using polymorphism and inheritance using the\n`motormongo` framework, enabling flexible and organized data models for various use cases.\n\n### Base Model: Item\n\nThe `Item` class serves as the base model for different types of items stored in a MongoDB collection. It defines common\nfields and methods that are shared across all item types.\n\n```python\nfrom motormongo import Document, StringField, FloatField\n\n\nclass Item(Document):\n    name = StringField()\n    cost = FloatField()\n```\n\n### Subclass Models\n\nSubclasses of `Item` can introduce specific fields or override methods to cater to different item categories.\n\n#### Book\n\nA `Book` represents a specific type of `Item` with additional attributes related to books.\n\n```python\nclass Book(Item):\n    title = StringField()\n    author = StringField()\n    isbn = StringField()\n```\n\n#### Electronics\n\nAn `Electronics` item represents electronic goods with attributes like warranty period and brand.\n\n```python\nclass Electronics(Item):\n    warranty_period = StringField()  # E.g., \"2 years\"\n    brand = StringField()\n```\n\n### Usage\n\n#### Creating and Inserting Items\n\nTo insert items into the database, use the `insert_one` method. The item's type is managed automatically.\n\n```python\n# Insert a book\nbook = await Book.insert_one(title=\"1984\", author=\"George Orwell\", isbn=\"123456789\", cost=20.0, name=\"Book\")\n\n# Insert an electronics item\nelectronics = await Electronics.insert_one(warranty_period=\"2 years\", brand=\"TechBrand\", cost=999.99, name=\"Laptop\")\n```\n\n#### Querying Items\n\nYou can query items of any type using their base or specific models. Polymorphism allows retrieved instances to be of\nthe correct subclass.\n\n```python\n# Find a book by ISBN\nfound_book = await Book.find_one(isbn=\"123456789\")\n\n# Find an electronics item by brand\nfound_electronics = await Electronics.find_one(brand=\"TechBrand\")\n```\n\n#### Polymorphic Behavior\n\nThe following operations are supported over the base `Item` Document class, enabling complex querying over base `Item`\nDocument class and all of its subclasses (i.e `Book` and `Electronics`):\n\n| CRUD Type | Operation                                                                                                         |\n|-----------|-------------------------------------------------------------------------------------------------------------------|\n| Read      | [`find_many(query: dict, limit: int = None, return_as_list: bool = True **kwargs) -\u003e List[Document]`](#find_many) |\n| Update    | [`update_many(query: dict, update_fields: dict) -\u003e Tuple[List[Document], int]`](#update_many)                     |\n| Delete    | [`delete_many(query: dict, **kwargs) -\u003e int`](#delete_many)                                                       |\n\nAs well as `aggregate` operations, see the [Aggregation Operation](#aggregation) section for more details.\n\nQuerying on the base `Item` model returns items of all types, correctly instantiated as their specific subclasses. See\nbelow for a logical example of polymorphic querying:\n\n```python\n# Find all items with a cost over 50\nexpensive_items = await Item.find_many(cost={\"$gt\": 50})\n\nfor item in expensive_items:\n    print(type(item))  # Prints the subclass (Book, Electronics, etc.)\n    if isinstance(item, Book):\n        print(f\"Book: {item.title} by {item.author}\")\n    elif isinstance(item, Electronics):\n        print(f\"Electronics: {item.brand} with {item.warranty_period} warranty\")\n```\n\n## Pooling Options Configuration\n\nIn `motormongo`, you have the flexibility to customize the pooling options for the Motor client. This allows you to\nfine-tune the behavior of database connections according to your application's needs. Below are some of the parameters\nyou can configure, along with their descriptions and example usage.\n\n### Configuration Parameters\n\n- **Max Pool Size**: The maximum number of connections in the connection pool.\n- **Min Pool Size**: The minimum number of connections in the connection pool.\n- **Max Idle Time**: The maximum time (in milliseconds) a connection can remain idle in the pool before being closed.\n- **Wait Queue Timeout**: The time (in milliseconds) a thread will wait for a connection to become available when the\n  pool is exhausted.\n- **Connect Timeout**: The time (in milliseconds) to wait for a connection to the MongoDB server to be established\n  before timing out.\n- **Socket Timeout**: The time (in milliseconds) to wait for a socket read or write to complete before timing out.\n\n### Example Configuration\n\n```python\nimport asyncio\nfrom motormongo import DataBase\n\n# Example pooling options\npooling_options = {\n    'maxPoolSize': 50,\n    'minPoolSize': 10,\n    'maxIdleTimeMS': 30000,\n    'waitQueueTimeoutMS': 5000,\n    'connectTimeoutMS': 10000,\n    'socketTimeoutMS': 20000\n}\n\n\nasync def init_db():\n    # This 'connect' method needs to be called inside of an async function\n    await DataBase.connect(uri=\"\u003cmongo_uri\u003e\", db=\"\u003cmongo_database\u003e\", **pooling_options)\n\n\nif __name__ == \"__main__\":\n    asyncio.run(init_db())\n```\n\nor in FastAPI:\n\n```python\nfrom fastapi import FastAPI\nfrom motormongo import DataBase\n\napp = FastAPI()\n\n# Example pooling options\npooling_options = {\n    'maxPoolSize': 50,\n    'minPoolSize': 10,\n    'maxIdleTimeMS': 30000,\n    'waitQueueTimeoutMS': 5000,\n    'connectTimeoutMS': 10000,\n    'socketTimeoutMS': 20000\n}\n\n\n@app.on_event(\"startup\")\nasync def startup_db_client():\n    await DataBase.connect(uri=\"\u003cmongodb_uri\u003e\", db=\"\u003cmongodb_db\u003e\", **pooling_options)\n\n```\n\nThis configuration demonstrates how to set up `motormongo` with specific pooling options to optimize performance\nand resource utilization in high-throughput environments.\n\nFor more information, consult the official documentation:\n\n- [Motor: Asynchronous Python driver for MongoDB](https://motor.readthedocs.io/en/stable/)\n- [MongoDB Manual: Tuning Your Connection Pool Settings](https://www.mongodb.com/docs/manual/reference/connection-string/#mongodb-urioption-urioption.maxPoolSize)\n\n## FastAPI integration\n\n`motormongo` can be easily integrated in FastAPI APIs to leverage the asynchronous ability of\nFastAPI. To leverage `motormongo`'s ease-of-use, Pydantic model's should be created to represent the MongoDB\n`motormongo` Document as a Pydantic model. Below is a light-weight CRUD FastAPI application using `motormongo`:\n\n```python\nfrom fastapi import FastAPI, HTTPException\nfrom pydantic import BaseModel, Field\nfrom motormongo import DataBase, Document, BinaryField, StringField\nimport re\nimport bcrypt\n\n\ndef hash_password(password: str) -\u003e bytes:\n    # Example hashing function\n    return bcrypt.hashpw(password.encode('utf-8'), salt=bcrypt.gensalt())\n\n\nclass User(Document):\n    username = StringField(help_text=\"The username for the user\", min_length=3, max_length=50)\n    email = StringField(help_text=\"The email for the user\", regex=re.compile(r'^\\S+@\\S+\\.\\S+$'))  # Simple email regex\n    password = BinaryField(help_text=\"The hashed password for the user\", hash_function=hash_password)\n\n    def verify_password(self, password: str) -\u003e bool:\n        return bcrypt.checkpw(password.encode(\"utf-8\"), self.password)\n\n    class Meta:\n        collection = \"users\"  # \u003c If not provided, will default to class name (ex. User-\u003euser, UserDetails-\u003euser_details)\n        created_at_timestamp = True  # \u003c Provide a DateTimeField for document creation\n        updated_at_timestamp = True  # \u003c Provide a DateTimeField for document updates\n\n\nclass UserModelRequest(BaseModel):\n    username: str = Field(example=\"johndoe\")\n    email: str = Field(example=\"johndoe@coldmail.com\")\n    password: str = Field(example=\"password123\")\n\n\napp = FastAPI()\n\n\n@app.on_event(\"startup\")\nasync def startup_db_client():\n    await DataBase.connect(uri=\"\u003cmongodb_uri\u003e\", db=\"\u003cmongodb_db\u003e\")\n\n\n@app.on_event(\"shutdown\")\nasync def shutdown_db_client():\n    await DataBase.close()\n\n\n@app.post(\"/users/\", status_code=201)\nasync def create_user(user: UserModelRequest):\n    new_user = await User.insert_one(**user.dict())\n    return new_user.to_dict()\n\n\n@app.post(\"/user/auth\", status_code=200)\nasync def is_authenticated(username: str, password: str):\n    user = await User.find_one({\"username\": username})\n    if not user:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    if not user.verify_password(password):\n        raise HTTPException(status_code=401, detail=\"Unauthorized\")\n    else:\n        return \"You are authenticated! You can see this!\"\n\n\n@app.get(\"/users\")\nasync def get_users():\n    users = await User.find_many()\n    if not users:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    return [user.to_dict() for user in users]\n\n\n@app.get(\"/users/{user_id}\")\nasync def get_user(user_id: str):\n    user = await User.find_one({\"_id\": user_id})\n    if not user:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    return user.to_dict()\n\n\n@app.put(\"/users/{user_id}\", status_code=200)\nasync def update_user(user_id: str, user_data: UserModelRequest):\n    updated_user = await User.update_one({\"_id\": user_id}, user_data.model_dump())\n    if not updated_user:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    return updated_user.to_dict()\n\n\n@app.delete(\"/users/{user_id}\", status_code=204)\nasync def delete_user(user_id: str):\n    user = await User.find_one({\"_id\": user_id})\n    if not user:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    await user.delete()\n    return {\"status\": \"User deleted successfully\"}\n```\n\n## License\n\nThis project is licensed under the MIT License.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpprunty%2Fmotormongo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpprunty%2Fmotormongo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpprunty%2Fmotormongo/lists"}