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())