An object mapper for MongoDB with type hints.
Project description
Monom: An object mapper for MongoDB with type hints
Installation
$ pip install monom
About
Monom is designed to manage your models clearly and easily, which is as simple and thin as possible.
-
schema declaration using type hints
-
document validation on insert and update
-
default value, field alias, index declaration, custom converter and validator
-
minimum api, least memory burden
A Quick Example
from monom import *
class User(EmbeddedModel):
name: str
email: str
class Post(Model):
user: User
title: str
content: str
tags: List[str]
rank: int
visible: bool = True
created_on: datetime = datetime.utcnow
placeholder: Any
class Meta:
required = ['user', 'title']
indexes = ['title']
converters = {
'title': lambda x: x.capitalize()
}
validators = {
'title': lambda x: len(x) > 5
}
@property
def url(self):
return '/posts/' + str(self.pk)
@classmethod
def find_posts_by_lucy(cls):
return Post.find({'user.name': 'Lucy'}).sort('created_on')
db = MongoClient().get_database('demo')
Post.set_db(db)
post = Post(
user={'name': 'Lucy', 'email': 'foo@example.com'},
# same as the above
# user=User(name='Lucy', email='foo@example.com'),
title='hello world',
content='monom is awesome...',
tags=['life', 'art']
)
post.save()
assert post.visible is True
assert isinstance(post.user, User)
assert post.user.name == 'Lucy'
Guide
Connection
No extra connection methods. Just pass pymongo's Database instance to your model.
from monom import Model, MongoClient
class MyModel(Model):
pass
db = MongoClient().get_database('demo')
MyModel.set_db(db)
Field Type
-
str,int,float,bool,bytes,datetime,ObjectId: you are familiar with them already -
dict: accepts adictregardless of its items -
list: accepts alistregardless of its items -
subclass of
EmbeddedModel: represents MongoDB's embedded document -
List:List[the above type] orList[List[the above type]] or any nested depth -
Any: any type that can be saved into MongoDB
Model
Model Instance
To create a new model object, provide values for its fields as constructor keyword arguments. Monom will convert and validate these values when constructing the new instance.
from monom import Model
class User(Model):
name: str
visible: bool = True
user = User(name='foo')
You can declare a field with an initial value, which acts as the field's default value.
If the value is a callable, it will be called on each saving or inserting.
Methods
save(full_update=False, *kw)
Save the data into MongoDB.
-
The new document will be inserted into MongoDB.
-
The existing document will be updated atomically using operator '$set' and '$unset'.
-
listmutation cannot be tracked; but you can pass an keyword argumentfull_update=Trueto perform a full update.
from monom import *
class User(Model):
name: str
email: str
hobbits: List[str]
active: bool = True
created_on: datetime = datetime.utcnow
# connect to mongodb
User.set_db(MongoClient().get_database('demo'))
# insert a doc
User(name='Lucy', email='lucy@foo.com', hobbits=['music', 'sport']).save()
# find a doc filtering out some fields
user = User.find_one({}, {'created_on': False})
user.name = 'foobar'
del user.email
# saved with an atomic update
user.save()
user.hobbits.append('programming')
# to save a doc with changed list items, you should set `full_update=True`
user.save(full_update=True)
delete(**kw)
Delete the data from MongoDB.
pk
An alias for the primary key (_id in MongoDB).
to_dict()
Return an ordered dict containing the instance's data with the same order as the field definition order.
to_json(**kw)
Return a json string. Some specific types (ObjectId, datetime, etc.) will be handled correctly.
get(name, default=None)
Return the value for name or default.
Class Methods
set_db(db)
Pass a pymongo.database.Database to the model.
set_collection(collection)
Pass a string or a pymongo.collection.Collection to the model.
If it isn't called explicitly, plural form of the model's name will be the collection name.
-
get_db() -
get_collection()
CRUD Methods
Monom adds no extra methods to operate MongoDB.
It proxies a subset of methods in pymongo.collection:Collection, which will perform data cleaning and convert the data from query operations to the model object.
-
insert_one,insert_many,replace_one,update_one,update_many,find_one_and_update,find_one_and_replacewill perform data conversion and validation. -
find_one,find,find_one_and_delete,find_one_and_replace,find_one_and_updatewill convert query results to the corresponding model instance.
find returns a Cursor of model instances instead of dicts. Before dump your documents to json, remember to do a small conversion.
from monom import *
class BinData(Model):
name: str
data: bytes
BinData.set_db(MongoClient().get_database('demo'))
BinData(name='foo', data=b'abc').save()
BinData(name='bar', data=b'xyz').save()
json_dumps([bd.to_dict() for bd in BinData.find()])
# or call pymongo's methods directly
json_dumps(BinData.get_collection().find())
Meta
You can add extra constraints for your models by defining an inner class named Meta in your model or embedded model.
required: the field must exist in your data
from monom import Model
class User(Model):
name: str
email: str
class Meta:
required = ['name']
validatorsandconverters
from monom import Model
class User(Model):
name: str
age: int
class Meta:
validators = {
'age': lambda x: x < 200
}
converters = {
'name': lambda x: x.strip()
}
aliases: sometimes you may want to save some fields in another names
from monom import Model
class User(Model):
id: int
first_name: str
class Meta:
aliases = [
('id', '_id'),
('first_name', 'firstName'),
]
user = User(id=42, first_name='Lucy')
user.id
# 42
user.to_dict()
# {'_id': 42, 'firstName': 'Lucy'}
Indexes
from monom import Model, DESCENDING
class FancyModel(Model):
class Meta:
indexes = [
'f1', # a single key ascending index
('f2', DESCENDING), # a single key descending index
['f3', 'f4'], # a compound index both ascending
['f5', ('f6', DESCENDING)], # a compound index on 'f5' ascending and 'f6' descending
[('f7', DESCENDING), ('f8', DESCENDING)], # a compound index both descending
{'key': 'f9', 'expire_after_seconds': 3600, 'unique': True} # a single key ascending index with ttl and unique property
]
Index declaration cannot appear in embedded model.
Options
dict_class
The underlying data of model instance are saved in an ordered dict. You may change it to bson.son.SON or other compatible types.
Default value is collections.OrderedDict.
warn_extra_data
Whether checks extra data that aren't declared in the model and emits some warnings.
Default value is True.
auto_build_index
Whether enables auto index creation or deletion.
You may disable it when in production because index management may be performed as part of a deployment system.
Default value is True.
Theses options can be set on Model or the subclass of Model; if set on Model, all subclasses will inherit them.
from monom import Model
from bson.son import SON
Model.dict_class = SON
class User(Model):
name: str
user = User(name='foo')
assert isinstance(user.to_dict(), SON)
Helpers
switch_db: switch to a different database temporarily
from monom import Model, MongoClient, switch_db
class FancyModel(Model):
pass
foo_db = MongoClient().get_database('foo')
FancyModel.set_db(foo_db)
bar_db = MongoClient().get_database('bar')
with switch_db(FancyModel, bar_db):
assert FancyModel.get_db().name == 'bar'
switch_collection: switch to a different collection temporarily
from monom import Model, MongoClient, switch_collection
class FancyModel(Model):
pass
db = MongoClient().get_database('my-db')
FancyModel.set_db(db)
with switch_collection(FancyModel, 'foobar'):
assert FancyModel.get_collection().name == 'foobar'
Logging
In several cases, some warnings will be emitted. If that's annoying, you can change the logger level or set a new logger.
import logging
from monom import get_logger, set_logger
# change level
get_logger().setLevel(logging.ERROR)
# or set a new logger
set_logger(logging.getLogger('foobar'))
Good Old Django-Style
If you like the classical style, here you are.
from monom.fields import *
from monom import Model, EmbeddedModel, datetime
class Comment(EmbeddedModel):
content = StringField()
created_on = DateTimeField(default=datetime.utcnow)
class Post(Model):
id = ObjectIdField(name='_id')
title = StringField(required=True, converter=lambda x: x.capitalize())
content = StringField(validator=lambda x: len(x) > 50)
comments = ArrayField(EmbeddedField(Comment))
rank = IntField(max_value=100)
visible = BooleanField(default=True)
Using this style, you can pass name (alias aka), required, default, converter, validator as constructor keyword arguments.
One-to-one match with type hints
-
str->StringField -
int->IntField -
float->FloatField -
bool->BooleanField -
bytes->BytesField -
datetime->DateTimeField -
ObjectId->ObjectIdField -
dict->DictField -
subclass of
EmbeddedModel->EmbeddedField -
list->ListField -
List->ArrayField -
Any->AnyField
Caveats
-
Inheritance of fields through class inheritance cannot work, for it will cause confusing relationships between model and embedded model.
-
You'd better not mix type-hints style with django-orm style; if you insist on that the field definition order may not be reserved.
Tests
To run the test suite, ensure you are running a local MongoDB instance on the default port and have pytest installed.
$ pytest
Dependencies
- Python >= 3.6
- pymongo >= 3.7
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file monom-1.1.0.tar.gz.
File metadata
- Download URL: monom-1.1.0.tar.gz
- Upload date:
- Size: 23.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.42.1 CPython/3.8.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e621de6caf5b2a00deef30e53dccb087265b40c204e5ced699bbb96541db0a09
|
|
| MD5 |
6a0112d4f359063659b82d8b58ec0d7d
|
|
| BLAKE2b-256 |
8b5aa57fdb8b0f71765afa7699b1fad1ac98abf7d09e78b3bc04a0edbd95bc17
|
File details
Details for the file monom-1.1.0-py3-none-any.whl.
File metadata
- Download URL: monom-1.1.0-py3-none-any.whl
- Upload date:
- Size: 21.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.42.1 CPython/3.8.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
157e3e94ee8d0a3dee131ddd0133c6704cb51cd1a03dd6f31a7621a1b8babeb4
|
|
| MD5 |
fe27a8489b10181d39c84060e8fa97e5
|
|
| BLAKE2b-256 |
447d03bdfd3ac245d3f07d980f879f43bff84d4274bf7a88b4c73149cbf9314d
|