rushdb 3.1.0__tar.gz → 3.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rushdb-3.1.0 → rushdb-3.2.0}/PKG-INFO +9 -2
- {rushdb-3.1.0 → rushdb-3.2.0}/README.md +8 -1
- {rushdb-3.1.0 → rushdb-3.2.0}/pyproject.toml +1 -1
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/relationships.py +56 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/direct/router.py +34 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_direct_mode.py +36 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_direct_mode_live.py +21 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/uv.lock +1 -1
- {rushdb-3.1.0 → rushdb-3.2.0}/.env.example +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/.github/workflows/ci.yml +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/.gitignore +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/CODE_OF_CONDUCT.md +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/CONTRIBUTING.md +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/LICENSE +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/rushdb-logo.svg +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/__init__.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/__init__.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/__init__.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/ai.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/base.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/labels.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/properties.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/query.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/records.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/relationship_patterns.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/text_indexes.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/api/transactions.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/client.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/common.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/direct/__init__.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/direct/capabilities.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/direct/engine.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/direct/schema.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/__init__.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/api_response.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/property.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/record.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/relationship.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/result.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/search_query.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/models/transaction.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/src/rushdb/utils/token_prefix.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/__init__.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_ai_vector_search.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_base_setup.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_create_import.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_relationship_patterns.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_search_query.py +0 -0
- {rushdb-3.1.0 → rushdb-3.2.0}/tests/test_search_result.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rushdb
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: RushDB SDK for Python: the client for RushDB, the database for AI (RushDB Cloud or self-hosted)
|
|
5
5
|
Project-URL: Homepage, https://github.com/rush-db/rushdb-python
|
|
6
6
|
Project-URL: Repository, https://github.com/rush-db/rushdb-python
|
|
@@ -93,7 +93,7 @@ What each offers:
|
|
|
93
93
|
| Feature | Cloud | Self-hosted engine |
|
|
94
94
|
| --- | :---: | :---: |
|
|
95
95
|
| Records: create, read, update, set, upsert, delete, `find` with `where`, `orderBy`, `select`, `groupBy` | ✓ | ✓ |
|
|
96
|
-
| Relationships: `attach`/`detach`, `relationships.create_many`/`delete_many`/`find` | ✓ | ✓ |
|
|
96
|
+
| Relationships: `attach`/`detach`, `relationships.create_many`/`delete_many`/`find`/`update` | ✓ | ✓ |
|
|
97
97
|
| Labels, properties, property values | ✓ | ✓ |
|
|
98
98
|
| Import JSON and CSV, export CSV | ✓ | ✓ |
|
|
99
99
|
| External vector indexes; vector search with a `queryVector` you supply | ✓ | ✓ |
|
|
@@ -227,6 +227,13 @@ relationships = db.relationships.find({
|
|
|
227
227
|
'target': {'labels': ['COMPANY']},
|
|
228
228
|
'where': {'type': 'WORKS_AT', 'source': 'profile'},
|
|
229
229
|
})
|
|
230
|
+
|
|
231
|
+
# Change properties on existing relationships (RushDB 3.0.4+): set writes, unset removes
|
|
232
|
+
db.relationships.update(user, company, type='WORKS_AT', set={'source': 'hr'}, unset=['legacy'])
|
|
233
|
+
# types marks values JSON can't type: 'datetime' stores an ISO string as a datetime
|
|
234
|
+
db.relationships.update(
|
|
235
|
+
user, company, type='WORKS_AT', set={'since': '2024-03-01'}, types={'since': 'datetime'}
|
|
236
|
+
)
|
|
230
237
|
```
|
|
231
238
|
|
|
232
239
|
---
|
|
@@ -79,7 +79,7 @@ What each offers:
|
|
|
79
79
|
| Feature | Cloud | Self-hosted engine |
|
|
80
80
|
| --- | :---: | :---: |
|
|
81
81
|
| Records: create, read, update, set, upsert, delete, `find` with `where`, `orderBy`, `select`, `groupBy` | ✓ | ✓ |
|
|
82
|
-
| Relationships: `attach`/`detach`, `relationships.create_many`/`delete_many`/`find` | ✓ | ✓ |
|
|
82
|
+
| Relationships: `attach`/`detach`, `relationships.create_many`/`delete_many`/`find`/`update` | ✓ | ✓ |
|
|
83
83
|
| Labels, properties, property values | ✓ | ✓ |
|
|
84
84
|
| Import JSON and CSV, export CSV | ✓ | ✓ |
|
|
85
85
|
| External vector indexes; vector search with a `queryVector` you supply | ✓ | ✓ |
|
|
@@ -213,6 +213,13 @@ relationships = db.relationships.find({
|
|
|
213
213
|
'target': {'labels': ['COMPANY']},
|
|
214
214
|
'where': {'type': 'WORKS_AT', 'source': 'profile'},
|
|
215
215
|
})
|
|
216
|
+
|
|
217
|
+
# Change properties on existing relationships (RushDB 3.0.4+): set writes, unset removes
|
|
218
|
+
db.relationships.update(user, company, type='WORKS_AT', set={'source': 'hr'}, unset=['legacy'])
|
|
219
|
+
# types marks values JSON can't type: 'datetime' stores an ISO string as a datetime
|
|
220
|
+
db.relationships.update(
|
|
221
|
+
user, company, type='WORKS_AT', set={'since': '2024-03-01'}, types={'since': 'datetime'}
|
|
222
|
+
)
|
|
216
223
|
```
|
|
217
224
|
|
|
218
225
|
---
|
|
@@ -10,6 +10,7 @@ from .relationship_patterns import RelationshipPatternsAPI
|
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from ..client import RushDB
|
|
13
|
+
from ..models.record import RecordTarget, RelationTarget
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class PaginationParams(TypedDict, total=False):
|
|
@@ -181,6 +182,61 @@ class RelationsAPI(BaseAPI):
|
|
|
181
182
|
"POST", "/relationships/create-many", payload, headers
|
|
182
183
|
)
|
|
183
184
|
|
|
185
|
+
def update(
|
|
186
|
+
self,
|
|
187
|
+
source: "RecordTarget",
|
|
188
|
+
target: "RelationTarget",
|
|
189
|
+
*,
|
|
190
|
+
type: str,
|
|
191
|
+
direction: Optional[str] = None,
|
|
192
|
+
set: Optional[dict] = None,
|
|
193
|
+
unset: Optional[typing.List[str]] = None,
|
|
194
|
+
types: Optional[typing.Dict[str, str]] = None,
|
|
195
|
+
) -> dict:
|
|
196
|
+
"""Update the properties of existing relationships of one type.
|
|
197
|
+
|
|
198
|
+
Changes the ``type`` relationships from ``source`` to each ``target`` (or the
|
|
199
|
+
reverse with ``direction='in'``). Pairs without such a relationship are skipped.
|
|
200
|
+
Not available inside a transaction. Needs RushDB 3.0.4 or newer.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
source: The source record (id, ``Record`` or dict with ``__id``).
|
|
204
|
+
target: The target record(s).
|
|
205
|
+
type (str): The relationship type to update.
|
|
206
|
+
direction (str, optional): ``'out'`` (default) or ``'in'``.
|
|
207
|
+
set (dict, optional): Properties to write; ``None`` values store an explicit null.
|
|
208
|
+
unset (list[str], optional): Names of properties to remove.
|
|
209
|
+
types (dict, optional): Type of ``set`` properties JSON can't express, one of
|
|
210
|
+
``'string'``, ``'number'``, ``'boolean'``, ``'datetime'``;
|
|
211
|
+
``{"since": "datetime"}`` stores an ISO string as a datetime.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
dict: API response; ``data["updated"]`` counts the relationships changed.
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
>>> db.relationships.update(
|
|
218
|
+
... customer, order, type="PLACES", set={"channel": "store"}, unset=["legacyCode"]
|
|
219
|
+
... )
|
|
220
|
+
"""
|
|
221
|
+
from .records import RecordsAPI
|
|
222
|
+
|
|
223
|
+
source_id = RecordsAPI._extract_target_ids(source)[0]
|
|
224
|
+
payload: dict = {
|
|
225
|
+
"targetIds": RecordsAPI._extract_target_ids(target),
|
|
226
|
+
"type": type,
|
|
227
|
+
}
|
|
228
|
+
if direction:
|
|
229
|
+
payload["direction"] = direction
|
|
230
|
+
if set is not None:
|
|
231
|
+
payload["set"] = set
|
|
232
|
+
if unset is not None:
|
|
233
|
+
payload["unset"] = unset
|
|
234
|
+
if types:
|
|
235
|
+
payload["types"] = types
|
|
236
|
+
return self.client._make_request(
|
|
237
|
+
"PATCH", f"/relationships/{source_id}", payload
|
|
238
|
+
)
|
|
239
|
+
|
|
184
240
|
def delete_many(
|
|
185
241
|
self,
|
|
186
242
|
*,
|
|
@@ -753,6 +753,39 @@ def _detach(request: _Request) -> Body:
|
|
|
753
753
|
)
|
|
754
754
|
|
|
755
755
|
|
|
756
|
+
def _update_relationships(request: _Request) -> Body:
|
|
757
|
+
body, (source_id,) = request.body, request.params
|
|
758
|
+
_not_in_transaction(request.tx_id, "relationships.update")
|
|
759
|
+
result = request.engine.execute(
|
|
760
|
+
{
|
|
761
|
+
"kind": "mutate",
|
|
762
|
+
"mutation": {
|
|
763
|
+
"kind": "updateRelationshipsById",
|
|
764
|
+
"command": {
|
|
765
|
+
"relationType": body.get("type"),
|
|
766
|
+
"direction": (
|
|
767
|
+
"in" if _direction(body.get("direction")) == "in" else "out"
|
|
768
|
+
),
|
|
769
|
+
"sourceIds": [source_id],
|
|
770
|
+
"targetIds": _as_list(body.get("targetIds")),
|
|
771
|
+
"set": body.get("set") or {},
|
|
772
|
+
"unset": body.get("unset") or [],
|
|
773
|
+
# Sent only when used, so untyped updates work on engines without it.
|
|
774
|
+
**({"types": body["types"]} if body.get("types") else {}),
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
}
|
|
778
|
+
)
|
|
779
|
+
updated = int((result or {}).get("affected") or 0)
|
|
780
|
+
plural = "" if updated == 1 else "s"
|
|
781
|
+
return _ok(
|
|
782
|
+
{
|
|
783
|
+
"message": f"{updated} relationship{plural} of Record {source_id} updated",
|
|
784
|
+
"updated": updated,
|
|
785
|
+
}
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
|
|
756
789
|
def _link_many(kind: str, message: str, what: str) -> Callable[[_Request], Body]:
|
|
757
790
|
def handle(request: _Request) -> Body:
|
|
758
791
|
_not_in_transaction(request.tx_id, what)
|
|
@@ -1283,6 +1316,7 @@ _ROUTES: List[Tuple[str, "re.Pattern[str]", Callable[[_Request], Body]]] = [
|
|
|
1283
1316
|
("POST", re.compile(r"^/records/export/csv$"), _export_csv),
|
|
1284
1317
|
("POST", re.compile(rf"^/relationships/{_ID}$"), _attach),
|
|
1285
1318
|
("PUT", re.compile(rf"^/relationships/{_ID}$"), _detach),
|
|
1319
|
+
("PATCH", re.compile(rf"^/relationships/{_ID}$"), _update_relationships),
|
|
1286
1320
|
(
|
|
1287
1321
|
"POST",
|
|
1288
1322
|
re.compile(r"^/relationships/create-many$"),
|
|
@@ -191,6 +191,9 @@ EVERY_METHOD = {
|
|
|
191
191
|
"records.detach": lambda db: db.records.detach(
|
|
192
192
|
RECORD["id"], RECORD["id"], options={"typeOrTypes": "LIKES"}
|
|
193
193
|
),
|
|
194
|
+
"relationships.update": lambda db: db.relationships.update(
|
|
195
|
+
RECORD["id"], RECORD["id"], type="LIKES", set={"weight": 1}
|
|
196
|
+
),
|
|
194
197
|
"records.create": lambda db: db.records.create("Book", {"title": "Dune"}),
|
|
195
198
|
"records.create_many": lambda db: db.records.create_many(
|
|
196
199
|
"Book", [{"title": "Dune"}]
|
|
@@ -531,6 +534,39 @@ def test_detach_without_type_or_direction_removes_every_link(engine_calls):
|
|
|
531
534
|
]
|
|
532
535
|
|
|
533
536
|
|
|
537
|
+
def test_relationship_properties_are_updated_by_record_ids(engine_calls):
|
|
538
|
+
result = direct_db().relationships.update(
|
|
539
|
+
RECORD["id"],
|
|
540
|
+
["t1", "t2"],
|
|
541
|
+
type="PLACES",
|
|
542
|
+
direction="in",
|
|
543
|
+
set={"channel": "web", "note": None, "since": "2026-11-06T10:00:00.000Z"},
|
|
544
|
+
unset=["old"],
|
|
545
|
+
types={"since": "datetime"},
|
|
546
|
+
)
|
|
547
|
+
assert result["data"]["updated"] == 1
|
|
548
|
+
commands = [
|
|
549
|
+
op["mutation"]["command"]
|
|
550
|
+
for op in operations(engine_calls)
|
|
551
|
+
if op.get("mutation", {}).get("kind") == "updateRelationshipsById"
|
|
552
|
+
]
|
|
553
|
+
assert commands == [
|
|
554
|
+
{
|
|
555
|
+
"relationType": "PLACES",
|
|
556
|
+
"direction": "in",
|
|
557
|
+
"sourceIds": [RECORD["id"]],
|
|
558
|
+
"targetIds": ["t1", "t2"],
|
|
559
|
+
"set": {
|
|
560
|
+
"channel": "web",
|
|
561
|
+
"note": None,
|
|
562
|
+
"since": "2026-11-06T10:00:00.000Z",
|
|
563
|
+
},
|
|
564
|
+
"unset": ["old"],
|
|
565
|
+
"types": {"since": "datetime"},
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
|
|
569
|
+
|
|
534
570
|
def test_keyword_search_uses_the_labels_text_index(engine_calls):
|
|
535
571
|
result = direct_db().records.text_search({"query": "dune", "labels": ["Book"]})
|
|
536
572
|
hybrid = [op for op in operations(engine_calls) if op.get("kind") == "query"][0]
|
|
@@ -85,6 +85,27 @@ def test_relationships(db):
|
|
|
85
85
|
)
|
|
86
86
|
assert found.total == 1
|
|
87
87
|
|
|
88
|
+
edge = found.data[0]
|
|
89
|
+
result = db.relationships.update(
|
|
90
|
+
edge["sourceId"],
|
|
91
|
+
edge["targetId"],
|
|
92
|
+
type="AUTHORED",
|
|
93
|
+
set={"year": 1965, "role": "author", "since": "1965-08-01T10:30:00.000Z"},
|
|
94
|
+
types={"since": "datetime"},
|
|
95
|
+
)
|
|
96
|
+
assert result["data"]["updated"] == 1
|
|
97
|
+
db.relationships.update(
|
|
98
|
+
edge["sourceId"], edge["targetId"], type="AUTHORED", unset=["role"]
|
|
99
|
+
)
|
|
100
|
+
updated = db.relationships.find(
|
|
101
|
+
{"where": {"type": "AUTHORED"}, "source": {"labels": [AUTHOR]}}
|
|
102
|
+
)
|
|
103
|
+
assert updated.data[0].get("properties") == {
|
|
104
|
+
"year": 1965,
|
|
105
|
+
"since": "1965-08-01T10:30:00.000Z",
|
|
106
|
+
}
|
|
107
|
+
assert updated.data[0].get("propertyTypes", {}).get("since") == "datetime"
|
|
108
|
+
|
|
88
109
|
|
|
89
110
|
def test_labels_properties_schema(db):
|
|
90
111
|
assert db.labels.find({})["data"][BOOK] >= 3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|