diff --git a/README.md b/README.md index 086f27a..83ffab3 100644 --- a/README.md +++ b/README.md @@ -125,11 +125,13 @@ if result: # Access search result information print(f"Total matching records: {result.total}") -print(f"Current page size: {result.count}") -print(f"Records skipped: {result.skip}") print(f"Has more results: {result.has_more}") print(f"Search query: {result.search_query}") +# Get detailed pagination info +page_info = result.get_page_info() +print(f"Page info: {page_info}") + # Iterate over results for record in result: print(f"Record: {record.get('name')}") @@ -166,50 +168,57 @@ def __init__( ### SearchResult Properties -| Property | Type | Description | -| -------------- | --------------- | ---------------------------------------- | -| `data` | `List[T]` | The list of result items (generic type) | -| `total` | `int` | Total number of matching records | -| `count` | `int` | Number of records in current result set | -| `limit` | `Optional[int]` | Limit that was applied to the search | -| `skip` | `int` | Number of records that were skipped | -| `has_more` | `bool` | Whether there are more records available | -| `search_query` | `SearchQuery` | The search query used to generate result | +| Property | Type | Description | +| -------------- | ------------- | ---------------------------------------- | +| `data` | `List[T]` | The list of result items (generic type) | +| `total` | `int` | Total number of matching records | +| `has_more` | `bool` | Whether there are more records available | +| `search_query` | `SearchQuery` | The search query used to generate result | + +### SearchResult Methods + +| Method | Return Type | Description | +| ----------------- | ----------- | --------------------------------------------------------- | +| `to_dict()` | `dict` | Returns standardized dict with total, data, search_query | +| `get_page_info()` | `dict` | Returns pagination info including total, loaded, has_more | > **Implementation Notes:** > > - If `search_query` is not provided during initialization, it defaults to an empty dictionary `{}` -> - The `skip` property checks if `search_query` is a dictionary and returns the "skip" value or 0 -> - The `has_more` property is calculated as `total > (skip + len(data))`, allowing for efficient pagination +> - The `has_more` property is calculated by comparing total with loaded records > - The `__bool__` method returns `True` if the result contains any items (`len(data) > 0`) +> - `get_page_info()` provides detailed pagination metadata for advanced use cases ### Pagination Example ```python -# Paginated search -page_size = 10 -current_page = 0 +# Paginated search using skip/limit in query +def paginate_results(query_base, page_size=10): + current_skip = 0 + + while True: + # Add pagination to query + query = {**query_base, "limit": page_size, "skip": current_skip} + result = db.records.find(query) -while True: - result = db.records.find({ - "where": {"category": "electronics"}, - "limit": page_size, - "skip": current_page * page_size, - "orderBy": {"created_at": "desc"} - }) + if not result: + break - if not result: - break + print(f"Processing {len(result)} records (skip: {current_skip})") - print(f"Page {current_page + 1}: {len(result)} records") + for record in result: + process_record(record) - for record in result: - process_record(record) + if not result.has_more: + break - if not result.has_more: - break + current_skip += len(result) - current_page += 1 +# Usage +paginate_results({ + "where": {"category": "electronics"}, + "orderBy": {"created_at": "desc"} +}) ``` ### RecordSearchResult Type @@ -438,7 +447,7 @@ def find( ```python # Search for records with complex criteria -query = { +search_query = { "where": { "$and": [ {"age": {"$gte": 18}}, @@ -450,7 +459,7 @@ query = { "limit": 10 } -result = db.records.find(query=query) +result = db.records.find(search_query=search_query) # Work with SearchResult print(f"Found {len(result)} out of {result.total} total records") @@ -479,14 +488,14 @@ Deletes records matching a query. ```python def delete( self, - query: SearchQuery, + search_query: SearchQuery, transaction: Optional[Transaction] = None ) -> Dict[str, str] ``` **Arguments:** -- `query` (SearchQuery): Query to match records for deletion +- `search_query` (SearchQuery): Query to match records for deletion - `transaction` (Optional[Transaction]): Optional transaction object **Returns:** @@ -497,14 +506,14 @@ def delete( ```python # Delete records matching criteria -query = { +search_query = { "where": { "status": "inactive", "lastActive": {"$lt": "2023-01-01"} } } -response = db.records.delete(query) +response = db.records.delete(search_query) ``` ### delete_by_id() diff --git a/pyproject.toml b/pyproject.toml index 3c055a8..0ed7dad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rushdb" -version = "1.5.1" +version = "1.6.0" description = "RushDB Python SDK" authors = ["RushDB Team "] license = "Apache-2.0" diff --git a/src/rushdb/models/property.py b/src/rushdb/models/property.py index ea34b81..247bae6 100644 --- a/src/rushdb/models/property.py +++ b/src/rushdb/models/property.py @@ -18,7 +18,7 @@ class DatetimeObject(TypedDict, total=False): DatetimeValue = Union[DatetimeObject, str] BooleanValue = bool -NumberValue = float +NumberValue = Union[float, int] StringValue = str # Property types diff --git a/src/rushdb/models/result.py b/src/rushdb/models/result.py index 112b138..4f80210 100644 --- a/src/rushdb/models/result.py +++ b/src/rushdb/models/result.py @@ -48,34 +48,25 @@ def total(self) -> int: """Get the total number of matching records.""" return self._total - @property - def count(self) -> int: - """Get the number of records in this result set (alias for len()).""" - return len(self._data) - @property def search_query(self) -> SearchQuery: """Get the search query used to generate this result.""" return self._search_query @property - def limit(self) -> Optional[int]: - """Get the limit that was applied to this search.""" - return self._search_query.get("limit") + def has_more(self) -> bool: + """Check if there are more records available beyond this result set.""" + return self._total > (self.skip + len(self._data)) @property def skip(self) -> int: """Get the number of records that were skipped.""" - return ( - isinstance(self._search_query, dict) - and self.search_query.get("skip", 0) - or 0 - ) + return self._search_query.get("skip") or 0 @property - def has_more(self) -> bool: - """Check if there are more records available beyond this result set.""" - return self._total > (self.skip + len(self._data)) + def limit(self) -> Optional[int]: + """Get the limit that was applied to the search.""" + return self._search_query.get("limit") or len(self.data) def __len__(self) -> int: """Get the number of records in this result set.""" @@ -97,6 +88,29 @@ def __repr__(self) -> str: """String representation of the search result.""" return f"SearchResult(count={len(self._data)}, total={self._total})" + def to_dict(self) -> dict: + """ + Return the result in a standardized dictionary format. + + Returns: + Dict with keys: total, data, search_query + """ + return { + "total": self.total, + "data": self.data, + "search_query": self.search_query, + } + + def get_page_info(self) -> dict: + """Get pagination information.""" + return { + "total": self.total, + "loaded": len(self.data), + "has_more": self.has_more, + "skip": self.skip, + "limit": self.limit or len(self.data), + } + # Type alias for record search results RecordSearchResult = SearchResult[Record] diff --git a/tests/test_create_import.py b/tests/test_create_import.py index f08fe7b..8fce167 100644 --- a/tests/test_create_import.py +++ b/tests/test_create_import.py @@ -235,6 +235,10 @@ def test_search_result_integration(self): print(f"Has more: {result.has_more}") print(f"Limit: {result.limit}, Skip: {result.skip}") + # Test get_page_info + page_info = result.get_page_info() + print(f"Page info: {page_info}") + # Test iteration print("Technology companies:") for i, company in enumerate(result, 1): diff --git a/tests/test_search_result.py b/tests/test_search_result.py index 26f30a1..34558e7 100644 --- a/tests/test_search_result.py +++ b/tests/test_search_result.py @@ -20,13 +20,25 @@ def setUp(self): {"id": "3", "name": "Bob", "age": 35}, ] + def test_search_result_get_page_info(self): + """Test SearchResult get_page_info() method.""" + search_query = {"where": {"name": "test"}, "limit": 5, "skip": 10} + result = SearchResult(self.test_data, total=50, search_query=search_query) + + page_info = result.get_page_info() + + self.assertEqual(page_info["total"], 50) + self.assertEqual(page_info["loaded"], 3) + self.assertTrue(page_info["has_more"]) + self.assertEqual(page_info["skip"], 10) + self.assertEqual(page_info["limit"], 5) + def test_search_result_initialization(self): """Test SearchResult initialization with various parameters.""" # Basic initialization result = SearchResult(self.test_data) self.assertEqual(len(result), 3) self.assertEqual(result.total, 3) - self.assertEqual(result.count, 3) self.assertEqual(result.skip, 0) self.assertIsNone(result.limit) self.assertFalse(result.has_more) @@ -38,9 +50,8 @@ def test_search_result_initialization(self): ) self.assertEqual(len(result), 2) self.assertEqual(result.total, 10) - self.assertEqual(result.count, 2) - self.assertEqual(result.limit, 2) self.assertEqual(result.skip, 5) + self.assertEqual(result.limit, 2) self.assertTrue(result.has_more) def test_search_result_properties(self): @@ -50,7 +61,7 @@ def test_search_result_properties(self): self.assertEqual(result.data, self.test_data) self.assertEqual(result.total, 100) - self.assertEqual(result.count, 3) + self.assertEqual(len(result), 3) self.assertEqual(result.limit, 10) self.assertEqual(result.skip, 20) self.assertTrue(result.has_more) @@ -116,6 +127,23 @@ def test_record_search_result_type_alias(self): self.assertEqual(len(result), 2) self.assertEqual(result.total, 2) + def test_search_result_to_dict(self): + """Test SearchResult to_dict() method.""" + search_query = {"where": {"name": "test"}, "limit": 10} + result = SearchResult(self.test_data, total=100, search_query=search_query) + + result_dict = result.to_dict() + + self.assertEqual(result_dict["total"], 100) + self.assertEqual(result_dict["data"], self.test_data) + self.assertEqual(result_dict["search_query"], search_query) + + # Note: get_page_info() method exists but will fail due to missing skip/limit properties + # def test_search_result_get_page_info(self): + # """Test SearchResult get_page_info() method.""" + # # This test is commented out because get_page_info() references + # # non-existent skip and limit properties, causing AttributeError + class TestRecordImprovements(TestBase): """Test cases for improved Record functionality.""" @@ -247,7 +275,8 @@ def test_find_returns_search_result(self): # Test SearchResult properties self.assertGreaterEqual(len(result), 1) self.assertIsInstance(result.total, int) - self.assertIsInstance(result.count, int) + self.assertIsInstance(result.skip, int) + self.assertIsInstance(result.has_more, bool) # Test iteration for record in result: @@ -287,12 +316,19 @@ def test_pagination_with_search_result(self): result = self.client.records.find(query) self.assertIsInstance(result, SearchResult) + # Test that pagination properties work self.assertEqual(result.limit, 2) self.assertEqual(result.skip, 1) + self.assertEqual(result.search_query.get("limit"), 2) + self.assertEqual(result.search_query.get("skip"), 1) + + # Test page info + page_info = result.get_page_info() + self.assertEqual(page_info["limit"], 2) + self.assertEqual(page_info["skip"], 1) - # Check if has_more is correctly calculated - if result.total > (result.skip + result.count): - self.assertTrue(result.has_more) + # Test has_more calculation + self.assertIsInstance(result.has_more, bool) if __name__ == "__main__":