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)
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