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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy