Mongoclass

A basic ORM like interface for mongodb in python that uses dataclasses.

Installation

To get started, install mongoclass using pip like so.

pip install -U mongoclass

Getting Started

This section will explain the basics of how to use mongoclass. After reading this, read the API Reference for more information.

from mongoclass import MongoClassClient
client = MongoClassClient("mongoclass", "localhost:27017")

This will create a MongoClassClient instance that exposes the features of mongoclass. MongoClassClient inherits from pymongo.MongoClient so you can also use it like you'd normally use pymongo.

Schemas

To create a schema (or a preferred term, mongoclass), this is all you have to do.

from dataclasses import dataclass

@client.mongoclass()
@dataclass
class User:
    name: str
    email: str
    phone: int
    country: str = "US"

It is important that @client.mongoclass() comes first before the @dataclass decorator.

This creates a User mongoclass that belongs in the user collection inside the default database. To create an actual User object and have it be inserted in the database, create an instance of User and call the .insert() method or .save() like so.

The difference between .save() and .insert() will be clarified in the Updating Data section below.

john = User("John Dee", "johndee@gmail.com", 5821)
insert_result = john.insert() # Works!
insert_result = john.save() # Also works!

The first line creates the user John Dee with the provided information. Notice how we didn't need to provide a country, that is because country defaults to US.

The second line inserts it to the user collection in the default database and then returns a pymongo.InsertOneResult

As an alternative to having to call .insert(), you can pass _insert=True to User() which will automatically insert as soon as the object is initialized. You do loose the ability to receive the pymongo.InsertOneResult

What if you don't want to call insert() or pass _insert=True? Simple, when decorating a dataclass just pass insert_on_init=True like so.

@client.mongoclass(insert_on_init=True)
@dataclass
class User:
    ...

john = ("John Dee", "johndee@gmail.com", 5821)
# John is immediately inserted

Now what if you want to insert multiple objects? Use the client.insert_classes method.

users = [
    User("John Dee", "johndee@gmail.com", 100),
    User("Michael Reeves", "michaelreeves@gmail.com", 42069)
]
client.insert_classes(users)

What if all the objects in the users list do not belong to the same collection and database? Will it insert them in their respective collections and databases?

Yes, you have to pass insert_one=True onto client.insert_classes in order for them to be inserted into their respective collections and databases. Under the hood, insert_one=True will tell the method to just call the insert() method on each object in the list.

Nested Mongoclasses

Support for nested mongoclasses was added on v0.4

By default nested is disabled for performance reasons. In order to enabled nested classes, pass nested=True onto the mongoclass decorator.

Example

@client.mongoclass()
@dataclass
class Metadata:
    age: int
    phone: int


@client.mongoclass(nested=True)
@dataclass
class User:
    name: str
    email: str
    metadata: Metadata

User("John Howards", "johnhowards@gmail.com", Metadata(23, 43123)).insert()

Once inserted in the database, the format is changed a bit so that it know how to reconstruct it back into a object once you query for it. Here's how the new format would look like.

{
    "name": "John Howards",
    "email": "johnhowards@gmail.com",
    "metadata": {
        "_nest_collection": "metadata",
        "_nest_database": "main",
        "data": {
            "age": 23,
            "phone": 43123
        }
    }
}

You only need to make the parent class nested. If there are childs with other childs, you don't need to make them nested anymore.

You don't need to do anything different when it comes to querying nested data. Just use the find_class or find_classes normally and it would automatically nest it. Check Finding Data section below for more information.

Mongoclass in a different collection

Now what if you want the User object to belong in a different collection and database? It's simple, you just provide some extra arguments to the mongoclass() wrapper.

@client.mongoclass("profile", "profile_list")
@dataclass
class User:
    name: str
    email: str
    phone: int
    country: str = "US"

This will create a User schema that belongs to the profile collection instead of user inside the profile_list database. By default, collections are chosen by the name of the decorated dataclass but lowered.

The second argument can also be a pymongo.Database instance if you prefer that.

Updating Data

Any dataclass that you decorate with @client.mongoclass() will have a update() and .save() method that basically allows you to update that specific object.

First, here's an example of how you would update an object or a document using .update() that has been already been inserted.

john = User("John Dee", "johndee@gmail.com", 5821)
john.insert()

# Let's change john's country to UK
update_result, new_john = john.update(
        {"$set": {"country": "UK"}}, 
        return_new=True
    )

This will update john's country to UK. As you can see, the update method simply accepts the same parameters as pymongo's update_one(). The return_new means it'll return the new object rather than returning the same object.

Here's an example using the .save() method.

# For better example sake, let's imagine we added an age attribute (The last one)
john = User("John Dee", "johndee@gmail.com", 5821, 21)
john.insert()

# Let's change john's country to UK
john.name = "UK"

# Let's also change john's age to 22 by incrementing it
john.age += 1

# Now let's update this entry in the database using the .save()
update_result, new_john = john.save()

As you can see, the difference is, .save() will basically update based on the current state of the attributes of the object.

Finding Data

To query data, mongoclass has two methods for that. client.find_class and client.find_classes

They're kind of like pymongo's find() and find_one() except they return the mongoclasses that contains the data instead of just a raw document.

# Find john in the default database inside the user collection
john = client.find_class("user", {"name": "John Dee"})

The first argument points to the collection john belongs in. The second argument is the filter to use. (As same as find_one()) Optionally, you can pass a database="database_name" parameter to tell mongoclass what database to look at.

What if we're looking for multiple data?

us_residents = client.find_classes("user", {"country": "US"})

This will return a cursor object similar to pymongo's cursor. Each data in the cursor has a country of US.

Deleting

Deleting is simple. You just have to call the .delete() method of a mongoclass like so.

delete_result = john.delete()
# Deletes john

Last updated