# Mongoclass

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

```python
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.

```python
from dataclasses import dataclass

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

{% hint style="info" %}
It is important that `@client.mongoclass()` comes first before the `@dataclass` decorator.
{% endhint %}

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.

{% hint style="info" %}
The difference between `.save()` and `.insert()` will be clarified in the **Updating Data** section below.
{% endhint %}

```python
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`

{% hint style="info" %}
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`
{% endhint %}

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.

```python
@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.

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

{% hint style="info" %}
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?&#x20;
{% endhint %}

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**&#x20;

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

#### Example

```python
@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.

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

{% hint style="info" %}
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.
{% endhint %}

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.

```python
@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.&#x20;

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

```python
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.

```python
# 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.&#x20;

```python
# 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.

{% hint style="warning" %}
If you've overwritten the collection name in `@client.mongoclass()` with something different, the collection you pass must be the same.
{% endhint %}

What if we're looking for multiple data?

```python
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.

```python
delete_result = john.delete()
# Deletes john
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://oppenheimer.gitbook.io/mongoclass/mongoclass.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
