Skip to content

Commit 2714424

Browse files
committed
Implements records result iterator
1 parent d89d75e commit 2714424

File tree

12 files changed

+896
-35
lines changed

12 files changed

+896
-35
lines changed

README.md

Lines changed: 196 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,28 @@ user = db.records.create(
4040
)
4141

4242
# Find records
43-
results = db.records.find({
43+
result = db.records.find({
4444
"where": {
4545
"age": {"$gte": 18},
4646
"name": {"$startsWith": "J"}
4747
},
4848
"limit": 10
4949
})
5050

51+
# Work with SearchResult
52+
print(f"Found {len(result)} records out of {result.total} total")
53+
54+
# Iterate over results
55+
for record in result:
56+
print(f"User: {record.get('name')} (Age: {record.get('age')})")
57+
58+
# Check if there are more results
59+
if result.has_more:
60+
print("There are more records available")
61+
62+
# Access specific records
63+
first_user = result[0] if result else None
64+
5165
# Create relationships
5266
company = db.records.create(
5367
label="COMPANY",
@@ -83,6 +97,166 @@ db.records.create_many("COMPANY", {
8397
})
8498
```
8599

100+
## SearchResult API
101+
102+
RushDB Python SDK uses a modern `SearchResult` container that follows Python SDK best practices similar to boto3, google-cloud libraries, and other popular SDKs.
103+
104+
### SearchResult Features
105+
106+
- **List-like access**: Index, slice, and iterate like a regular list
107+
- **Search context**: Access total count, pagination info, and the original search query
108+
- **Boolean conversion**: Use in if statements naturally
109+
- **Pagination support**: Built-in pagination information and `has_more` property
110+
111+
### Basic Usage
112+
113+
```python
114+
# Perform a search
115+
result = db.records.find({
116+
"where": {"status": "active"},
117+
"limit": 10,
118+
"skip": 20
119+
})
120+
121+
# Check if we have results
122+
if result:
123+
print(f"Found {len(result)} records")
124+
125+
# Access search result information
126+
print(f"Total matching records: {result.total}")
127+
print(f"Current page size: {result.count}")
128+
print(f"Records skipped: {result.skip}")
129+
print(f"Has more results: {result.has_more}")
130+
print(f"Search query: {result.search_query}")
131+
132+
# Iterate over results
133+
for record in result:
134+
print(f"Record: {record.get('name')}")
135+
136+
# List comprehensions work
137+
names = [r.get('name') for r in result]
138+
139+
# Indexing and slicing
140+
first_record = result[0] if result else None
141+
first_five = result[:5]
142+
```
143+
144+
### SearchResult Properties
145+
146+
| Property | Type | Description |
147+
| -------------- | --------------- | ---------------------------------------- |
148+
| `data` | `List[Record]` | The list of record results |
149+
| `total` | `int` | Total number of matching records |
150+
| `count` | `int` | Number of records in current result set |
151+
| `limit` | `Optional[int]` | Limit that was applied to the search |
152+
| `skip` | `int` | Number of records that were skipped |
153+
| `has_more` | `bool` | Whether there are more records available |
154+
| `search_query` | `SearchQuery` | The search query used to generate result |
155+
156+
### Pagination Example
157+
158+
```python
159+
# Paginated search
160+
page_size = 10
161+
current_page = 0
162+
163+
while True:
164+
result = db.records.find({
165+
"where": {"category": "electronics"},
166+
"limit": page_size,
167+
"skip": current_page * page_size,
168+
"orderBy": {"created_at": "desc"}
169+
})
170+
171+
if not result:
172+
break
173+
174+
print(f"Page {current_page + 1}: {len(result)} records")
175+
176+
for record in result:
177+
process_record(record)
178+
179+
if not result.has_more:
180+
break
181+
182+
current_page += 1
183+
```
184+
185+
## Improved Record API
186+
187+
The Record class has been enhanced with better data access patterns and utility methods.
188+
189+
### Enhanced Data Access
190+
191+
```python
192+
# Create a record
193+
user = db.records.create("User", {
194+
"name": "John Doe",
195+
"email": "john@example.com",
196+
"age": 30,
197+
"department": "Engineering"
198+
})
199+
200+
# Safe field access with defaults
201+
name = user.get("name") # "John Doe"
202+
phone = user.get("phone", "Not provided") # "Not provided"
203+
204+
# Get clean user data (excludes internal fields like __id, __label)
205+
user_data = user.get_data()
206+
# Returns: {"name": "John Doe", "email": "john@example.com", "age": 30, "department": "Engineering"}
207+
208+
# Get all data including internal fields
209+
full_data = user.get_data(exclude_internal=False)
210+
# Includes: __id, __label, __proptypes, etc.
211+
212+
# Convenient fields property
213+
fields = user.fields # Same as user.get_data()
214+
215+
# Dictionary conversion
216+
user_dict = user.to_dict() # Clean user data
217+
full_dict = user.to_dict(exclude_internal=False) # All data
218+
219+
# Direct field access
220+
user_name = user["name"] # Direct access
221+
user_id = user["__id"] # Internal field access
222+
```
223+
224+
### Record Existence Checking
225+
226+
```python
227+
# Safe existence checking (no exceptions)
228+
if user.exists():
229+
print("Record is valid and accessible")
230+
user.update({"status": "active"})
231+
else:
232+
print("Record doesn't exist or is not accessible")
233+
234+
# Perfect for validation workflows
235+
def process_record_safely(record):
236+
if not record.exists():
237+
return None
238+
return record.get_data()
239+
240+
# Conditional operations
241+
records = db.records.find({"where": {"status": "pending"}})
242+
for record in records:
243+
if record.exists():
244+
record.update({"processed_at": datetime.now()})
245+
```
246+
247+
### String Representations
248+
249+
```python
250+
user = db.records.create("User", {"name": "Alice Johnson"})
251+
252+
print(repr(user)) # Record(id='abc-123', label='User')
253+
print(str(user)) # User: Alice Johnson
254+
255+
# For records without names
256+
product = db.records.create("Product", {"sku": "ABC123"})
257+
print(str(product)) # Product (product-id-here)
258+
```
259+
86260
## Complete Documentation
87261

88262
For comprehensive documentation, tutorials, and examples, please visit:
@@ -206,18 +380,18 @@ def find(
206380
search_query: Optional[SearchQuery] = None,
207381
record_id: Optional[str] = None,
208382
transaction: Optional[Transaction] = None
209-
) -> List[Record]
383+
) -> RecordSearchResult
210384
```
211385

212386
**Arguments:**
213387

214-
- `query` (Optional[SearchQuery]): Search query parameters
388+
- `search_query` (Optional[SearchQuery]): Search query parameters
215389
- `record_id` (Optional[str]): Optional record ID to search from
216390
- `transaction` (Optional[Transaction]): Optional transaction object
217391

218392
**Returns:**
219393

220-
- `List[Record]`: List of matching records
394+
- `RecordSearchResult`: SearchResult container with matching records and metadata
221395

222396
**Example:**
223397

@@ -235,7 +409,24 @@ query = {
235409
"limit": 10
236410
}
237411

238-
records = db.records.find(query=query)
412+
result = db.records.find(query=query)
413+
414+
# Work with SearchResult
415+
print(f"Found {len(result)} out of {result.total} total records")
416+
417+
# Iterate over results
418+
for record in result:
419+
print(f"Employee: {record.get('name')} - {record.get('department')}")
420+
421+
# Check pagination
422+
if result.has_more:
423+
print("More results available")
424+
425+
# Access specific records
426+
first_employee = result[0] if result else None
427+
428+
# List operations
429+
senior_employees = [r for r in result if r.get('age', 0) > 30]
239430
```
240431

241432
### delete()

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[tool.poetry]
22
name = "rushdb"
3-
version = "1.4.0"
3+
version = "1.5.0"
44
description = "RushDB Python SDK"
55
authors = ["RushDB Team <hi@rushdb.com>"]
66
license = "Apache-2.0"
77
readme = "README.md"
8-
homepage = "https://github.com/rushdb/rushdb-python"
9-
repository = "https://github.com/rushdb/rushdb-python"
8+
homepage = "https://github.com/rush-db/rushdb-python"
9+
repository = "https://github.com/rush-db/rushdb-python"
1010
documentation = "https://docs.rushdb.com"
1111
packages = [{ include = "rushdb", from = "src" }]
1212
keywords = [

src/rushdb/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
from .models.property import Property
99
from .models.record import Record
1010
from .models.relationship import RelationshipDetachOptions, RelationshipOptions
11+
from .models.result import RecordSearchResult, SearchResult
1112
from .models.transaction import Transaction
1213

1314
__all__ = [
1415
"RushDB",
1516
"RushDBError",
1617
"Record",
18+
"RecordSearchResult",
19+
"SearchResult",
1720
"Transaction",
1821
"Property",
1922
"RelationshipOptions",

src/rushdb/api/records.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import typing
2-
from typing import Any, Dict, List, Optional, Tuple, Union
2+
from typing import Any, Dict, List, Optional, Union
33

44
from ..models.record import Record
55
from ..models.relationship import RelationshipDetachOptions, RelationshipOptions
6+
from ..models.result import RecordSearchResult
67
from ..models.search_query import SearchQuery
78
from ..models.transaction import Transaction
89
from .base import BaseAPI
@@ -427,7 +428,7 @@ def find(
427428
search_query: Optional[SearchQuery] = None,
428429
record_id: Optional[str] = None,
429430
transaction: Optional[Transaction] = None,
430-
) -> Tuple[List[Record], int]:
431+
) -> RecordSearchResult:
431432
"""Search for and retrieve records matching the specified criteria.
432433
433434
Searches the database for records that match the provided search query.
@@ -443,25 +444,34 @@ def find(
443444
If provided, the operation will be part of the transaction. Defaults to None.
444445
445446
Returns:
446-
Tuple[List[Record], int]: A tuple containing:
447-
- List[Record]: List of Record objects matching the search criteria
448-
- int: Total count of matching records (may be larger than returned list if pagination applies)
449-
450-
Note:
451-
The method includes exception handling that returns an empty list if an error occurs.
452-
In production code, you may want to handle specific exceptions differently.
447+
RecordSearchResult: A result object containing:
448+
- Iterable list of Record objects matching the search criteria
449+
- Total count of matching records (may be larger than returned list if pagination applies)
450+
- Additional metadata about the search operation
451+
- Convenient properties like .has_more, .count, etc.
453452
454453
Example:
455454
>>> from rushdb.models.search_query import SearchQuery
456455
>>> records_api = RecordsAPI(client)
457456
>>>
458457
>>> # Find all records with a specific label
459458
>>> query = SearchQuery(labels=["User"])
460-
>>> records, total = records_api.find(query)
461-
>>> print(f"Found {len(records)} records out of {total} total")
459+
>>> result = records_api.find(query)
460+
>>> print(f"Found {result.count} records out of {result.total} total")
461+
>>>
462+
>>> # Iterate over results
463+
>>> for record in result:
464+
... print(f"User: {record.get('name', 'Unknown')}")
465+
>>>
466+
>>> # Access specific records
467+
>>> first_user = result[0] if result else None
468+
>>>
469+
>>> # Check if there are more results
470+
>>> if result.has_more:
471+
... print("There are more records available")
462472
>>>
463473
>>> # Find records related to a specific record
464-
>>> related_records, total = records_api.find(query, record_id="parent_123")
474+
>>> related_result = records_api.find(query, record_id="parent_123")
465475
"""
466476

467477
try:
@@ -474,11 +484,17 @@ def find(
474484
data=typing.cast(typing.Dict[str, typing.Any], search_query or {}),
475485
headers=headers,
476486
)
477-
return [
478-
Record(self.client, record) for record in response.get("data")
479-
], response.get("total") or 0
487+
488+
records = [
489+
Record(self.client, record) for record in response.get("data", [])
490+
]
491+
total = response.get("total", 0)
492+
493+
return RecordSearchResult(
494+
data=records, total=total, search_query=search_query
495+
)
480496
except Exception:
481-
return [], 0
497+
return RecordSearchResult(data=[], total=0)
482498

483499
def import_csv(
484500
self,

src/rushdb/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def ping(self) -> bool:
240240
... return client
241241
"""
242242
try:
243-
self._make_request("GET", "/")
243+
self._make_request("GET", "/settings")
244244
return True
245245
except RushDBError:
246246
return False

src/rushdb/models/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from .property import Property
2+
from .record import Record
3+
from .relationship import RelationshipDetachOptions, RelationshipOptions
4+
from .result import RecordSearchResult, SearchResult
5+
from .search_query import SearchQuery
6+
from .transaction import Transaction
7+
8+
__all__ = [
9+
"Property",
10+
"Record",
11+
"RelationshipDetachOptions",
12+
"RelationshipOptions",
13+
"RecordSearchResult",
14+
"SearchResult",
15+
"SearchQuery",
16+
"Transaction",
17+
]

0 commit comments

Comments
 (0)