yadm
yadm copied to clipboard
Yet Another Document Mapper (ODM) for MongoDB
=========================== Yet Another Document Mapper
.. image:: https://travis-ci.org/zzzsochi/yadm.svg?branch=master :target: https://travis-ci.org/zzzsochi/yadm
.. image:: https://coveralls.io/repos/github/zzzsochi/yadm/badge.svg?branch=master :target: https://coveralls.io/github/zzzsochi/yadm?branch=master
It's small and simple ODM for use with MongoDB.
.. Full documentation: http://yadm.readthedocs.org
Requirements
YADM support MongoDB version 3.x only. MongoDB 2.x is not supported.
Minimal version of python — 3.6.
Quick start
Create the models
.. code:: python
from datetime import datetime
import pymongo
from yadm import Database
from yadm import Document, EmbeddedDocument
from yadm import fields
from yadm.serialize import to_mongo
class User(Document):
__collection__ = 'users'
name = fields.StringField()
email = fields.EmailField()
class PostLogItem(EmbeddedDocument):
op = fields.StringField(choices=['created', 'comment_added'])
at = fields.DatetimeField()
data = fields.MongoMapField()
class Post(Document):
__collection__ = 'posts'
user = fields.ReferenceField(User)
created_at = fields.DatetimeField(auto_now=True)
title = fields.StringField()
body = fields.StringField()
log = fields.ListField(fields.EmbeddedDocumentField(PostLogItem))
class Comment(Document):
__collection__ = 'comments'
user = fields.ReferenceField(User)
created_at = fields.DatetimeField(auto_now=True)
post = fields.ReferenceField(Post)
text = fields.StringField()
All documents creates from class Document. You can use multiple inheritance.
__collection__ magic attribute setups the collection name for documents of model.
Connect to database
.. code:: python
client = pymongo.MongoClient('mongodb://localhost:27017')
db = Database(client, 'blog')
Database object is a wrapper about pymongo or motor Database.
Create documents
User
.. code:: python
user = User(name='Bill', email='[email protected]')
db.insert_one(user)
Just insert document to database.
Post
.. code:: python
post = Post()
post.user = user
post.title = 'Small post'
post.body = 'Bla-bla-bla...'
post.log = [PostLogItem(op='created', at=datetime.utcnow())]
db.insert_one(post)
You can fill documents as above.
Comment the post
.. code:: python
comment = Comment()
comment.user = user
comment.post = post
comment.text = "RE: Bla-bla-bla..."
db.insert_one(comment)
db.update_one(post, push={
'log': to_mongo(PostLogItem(op='comment_added',
at=comment.created_at,
data={
'comment': comment.id,
'user': comment.user.id,
}))
})
We add log item to post's log. This is very usefull case.
Queries
find
.. code:: python
qs = db(Post).find({'title': {'$regex': '^S'}})
assert qs.count() > 0
db(Post)creates theQuerySetobject;findmethod get the raw-query and return newQuerySetobject with updated criteria;countmethod make the query to database and return value.
.. code:: python
for post in qs:
assert post.title.startswith('S')
__iter__ method make the find-query and returns the generator of documents.
find_one
Get the first finded document.
.. code:: python
post = db(Post).find_one({'user': user.id})
get_document
Get the document by id from primary.
.. code:: python
user = db.get_document(User, user.id)
References
.. code:: python
user = post.user
Get attribute with reference makes the query to referred collection. Warning: N+1 problem!
We have a cache in QuerySet object and get one referred document only once for one queryset.
Lookups
.. code:: python
comments = db(Comment).find({'post': post.id}).sort(('created_at', 1))
for comment in comments.lookup('user'):
print(comment.user.name, comment.text)
This code create the aggregate query with $lookup statement for resolve the references.
Aggregations
.. code:: python
agg = (db.aggregate(Comment)
.match(user=user.id)
.group(_id='post', count={'$sum': 1})
.sort(count=-1))
for item in agg:
print(item)
Or traditional MongoDB syntax:
.. code:: python
agg = db.aggregate(Comment, pipeline=[
{'match': {'user': user.id}},
{'group': {'_id': 'post', 'count': {'$sum': 1}}},
{'sort': {'count': -1}},
])
CHANGES
2.0.9 (2023-08-23)
- Add
commentmethods forQuerySetandAggregationto specify comment feature of MongoDB.
2.0.8 (2021-09-23)
- Asyncio support for testing.
2.0.7 (2021-04-21)
- Some bugfixes.
2.0.5 (2019-02-25)
- Add
Aggregation.hintmethod.
2.0.4 (2019-02-20)
- Add
Database.estimated_document_countmethod for quickly count documents in the collection.
2.0.1 (2018-11-04)
- Add
QuerySet.hintfor specify index for query.
2.0.0 (2018-10-25)
-
A
Big Rewrite <https://www.youtube.com/watch?v=xCGu5Z_vaps>_ document logic:Document.__raw__now contains only data from pymongo, without anyAttributeNotSetorNotLoaded;Document.__changed__is removed: all changes reflects toDocument.__cache__;Document.__not_loaded__frozenset of fields whitch not loaded by projection;Document.__new_document__flag isTruefor document's objects whitch created directly in your code;Document.__log__list-like container with log of document changes (unstable API at now);Document.__data__is removed as deprecated;- Now is not allow to set fields as classes;
- Defaults is not lazy and creates with document instance;
-
Update for minimal versions of pymongo (3.7) and motor (2.0):
- Add
Database.bulk_write; - Add
Database.insert_one,Database.insert_manyandDatabase.delete_one; - Deprecate
Database.insert,Database.remove; - Remove
Database.bulk(without deprecation period, sorry); - Add
QuerySet.count_documents; - Add
QuerySet.update_oneandQuerySet.update_many; - Add
QuerySet.delete_oneandQuerySet.delete_many; - Add
QuerySet.find_one_and_update,QuerySet.find_one_and_replaceandQuerySet.find_one_and_delete; - Deprecate
QuerySet.count; - Deprecate
QuerySet.update,QuerySet.removeandQuerySet.find_and_modify; - Remove deprecated
QuerySet.with_id;
- Add
-
Simple interface for build lookups:
QuerySet.lookup; -
Remove
bccargument fromMoneyField; -
Add
Decimal128Field.
1.5.0 (2017-12-31)
- Experimental
asynciosupport; - Add
ReferencesListFieldfor lists of references.
1.4.15 (2017-12-27)
- Add
projectionargument toDatabase.get_documentandDatabase.reload; - Add
Document.__default_projection__attribute.
1.4.14 (2017-11-06)
- Add
EnumFieldfor saveenum.Enum; - Add
EnumStateFieldfor simple state machines based onenum.Enum.
1.4.13 (2017-10-31)
- Add
QuerySet.batch_sizemethod for setup batch size for cursor; - Some minor fixes.
1.4.10 (2017-07-07)
ReferenceField.from_mongotry to get document from primary if not found by default.
1.4.9 (2017-07-06)
- Add
QuerySet.read_primarymethod for simple setupread_preference.Primary.
1.4.4 (2017-05-17)
- Add
TimedeltaFieldfor stores durations; - Add
SimpleEmbeddedDocumentFieldfor simply create embedded documents.
.. code:: python
class Doc(Document):
embedded = SimpleEmbeddedDocumentField({
'i': IntegerField(),
's': StringField(),
})
1.4.3 (2017-05-14)
- Add
StaticFieldfor static data.
1.4.2 (2017-04-09)
- Additional arguments (like
write_concern) for write operations; create_fakesave the documents with write concern "majority" by default.
1.4.0 (2017-04-05)
- Drop pymongo 2 support;
- Additional options for databases and collections;
- Add
Database.get_document; - Add
TypedEmbeddedDocumentField; reloadargument ofDatabase.update_onemust be keyword (may be backward incompotable).
1.3.1 (2017-02-21)
- Change raw data for
Money;
1.3.0 (2017-02-19)
- Add currency support to
Money:- Totaly rewrite
Moneytype. Now it is not subclass ofDecimal; - Add storage for currencies:
yadm.fields.money.currency.DEFAULT_CURRENCY_STORAGE;
- Totaly rewrite
1.2.1 (2017-01-19)
- Add
QuerySet.find_infor$inqueries with specified order;
1.2.0 (2016-12-27)
- Drop MongoDB 2.X suport;
- Objects for update and remove results;
- Use Faker instead fake-factory.
1.1.4 (2016-08-20)
- Add some features to
Bulk:Bulk.update_one(document, **kw): method for add update one document in bulk;Bulk.find(query).update(**kw): update many documents by query;Bulk.find(query).upsert().update(**kw): upsert document;Bulk.find(query).remove(**kw): remove documents;
1.1.3 (2016-07-23)
-
Add
QuerySet.idsmethod for get only documents id's from queryset; -
Add
Money.total_centsmethod andMoney.from_centsclassmethod;
1.1 (2016-04-26)
-
Add cacheing on queryset level and use it for
ReferenceField; -
Add mongo aggregation framework support;
-
Add
read_preferencesetting; -
Add
excargument toQuerySet.find_onefor raise exception if not found; -
Add
multiargument toQuerySet.remove; -
Deprecate
QuerySet.with_id; -
Refactoring.
1.0 (2015-11-14)
-
Change document structure. No more bad
BaseDocument.__data__attribute:BaseDocument.__raw__: raw data from mongo;BaseDocument.__cache__: cached objects, casted with fields;BaseDocument.__changed__: changed objects.
-
Changes api for custom fields:
- Not more need create field descriptors for every field;
prepare_valuecalled only for setattr;to_mongocalled only for save objects to mongo;from_mongocalled only for load values fromBaseDocument.__raw__;- Remove
Field.defaultattribute. UseField.get_defaultmethod; - Add
Field.get_if_not_loadedandField.get_if_attribute_not_setmethod; - By default raise
NotLoadedErrorif field not loaded from projection;
-
Changes in
ReferenceField:- Raise
BrokenReferenceif link is bloken; - Raise
NotBindingToDatabaseif document not saved to database;
- Raise
-
smart_nullkeyword forField; -
Fields in document must be instances (not classes!);
-
Remove
ArrayContainerandArrayContainerField; -
Remove old
MapIntKeysFieldandMapObjectIdKeysField. Use newMapCustomKeysField; -
Add
Database.update_onemethod for run simple update query with specified document; -
Add
QuerySet.distinct; -
serialize.from_mongonow acceptnot_loadedsequence with filed names who must mark as not loaded,parentandname; -
serialize.to_mongodo not callFieldDescriptor.__set__; -
Fakers! Subsystem for generate test objects;
-
Tests now use pytest;
-
And more, and more...