Skip to content

Commit fe913f4

Browse files
authored
Merge pull request #10 from rush-db/feature/records-result
Implements records result iterator
2 parents d89d75e + df2f6f6 commit fe913f4

File tree

12 files changed

+937
-35
lines changed

12 files changed

+937
-35
lines changed

README.md

Lines changed: 237 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,207 @@ 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+
- **Generic type support**: Uses Python's typing generics (`SearchResult[T]`) with `RecordSearchResult` as a type alias for `SearchResult[Record]`
107+
- **List-like access**: Index, slice, and iterate like a regular list
108+
- **Search context**: Access total count, pagination info, and the original search query
109+
- **Boolean conversion**: Use in if statements naturally (returns `True` if the result contains any items)
110+
- **Pagination support**: Built-in pagination information and `has_more` property
111+
112+
### Basic Usage
113+
114+
```python
115+
# Perform a search
116+
result = db.records.find({
117+
"where": {"status": "active"},
118+
"limit": 10,
119+
"skip": 20
120+
})
121+
122+
# Check if we have results
123+
if result:
124+
print(f"Found {len(result)} records")
125+
126+
# Access search result information
127+
print(f"Total matching records: {result.total}")
128+
print(f"Current page size: {result.count}")
129+
print(f"Records skipped: {result.skip}")
130+
print(f"Has more results: {result.has_more}")
131+
print(f"Search query: {result.search_query}")
132+
133+
# Iterate over results
134+
for record in result:
135+
print(f"Record: {record.get('name')}")
136+
137+
# List comprehensions work
138+
names = [r.get('name') for r in result]
139+
140+
# Indexing and slicing
141+
first_record = result[0] if result else None
142+
first_five = result[:5]
143+
144+
# String representation
145+
print(repr(result)) # SearchResult(count=10, total=42)
146+
```
147+
148+
### SearchResult Constructor
149+
150+
```python
151+
def __init__(
152+
self,
153+
data: List[T],
154+
total: Optional[int] = None,
155+
search_query: Optional[SearchQuery] = None,
156+
):
157+
"""
158+
Initialize search result.
159+
160+
Args:
161+
data: List of result items
162+
total: Total number of matching records (defaults to len(data) if not provided)
163+
search_query: The search query used to generate this result (defaults to {})
164+
"""
165+
```
166+
167+
### SearchResult Properties
168+
169+
| Property | Type | Description |
170+
| -------------- | --------------- | ---------------------------------------- |
171+
| `data` | `List[T]` | The list of result items (generic type) |
172+
| `total` | `int` | Total number of matching records |
173+
| `count` | `int` | Number of records in current result set |
174+
| `limit` | `Optional[int]` | Limit that was applied to the search |
175+
| `skip` | `int` | Number of records that were skipped |
176+
| `has_more` | `bool` | Whether there are more records available |
177+
| `search_query` | `SearchQuery` | The search query used to generate result |
178+
179+
> **Implementation Notes:**
180+
>
181+
> - If `search_query` is not provided during initialization, it defaults to an empty dictionary `{}`
182+
> - The `skip` property checks if `search_query` is a dictionary and returns the "skip" value or 0
183+
> - The `has_more` property is calculated as `total > (skip + len(data))`, allowing for efficient pagination
184+
> - The `__bool__` method returns `True` if the result contains any items (`len(data) > 0`)
185+
186+
### Pagination Example
187+
188+
```python
189+
# Paginated search
190+
page_size = 10
191+
current_page = 0
192+
193+
while True:
194+
result = db.records.find({
195+
"where": {"category": "electronics"},
196+
"limit": page_size,
197+
"skip": current_page * page_size,
198+
"orderBy": {"created_at": "desc"}
199+
})
200+
201+
if not result:
202+
break
203+
204+
print(f"Page {current_page + 1}: {len(result)} records")
205+
206+
for record in result:
207+
process_record(record)
208+
209+
if not result.has_more:
210+
break
211+
212+
current_page += 1
213+
```
214+
215+
### RecordSearchResult Type
216+
217+
The SDK provides a specialized type alias for search results containing Record objects:
218+
219+
```python
220+
# Type alias for record search results
221+
RecordSearchResult = SearchResult[Record]
222+
```
223+
224+
This type is what's returned by methods like `db.records.find()`, providing type safety and specialized handling for Record objects while leveraging all the functionality of the generic SearchResult class.
225+
226+
## Improved Record API
227+
228+
The Record class has been enhanced with better data access patterns and utility methods.
229+
230+
### Enhanced Data Access
231+
232+
```python
233+
# Create a record
234+
user = db.records.create("User", {
235+
"name": "John Doe",
236+
"email": "john@example.com",
237+
"age": 30,
238+
"department": "Engineering"
239+
})
240+
241+
# Safe field access with defaults
242+
name = user.get("name") # "John Doe"
243+
phone = user.get("phone", "Not provided") # "Not provided"
244+
245+
# Get clean user data (excludes internal fields like __id, __label)
246+
user_data = user.get_data()
247+
# Returns: {"name": "John Doe", "email": "john@example.com", "age": 30, "department": "Engineering"}
248+
249+
# Get all data including internal fields
250+
full_data = user.get_data(exclude_internal=False)
251+
# Includes: __id, __label, __proptypes, etc.
252+
253+
# Convenient fields property
254+
fields = user.fields # Same as user.get_data()
255+
256+
# Dictionary conversion
257+
user_dict = user.to_dict() # Clean user data
258+
full_dict = user.to_dict(exclude_internal=False) # All data
259+
260+
# Direct field access
261+
user_name = user["name"] # Direct access
262+
user_id = user["__id"] # Internal field access
263+
```
264+
265+
### Record Existence Checking
266+
267+
```python
268+
# Safe existence checking (no exceptions)
269+
if user.exists():
270+
print("Record is valid and accessible")
271+
user.update({"status": "active"})
272+
else:
273+
print("Record doesn't exist or is not accessible")
274+
275+
# Perfect for validation workflows
276+
def process_record_safely(record):
277+
if not record.exists():
278+
return None
279+
return record.get_data()
280+
281+
# Conditional operations
282+
records = db.records.find({"where": {"status": "pending"}})
283+
for record in records:
284+
if record.exists():
285+
record.update({"processed_at": datetime.now()})
286+
```
287+
288+
### String Representations
289+
290+
```python
291+
user = db.records.create("User", {"name": "Alice Johnson"})
292+
293+
print(repr(user)) # Record(id='abc-123', label='User')
294+
print(str(user)) # User: Alice Johnson
295+
296+
# For records without names
297+
product = db.records.create("Product", {"sku": "ABC123"})
298+
print(str(product)) # Product (product-id-here)
299+
```
300+
86301
## Complete Documentation
87302

88303
For comprehensive documentation, tutorials, and examples, please visit:
@@ -206,18 +421,18 @@ def find(
206421
search_query: Optional[SearchQuery] = None,
207422
record_id: Optional[str] = None,
208423
transaction: Optional[Transaction] = None
209-
) -> List[Record]
424+
) -> RecordSearchResult
210425
```
211426

212427
**Arguments:**
213428

214-
- `query` (Optional[SearchQuery]): Search query parameters
429+
- `search_query` (Optional[SearchQuery]): Search query parameters
215430
- `record_id` (Optional[str]): Optional record ID to search from
216431
- `transaction` (Optional[Transaction]): Optional transaction object
217432

218433
**Returns:**
219434

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

222437
**Example:**
223438

@@ -235,7 +450,24 @@ query = {
235450
"limit": 10
236451
}
237452

238-
records = db.records.find(query=query)
453+
result = db.records.find(query=query)
454+
455+
# Work with SearchResult
456+
print(f"Found {len(result)} out of {result.total} total records")
457+
458+
# Iterate over results
459+
for record in result:
460+
print(f"Employee: {record.get('name')} - {record.get('department')}")
461+
462+
# Check pagination
463+
if result.has_more:
464+
print("More results available")
465+
466+
# Access specific records
467+
first_employee = result[0] if result else None
468+
469+
# List operations
470+
senior_employees = [r for r in result if r.get('age', 0) > 30]
239471
```
240472

241473
### 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",

0 commit comments

Comments
 (0)