diff --git a/README.md b/README.md index 6df0bff..76e375a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ PantherDB is a Simple, FileBase and Document Oriented datab - Easy to use - Written in pure Python +3.8 based on standard type hints - Handle Database Encryption - +- Singleton connection per `db_name` ## Usage @@ -43,7 +43,7 @@ PantherDB is a Simple, FileBase and Document Oriented datab ``` ### Get: -- #### Find first document: +- #### Find one document: ```python user: PantherDocument = db.collection('User').find_one(first_name='Ali', last_name='Rn') ``` @@ -51,7 +51,25 @@ PantherDB is a Simple, FileBase and Document Oriented datab ```python user: PantherDocument = db.collection('User').find_one() ``` - + +- #### Find first document (alias of `find_one()`): + ```python + user: PantherDocument = db.collection('User').first(first_name='Ali', last_name='Rn') + ``` + or + ```python + user: PantherDocument = db.collection('User').first() + ``` + +- #### Find last document: + ```python + user: PantherDocument = db.collection('User').last(first_name='Ali', last_name='Rn') + ``` + or + ```python + user: PantherDocument = db.collection('User').lasst() + ``` + - #### Find documents: ```python users: list[PantherDocument] = db.collection('User').find(last_name='Rn') diff --git a/pantherdb/__init__.py b/pantherdb/__init__.py index 7dcc2bc..f86686a 100644 --- a/pantherdb/__init__.py +++ b/pantherdb/__init__.py @@ -1,6 +1,6 @@ from pantherdb.pantherdb import * # noqa: F403 -__version__ = '1.2.9' +__version__ = '1.3.0' __all__ = ('__version__', 'PantherDB', 'PantherCollection', 'PantherDocument', 'PantherDBException') diff --git a/pantherdb/pantherdb.py b/pantherdb/pantherdb.py index c049433..130cc4a 100644 --- a/pantherdb/pantherdb.py +++ b/pantherdb/pantherdb.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +from typing import ClassVar import orjson as json from cryptography.fernet import Fernet, InvalidToken @@ -11,12 +12,29 @@ class PantherDBException(Exception): class PantherDB: + _instances: ClassVar[dict] = {} db_name: str = 'database.pdb' __secret_key: bytes | None __fernet: Fernet | None __return_dict: bool __content: dict + def __new__(cls, *args, **kwargs): + if cls.__name__ != 'PantherDB': + return super().__new__(cls) + + if args: + db_name = args[0] + elif 'db_name' in kwargs: + db_name = kwargs['db_name'] + else: + db_name = cls.db_name + + db_name = db_name.removesuffix('.pdb') + if db_name not in cls._instances: + cls._instances[db_name] = super().__new__(cls) + return cls._instances[db_name] + def __init__( self, db_name: str | None = None, @@ -154,6 +172,11 @@ def _get_collection(self) -> list[dict]: def find_one(self, **kwargs) -> PantherDocument | dict | None: documents = self._get_collection() + + # Empty Collection + if not documents: + return None + if not kwargs: if self.return_dict: return documents[0] @@ -180,6 +203,14 @@ def find(self, **kwargs) -> list[PantherDocument | dict]: result.append(self.__create_result(r)) return result + def first(self, **kwargs) -> PantherDocument | dict | None: + return self.find_one(**kwargs) + + def last(self, **kwargs) -> PantherDocument | dict | None: + if result := self.find(**kwargs): + return result[-1] + return None + def all(self) -> list[PantherDocument | dict]: if self.return_dict: return self._get_collection() diff --git a/tests/test_normal.py b/tests/test_normal.py index 4d3db9e..bff5d97 100644 --- a/tests/test_normal.py +++ b/tests/test_normal.py @@ -30,6 +30,22 @@ def create_junk_document(cls, collection) -> int: collection.insert_one(first_name=f'{f.first_name()}{i}', last_name=f'{f.last_name()}{i}') return _count + # Singleton + def test_pantherdb_singleton(self): + test_1 = PantherDB(db_name='test1') + test_2 = PantherDB('test1') + self.assertEqual(test_1, test_2) + + default_1 = PantherDB() + default_2 = PantherDB() + self.assertEqual(default_1, default_2) + + self.assertNotEqual(test_1, default_1) + self.assertNotEqual(test_2, default_2) + + Path(test_1.db_name).unlink() + Path(default_1.db_name).unlink() + # Create DB def test_creation_of_db(self): self.assertTrue(Path(self.db_name).exists()) @@ -131,6 +147,180 @@ def test_find_one_none(self): obj = collection.find_one(first_name=first_name, last_name=last_name) self.assertIsNone(obj) + def test_find_one_with_kwargs_from_empty_collection(self): + collection = self.db.collection(f.word()) + + # Find + obj = collection.find_one(first_name=f.first_name(), last_name=f.last_name()) + self.assertIsNone(obj) + + def test_find_one_without_kwargs_from_empty_collection(self): + collection = self.db.collection(f.word()) + + # Find + obj = collection.find_one() + self.assertIsNone(obj) + + # First + def test_first_when_its_first(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Insert with specific names + collection.insert_one(first_name=first_name, last_name=last_name) + + # Add others + self.create_junk_document(collection) + + # Find + obj = collection.first(first_name=first_name, last_name=last_name) + self.assertTrue(isinstance(obj, PantherDocument)) + self.assertEqual(obj.first_name, first_name) + self.assertEqual(obj.last_name, last_name) + + def test_first_of_many_finds(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Insert with specific names + expected = collection.insert_one(first_name=first_name, last_name=last_name) + collection.insert_one(first_name=first_name, last_name=last_name) + collection.insert_one(first_name=first_name, last_name=last_name) + + # Add others + self.create_junk_document(collection) + + # Find + obj = collection.first(first_name=first_name, last_name=last_name) + self.assertTrue(isinstance(obj, PantherDocument)) + self.assertEqual(obj.id, expected.id) + + def test_first_when_its_last(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Add others + self.create_junk_document(collection) + + # Insert with specific names + expected = collection.insert_one(first_name=first_name, last_name=last_name) + + # Find + obj = collection.first(first_name=first_name, last_name=last_name) + self.assertTrue(isinstance(obj, PantherDocument)) + self.assertEqual(obj.first_name, first_name) + self.assertEqual(obj.last_name, last_name) + self.assertEqual(obj.id, expected.id) + + def test_first_none(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Add others + self.create_junk_document(collection) + + # Find + obj = collection.first(first_name=first_name, last_name=last_name) + self.assertIsNone(obj) + + def test_first_with_kwargs_from_empty_collection(self): + collection = self.db.collection(f.word()) + + # Find + obj = collection.first(first_name=f.first_name(), last_name=f.last_name()) + self.assertIsNone(obj) + + def test_first_without_kwargs_from_empty_collection(self): + collection = self.db.collection(f.word()) + + # Find + obj = collection.first() + self.assertIsNone(obj) + + # Last + def test_last_when_its_first(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Insert with specific names + collection.insert_one(first_name=first_name, last_name=last_name) + + # Add others + self.create_junk_document(collection) + + # Find + obj = collection.first(first_name=first_name, last_name=last_name) + self.assertTrue(isinstance(obj, PantherDocument)) + self.assertEqual(obj.first_name, first_name) + self.assertEqual(obj.last_name, last_name) + + def test_last_of_many_finds(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Insert with specific names + collection.insert_one(first_name=first_name, last_name=last_name) + collection.insert_one(first_name=first_name, last_name=last_name) + expected = collection.insert_one(first_name=first_name, last_name=last_name) + + # Add others + self.create_junk_document(collection) + + # Find + obj = collection.last(first_name=first_name, last_name=last_name) + self.assertTrue(isinstance(obj, PantherDocument)) + self.assertEqual(obj.id, expected.id) + + def test_last_when_its_last(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Add others + self.create_junk_document(collection) + + # Insert with specific names + expected = collection.insert_one(first_name=first_name, last_name=last_name) + + # Find + obj = collection.last(first_name=first_name, last_name=last_name) + self.assertTrue(isinstance(obj, PantherDocument)) + self.assertEqual(obj.first_name, first_name) + self.assertEqual(obj.last_name, last_name) + self.assertEqual(obj.id, expected.id) + + def test_last_none(self): + collection = self.db.collection(f.word()) + first_name = f.first_name() + last_name = f.last_name() + + # Add others + self.create_junk_document(collection) + + # Find + obj = collection.last(first_name=first_name, last_name=last_name) + self.assertIsNone(obj) + + def test_last_with_kwargs_from_empty_collection(self): + collection = self.db.collection(f.word()) + + # Find + obj = collection.last(first_name=f.first_name(), last_name=f.last_name()) + self.assertIsNone(obj) + + def test_last_without_kwargs_from_empty_collection(self): + collection = self.db.collection(f.word()) + + # Find + obj = collection.last() + self.assertIsNone(obj) + # Find def test_find_response_type(self): collection = self.db.collection(f.word())