rushdb 2.0.0__tar.gz → 2.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.
Files changed (40) hide show
  1. {rushdb-2.0.0 → rushdb-2.2.0}/PKG-INFO +1 -1
  2. {rushdb-2.0.0 → rushdb-2.2.0}/pyproject.toml +1 -1
  3. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/__init__.py +7 -1
  4. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/__init__.py +2 -0
  5. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/records.py +82 -81
  6. rushdb-2.2.0/src/rushdb/api/relationship_patterns.py +61 -0
  7. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/relationships.py +9 -1
  8. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/client.py +2 -2
  9. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/record.py +13 -16
  10. rushdb-2.2.0/tests/test_relationship_patterns.py +54 -0
  11. {rushdb-2.0.0 → rushdb-2.2.0}/uv.lock +2 -2
  12. {rushdb-2.0.0 → rushdb-2.2.0}/.env.example +0 -0
  13. {rushdb-2.0.0 → rushdb-2.2.0}/.github/workflows/ci.yml +0 -0
  14. {rushdb-2.0.0 → rushdb-2.2.0}/.gitignore +0 -0
  15. {rushdb-2.0.0 → rushdb-2.2.0}/CODE_OF_CONDUCT.md +0 -0
  16. {rushdb-2.0.0 → rushdb-2.2.0}/CONTRIBUTING.md +0 -0
  17. {rushdb-2.0.0 → rushdb-2.2.0}/LICENSE +0 -0
  18. {rushdb-2.0.0 → rushdb-2.2.0}/README.md +0 -0
  19. {rushdb-2.0.0 → rushdb-2.2.0}/rushdb-logo.svg +0 -0
  20. {rushdb-2.0.0 → rushdb-2.2.0}/src/__init__.py +0 -0
  21. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/ai.py +0 -0
  22. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/base.py +0 -0
  23. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/labels.py +0 -0
  24. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/properties.py +0 -0
  25. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/query.py +0 -0
  26. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/api/transactions.py +0 -0
  27. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/common.py +0 -0
  28. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/__init__.py +0 -0
  29. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/api_response.py +0 -0
  30. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/property.py +0 -0
  31. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/relationship.py +0 -0
  32. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/result.py +0 -0
  33. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/search_query.py +0 -0
  34. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/models/transaction.py +0 -0
  35. {rushdb-2.0.0 → rushdb-2.2.0}/src/rushdb/utils/token_prefix.py +0 -0
  36. {rushdb-2.0.0 → rushdb-2.2.0}/tests/__init__.py +0 -0
  37. {rushdb-2.0.0 → rushdb-2.2.0}/tests/test_base_setup.py +0 -0
  38. {rushdb-2.0.0 → rushdb-2.2.0}/tests/test_create_import.py +0 -0
  39. {rushdb-2.0.0 → rushdb-2.2.0}/tests/test_search_query.py +0 -0
  40. {rushdb-2.0.0 → rushdb-2.2.0}/tests/test_search_result.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rushdb
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: RushDB Python SDK — memory layer for AI agents and modern apps
5
5
  Project-URL: Homepage, https://github.com/rush-db/rushdb-python
6
6
  Project-URL: Repository, https://github.com/rush-db/rushdb-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rushdb"
3
- version = "2.0.0"
3
+ version = "2.2.0"
4
4
  description = "RushDB Python SDK — memory layer for AI agents and modern apps"
5
5
  authors = [
6
6
  {name = "RushDB Team", email = "hi@rushdb.com"}
@@ -5,12 +5,14 @@ Exposes the RushDB class.
5
5
 
6
6
  from .api.ai import AIAPI
7
7
  from .api.query import QueryAPI
8
+ from .api.records import pick_record_id
9
+ from .api.relationship_patterns import RelationshipPatternsAPI
8
10
  from .api.relationships import RelationsAPI
9
11
  from .client import RushDB
10
12
  from .common import NonUniqueResultError, RushDBError
11
13
  from .models.api_response import ApiResponse
12
14
  from .models.property import Property
13
- from .models.record import Record
15
+ from .models.record import Record, RecordTarget, RelationTarget
14
16
  from .models.relationship import RelationshipDetachOptions, RelationshipOptions
15
17
  from .models.result import RecordSearchResult, SearchResult
16
18
  from .models.transaction import Transaction
@@ -20,6 +22,9 @@ __all__ = [
20
22
  "RushDBError",
21
23
  "NonUniqueResultError",
22
24
  "Record",
25
+ "RecordTarget",
26
+ "RelationTarget",
27
+ "pick_record_id",
23
28
  "RecordSearchResult",
24
29
  "SearchResult",
25
30
  "Transaction",
@@ -28,6 +33,7 @@ __all__ = [
28
33
  "RelationshipDetachOptions",
29
34
  "QueryAPI",
30
35
  "RelationsAPI",
36
+ "RelationshipPatternsAPI",
31
37
  "ApiResponse",
32
38
  "AIAPI",
33
39
  ]
@@ -2,6 +2,7 @@ from .labels import LabelsAPI
2
2
  from .properties import PropertiesAPI
3
3
  from .query import QueryAPI
4
4
  from .records import RecordsAPI
5
+ from .relationship_patterns import RelationshipPatternsAPI
5
6
  from .relationships import RelationsAPI
6
7
  from .transactions import TransactionsAPI
7
8
 
@@ -12,4 +13,5 @@ __all__ = [
12
13
  "TransactionsAPI",
13
14
  "QueryAPI",
14
15
  "RelationsAPI",
16
+ "RelationshipPatternsAPI",
15
17
  ]
@@ -1,7 +1,7 @@
1
1
  import typing
2
2
  from typing import Any, Dict, List, Optional, Union
3
3
 
4
- from ..models.record import Record
4
+ from ..models.record import Record, RecordTarget, RelationTarget
5
5
  from ..models.relationship import RelationshipDetachOptions, RelationshipOptions
6
6
  from ..models.result import RecordSearchResult
7
7
  from ..models.search_query import SearchQuery
@@ -9,6 +9,29 @@ from ..models.transaction import Transaction
9
9
  from .base import BaseAPI
10
10
 
11
11
 
12
+ def pick_record_id(target: RecordTarget) -> Optional[str]:
13
+ """Extract a record ID string from any RecordTarget representation.
14
+
15
+ Mirrors the JavaScript SDK's ``pickRecordId`` utility in ``src/api/utils.ts``.
16
+ Accepts a UUID string, a ``Record`` instance, or a plain dict containing
17
+ an ``__id`` field — in all cases the resolved UUID string is returned.
18
+
19
+ Args:
20
+ target: A record reference as a string ID, ``Record`` object, or dict
21
+ with an ``"__id"`` key.
22
+
23
+ Returns:
24
+ The UUID string, or ``None`` if it cannot be resolved.
25
+ """
26
+ if isinstance(target, str):
27
+ return target
28
+ elif isinstance(target, Record):
29
+ return target.data.get("__id")
30
+ elif isinstance(target, dict):
31
+ return target.get("__id")
32
+ return None
33
+
34
+
12
35
  def _is_flat(obj: Any) -> bool:
13
36
  """Return True if obj is a dict with no nested dict/list values."""
14
37
  return isinstance(obj, dict) and not any(
@@ -55,7 +78,7 @@ class RecordsAPI(BaseAPI):
55
78
 
56
79
  def set(
57
80
  self,
58
- record_id: str,
81
+ target: RecordTarget,
59
82
  data: Dict[str, Any],
60
83
  label: Optional[str] = None,
61
84
  vectors: Optional[List[Dict[str, Any]]] = None,
@@ -67,7 +90,8 @@ class RecordsAPI(BaseAPI):
67
90
  removing any existing fields that are not present in the new data.
68
91
 
69
92
  Args:
70
- record_id (str): The unique identifier of the record to update.
93
+ target (RecordTarget): The record to update. Accepts a UUID string,
94
+ a ``Record`` instance, or a dict containing an ``"__id"`` field.
71
95
  data (Dict[str, Any]): The new data to replace the existing record data.
72
96
  label (Optional[str]): Optional label to re-assign to the record.
73
97
  vectors (Optional[List[Dict[str, Any]]]): Optional pre-computed embedding vectors
@@ -81,9 +105,10 @@ class RecordsAPI(BaseAPI):
81
105
  Dict[str, str]: Response from the server containing operation status.
82
106
 
83
107
  Raises:
84
- ValueError: If the record_id is invalid or empty.
108
+ ValueError: If the target cannot be resolved to a valid record ID.
85
109
  RequestError: If the server request fails.
86
110
  """
111
+ record_id = pick_record_id(target)
87
112
  headers = Transaction._build_transaction_header(transaction)
88
113
  payload: Dict[str, Any] = {"data": data}
89
114
  if label is not None:
@@ -96,7 +121,7 @@ class RecordsAPI(BaseAPI):
96
121
 
97
122
  def update(
98
123
  self,
99
- record_id: str,
124
+ target: RecordTarget,
100
125
  data: Dict[str, Any],
101
126
  transaction: Optional[Transaction] = None,
102
127
  ) -> Dict[str, str]:
@@ -106,7 +131,8 @@ class RecordsAPI(BaseAPI):
106
131
  with existing record data without removing existing fields.
107
132
 
108
133
  Args:
109
- record_id (str): The unique identifier of the record to update.
134
+ target (RecordTarget): The record to update. Accepts a UUID string,
135
+ a ``Record`` instance, or a dict containing an ``"__id"`` field.
110
136
  data (Dict[str, Any]): The data to merge with the existing record data.
111
137
  transaction (Optional[Transaction], optional): Transaction context for the operation.
112
138
  If provided, the operation will be part of the transaction. Defaults to None.
@@ -115,9 +141,10 @@ class RecordsAPI(BaseAPI):
115
141
  Dict[str, str]: Response from the server containing operation status.
116
142
 
117
143
  Raises:
118
- ValueError: If the record_id is invalid or empty.
144
+ ValueError: If the target cannot be resolved to a valid record ID.
119
145
  RequestError: If the server request fails.
120
146
  """
147
+ record_id = pick_record_id(target)
121
148
  headers = Transaction._build_transaction_header(transaction)
122
149
 
123
150
  return self.client._make_request(
@@ -383,15 +410,8 @@ class RecordsAPI(BaseAPI):
383
410
 
384
411
  def attach(
385
412
  self,
386
- source: Union[str, Dict[str, Any]],
387
- target: Union[
388
- str,
389
- List[str],
390
- Dict[str, Any],
391
- List[Dict[str, Any]],
392
- "Record",
393
- List["Record"],
394
- ],
413
+ source: RecordTarget,
414
+ target: RelationTarget,
395
415
  options: Optional[RelationshipOptions] = None,
396
416
  transaction: Optional[Transaction] = None,
397
417
  ) -> Dict[str, str]:
@@ -402,16 +422,11 @@ class RecordsAPI(BaseAPI):
402
422
  record dictionaries, or Record objects.
403
423
 
404
424
  Args:
405
- source (Union[str, Dict[str, Any]]): The source record to attach targets to.
406
- Can be a record ID string or a record dictionary containing '__id'.
407
- target (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]):
408
- The target record(s) to attach to the source. Accepts multiple formats:
409
- - Single record ID (str)
410
- - List of record IDs (List[str])
411
- - Record dictionary with '__id' field
412
- - List of record dictionaries
413
- - Record object
414
- - List of Record objects
425
+ source (RecordTarget): The source record to attach targets to.
426
+ Accepts a UUID string, a ``Record`` instance, or a dict with ``"__id"``.
427
+ target (RelationTarget): The target record(s) to attach to the source.
428
+ Accepts a single or list of: UUID string, ``Record`` instance, or
429
+ dict with ``"__id"``.
415
430
  options (Optional[RelationshipOptions], optional): Additional options for the relationship.
416
431
  Defaults to None.
417
432
  transaction (Optional[Transaction], optional): Transaction context for the operation.
@@ -445,15 +460,8 @@ class RecordsAPI(BaseAPI):
445
460
 
446
461
  def detach(
447
462
  self,
448
- source: Union[str, Dict[str, Any]],
449
- target: Union[
450
- str,
451
- List[str],
452
- Dict[str, Any],
453
- List[Dict[str, Any]],
454
- "Record",
455
- List["Record"],
456
- ],
463
+ source: RecordTarget,
464
+ target: RelationTarget,
457
465
  options: Optional[RelationshipDetachOptions] = None,
458
466
  transaction: Optional[Transaction] = None,
459
467
  ) -> Dict[str, str]:
@@ -464,16 +472,11 @@ class RecordsAPI(BaseAPI):
464
472
  record dictionaries, or Record objects.
465
473
 
466
474
  Args:
467
- source (Union[str, Dict[str, Any]]): The source record to detach targets from.
468
- Can be a record ID string or a record dictionary containing '__id'.
469
- target (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]):
470
- The target record(s) to detach from the source. Accepts multiple formats:
471
- - Single record ID (str)
472
- - List of record IDs (List[str])
473
- - Record dictionary with '__id' field
474
- - List of record dictionaries
475
- - Record object
476
- - List of Record objects
475
+ source (RecordTarget): The source record to detach targets from.
476
+ Accepts a UUID string, a ``Record`` instance, or a dict with ``"__id"``.
477
+ target (RelationTarget): The target record(s) to detach from the source.
478
+ Accepts a single or list of: UUID string, ``Record`` instance, or
479
+ dict with ``"__id"``.
477
480
  options (Optional[RelationshipDetachOptions], optional): Additional options for the detach operation.
478
481
  Defaults to None.
479
482
  transaction (Optional[Transaction], optional): Transaction context for the operation.
@@ -550,7 +553,7 @@ class RecordsAPI(BaseAPI):
550
553
 
551
554
  def delete_by_id(
552
555
  self,
553
- id_or_ids: Union[str, List[str]],
556
+ target: Union[RecordTarget, List[RecordTarget]],
554
557
  transaction: Optional[Transaction] = None,
555
558
  ) -> Dict[str, str]:
556
559
  """Delete one or more records by their unique identifiers.
@@ -559,8 +562,9 @@ class RecordsAPI(BaseAPI):
559
562
  deletion and bulk deletion of multiple records.
560
563
 
561
564
  Args:
562
- id_or_ids (Union[str, List[str]]): The record identifier(s) to delete.
563
- Can be a single record ID string or a list of record ID strings.
565
+ target (Union[RecordTarget, List[RecordTarget]]): The record(s) to delete.
566
+ Accepts a UUID string, a ``Record`` instance, a dict containing
567
+ ``"__id"``, or a list of any of those forms.
564
568
  transaction (Optional[Transaction], optional): Transaction context for the operation.
565
569
  If provided, the operation will be part of the transaction. Defaults to None.
566
570
 
@@ -569,39 +573,42 @@ class RecordsAPI(BaseAPI):
569
573
  information about the deletion operation.
570
574
 
571
575
  Raises:
572
- ValueError: If any of the provided IDs are invalid or empty.
576
+ ValueError: If any target cannot be resolved to a valid record ID.
573
577
  RequestError: If the server request fails.
574
578
 
575
579
  Note:
576
- When deleting multiple records (list of IDs), the operation uses a
580
+ When deleting multiple records (list), the operation uses a
577
581
  batch delete with a limit of 1000 records. For single record deletion,
578
582
  it uses a direct DELETE request.
579
583
 
580
584
  Example:
581
585
  >>> records_api = RecordsAPI(client)
582
- >>> # Delete a single record
586
+ >>> # Delete a single record — string, Record object, or dict all work
583
587
  >>> response = records_api.delete_by_id("record_123")
588
+ >>> response = records_api.delete_by_id(my_record)
584
589
  >>>
585
590
  >>> # Delete multiple records
586
- >>> record_ids = ["record_123", "record_456", "record_789"]
587
- >>> response = records_api.delete_by_id(record_ids)
591
+ >>> response = records_api.delete_by_id(["record_123", "record_456"])
592
+ >>> response = records_api.delete_by_id([record_a, record_b])
588
593
  """
589
594
  headers = Transaction._build_transaction_header(transaction)
590
595
 
591
- if isinstance(id_or_ids, list):
596
+ if isinstance(target, list):
597
+ ids = self._extract_target_ids(target)
592
598
  return self.client._make_request(
593
599
  "POST",
594
600
  "/records/delete",
595
- {"limit": 1000, "where": {"$id": {"$in": id_or_ids}}},
601
+ {"limit": 1000, "where": {"$id": {"$in": ids}}},
596
602
  headers,
597
603
  )
604
+ record_id = pick_record_id(target)
598
605
  return self.client._make_request(
599
- "DELETE", f"/records/{id_or_ids}", None, headers
606
+ "DELETE", f"/records/{record_id}", None, headers
600
607
  )
601
608
 
602
609
  def find_by_id(
603
610
  self,
604
- id_or_ids: Union[str, List[str]],
611
+ target: Union[RecordTarget, List[RecordTarget]],
605
612
  transaction: Optional[Transaction] = None,
606
613
  ) -> Union["Record", RecordSearchResult]:
607
614
  """Retrieve one or more records by their unique identifiers.
@@ -609,17 +616,19 @@ class RecordsAPI(BaseAPI):
609
616
  Mirrors the TypeScript SDK ``records.findById`` method.
610
617
 
611
618
  Args:
612
- id_or_ids: A single record ID string or a list of ID strings.
619
+ target: A single record reference (UUID string, ``Record`` instance, or
620
+ dict with ``"__id"``), or a list of any of those forms.
613
621
  transaction: Optional transaction context for the operation.
614
622
 
615
623
  Returns:
616
- A single :class:`Record` when ``id_or_ids`` is a string, or a
617
- :class:`RecordSearchResult` when ``id_or_ids`` is a list.
624
+ A single :class:`Record` when ``target`` is a single reference, or a
625
+ :class:`RecordSearchResult` when ``target`` is a list.
618
626
  """
619
627
  headers = Transaction._build_transaction_header(transaction)
620
- if isinstance(id_or_ids, list):
628
+ if isinstance(target, list):
629
+ ids = self._extract_target_ids(target)
621
630
  response = self.client._make_request(
622
- "POST", "/records", {"ids": id_or_ids}, headers
631
+ "POST", "/records", {"ids": ids}, headers
623
632
  )
624
633
  records = [Record(self.client, r) for r in (response.get("data") or [])]
625
634
  return RecordSearchResult(
@@ -627,21 +636,25 @@ class RecordsAPI(BaseAPI):
627
636
  total=response.get("total", len(records)),
628
637
  client=self.client,
629
638
  )
639
+ record_id = pick_record_id(target)
630
640
  response = self.client._make_request(
631
- "GET", f"/records/{id_or_ids}", None, headers
641
+ "GET", f"/records/{record_id}", None, headers
632
642
  )
633
643
  return Record(self.client, response.get("data", response))
634
644
 
635
645
  def find(
636
646
  self,
637
647
  search_query: Optional[SearchQuery] = None,
638
- record_id: Optional[str] = None,
648
+ record_id: Optional[RecordTarget] = None,
639
649
  transaction: Optional[Transaction] = None,
640
650
  ) -> RecordSearchResult:
641
651
  try:
642
652
  headers = Transaction._build_transaction_header(transaction)
643
653
 
644
- path = f"/records/{record_id}/search" if record_id else "/records/search"
654
+ resolved_id = pick_record_id(record_id) if record_id is not None else None
655
+ path = (
656
+ f"/records/{resolved_id}/search" if resolved_id else "/records/search"
657
+ )
645
658
  response = self.client._make_request(
646
659
  "POST",
647
660
  path,
@@ -825,14 +838,7 @@ class RecordsAPI(BaseAPI):
825
838
 
826
839
  @staticmethod
827
840
  def _extract_target_ids(
828
- target: Union[
829
- str,
830
- List[str],
831
- Dict[str, Any],
832
- List[Dict[str, Any]],
833
- "Record",
834
- List["Record"],
835
- ],
841
+ target: Union[RecordTarget, List[RecordTarget]],
836
842
  ) -> List[str]:
837
843
  """Extract record IDs from various input types and formats.
838
844
 
@@ -841,14 +847,9 @@ class RecordsAPI(BaseAPI):
841
847
  types commonly used throughout the API for specifying target records.
842
848
 
843
849
  Args:
844
- target (Union[str, List[str], Dict[str, Any], List[Dict[str, Any]], Record, List[Record]]):
845
- The target input to extract IDs from. Supported formats:
846
- - str: Single record ID
847
- - List[str]: List of record IDs
848
- - Dict[str, Any]: Record dictionary containing '__id' field
849
- - List[Dict[str, Any]]: List of record dictionaries with '__id' fields
850
- - Record: Record object with data containing '__id'
851
- - List[Record]: List of Record objects
850
+ target (Union[RecordTarget, List[RecordTarget]]): The target input to
851
+ extract IDs from. Accepts a UUID string, a ``Record`` instance, a
852
+ dict with ``"__id"``, or a list of any of those forms.
852
853
 
853
854
  Returns:
854
855
  List[str]: List of extracted record ID strings.
@@ -0,0 +1,61 @@
1
+ """Relationship pattern suggestions API for the RushDB Python SDK."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Dict
4
+
5
+ from ..models.api_response import ApiResponse
6
+ from .base import BaseAPI
7
+
8
+ if TYPE_CHECKING:
9
+ from ..client import RushDB
10
+
11
+
12
+ class RelationshipPatternsAPI(BaseAPI):
13
+ """Review and manage relationship patterns inferred from project ontology.
14
+
15
+ Accessed via ``db.relationships.patterns``.
16
+ """
17
+
18
+ def __init__(self, client: "RushDB"):
19
+ super().__init__(client)
20
+
21
+ @staticmethod
22
+ def _wrap(response: Dict[str, Any]) -> ApiResponse:
23
+ return ApiResponse(
24
+ data=response.get("data"),
25
+ success=response.get("success", True),
26
+ total=response.get("total"),
27
+ )
28
+
29
+ def list(self) -> ApiResponse:
30
+ """List inferred patterns, ontology relationships, and analysis status."""
31
+ response = self.client._make_request("GET", "/relationships/patterns")
32
+ return self._wrap(response)
33
+
34
+ def analyze(self) -> ApiResponse:
35
+ """Queue ontology analysis to generate relationship pattern suggestions."""
36
+ response = self.client._make_request(
37
+ "POST", "/relationships/patterns/analyze", {}
38
+ )
39
+ return self._wrap(response)
40
+
41
+ def approve(self, pattern_id: str) -> ApiResponse:
42
+ """Approve and apply a suggested relationship pattern."""
43
+ response = self.client._make_request(
44
+ "POST", f"/relationships/patterns/{pattern_id}/approve", {}
45
+ )
46
+ return self._wrap(response)
47
+
48
+ def ignore(self, pattern_id: str) -> ApiResponse:
49
+ """Ignore a suggested relationship pattern without applying it."""
50
+ response = self.client._make_request(
51
+ "POST", f"/relationships/patterns/{pattern_id}/ignore", {}
52
+ )
53
+ return self._wrap(response)
54
+
55
+ def delete(self, pattern_id: str, *, delete_existing: bool = False) -> ApiResponse:
56
+ """Delete a saved pattern and optionally its materialized relationships."""
57
+ params = {"deleteExisting": "true"} if delete_existing else None
58
+ response = self.client._make_request(
59
+ "DELETE", f"/relationships/patterns/{pattern_id}", params=params
60
+ )
61
+ return self._wrap(response)
@@ -1,11 +1,15 @@
1
1
  import typing
2
- from typing import List, Optional, TypedDict, Union
2
+ from typing import TYPE_CHECKING, List, Optional, TypedDict, Union
3
3
  from urllib.parse import urlencode
4
4
 
5
5
  from ..models.relationship import Relationship
6
6
  from ..models.search_query import SearchQuery
7
7
  from ..models.transaction import Transaction
8
8
  from .base import BaseAPI
9
+ from .relationship_patterns import RelationshipPatternsAPI
10
+
11
+ if TYPE_CHECKING:
12
+ from ..client import RushDB
9
13
 
10
14
 
11
15
  class PaginationParams(TypedDict, total=False):
@@ -60,6 +64,10 @@ class RelationsAPI(BaseAPI):
60
64
  >>> page_1 = await relations_api.find(pagination=pagination)
61
65
  """
62
66
 
67
+ def __init__(self, client: "RushDB"):
68
+ super().__init__(client)
69
+ self.patterns = RelationshipPatternsAPI(client)
70
+
63
71
  def find(
64
72
  self,
65
73
  search_query: Optional[SearchQuery] = None,
@@ -34,7 +34,7 @@ class _SettingsNamespace:
34
34
  Returns:
35
35
  Dict[str, Any]: Current project settings object.
36
36
  """
37
- response = self._client._make_request("GET", "/settings")
37
+ response = self._client._make_request("GET", "/sdk/settings")
38
38
  return response.get("data", response)
39
39
 
40
40
 
@@ -288,7 +288,7 @@ class RushDB:
288
288
  ... return client
289
289
  """
290
290
  try:
291
- self._make_request("GET", "/settings")
291
+ self._make_request("GET", "/sdk/settings")
292
292
  return True
293
293
  except RushDBError:
294
294
  return False
@@ -7,6 +7,17 @@ from .transaction import Transaction
7
7
  if TYPE_CHECKING:
8
8
  from ..client import RushDB
9
9
 
10
+ # RecordTarget mirrors the JS SDK's DBRecordTarget type:
11
+ # a record can be identified by a UUID string, a dict containing "__id", or a Record instance.
12
+ RecordTarget = Union[str, Dict[str, Any], "Record"]
13
+
14
+ # RelationTarget mirrors the JS SDK's RelationTarget type:
15
+ # one or more records used as the target in attach/detach operations.
16
+ RelationTarget = Union[
17
+ "RecordTarget",
18
+ List["RecordTarget"],
19
+ ]
20
+
10
21
 
11
22
  class Record:
12
23
  """Represents a record in RushDB with methods for manipulation."""
@@ -74,14 +85,7 @@ class Record:
74
85
 
75
86
  def attach(
76
87
  self,
77
- target: Union[
78
- str,
79
- List[str],
80
- Dict[str, Any],
81
- List[Dict[str, Any]],
82
- "Record",
83
- List["Record"],
84
- ],
88
+ target: "RelationTarget",
85
89
  options: Optional[RelationshipOptions] = None,
86
90
  transaction: Optional[Transaction] = None,
87
91
  ) -> Dict[str, str]:
@@ -90,14 +94,7 @@ class Record:
90
94
 
91
95
  def detach(
92
96
  self,
93
- target: Union[
94
- str,
95
- List[str],
96
- Dict[str, Any],
97
- List[Dict[str, Any]],
98
- "Record",
99
- List["Record"],
100
- ],
97
+ target: "RelationTarget",
101
98
  options: Optional[RelationshipDetachOptions] = None,
102
99
  transaction: Optional[Transaction] = None,
103
100
  ) -> Dict[str, str]:
@@ -0,0 +1,54 @@
1
+ from unittest.mock import Mock
2
+
3
+ from src.rushdb.api.relationship_patterns import RelationshipPatternsAPI
4
+ from src.rushdb.api.relationships import RelationsAPI
5
+
6
+
7
+ def test_relationships_exposes_patterns_namespace():
8
+ client = Mock()
9
+
10
+ relationships = RelationsAPI(client)
11
+
12
+ assert isinstance(relationships.patterns, RelationshipPatternsAPI)
13
+
14
+
15
+ def test_list_relationship_patterns():
16
+ client = Mock()
17
+ client._make_request.return_value = {
18
+ "data": {"patterns": [], "relationships": [], "analysis": {"status": "idle"}},
19
+ "success": True,
20
+ }
21
+
22
+ response = RelationsAPI(client).patterns.list()
23
+
24
+ client._make_request.assert_called_once_with("GET", "/relationships/patterns")
25
+ assert response.data["patterns"] == []
26
+ assert response.data["analysis"]["status"] == "idle"
27
+
28
+
29
+ def test_mutate_relationship_patterns():
30
+ client = Mock()
31
+ client._make_request.return_value = {"data": {"deleted": True}, "success": True}
32
+ patterns = RelationsAPI(client).patterns
33
+
34
+ patterns.analyze()
35
+ client._make_request.assert_called_with(
36
+ "POST", "/relationships/patterns/analyze", {}
37
+ )
38
+
39
+ patterns.approve("pattern-id")
40
+ client._make_request.assert_called_with(
41
+ "POST", "/relationships/patterns/pattern-id/approve", {}
42
+ )
43
+
44
+ patterns.ignore("pattern-id")
45
+ client._make_request.assert_called_with(
46
+ "POST", "/relationships/patterns/pattern-id/ignore", {}
47
+ )
48
+
49
+ patterns.delete("pattern-id", delete_existing=True)
50
+ client._make_request.assert_called_with(
51
+ "DELETE",
52
+ "/relationships/patterns/pattern-id",
53
+ params={"deleteExisting": "true"},
54
+ )
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 2
2
+ revision = 3
3
3
  requires-python = ">=3.8"
4
4
  resolution-markers = [
5
5
  "python_full_version >= '3.10'",
@@ -785,7 +785,7 @@ wheels = [
785
785
 
786
786
  [[package]]
787
787
  name = "rushdb"
788
- version = "2.0.0"
788
+ version = "2.2.0"
789
789
  source = { editable = "." }
790
790
  dependencies = [
791
791
  { name = "python-dotenv", version = "1.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
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