Commit 3a09e46a authored by Yury's avatar Yury

document: add automatic indexes creation

parent 66457b81
Pipeline #334 passed with stages
in 4 minutes and 1 second
......@@ -2,6 +2,7 @@ import logging
from functools import wraps
from motor.motor_asyncio import AsyncIOMotorCollection, AsyncIOMotorClient
from pymongo import MongoClient
from .document_dict import DocumentDict
from .document_array import DocumentArray
......@@ -124,6 +125,10 @@ class MetaDocument(type):
client: AsyncIOMotorClient
collection: AsyncIOMotorCollection
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
cls.create_indexes()
@property
def collection(cls) -> AsyncIOMotorCollection:
for field in ("client", "database", "collection"):
......@@ -138,6 +143,19 @@ class MetaDocument(type):
return client[database][collection]
@property
def sync_collection(cls):
for field in ("client", "database", "collection"):
if field not in cls.__dict__:
raise type("{}NotFound".format(field.capitalize()), (DocumentException,), {})(
f"Required attribute {field} is missing."
)
client = cls.__dict__["client"]
database = cls.__dict__["database"]
collection = cls.__dict__["collection"]
return MongoClient(host=client.HOST, port=client.PORT)[database][collection]
def __getattribute__(cls, item: str):
if item in ("database", "client"):
raise AttributeError(item)
......@@ -147,6 +165,12 @@ class MetaDocument(type):
def Field(cls):
return DocumentField(cls)
def create_indexes(cls):
"""Called on class creation. Made for automatic indexes creation.
Can be implemented in subclass (Optional).
Here cls.sync_collection should be used.
"""
class Document(metaclass=MetaDocument):
Relations = DocumentRelations()
......
import asyncio
from unittest import TestCase
import motor.motor_asyncio
from mdocument import ClientNotFound, Document, DocumentDoesntExist, DocumentException
class DocumentTestCase(TestCase):
def setUp(self) -> None:
self.loop = asyncio.get_event_loop()
self.client = motor.motor_asyncio.AsyncIOMotorClient()
def loop_run_lambda(self, func, *args, **kwargs):
return lambda: self.loop.run_until_complete(func(*args, **kwargs))
def tearDown(self) -> None:
self.loop.run_until_complete(self.client.drop_database("mdocument"))
def test___init__(self):
"""Tests initiation of document."""
class Video(Document):
collection = "videos"
database = "mdocument"
video = Video(length=60, author="some_id", **{"other_field": {"address": "test"}})
self.assertEqual(video.length, 60)
self.assertEqual(video["length"], 60)
self.assertEqual(video.author, "some_id")
self.assertEqual(video["author"], "some_id")
self.assertEqual(video.other_field, {"address": "test"})
self.assertEqual(video["other_field"], {"address": "test"})
self.assertEqual(video.other_field.address, "test")
self.assertEqual(video["other_field"]["address"], "test")
video.other_field.address = "1234"
self.assertEqual(video._document_.other_field.address, "1234")
del video.other_field.address
self.assertRaises(AttributeError, lambda: video.other_field.address)
def test_create(self):
"""Tests creation of document in database."""
class Video(Document):
collection = "videos"
database = "mdocument"
client = self.client
video = self.loop.run_until_complete(Video.create(length=101))
self.assertIsInstance(video, Video)
self.assertEqual(video.length, 101)
self.assertTrue(video._id)
def test_one(self):
"""Tests search of single document."""
class Video(Document):
collection = "videos"
database = "mdocument"
client = self.client
video1 = self.loop.run_until_complete(Video.create(length=101))
video2 = self.loop.run_until_complete(Video.one(length=101))
self.assertEqual(video2.__dict__, video1.__dict__)
def test_many(self):
"""Tests search of multiple documents."""
class Video(Document):
collection = "videos"
database = "mdocument"
client = self.client
video1 = self.loop.run_until_complete(Video.create(length=105, author="Author"))
video2 = self.loop.run_until_complete(Video.create(length=101, author="Author"))
videos = self.loop.run_until_complete(Video.many(author="Author"))
self.assertEqual(len(videos), 2)
for video in videos:
self.assertTrue(
video.__dict__ == video1.__dict__ or
video.__dict__ == video2.__dict__
)
def test_delete(self):
"""Tests deletion of document."""
class Video(Document):
collection = "videos"
database = "mdocument"
client = self.client
video1 = self.loop.run_until_complete(Video.create(length=105))
self.assertEqual(video1.__dict__,
self.loop.run_until_complete(Video.one()).__dict__)
self.loop.run_until_complete(video1.delete())
self.assertRaises(DocumentDoesntExist,
lambda *args, **kwargs: self.loop.run_until_complete(
Video.one(**args[0])),
{"_id": video1._id})
def test_push_update(self):
"""Tests that updated document is synced to database."""
class Video(Document):
collection = "videos"
database = "mdocument"
client = self.client
video1 = self.loop.run_until_complete(Video.create(length=105))
video1.admin = "Me"
self.loop.run_until_complete(video1.push_update())
video = self.loop.run_until_complete(Video.one(admin="Me"))
self.assertEqual(video1, video)
def test_client_not_set(self):
class Video(Document):
collection = "videos"
database = "mdocument"
self.assertRaises(ClientNotFound,
self.loop_run_lambda(Video.create, author="me"))
def test_missing_attrs(self):
class Video1(Document):
client = self.client
self.assertRaisesRegex(DocumentException, r"Required attribute database is missing.",
self.loop_run_lambda(Video1.create, author="me")
)
class Video2(Document):
client = self.client
database = "mdocument"
self.assertRaisesRegex(DocumentException, r"Required attribute collection is missing.",
self.loop_run_lambda(Video2.create, author="me")
)
def test_repr(self):
class Video(Document):
pass
video = Video(author="me", length=101)
self.assertEqual(str(video), "{0}({1})".format(
video.__class__.__name__,
", ".join([f"{key}={value}" for key, value in {
"author": "me", "length": 101}.items()])
))
def test_missing_attr(self):
class Video(Document):
pass
video = Video(title="wow!")
self.assertRaises(AttributeError, lambda: video.wow)
def test_del_attr(self):
class Video(Document):
pass
video = Video(title="wow!")
self.assertEqual(video.title, "wow!")
del video.title
self.assertRaises(AttributeError, lambda: video.title)
def test__setitem__(self):
class Video(Document):
pass
video = Video(title="wow!")
video["test"] = True
self.assertTrue(video.test)
def test_del_item(self):
class Video(Document):
pass
video = Video(title="wow!")
video["test"] = True
self.assertTrue(video.test)
del video["test"]
self.assertRaises(KeyError, lambda: video["test"])
def test_exists(self):
class Video(Document):
collection = "videos"
database = "mdocument"
client = self.client
video = self.loop.run_until_complete(Video.create(author="me"))
self.assertTrue(
self.loop.run_until_complete(Video.exists({"_id": video._id}))
)
self.assertFalse(
self.loop.run_until_complete(Video.exists({"_id": "fakeee"}))
)
def test_related(self):
class CommentsREL(Document):
collection = "comments"
database = "mdocument"
client = self.client
class VideoREL(Document):
collection = "videos"
database = "mdocument"
client = self.client
@Document.related(CommentsREL.Field.video, self_field_name="_id")
def comments(self):
return VideoREL
video = self.loop.run_until_complete(VideoREL.create(author="me"))
comment1 = self.loop.run_until_complete(CommentsREL.create(video=video._id, text="test1"))
comment2 = self.loop.run_until_complete(CommentsREL.create(video=video._id, text="test2"))
for comment in self.loop.run_until_complete(video.comments):
self.assertTrue(
comment == comment1 or comment == comment2
)
class LikeREL(Document):
collection = "likes"
database = "mdocument"
client = self.client
@Document.related(VideoREL.Field._id, multiple=False, parent=False)
def video(self):
return LikeREL
like = self.loop.run_until_complete(LikeREL.create(video=video._id, count=1))
self.assertEqual(
self.loop.run_until_complete(like.video), video)
self.loop.run_until_complete(video.delete())
self.assertEqual({
"mdocument": {
"videos": [],
"comments": [],
"likes": [dict(like)]
}
}, {"mdocument": {
"videos": self.loop.run_until_complete(
self.client["mdocument"]["videos"].find().to_list(9999)),
"comments": self.loop.run_until_complete(
self.client["mdocument"]["comments"].find().to_list(9999)),
"likes": self.loop.run_until_complete(
self.client["mdocument"]["likes"].find().to_list(9999)),
}})
def test_quals(self):
"""Tests valid comparison."""
d1 = Document(a=1)
d2 = Document(a=1)
d3 = Document(a=2)
self.assertTrue(d1 == d2)
self.assertFalse(d2 == d3)
import motor.motor_asyncio
import pymongo
import pytest
from mdocument import ClientNotFound, Document, DocumentDoesntExist, DocumentException
from mdocument.document import DocumentField, DocumentRelations
from mdocument.document import DocumentField
@pytest.fixture()
......@@ -15,17 +16,21 @@ async def teardown(client):
await client.drop_database("mdocument")
def mock_Document(name: str, client, **related_funcs):
def mock_Document(name: str, client, relations=None, methods=None) -> type:
mocked = type(name, (Document,), {
"collection": name.lower(),
"database": "mdocument",
"client": client,
})
for name, decorator in related_funcs.items():
def prop(self):
return mocked
prop.__name__ = name
setattr(mocked, name, decorator(prop))
if relations is not None:
for name, decorator in relations.items():
def prop(self):
return mocked
prop.__name__ = name
setattr(mocked, name, decorator(prop))
if methods is not None:
for name, method in methods.items():
setattr(mocked, name, method)
return mocked
......@@ -224,11 +229,11 @@ async def test_related(client):
Comment = mock_Document("comments", client)
Video = mock_Document("videos", client, **{
Video = mock_Document("videos", client, relations={
"comments": Document.related(Comment.Field.video, self_field_name="_id")
})
Like = mock_Document("likes", client, **{
Like = mock_Document("likes", client, relations={
"video": Document.related(Video.Field._id, multiple=False, parent=False,
other_is_parent=False)
})
......@@ -287,7 +292,7 @@ async def test_search_by_field(client):
Comment = mock_Document("comments", client)
Video = mock_Document("videos", client, **{
Video = mock_Document("videos", client, relations={
"comments": Document.related(Comment.Field.video, self_field_name="_id")
})
......@@ -320,3 +325,30 @@ def test_document___getattribute__(client):
assert video2.database == "test_database"
assert video2.client == "test_client"
@pytest.mark.asyncio
async def test_create_indexes(client):
test_client = client
class Video(Document):
client = test_client
collection = "videos"
database = "mdocument"
@classmethod
def create_indexes(cls):
cls.sync_collection.create_index([
("user", pymongo.ASCENDING),
("value", pymongo.ASCENDING),
], unique=True)
# print(mocked_created_indexes)
user_1_value_1 = {
"v": 2,
"unique": True,
"key": [("user", 1), ("value", 1)],
"ns": "mdocument.videos"
}
indexes = await Video.collection.index_information()
assert indexes.get("user_1_value_1") == user_1_value_1
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment