Skip to content

Update SearchResult behavior, add to_dict method #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 46 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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')}")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -438,7 +447,7 @@ def find(

```python
# Search for records with complex criteria
query = {
search_query = {
"where": {
"$and": [
{"age": {"$gte": 18}},
Expand All @@ -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")
Expand Down Expand Up @@ -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:**
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "rushdb"
version = "1.5.1"
version = "1.6.0"
description = "RushDB Python SDK"
authors = ["RushDB Team <hi@rushdb.com>"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/rushdb/models/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 30 additions & 16 deletions src/rushdb/models/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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]
4 changes: 4 additions & 0 deletions tests/test_create_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
52 changes: 44 additions & 8 deletions tests/test_search_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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)
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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__":
Expand Down