Skip to content

API Client

grizabella.api.client

Grizabella API Client.

This module provides the main Grizabella class, which serves as the public API for interacting with the Grizabella data store.

Grizabella

Public API for interacting with the Grizabella data store.

This class provides a high-level interface to manage and query data within a Grizabella database instance. It handles connection management, schema operations (object and relation types), data manipulation (objects and relations), embedding definitions, and complex queries.

Attributes:

Name Type Description
_db_manager GrizabellaDBManager

An instance of the database manager responsible for lower-level database interactions.

_is_connected bool

Tracks the connection state to the database.

Source code in grizabella/api/client.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
class Grizabella:
    """Public API for interacting with the Grizabella data store.

    This class provides a high-level interface to manage and query data
    within a Grizabella database instance. It handles connection management,
    schema operations (object and relation types), data manipulation (objects
    and relations), embedding definitions, and complex queries.

    Attributes:
        _db_manager (GrizabellaDBManager): An instance of the database manager
            responsible for lower-level database interactions.
        _is_connected (bool): Tracks the connection state to the database.

    """

    def __init__(
        self,
        db_name_or_path: Union[str, Path] = "default",
        create_if_not_exists: bool = True,
    ) -> None:
        """Initializes the Grizabella API client.

        Sets up the connection to the specified Grizabella database. If the
        database does not exist and `create_if_not_exists` is True, it will
        be created.

        Args:
            db_name_or_path (Union[str, Path]): The name of the database or
                the file system path to the database directory.
                Defaults to "default".
            create_if_not_exists (bool): If True, the database will be
                created if it does not already exist. Defaults to True.

        """
        self._logger = logging.getLogger(__name__) # Initialize logger
        self._initial_db_name_or_path = db_name_or_path # Store the initial path/name
        self._db_manager = GrizabellaDBManager(
            db_name_or_path=db_name_or_path,
            create_if_not_exists=create_if_not_exists,
        )
        self._is_connected = False

    @property
    def db_name_or_path(self) -> Union[str, Path]:
        """Returns the database name or path this client was initialized with."""
        return self._initial_db_name_or_path

    def connect(self) -> None:
        """Connects to the underlying Grizabella database.

        Establishes a connection to the database if not already connected.
        This method is typically called automatically when using the client
        as a context manager or before performing database operations if
        the connection was previously closed.

        Raises:
            GrizabellaException: If there is an error connecting to the database.

        """
        if not self._is_connected:
            self._db_manager.connect()
            self._is_connected = True

    def close(self) -> None:
        """Closes the connection to the underlying Grizabella database.

        Releases any resources held by the database connection. It's important
        to close the connection when it's no longer needed, especially if not
        using the client as a context manager.

        Raises:
            GrizabellaException: If there is an error closing the database connection.

        """
        self._logger.info(f"Grizabella client close() called for db: {self.db_name_or_path}. Connected: {self._is_connected}")
        if self._is_connected:
            try:
                self._db_manager.close() # This should call _ConnectionHelper.close_all_adapters()
                self._logger.info(f"Grizabella client: self._db_manager.close() completed for {self.db_name_or_path}.")
            except Exception as e:
                self._logger.error(f"Grizabella client: Error during self._db_manager.close() for {self.db_name_or_path}: {e}", exc_info=True)
            finally:
                self._is_connected = False
                self._logger.info(f"Grizabella client: _is_connected set to False for {self.db_name_or_path}.")
        else:
            self._logger.info(f"Grizabella client: Already not connected for {self.db_name_or_path}, no action taken in close().")

    def __enter__(self) -> "Grizabella":
        """Context manager entry point. Connects to the database.

        Returns:
            Grizabella: The Grizabella API client instance.

        """
        self.connect()
        return self

    def __exit__(
        self,
        exc_type: Optional[type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[Any],
    ) -> None:
        """Context manager exit point. Closes the database connection.

        Args:
            exc_type: The type of the exception, if any.
            exc_val: The exception instance, if any.
            exc_tb: The traceback object, if any.

        """
        self.close()

    # --- Schema Management ---
    def create_object_type(self, object_type_def: ObjectTypeDefinition) -> None:
        """Creates a new object type in the database.

        Object types define the schema for a category of objects, similar to
        tables in a relational database or node labels in a graph database.

        Args:
            object_type_def (ObjectTypeDefinition): The definition of the
                object type to create, including its name and properties.

        Raises:
            GrizabellaException: If the object type already exists or if there
                is an error during creation.
            NotConnectedError: If the client is not connected to the database.

        """
        self._db_manager.add_object_type_definition(object_type_def)

    def get_object_type_definition(
        self, type_name: str,
    ) -> Optional[ObjectTypeDefinition]:
        """Retrieves the definition of an object type.

        Args:
            type_name (str): The name of the object type to retrieve.

        Returns:
            Optional[ObjectTypeDefinition]: The definition of the object type
            if found, otherwise None.

        Raises:
            NotConnectedError: If the client is not connected to the database.

        """
        return self._db_manager.get_object_type_definition(type_name)

    def list_object_types(self) -> list[ObjectTypeDefinition]:
        """Lists all defined object types in the database.

        Returns:
            List[ObjectTypeDefinition]: A list of all object type definitions.

        Raises:
            NotConnectedError: If the client is not connected to the database.

        """
        if not self._is_connected:
            self._db_manager.connect()  # Ensure connection
        return self._db_manager.list_object_type_definitions()

    def delete_object_type(self, type_name: str) -> None:
        """Deletes an object type from the database.

        Warning: This operation may also delete all associated object instances
        and relations, depending on the underlying database implementation and
        cascade rules.

        Args:
            type_name (str): The name of the object type to delete.

        Raises:
            GrizabellaException: If the object type does not exist or if there
                is an error during deletion.
            NotConnectedError: If the client is not connected to the database.

        """
        self._db_manager.remove_object_type_definition(type_name)

    def create_relation_type(self, relation_type_def: RelationTypeDefinition) -> None:
        """Creates a new relation type in the database.

        Relation types define the schema for relationships between objects,
        including the source and target object types and any properties of
        the relation itself.

        Args:
            relation_type_def (RelationTypeDefinition): The definition of the
                relation type to create.

        Raises:
            GrizabellaException: If the relation type already exists or if
                there is an error during creation.
            NotConnectedError: If the client is not connected to the database.

        """
        self._db_manager.add_relation_type_definition(relation_type_def)

    def get_relation_type(self, type_name: str) -> Optional[RelationTypeDefinition]:
        """Retrieves the definition of a relation type.

        Args:
            type_name (str): The name of the relation type to retrieve.

        Returns:
            Optional[RelationTypeDefinition]: The definition of the relation
            type if found, otherwise None.

        Raises:
            NotConnectedError: If the client is not connected to the database.

        """
        return self._db_manager.get_relation_type_definition(type_name)

    def delete_relation_type(self, type_name: str) -> None:
        """Deletes a relation type from the database.

        Warning: This operation may also delete all associated relation instances,
        depending on the underlying database implementation.

        Args:
            type_name (str): The name of the relation type to delete.

        Raises:
            GrizabellaException: If the relation type does not exist or if
                there is an error during deletion.
            NotConnectedError: If the client is not connected to the database.

        """
        self._db_manager.remove_relation_type_definition(type_name)

    def list_relation_types(self) -> list[RelationTypeDefinition]:
        """Lists all defined relation types in the database.

        Returns:
            List[RelationTypeDefinition]: A list of all relation type definitions.

        Raises:
            NotConnectedError: If the client is not connected to the database.

        """
        if not self._is_connected:
            self._db_manager.connect()  # Ensure connection
        return self._db_manager.list_relation_type_definitions()

    # --- Data Management (Objects) ---
    def upsert_object(self, obj: ObjectInstance) -> ObjectInstance:
        """Creates a new object or updates an existing one.

        If an object with the same ID already exists, it will be updated
        with the properties from the provided `ObjectInstance`. Otherwise, a
        new object will be created.

        Args:
            obj (ObjectInstance): The object instance to create or update.
                It must include the object type name and its properties.
                The `id` field can be provided for updates or will be
                generated for new objects if not supplied.

        Returns:
            ObjectInstance: The created or updated object instance, potentially
            with a newly assigned ID or updated timestamps.

        Raises:
            GrizabellaException: If the object type does not exist or if there
                is an error during the upsert operation.
            NotConnectedError: If the client is not connected to the database.

        """
        return self._db_manager.upsert_object_instance(obj)

    def get_object_by_id(
        self, object_id: str, type_name: str,
    ) -> Optional[ObjectInstance]:
        """Retrieves an object by its ID and type.

        Args:
            object_id (str): The unique identifier of the object.
            type_name (str): The name of the object type.

        Returns:
            Optional[ObjectInstance]: The object instance if found,
            otherwise None.

        Raises:
            GrizabellaException: If the object type does not exist or if there
                is an error during retrieval.
            NotConnectedError: If the client is not connected to the database.

        """
        return self._db_manager.get_object_instance(
            object_type_name=type_name, instance_id=object_id,
        )

    def delete_object(self, object_id: str, type_name: str) -> bool:
        """Deletes an object by its ID and type.

        Args:
            object_id (str): The unique identifier of the object to delete.
            type_name (str): The name of the object type.

        Returns:
            bool: True if the object was successfully deleted, False otherwise
            (e.g., if the object was not found).

        Raises:
            GrizabellaException: If the object type does not exist or if there
                is an error during deletion.
            NotConnectedError: If the client is not connected to the database.

        """
        return self._db_manager.delete_object_instance(
            object_type_name=type_name, instance_id=object_id,
        )

    def find_objects(
        self,
        type_name: str,
        filter_criteria: Optional[dict[str, Any]] = None,
        limit: Optional[int] = None,
    ) -> list[ObjectInstance]:
        """Finds objects of a given type, optionally matching filter criteria.

        Args:
            type_name (str): The name of the object type to search for.
            filter_criteria (Optional[Dict[str, Any]]): A dictionary where
                keys are property names and values are the values to filter by.
                Only objects matching all criteria will be returned.
                Defaults to None (no filtering).
            limit (Optional[int]): The maximum number of objects to return.
                Defaults to None (no limit).

        Returns:
            List[ObjectInstance]: A list of object instances matching the
            criteria.

        Raises:
            GrizabellaException: If the object type does not exist or if there
                is an error during the search.
            NotConnectedError: If the client is not connected to the database.

        """
        return self._db_manager.query_object_instances(
            object_type_name=type_name,
            conditions=filter_criteria or {},
            limit=limit,
        )

    # --- Data Management (Relations) ---
    def add_relation(self, relation: RelationInstance) -> RelationInstance:
        """Adds a new relation instance or updates an existing one (upsert).

        If the provided `RelationInstance` object includes an `id` that matches
        an existing relation, it will be updated. Otherwise, a new relation
        instance will be created. The `upsert_date` metadata field is
        automatically updated.

        Args:
            relation (RelationInstance): The relation instance to add or update.
                It must specify the relation type name, source object ID,
                target object ID, and any properties of the relation.
                The `id` field can be provided for updates.

        Returns:
            RelationInstance: The created or updated relation instance,
            potentially with a newly assigned ID or updated timestamps.

        Raises:
            GrizabellaException: If the relation type or involved objects do
                not exist, or if there is an error during the upsert operation.
            NotConnectedError: If the client is not connected to the database.

        """
        return self._db_manager.add_relation_instance(relation)

    def get_relation(
        self, from_object_id: str, to_object_id: str, relation_type_name: str,
    ) -> list[RelationInstance]:
        """Retrieves relation instances between two objects of a specific type.

        Note:
            This method's current implementation in the client API differs
            from the underlying `GrizabellaDBManager` which expects a
            `relation_id`. This method currently raises `NotImplementedError`
            and requires rework to correctly map to the DBManager's capabilities
            or a change in the client API signature.

        Args:
            from_object_id (str): The ID of the source object of the relation.
            to_object_id (str): The ID of the target object of the relation.
            relation_type_name (str): The name of the relation type.

        Returns:
            List[RelationInstance]: A list of relation instances matching the criteria.
            An empty list is returned if no matching relations are found.

        Raises:
            GrizabellaException: If there is an error during retrieval.
            NotConnectedError: If the client is not connected to the database.
            ValueError: If `from_object_id` or `to_object_id` are not valid UUID strings.

        """
        if not self._is_connected:
            self._db_manager.connect() # Ensure connection

        try:
            source_uuid = uuid.UUID(from_object_id)
            target_uuid = uuid.UUID(to_object_id)
        except ValueError as e:
            msg = (
                f"Invalid UUID string for from_object_id ('{from_object_id}') or "
                f"to_object_id ('{to_object_id}')."
            )
            raise ValueError(
                msg,
            ) from e

        return self._db_manager.find_relation_instances(
            relation_type_name=relation_type_name,
            source_object_id=source_uuid,
            target_object_id=target_uuid,
        )

    def delete_relation(
        self, relation_type_name: str, relation_id: str,
    ) -> bool:
        """Deletes a specific relation instance by its type and unique ID.

        Args:
            relation_type_name (str): The name of the relation type.
            relation_id (str): The unique identifier of the relation instance to delete.

        Returns:
            bool: True if the relation was successfully deleted, False otherwise
            (e.g., if the relation was not found).

        Raises:
            GrizabellaException: If there is an error during deletion.
            NotConnectedError: If the client is not connected to the database.
            ValueError: If `relation_id` is not a valid UUID string.

        """
        if not self._is_connected:
            self._db_manager.connect() # Ensure connection

        try:
            rel_uuid = uuid.UUID(relation_id)
        except ValueError as e:
            msg = f"Invalid UUID string for relation_id: '{relation_id}'."
            raise ValueError(
                msg,
            ) from e

        return self._db_manager.delete_relation_instance(
            relation_type_name=relation_type_name,
            relation_id=rel_uuid,
        )

    def query_relations(
        self,
        relation_type_name: Optional[str] = None,
        source_object_instance_id: Optional[str] = None,
        target_object_instance_id: Optional[str] = None,
        properties_query: Optional[dict[str, Any]] = None, # Matches 'query' in db_manager
        limit: Optional[int] = None,
    ) -> list[RelationInstance]:
        """Queries relation instances based on various criteria.

        Args:
            relation_type_name (Optional[str]): Filter by relation type name.
            source_object_instance_id (Optional[str]): Filter by source object ID (UUID string).
            target_object_instance_id (Optional[str]): Filter by target object ID (UUID string).
            properties_query (Optional[dict[str, Any]]): Filter by relation properties.
            limit (Optional[int]): Maximum number of results to return.

        Returns:
            List[RelationInstance]: A list of matching relation instances.

        Raises:
            NotConnectedError: If the client is not connected to the database.
            ValueError: If provided UUID strings are invalid.
            GrizabellaException: For other underlying database or processing errors.

        """
        if not self._is_connected:
            self._db_manager.connect()  # Ensure connection

        source_uuid: Optional[uuid.UUID] = None
        if source_object_instance_id:
            try:
                source_uuid = uuid.UUID(source_object_instance_id)
            except ValueError as e:
                raise ValueError(f"Invalid UUID string for source_object_instance_id: '{source_object_instance_id}'.") from e

        target_uuid: Optional[uuid.UUID] = None
        if target_object_instance_id:
            try:
                target_uuid = uuid.UUID(target_object_instance_id)
            except ValueError as e:
                raise ValueError(f"Invalid UUID string for target_object_instance_id: '{target_object_instance_id}'.") from e

        return self._db_manager.find_relation_instances(
            relation_type_name=relation_type_name,
            source_object_id=source_uuid, # Pass UUID object
            target_object_id=target_uuid, # Pass UUID object
            query=properties_query,
            limit=limit,
        )

    def get_outgoing_relations(
        self, object_id: str, type_name: str, relation_type_name: Optional[str] = None, # pylint: disable=unused-argument
    ) -> list[RelationInstance]:
        """Retrieves all outgoing relations from a given object.

        Args:
            object_id (str): The ID of the source object.
            type_name (str): The type name of the source object. (Note: This
                parameter is not directly used by the underlying DBManager's
                `find_relation_instances` for this specific query but is kept
                for API consistency or future use).
            relation_type_name (Optional[str]): If provided, filters relations
                by this specific relation type name. Defaults to None (no filter).

        Returns:
            List[RelationInstance]: A list of outgoing relation instances.

        Raises:
            GrizabellaException: If there is an error during retrieval.
            NotConnectedError: If the client is not connected to the database.
            ValueError: If `object_id` is not a valid UUID string.

        """
        if not self._is_connected:
            self._db_manager.connect()  # Ensure connection
        try:
            source_uuid = uuid.UUID(object_id)
        except ValueError as e:
            # It's generally better to let exceptions propagate or handle them more specifically.
            # For now, re-raising to make it clear to the caller.
            msg = f"Invalid UUID string for source_object_id: {object_id}"
            raise ValueError(
                msg,
            ) from e

        return self._db_manager.find_relation_instances(
            relation_type_name=relation_type_name, source_object_id=source_uuid,
        )

    def get_incoming_relations(
        self, object_id: str, type_name: str, relation_type_name: Optional[str] = None, # pylint: disable=unused-argument
    ) -> list[RelationInstance]:
        """Retrieves all incoming relations to a given object.

        Args:
            object_id (str): The ID of the target object.
            type_name (str): The type name of the target object. (Note: This
                parameter is not directly used by the underlying DBManager's
                `find_relation_instances` for this specific query but is kept
                for API consistency or future use).
            relation_type_name (Optional[str]): If provided, filters relations
                by this specific relation type name. Defaults to None (no filter).

        Returns:
            List[RelationInstance]: A list of incoming relation instances.

        Raises:
            GrizabellaException: If there is an error during retrieval.
            NotConnectedError: If the client is not connected to the database.
            ValueError: If `object_id` is not a valid UUID string.

        """
        if not self._is_connected:
            self._db_manager.connect()  # Ensure connection
        try:
            target_uuid = uuid.UUID(object_id)
        except ValueError as e:
            msg = f"Invalid UUID string for target_object_id: {object_id}"
            raise ValueError(
                msg,
            ) from e

        return self._db_manager.find_relation_instances(
            relation_type_name=relation_type_name, target_object_id=target_uuid,
        )

    # --- Embedding Definition Management ---
    def create_embedding_definition(
        self, embedding_def: EmbeddingDefinition,
    ) -> EmbeddingDefinition:
        """Creates a new embedding definition.

        Embedding definitions specify how embeddings should be generated and
        stored for objects of a particular type or for specific properties.

        Args:
            embedding_def (EmbeddingDefinition): The definition of the
                embedding to create, including its name, model details,
                and associated object type or properties.

        Returns:
            EmbeddingDefinition: The created embedding definition.

        Raises:
            GrizabellaException: If an embedding definition with the same name
                already exists or if there is an error during creation.
            NotConnectedError: If the client is not connected to the database.

        """
        if not self._is_connected:
            self._db_manager.connect()
        self._db_manager.add_embedding_definition(embedding_def, persist=True)
        return embedding_def

    def get_embedding_definition(self, name: str) -> Optional[EmbeddingDefinition]:
        """Retrieves an embedding definition by its name.

        Args:
            name (str): The name of the embedding definition to retrieve.

        Returns:
            Optional[EmbeddingDefinition]: The embedding definition if found,
            otherwise None.

        Raises:
            NotConnectedError: If the client is not connected to the database.

        """
        if not self._is_connected:
            self._db_manager.connect()
        return self._db_manager.get_embedding_definition(name)

    def list_embedding_definitions(self) -> list[EmbeddingDefinition]:
        """Lists all embedding definitions in the database.

        Returns:
            List[EmbeddingDefinition]: A list of all embedding definitions.

        Raises:
            NotConnectedError: If the client is not connected to the database.

        """
        if not self._is_connected:
            self._db_manager.connect()
        return self._db_manager.list_embedding_definitions()

    def delete_embedding_definition(self, name: str) -> bool:
        """Deletes an embedding definition.

        Warning: This may also delete associated embedding vectors from the
        vector store, depending on the implementation.

        Args:
            name (str): The name of the embedding definition to delete.

        Returns:
            bool: True if the definition was successfully deleted, False
            otherwise (e.g., if not found).

        Raises:
            GrizabellaException: If there is an error during deletion.
            NotConnectedError: If the client is not connected to the database.

        """
        if not self._is_connected:
            self._db_manager.connect()
        return self._db_manager.remove_embedding_definition(name, persist=True)

    # --- Querying (Example: Embedding Search) ---
    def search_similar_objects(
        self,
        object_id: str,
        type_name: str,
        n_results: int = 5,
        search_properties: Optional[list[str]] = None,
    ) -> list[tuple[ObjectInstance, float]]:
        """Searches for objects similar to a given object using its embeddings.

        This method finds objects of the same type as the source object that
        are semantically similar, based on a specified or inferred embedding definition.

        Args:
            object_id (str): The ID of the source object to find similar items for.
            type_name (str): The type name of the source object.
            n_results (int): The maximum number of similar results to return.
                Defaults to 5.
            search_properties (Optional[List[str]]): If provided, the first element
                is used as the specific `embedding_definition_name` to use for both
                the source object's vector retrieval and the target search.
                If None or empty, the first available `EmbeddingDefinition` for the
                `type_name` will be used.

        Returns:
            List[Tuple[ObjectInstance, float]]: A list of tuples, where each
            tuple contains a similar `ObjectInstance` and its similarity score
            (typically distance, where lower is more similar).

        Raises:
            NotConnectedError: If the client is not connected to the database.
            ValueError: If `object_id` is not a valid UUID string.
            SchemaError: If a suitable `EmbeddingDefinition` cannot be found.
            EmbeddingError: If an embedding for the source object cannot be found.
            GrizabellaException: For other underlying database or processing errors.

        """
        if not self._is_connected:
            self._db_manager.connect()

        try:
            source_uuid = uuid.UUID(object_id)
        except ValueError as e:
            msg = f"Invalid UUID string for object_id: '{object_id}'."
            raise ValueError(msg) from e

        embedding_definition_name: Optional[str] = None
        if search_properties and search_properties[0]:
            embedding_definition_name = search_properties[0]
            # Validate this ED exists and is for the correct object type
            ed = self._db_manager.get_embedding_definition(embedding_definition_name)
            if not ed:
                msg = f"Specified EmbeddingDefinition '{embedding_definition_name}' not found."
                raise SchemaError(
                    msg,
                )
            if ed.object_type_name != type_name:
                msg = (
                    f"Specified EmbeddingDefinition '{embedding_definition_name}' is for object type "
                    f"'{ed.object_type_name}', but expected type '{type_name}' for object '{object_id}'."
                )
                raise SchemaError(
                    msg,
                )
        else:
            # Find the first available EmbeddingDefinition for the type_name
            all_eds = self._db_manager.list_embedding_definitions()
            for ed in all_eds:
                if ed.object_type_name == type_name:
                    embedding_definition_name = ed.name
                    break
            if not embedding_definition_name:
                msg = (
                    f"No EmbeddingDefinition found for object type '{type_name}'. "
                    "Cannot perform similarity search."
                )
                raise SchemaError(
                    msg,
                )

        return self._db_manager.find_objects_similar_to_instance(
            source_object_id=source_uuid,
            source_object_type_name=type_name,
            embedding_definition_name=embedding_definition_name,
            n_results=n_results,
        )

    def find_similar(
        self,
        embedding_name: str,
        query_text: str,
        limit: int = 5,
        filter_condition: Optional[str] = None,
    ) -> list[ObjectInstance]: # Changed return type to list[ObjectInstance] for simplicity in test
        """Finds objects semantically similar to a given query text.

        Args:
            embedding_name (str): The name of the EmbeddingDefinition to use.
            query_text (str): The text to find similar objects for.
            limit (int): The maximum number of similar results to return. Defaults to 5.
            filter_condition (Optional[str]): An SQL-like WHERE clause to pre-filter results.

        Returns:
            List[ObjectInstance]: A list of ObjectInstances that are semantically
            similar to the query_text, ordered by similarity.

        Raises:
            NotConnectedError: If the client is not connected to the database.
            SchemaError: If the specified EmbeddingDefinition is not found or is invalid.
            EmbeddingError: If an error occurs during embedding generation or search.
            GrizabellaException: For other underlying database or processing errors.

        """
        if not self._is_connected:
            self._db_manager.connect()

        ed = self._db_manager.get_embedding_definition(embedding_name)
        if not ed:
            msg = f"EmbeddingDefinition '{embedding_name}' not found."
            raise SchemaError(msg)

        # Call the db_manager's method that can handle query_text
        # This method in _InstanceManager returns raw results: list[dict[str, Any]]
        # where dicts contain 'object_instance_id' and '_distance'
        raw_results = self._db_manager.find_similar_objects_by_embedding(
            embedding_definition_name=embedding_name,
            query_text=query_text,
            limit=limit,
            retrieve_full_objects=False, # We need IDs and scores to process further
            filter_condition=filter_condition,
        )

        if not raw_results:
            return []

        # We need to convert raw_results (list of dicts with id and score)
        # into a list of ObjectInstance, similar to how find_objects_similar_to_instance does.
        # The _process_raw_similarity_results method in DBManager is suitable.
        # It expects a source_object_id for filtering, which is not applicable here.
        # We'll adapt its logic or call it carefully.

        # Let's get the target object type from the embedding definition
        target_object_type_name = ed.object_type_name

        # Re-implementing parts of _process_raw_similarity_results logic here
        # as it's not directly callable with a query_text scenario (no source_object_id)

        final_results_with_scores: list[tuple[ObjectInstance, float]] = []
        result_ids_map: dict[uuid.UUID, float] = {}

        for res in raw_results:
            try:
                obj_id_str = res.get("object_instance_id")
                if not obj_id_str:
                    continue
                obj_id = uuid.UUID(obj_id_str)
                score = float(res.get("_distance", 0.0)) # LanceDB uses _distance
                result_ids_map[obj_id] = score
            except (ValueError, KeyError, TypeError) as e:
                # self._db_manager._logger.warning(...) # Can't access logger directly
                print(f"Warning: Skipping result due to parsing error: {res}, error: {e}") # Basic print for now
                continue

        if not result_ids_map:
            return []

        sorted_similar_items = sorted(result_ids_map.items(), key=lambda item: item[1])[:limit]
        result_ids_to_fetch = [item[0] for item in sorted_similar_items]

        if result_ids_to_fetch:
            # Convert UUIDs to strings for get_objects_by_ids if it expects strings
            # However, db_manager.get_objects_by_ids takes list[uuid.UUID]
            fetched_objects = self._db_manager.get_objects_by_ids(
                target_object_type_name, result_ids_to_fetch,
            )
            fetched_objects_map = {obj.id: obj for obj in fetched_objects}

            for obj_id_val, _ in sorted_similar_items: # score_val not used in final list of ObjectInstance
                if obj_id_val in fetched_objects_map:
                    final_results_with_scores.append(
                        (fetched_objects_map[obj_id_val], _), # Keep score for potential future use
                    )

        # Return only the ObjectInstances, ordered by similarity (which sorted_similar_items already did)
        return [obj_inst for obj_inst, score in final_results_with_scores]

    # --- Complex Querying ---
    def execute_complex_query(self, query: ComplexQuery) -> QueryResult:
        """Executes a complex query spanning multiple database layers.

        Complex queries allow for sophisticated search patterns, including
        graph traversals, relational filters, and embedding-based searches,
        combined into a single query operation.

        Args:
            query (ComplexQuery): A ``ComplexQuery`` object defining the
                components of the search, such as graph traversals,
                relational filters, and embedding searches.

        Returns:
            QueryResult: A ``QueryResult`` object containing a list of matching
            ``ObjectInstance``s and a list of any errors encountered during
            query processing.

        Raises:
            NotConnectedError: If the client is not connected to the database.
            GrizabellaException: If there is an error during query planning
                or execution.

        """
        if not self._is_connected:
            raise DatabaseError("Database not connected. Call connect() before executing queries.")

        if hasattr(self._db_manager, "process_complex_query"):
            return self._db_manager.process_complex_query(query)
        # This indicates a missing implementation in the DBManager,
        # which is a development-time issue.
        # Raising NotImplementedError is more appropriate here.
        msg = "GrizabellaDBManager.process_complex_query not yet implemented."
        raise NotImplementedError(
            msg,
        )

db_name_or_path: Union[str, Path] property

Returns the database name or path this client was initialized with.

__enter__()

Context manager entry point. Connects to the database.

Returns:

Name Type Description
Grizabella Grizabella

The Grizabella API client instance.

Source code in grizabella/api/client.py
111
112
113
114
115
116
117
118
119
def __enter__(self) -> "Grizabella":
    """Context manager entry point. Connects to the database.

    Returns:
        Grizabella: The Grizabella API client instance.

    """
    self.connect()
    return self

__exit__(exc_type, exc_val, exc_tb)

Context manager exit point. Closes the database connection.

Parameters:

Name Type Description Default
exc_type Optional[type[BaseException]]

The type of the exception, if any.

required
exc_val Optional[BaseException]

The exception instance, if any.

required
exc_tb Optional[Any]

The traceback object, if any.

required
Source code in grizabella/api/client.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def __exit__(
    self,
    exc_type: Optional[type[BaseException]],
    exc_val: Optional[BaseException],
    exc_tb: Optional[Any],
) -> None:
    """Context manager exit point. Closes the database connection.

    Args:
        exc_type: The type of the exception, if any.
        exc_val: The exception instance, if any.
        exc_tb: The traceback object, if any.

    """
    self.close()

__init__(db_name_or_path='default', create_if_not_exists=True)

Initializes the Grizabella API client.

Sets up the connection to the specified Grizabella database. If the database does not exist and create_if_not_exists is True, it will be created.

Parameters:

Name Type Description Default
db_name_or_path Union[str, Path]

The name of the database or the file system path to the database directory. Defaults to "default".

'default'
create_if_not_exists bool

If True, the database will be created if it does not already exist. Defaults to True.

True
Source code in grizabella/api/client.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def __init__(
    self,
    db_name_or_path: Union[str, Path] = "default",
    create_if_not_exists: bool = True,
) -> None:
    """Initializes the Grizabella API client.

    Sets up the connection to the specified Grizabella database. If the
    database does not exist and `create_if_not_exists` is True, it will
    be created.

    Args:
        db_name_or_path (Union[str, Path]): The name of the database or
            the file system path to the database directory.
            Defaults to "default".
        create_if_not_exists (bool): If True, the database will be
            created if it does not already exist. Defaults to True.

    """
    self._logger = logging.getLogger(__name__) # Initialize logger
    self._initial_db_name_or_path = db_name_or_path # Store the initial path/name
    self._db_manager = GrizabellaDBManager(
        db_name_or_path=db_name_or_path,
        create_if_not_exists=create_if_not_exists,
    )
    self._is_connected = False

add_relation(relation)

Adds a new relation instance or updates an existing one (upsert).

If the provided RelationInstance object includes an id that matches an existing relation, it will be updated. Otherwise, a new relation instance will be created. The upsert_date metadata field is automatically updated.

Parameters:

Name Type Description Default
relation RelationInstance

The relation instance to add or update. It must specify the relation type name, source object ID, target object ID, and any properties of the relation. The id field can be provided for updates.

required

Returns:

Name Type Description
RelationInstance RelationInstance

The created or updated relation instance,

RelationInstance

potentially with a newly assigned ID or updated timestamps.

Raises:

Type Description
GrizabellaException

If the relation type or involved objects do not exist, or if there is an error during the upsert operation.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
def add_relation(self, relation: RelationInstance) -> RelationInstance:
    """Adds a new relation instance or updates an existing one (upsert).

    If the provided `RelationInstance` object includes an `id` that matches
    an existing relation, it will be updated. Otherwise, a new relation
    instance will be created. The `upsert_date` metadata field is
    automatically updated.

    Args:
        relation (RelationInstance): The relation instance to add or update.
            It must specify the relation type name, source object ID,
            target object ID, and any properties of the relation.
            The `id` field can be provided for updates.

    Returns:
        RelationInstance: The created or updated relation instance,
        potentially with a newly assigned ID or updated timestamps.

    Raises:
        GrizabellaException: If the relation type or involved objects do
            not exist, or if there is an error during the upsert operation.
        NotConnectedError: If the client is not connected to the database.

    """
    return self._db_manager.add_relation_instance(relation)

close()

Closes the connection to the underlying Grizabella database.

Releases any resources held by the database connection. It's important to close the connection when it's no longer needed, especially if not using the client as a context manager.

Raises:

Type Description
GrizabellaException

If there is an error closing the database connection.

Source code in grizabella/api/client.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def close(self) -> None:
    """Closes the connection to the underlying Grizabella database.

    Releases any resources held by the database connection. It's important
    to close the connection when it's no longer needed, especially if not
    using the client as a context manager.

    Raises:
        GrizabellaException: If there is an error closing the database connection.

    """
    self._logger.info(f"Grizabella client close() called for db: {self.db_name_or_path}. Connected: {self._is_connected}")
    if self._is_connected:
        try:
            self._db_manager.close() # This should call _ConnectionHelper.close_all_adapters()
            self._logger.info(f"Grizabella client: self._db_manager.close() completed for {self.db_name_or_path}.")
        except Exception as e:
            self._logger.error(f"Grizabella client: Error during self._db_manager.close() for {self.db_name_or_path}: {e}", exc_info=True)
        finally:
            self._is_connected = False
            self._logger.info(f"Grizabella client: _is_connected set to False for {self.db_name_or_path}.")
    else:
        self._logger.info(f"Grizabella client: Already not connected for {self.db_name_or_path}, no action taken in close().")

connect()

Connects to the underlying Grizabella database.

Establishes a connection to the database if not already connected. This method is typically called automatically when using the client as a context manager or before performing database operations if the connection was previously closed.

Raises:

Type Description
GrizabellaException

If there is an error connecting to the database.

Source code in grizabella/api/client.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def connect(self) -> None:
    """Connects to the underlying Grizabella database.

    Establishes a connection to the database if not already connected.
    This method is typically called automatically when using the client
    as a context manager or before performing database operations if
    the connection was previously closed.

    Raises:
        GrizabellaException: If there is an error connecting to the database.

    """
    if not self._is_connected:
        self._db_manager.connect()
        self._is_connected = True

create_embedding_definition(embedding_def)

Creates a new embedding definition.

Embedding definitions specify how embeddings should be generated and stored for objects of a particular type or for specific properties.

Parameters:

Name Type Description Default
embedding_def EmbeddingDefinition

The definition of the embedding to create, including its name, model details, and associated object type or properties.

required

Returns:

Name Type Description
EmbeddingDefinition EmbeddingDefinition

The created embedding definition.

Raises:

Type Description
GrizabellaException

If an embedding definition with the same name already exists or if there is an error during creation.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
def create_embedding_definition(
    self, embedding_def: EmbeddingDefinition,
) -> EmbeddingDefinition:
    """Creates a new embedding definition.

    Embedding definitions specify how embeddings should be generated and
    stored for objects of a particular type or for specific properties.

    Args:
        embedding_def (EmbeddingDefinition): The definition of the
            embedding to create, including its name, model details,
            and associated object type or properties.

    Returns:
        EmbeddingDefinition: The created embedding definition.

    Raises:
        GrizabellaException: If an embedding definition with the same name
            already exists or if there is an error during creation.
        NotConnectedError: If the client is not connected to the database.

    """
    if not self._is_connected:
        self._db_manager.connect()
    self._db_manager.add_embedding_definition(embedding_def, persist=True)
    return embedding_def

create_object_type(object_type_def)

Creates a new object type in the database.

Object types define the schema for a category of objects, similar to tables in a relational database or node labels in a graph database.

Parameters:

Name Type Description Default
object_type_def ObjectTypeDefinition

The definition of the object type to create, including its name and properties.

required

Raises:

Type Description
GrizabellaException

If the object type already exists or if there is an error during creation.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def create_object_type(self, object_type_def: ObjectTypeDefinition) -> None:
    """Creates a new object type in the database.

    Object types define the schema for a category of objects, similar to
    tables in a relational database or node labels in a graph database.

    Args:
        object_type_def (ObjectTypeDefinition): The definition of the
            object type to create, including its name and properties.

    Raises:
        GrizabellaException: If the object type already exists or if there
            is an error during creation.
        NotConnectedError: If the client is not connected to the database.

    """
    self._db_manager.add_object_type_definition(object_type_def)

create_relation_type(relation_type_def)

Creates a new relation type in the database.

Relation types define the schema for relationships between objects, including the source and target object types and any properties of the relation itself.

Parameters:

Name Type Description Default
relation_type_def RelationTypeDefinition

The definition of the relation type to create.

required

Raises:

Type Description
GrizabellaException

If the relation type already exists or if there is an error during creation.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def create_relation_type(self, relation_type_def: RelationTypeDefinition) -> None:
    """Creates a new relation type in the database.

    Relation types define the schema for relationships between objects,
    including the source and target object types and any properties of
    the relation itself.

    Args:
        relation_type_def (RelationTypeDefinition): The definition of the
            relation type to create.

    Raises:
        GrizabellaException: If the relation type already exists or if
            there is an error during creation.
        NotConnectedError: If the client is not connected to the database.

    """
    self._db_manager.add_relation_type_definition(relation_type_def)

delete_embedding_definition(name)

Deletes an embedding definition.

Warning: This may also delete associated embedding vectors from the vector store, depending on the implementation.

Parameters:

Name Type Description Default
name str

The name of the embedding definition to delete.

required

Returns:

Name Type Description
bool bool

True if the definition was successfully deleted, False

bool

otherwise (e.g., if not found).

Raises:

Type Description
GrizabellaException

If there is an error during deletion.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
def delete_embedding_definition(self, name: str) -> bool:
    """Deletes an embedding definition.

    Warning: This may also delete associated embedding vectors from the
    vector store, depending on the implementation.

    Args:
        name (str): The name of the embedding definition to delete.

    Returns:
        bool: True if the definition was successfully deleted, False
        otherwise (e.g., if not found).

    Raises:
        GrizabellaException: If there is an error during deletion.
        NotConnectedError: If the client is not connected to the database.

    """
    if not self._is_connected:
        self._db_manager.connect()
    return self._db_manager.remove_embedding_definition(name, persist=True)

delete_object(object_id, type_name)

Deletes an object by its ID and type.

Parameters:

Name Type Description Default
object_id str

The unique identifier of the object to delete.

required
type_name str

The name of the object type.

required

Returns:

Name Type Description
bool bool

True if the object was successfully deleted, False otherwise

bool

(e.g., if the object was not found).

Raises:

Type Description
GrizabellaException

If the object type does not exist or if there is an error during deletion.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
def delete_object(self, object_id: str, type_name: str) -> bool:
    """Deletes an object by its ID and type.

    Args:
        object_id (str): The unique identifier of the object to delete.
        type_name (str): The name of the object type.

    Returns:
        bool: True if the object was successfully deleted, False otherwise
        (e.g., if the object was not found).

    Raises:
        GrizabellaException: If the object type does not exist or if there
            is an error during deletion.
        NotConnectedError: If the client is not connected to the database.

    """
    return self._db_manager.delete_object_instance(
        object_type_name=type_name, instance_id=object_id,
    )

delete_object_type(type_name)

Deletes an object type from the database.

Warning: This operation may also delete all associated object instances and relations, depending on the underlying database implementation and cascade rules.

Parameters:

Name Type Description Default
type_name str

The name of the object type to delete.

required

Raises:

Type Description
GrizabellaException

If the object type does not exist or if there is an error during deletion.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def delete_object_type(self, type_name: str) -> None:
    """Deletes an object type from the database.

    Warning: This operation may also delete all associated object instances
    and relations, depending on the underlying database implementation and
    cascade rules.

    Args:
        type_name (str): The name of the object type to delete.

    Raises:
        GrizabellaException: If the object type does not exist or if there
            is an error during deletion.
        NotConnectedError: If the client is not connected to the database.

    """
    self._db_manager.remove_object_type_definition(type_name)

delete_relation(relation_type_name, relation_id)

Deletes a specific relation instance by its type and unique ID.

Parameters:

Name Type Description Default
relation_type_name str

The name of the relation type.

required
relation_id str

The unique identifier of the relation instance to delete.

required

Returns:

Name Type Description
bool bool

True if the relation was successfully deleted, False otherwise

bool

(e.g., if the relation was not found).

Raises:

Type Description
GrizabellaException

If there is an error during deletion.

NotConnectedError

If the client is not connected to the database.

ValueError

If relation_id is not a valid UUID string.

Source code in grizabella/api/client.py
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
def delete_relation(
    self, relation_type_name: str, relation_id: str,
) -> bool:
    """Deletes a specific relation instance by its type and unique ID.

    Args:
        relation_type_name (str): The name of the relation type.
        relation_id (str): The unique identifier of the relation instance to delete.

    Returns:
        bool: True if the relation was successfully deleted, False otherwise
        (e.g., if the relation was not found).

    Raises:
        GrizabellaException: If there is an error during deletion.
        NotConnectedError: If the client is not connected to the database.
        ValueError: If `relation_id` is not a valid UUID string.

    """
    if not self._is_connected:
        self._db_manager.connect() # Ensure connection

    try:
        rel_uuid = uuid.UUID(relation_id)
    except ValueError as e:
        msg = f"Invalid UUID string for relation_id: '{relation_id}'."
        raise ValueError(
            msg,
        ) from e

    return self._db_manager.delete_relation_instance(
        relation_type_name=relation_type_name,
        relation_id=rel_uuid,
    )

delete_relation_type(type_name)

Deletes a relation type from the database.

Warning: This operation may also delete all associated relation instances, depending on the underlying database implementation.

Parameters:

Name Type Description Default
type_name str

The name of the relation type to delete.

required

Raises:

Type Description
GrizabellaException

If the relation type does not exist or if there is an error during deletion.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def delete_relation_type(self, type_name: str) -> None:
    """Deletes a relation type from the database.

    Warning: This operation may also delete all associated relation instances,
    depending on the underlying database implementation.

    Args:
        type_name (str): The name of the relation type to delete.

    Raises:
        GrizabellaException: If the relation type does not exist or if
            there is an error during deletion.
        NotConnectedError: If the client is not connected to the database.

    """
    self._db_manager.remove_relation_type_definition(type_name)

execute_complex_query(query)

Executes a complex query spanning multiple database layers.

Complex queries allow for sophisticated search patterns, including graph traversals, relational filters, and embedding-based searches, combined into a single query operation.

Parameters:

Name Type Description Default
query ComplexQuery

A ComplexQuery object defining the components of the search, such as graph traversals, relational filters, and embedding searches.

required

Returns:

Name Type Description
QueryResult QueryResult

A QueryResult object containing a list of matching

QueryResult

ObjectInstances and a list of any errors encountered during

QueryResult

query processing.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

GrizabellaException

If there is an error during query planning or execution.

Source code in grizabella/api/client.py
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
def execute_complex_query(self, query: ComplexQuery) -> QueryResult:
    """Executes a complex query spanning multiple database layers.

    Complex queries allow for sophisticated search patterns, including
    graph traversals, relational filters, and embedding-based searches,
    combined into a single query operation.

    Args:
        query (ComplexQuery): A ``ComplexQuery`` object defining the
            components of the search, such as graph traversals,
            relational filters, and embedding searches.

    Returns:
        QueryResult: A ``QueryResult`` object containing a list of matching
        ``ObjectInstance``s and a list of any errors encountered during
        query processing.

    Raises:
        NotConnectedError: If the client is not connected to the database.
        GrizabellaException: If there is an error during query planning
            or execution.

    """
    if not self._is_connected:
        raise DatabaseError("Database not connected. Call connect() before executing queries.")

    if hasattr(self._db_manager, "process_complex_query"):
        return self._db_manager.process_complex_query(query)
    # This indicates a missing implementation in the DBManager,
    # which is a development-time issue.
    # Raising NotImplementedError is more appropriate here.
    msg = "GrizabellaDBManager.process_complex_query not yet implemented."
    raise NotImplementedError(
        msg,
    )

find_objects(type_name, filter_criteria=None, limit=None)

Finds objects of a given type, optionally matching filter criteria.

Parameters:

Name Type Description Default
type_name str

The name of the object type to search for.

required
filter_criteria Optional[Dict[str, Any]]

A dictionary where keys are property names and values are the values to filter by. Only objects matching all criteria will be returned. Defaults to None (no filtering).

None
limit Optional[int]

The maximum number of objects to return. Defaults to None (no limit).

None

Returns:

Type Description
list[ObjectInstance]

List[ObjectInstance]: A list of object instances matching the

list[ObjectInstance]

criteria.

Raises:

Type Description
GrizabellaException

If the object type does not exist or if there is an error during the search.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def find_objects(
    self,
    type_name: str,
    filter_criteria: Optional[dict[str, Any]] = None,
    limit: Optional[int] = None,
) -> list[ObjectInstance]:
    """Finds objects of a given type, optionally matching filter criteria.

    Args:
        type_name (str): The name of the object type to search for.
        filter_criteria (Optional[Dict[str, Any]]): A dictionary where
            keys are property names and values are the values to filter by.
            Only objects matching all criteria will be returned.
            Defaults to None (no filtering).
        limit (Optional[int]): The maximum number of objects to return.
            Defaults to None (no limit).

    Returns:
        List[ObjectInstance]: A list of object instances matching the
        criteria.

    Raises:
        GrizabellaException: If the object type does not exist or if there
            is an error during the search.
        NotConnectedError: If the client is not connected to the database.

    """
    return self._db_manager.query_object_instances(
        object_type_name=type_name,
        conditions=filter_criteria or {},
        limit=limit,
    )

find_similar(embedding_name, query_text, limit=5, filter_condition=None)

Finds objects semantically similar to a given query text.

Parameters:

Name Type Description Default
embedding_name str

The name of the EmbeddingDefinition to use.

required
query_text str

The text to find similar objects for.

required
limit int

The maximum number of similar results to return. Defaults to 5.

5
filter_condition Optional[str]

An SQL-like WHERE clause to pre-filter results.

None

Returns:

Type Description
list[ObjectInstance]

List[ObjectInstance]: A list of ObjectInstances that are semantically

list[ObjectInstance]

similar to the query_text, ordered by similarity.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

SchemaError

If the specified EmbeddingDefinition is not found or is invalid.

EmbeddingError

If an error occurs during embedding generation or search.

GrizabellaException

For other underlying database or processing errors.

Source code in grizabella/api/client.py
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
def find_similar(
    self,
    embedding_name: str,
    query_text: str,
    limit: int = 5,
    filter_condition: Optional[str] = None,
) -> list[ObjectInstance]: # Changed return type to list[ObjectInstance] for simplicity in test
    """Finds objects semantically similar to a given query text.

    Args:
        embedding_name (str): The name of the EmbeddingDefinition to use.
        query_text (str): The text to find similar objects for.
        limit (int): The maximum number of similar results to return. Defaults to 5.
        filter_condition (Optional[str]): An SQL-like WHERE clause to pre-filter results.

    Returns:
        List[ObjectInstance]: A list of ObjectInstances that are semantically
        similar to the query_text, ordered by similarity.

    Raises:
        NotConnectedError: If the client is not connected to the database.
        SchemaError: If the specified EmbeddingDefinition is not found or is invalid.
        EmbeddingError: If an error occurs during embedding generation or search.
        GrizabellaException: For other underlying database or processing errors.

    """
    if not self._is_connected:
        self._db_manager.connect()

    ed = self._db_manager.get_embedding_definition(embedding_name)
    if not ed:
        msg = f"EmbeddingDefinition '{embedding_name}' not found."
        raise SchemaError(msg)

    # Call the db_manager's method that can handle query_text
    # This method in _InstanceManager returns raw results: list[dict[str, Any]]
    # where dicts contain 'object_instance_id' and '_distance'
    raw_results = self._db_manager.find_similar_objects_by_embedding(
        embedding_definition_name=embedding_name,
        query_text=query_text,
        limit=limit,
        retrieve_full_objects=False, # We need IDs and scores to process further
        filter_condition=filter_condition,
    )

    if not raw_results:
        return []

    # We need to convert raw_results (list of dicts with id and score)
    # into a list of ObjectInstance, similar to how find_objects_similar_to_instance does.
    # The _process_raw_similarity_results method in DBManager is suitable.
    # It expects a source_object_id for filtering, which is not applicable here.
    # We'll adapt its logic or call it carefully.

    # Let's get the target object type from the embedding definition
    target_object_type_name = ed.object_type_name

    # Re-implementing parts of _process_raw_similarity_results logic here
    # as it's not directly callable with a query_text scenario (no source_object_id)

    final_results_with_scores: list[tuple[ObjectInstance, float]] = []
    result_ids_map: dict[uuid.UUID, float] = {}

    for res in raw_results:
        try:
            obj_id_str = res.get("object_instance_id")
            if not obj_id_str:
                continue
            obj_id = uuid.UUID(obj_id_str)
            score = float(res.get("_distance", 0.0)) # LanceDB uses _distance
            result_ids_map[obj_id] = score
        except (ValueError, KeyError, TypeError) as e:
            # self._db_manager._logger.warning(...) # Can't access logger directly
            print(f"Warning: Skipping result due to parsing error: {res}, error: {e}") # Basic print for now
            continue

    if not result_ids_map:
        return []

    sorted_similar_items = sorted(result_ids_map.items(), key=lambda item: item[1])[:limit]
    result_ids_to_fetch = [item[0] for item in sorted_similar_items]

    if result_ids_to_fetch:
        # Convert UUIDs to strings for get_objects_by_ids if it expects strings
        # However, db_manager.get_objects_by_ids takes list[uuid.UUID]
        fetched_objects = self._db_manager.get_objects_by_ids(
            target_object_type_name, result_ids_to_fetch,
        )
        fetched_objects_map = {obj.id: obj for obj in fetched_objects}

        for obj_id_val, _ in sorted_similar_items: # score_val not used in final list of ObjectInstance
            if obj_id_val in fetched_objects_map:
                final_results_with_scores.append(
                    (fetched_objects_map[obj_id_val], _), # Keep score for potential future use
                )

    # Return only the ObjectInstances, ordered by similarity (which sorted_similar_items already did)
    return [obj_inst for obj_inst, score in final_results_with_scores]

get_embedding_definition(name)

Retrieves an embedding definition by its name.

Parameters:

Name Type Description Default
name str

The name of the embedding definition to retrieve.

required

Returns:

Type Description
Optional[EmbeddingDefinition]

Optional[EmbeddingDefinition]: The embedding definition if found,

Optional[EmbeddingDefinition]

otherwise None.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
def get_embedding_definition(self, name: str) -> Optional[EmbeddingDefinition]:
    """Retrieves an embedding definition by its name.

    Args:
        name (str): The name of the embedding definition to retrieve.

    Returns:
        Optional[EmbeddingDefinition]: The embedding definition if found,
        otherwise None.

    Raises:
        NotConnectedError: If the client is not connected to the database.

    """
    if not self._is_connected:
        self._db_manager.connect()
    return self._db_manager.get_embedding_definition(name)

get_incoming_relations(object_id, type_name, relation_type_name=None)

Retrieves all incoming relations to a given object.

Parameters:

Name Type Description Default
object_id str

The ID of the target object.

required
type_name str

The type name of the target object. (Note: This parameter is not directly used by the underlying DBManager's find_relation_instances for this specific query but is kept for API consistency or future use).

required
relation_type_name Optional[str]

If provided, filters relations by this specific relation type name. Defaults to None (no filter).

None

Returns:

Type Description
list[RelationInstance]

List[RelationInstance]: A list of incoming relation instances.

Raises:

Type Description
GrizabellaException

If there is an error during retrieval.

NotConnectedError

If the client is not connected to the database.

ValueError

If object_id is not a valid UUID string.

Source code in grizabella/api/client.py
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
def get_incoming_relations(
    self, object_id: str, type_name: str, relation_type_name: Optional[str] = None, # pylint: disable=unused-argument
) -> list[RelationInstance]:
    """Retrieves all incoming relations to a given object.

    Args:
        object_id (str): The ID of the target object.
        type_name (str): The type name of the target object. (Note: This
            parameter is not directly used by the underlying DBManager's
            `find_relation_instances` for this specific query but is kept
            for API consistency or future use).
        relation_type_name (Optional[str]): If provided, filters relations
            by this specific relation type name. Defaults to None (no filter).

    Returns:
        List[RelationInstance]: A list of incoming relation instances.

    Raises:
        GrizabellaException: If there is an error during retrieval.
        NotConnectedError: If the client is not connected to the database.
        ValueError: If `object_id` is not a valid UUID string.

    """
    if not self._is_connected:
        self._db_manager.connect()  # Ensure connection
    try:
        target_uuid = uuid.UUID(object_id)
    except ValueError as e:
        msg = f"Invalid UUID string for target_object_id: {object_id}"
        raise ValueError(
            msg,
        ) from e

    return self._db_manager.find_relation_instances(
        relation_type_name=relation_type_name, target_object_id=target_uuid,
    )

get_object_by_id(object_id, type_name)

Retrieves an object by its ID and type.

Parameters:

Name Type Description Default
object_id str

The unique identifier of the object.

required
type_name str

The name of the object type.

required

Returns:

Type Description
Optional[ObjectInstance]

Optional[ObjectInstance]: The object instance if found,

Optional[ObjectInstance]

otherwise None.

Raises:

Type Description
GrizabellaException

If the object type does not exist or if there is an error during retrieval.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def get_object_by_id(
    self, object_id: str, type_name: str,
) -> Optional[ObjectInstance]:
    """Retrieves an object by its ID and type.

    Args:
        object_id (str): The unique identifier of the object.
        type_name (str): The name of the object type.

    Returns:
        Optional[ObjectInstance]: The object instance if found,
        otherwise None.

    Raises:
        GrizabellaException: If the object type does not exist or if there
            is an error during retrieval.
        NotConnectedError: If the client is not connected to the database.

    """
    return self._db_manager.get_object_instance(
        object_type_name=type_name, instance_id=object_id,
    )

get_object_type_definition(type_name)

Retrieves the definition of an object type.

Parameters:

Name Type Description Default
type_name str

The name of the object type to retrieve.

required

Returns:

Type Description
Optional[ObjectTypeDefinition]

Optional[ObjectTypeDefinition]: The definition of the object type

Optional[ObjectTypeDefinition]

if found, otherwise None.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def get_object_type_definition(
    self, type_name: str,
) -> Optional[ObjectTypeDefinition]:
    """Retrieves the definition of an object type.

    Args:
        type_name (str): The name of the object type to retrieve.

    Returns:
        Optional[ObjectTypeDefinition]: The definition of the object type
        if found, otherwise None.

    Raises:
        NotConnectedError: If the client is not connected to the database.

    """
    return self._db_manager.get_object_type_definition(type_name)

get_outgoing_relations(object_id, type_name, relation_type_name=None)

Retrieves all outgoing relations from a given object.

Parameters:

Name Type Description Default
object_id str

The ID of the source object.

required
type_name str

The type name of the source object. (Note: This parameter is not directly used by the underlying DBManager's find_relation_instances for this specific query but is kept for API consistency or future use).

required
relation_type_name Optional[str]

If provided, filters relations by this specific relation type name. Defaults to None (no filter).

None

Returns:

Type Description
list[RelationInstance]

List[RelationInstance]: A list of outgoing relation instances.

Raises:

Type Description
GrizabellaException

If there is an error during retrieval.

NotConnectedError

If the client is not connected to the database.

ValueError

If object_id is not a valid UUID string.

Source code in grizabella/api/client.py
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
def get_outgoing_relations(
    self, object_id: str, type_name: str, relation_type_name: Optional[str] = None, # pylint: disable=unused-argument
) -> list[RelationInstance]:
    """Retrieves all outgoing relations from a given object.

    Args:
        object_id (str): The ID of the source object.
        type_name (str): The type name of the source object. (Note: This
            parameter is not directly used by the underlying DBManager's
            `find_relation_instances` for this specific query but is kept
            for API consistency or future use).
        relation_type_name (Optional[str]): If provided, filters relations
            by this specific relation type name. Defaults to None (no filter).

    Returns:
        List[RelationInstance]: A list of outgoing relation instances.

    Raises:
        GrizabellaException: If there is an error during retrieval.
        NotConnectedError: If the client is not connected to the database.
        ValueError: If `object_id` is not a valid UUID string.

    """
    if not self._is_connected:
        self._db_manager.connect()  # Ensure connection
    try:
        source_uuid = uuid.UUID(object_id)
    except ValueError as e:
        # It's generally better to let exceptions propagate or handle them more specifically.
        # For now, re-raising to make it clear to the caller.
        msg = f"Invalid UUID string for source_object_id: {object_id}"
        raise ValueError(
            msg,
        ) from e

    return self._db_manager.find_relation_instances(
        relation_type_name=relation_type_name, source_object_id=source_uuid,
    )

get_relation(from_object_id, to_object_id, relation_type_name)

Retrieves relation instances between two objects of a specific type.

Note

This method's current implementation in the client API differs from the underlying GrizabellaDBManager which expects a relation_id. This method currently raises NotImplementedError and requires rework to correctly map to the DBManager's capabilities or a change in the client API signature.

Parameters:

Name Type Description Default
from_object_id str

The ID of the source object of the relation.

required
to_object_id str

The ID of the target object of the relation.

required
relation_type_name str

The name of the relation type.

required

Returns:

Type Description
list[RelationInstance]

List[RelationInstance]: A list of relation instances matching the criteria.

list[RelationInstance]

An empty list is returned if no matching relations are found.

Raises:

Type Description
GrizabellaException

If there is an error during retrieval.

NotConnectedError

If the client is not connected to the database.

ValueError

If from_object_id or to_object_id are not valid UUID strings.

Source code in grizabella/api/client.py
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
def get_relation(
    self, from_object_id: str, to_object_id: str, relation_type_name: str,
) -> list[RelationInstance]:
    """Retrieves relation instances between two objects of a specific type.

    Note:
        This method's current implementation in the client API differs
        from the underlying `GrizabellaDBManager` which expects a
        `relation_id`. This method currently raises `NotImplementedError`
        and requires rework to correctly map to the DBManager's capabilities
        or a change in the client API signature.

    Args:
        from_object_id (str): The ID of the source object of the relation.
        to_object_id (str): The ID of the target object of the relation.
        relation_type_name (str): The name of the relation type.

    Returns:
        List[RelationInstance]: A list of relation instances matching the criteria.
        An empty list is returned if no matching relations are found.

    Raises:
        GrizabellaException: If there is an error during retrieval.
        NotConnectedError: If the client is not connected to the database.
        ValueError: If `from_object_id` or `to_object_id` are not valid UUID strings.

    """
    if not self._is_connected:
        self._db_manager.connect() # Ensure connection

    try:
        source_uuid = uuid.UUID(from_object_id)
        target_uuid = uuid.UUID(to_object_id)
    except ValueError as e:
        msg = (
            f"Invalid UUID string for from_object_id ('{from_object_id}') or "
            f"to_object_id ('{to_object_id}')."
        )
        raise ValueError(
            msg,
        ) from e

    return self._db_manager.find_relation_instances(
        relation_type_name=relation_type_name,
        source_object_id=source_uuid,
        target_object_id=target_uuid,
    )

get_relation_type(type_name)

Retrieves the definition of a relation type.

Parameters:

Name Type Description Default
type_name str

The name of the relation type to retrieve.

required

Returns:

Type Description
Optional[RelationTypeDefinition]

Optional[RelationTypeDefinition]: The definition of the relation

Optional[RelationTypeDefinition]

type if found, otherwise None.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def get_relation_type(self, type_name: str) -> Optional[RelationTypeDefinition]:
    """Retrieves the definition of a relation type.

    Args:
        type_name (str): The name of the relation type to retrieve.

    Returns:
        Optional[RelationTypeDefinition]: The definition of the relation
        type if found, otherwise None.

    Raises:
        NotConnectedError: If the client is not connected to the database.

    """
    return self._db_manager.get_relation_type_definition(type_name)

list_embedding_definitions()

Lists all embedding definitions in the database.

Returns:

Type Description
list[EmbeddingDefinition]

List[EmbeddingDefinition]: A list of all embedding definitions.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
658
659
660
661
662
663
664
665
666
667
668
669
670
def list_embedding_definitions(self) -> list[EmbeddingDefinition]:
    """Lists all embedding definitions in the database.

    Returns:
        List[EmbeddingDefinition]: A list of all embedding definitions.

    Raises:
        NotConnectedError: If the client is not connected to the database.

    """
    if not self._is_connected:
        self._db_manager.connect()
    return self._db_manager.list_embedding_definitions()

list_object_types()

Lists all defined object types in the database.

Returns:

Type Description
list[ObjectTypeDefinition]

List[ObjectTypeDefinition]: A list of all object type definitions.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
174
175
176
177
178
179
180
181
182
183
184
185
186
def list_object_types(self) -> list[ObjectTypeDefinition]:
    """Lists all defined object types in the database.

    Returns:
        List[ObjectTypeDefinition]: A list of all object type definitions.

    Raises:
        NotConnectedError: If the client is not connected to the database.

    """
    if not self._is_connected:
        self._db_manager.connect()  # Ensure connection
    return self._db_manager.list_object_type_definitions()

list_relation_types()

Lists all defined relation types in the database.

Returns:

Type Description
list[RelationTypeDefinition]

List[RelationTypeDefinition]: A list of all relation type definitions.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
258
259
260
261
262
263
264
265
266
267
268
269
270
def list_relation_types(self) -> list[RelationTypeDefinition]:
    """Lists all defined relation types in the database.

    Returns:
        List[RelationTypeDefinition]: A list of all relation type definitions.

    Raises:
        NotConnectedError: If the client is not connected to the database.

    """
    if not self._is_connected:
        self._db_manager.connect()  # Ensure connection
    return self._db_manager.list_relation_type_definitions()

query_relations(relation_type_name=None, source_object_instance_id=None, target_object_instance_id=None, properties_query=None, limit=None)

Queries relation instances based on various criteria.

Parameters:

Name Type Description Default
relation_type_name Optional[str]

Filter by relation type name.

None
source_object_instance_id Optional[str]

Filter by source object ID (UUID string).

None
target_object_instance_id Optional[str]

Filter by target object ID (UUID string).

None
properties_query Optional[dict[str, Any]]

Filter by relation properties.

None
limit Optional[int]

Maximum number of results to return.

None

Returns:

Type Description
list[RelationInstance]

List[RelationInstance]: A list of matching relation instances.

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

ValueError

If provided UUID strings are invalid.

GrizabellaException

For other underlying database or processing errors.

Source code in grizabella/api/client.py
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
def query_relations(
    self,
    relation_type_name: Optional[str] = None,
    source_object_instance_id: Optional[str] = None,
    target_object_instance_id: Optional[str] = None,
    properties_query: Optional[dict[str, Any]] = None, # Matches 'query' in db_manager
    limit: Optional[int] = None,
) -> list[RelationInstance]:
    """Queries relation instances based on various criteria.

    Args:
        relation_type_name (Optional[str]): Filter by relation type name.
        source_object_instance_id (Optional[str]): Filter by source object ID (UUID string).
        target_object_instance_id (Optional[str]): Filter by target object ID (UUID string).
        properties_query (Optional[dict[str, Any]]): Filter by relation properties.
        limit (Optional[int]): Maximum number of results to return.

    Returns:
        List[RelationInstance]: A list of matching relation instances.

    Raises:
        NotConnectedError: If the client is not connected to the database.
        ValueError: If provided UUID strings are invalid.
        GrizabellaException: For other underlying database or processing errors.

    """
    if not self._is_connected:
        self._db_manager.connect()  # Ensure connection

    source_uuid: Optional[uuid.UUID] = None
    if source_object_instance_id:
        try:
            source_uuid = uuid.UUID(source_object_instance_id)
        except ValueError as e:
            raise ValueError(f"Invalid UUID string for source_object_instance_id: '{source_object_instance_id}'.") from e

    target_uuid: Optional[uuid.UUID] = None
    if target_object_instance_id:
        try:
            target_uuid = uuid.UUID(target_object_instance_id)
        except ValueError as e:
            raise ValueError(f"Invalid UUID string for target_object_instance_id: '{target_object_instance_id}'.") from e

    return self._db_manager.find_relation_instances(
        relation_type_name=relation_type_name,
        source_object_id=source_uuid, # Pass UUID object
        target_object_id=target_uuid, # Pass UUID object
        query=properties_query,
        limit=limit,
    )

search_similar_objects(object_id, type_name, n_results=5, search_properties=None)

Searches for objects similar to a given object using its embeddings.

This method finds objects of the same type as the source object that are semantically similar, based on a specified or inferred embedding definition.

Parameters:

Name Type Description Default
object_id str

The ID of the source object to find similar items for.

required
type_name str

The type name of the source object.

required
n_results int

The maximum number of similar results to return. Defaults to 5.

5
search_properties Optional[List[str]]

If provided, the first element is used as the specific embedding_definition_name to use for both the source object's vector retrieval and the target search. If None or empty, the first available EmbeddingDefinition for the type_name will be used.

None

Returns:

Type Description
list[tuple[ObjectInstance, float]]

List[Tuple[ObjectInstance, float]]: A list of tuples, where each

list[tuple[ObjectInstance, float]]

tuple contains a similar ObjectInstance and its similarity score

list[tuple[ObjectInstance, float]]

(typically distance, where lower is more similar).

Raises:

Type Description
NotConnectedError

If the client is not connected to the database.

ValueError

If object_id is not a valid UUID string.

SchemaError

If a suitable EmbeddingDefinition cannot be found.

EmbeddingError

If an embedding for the source object cannot be found.

GrizabellaException

For other underlying database or processing errors.

Source code in grizabella/api/client.py
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
def search_similar_objects(
    self,
    object_id: str,
    type_name: str,
    n_results: int = 5,
    search_properties: Optional[list[str]] = None,
) -> list[tuple[ObjectInstance, float]]:
    """Searches for objects similar to a given object using its embeddings.

    This method finds objects of the same type as the source object that
    are semantically similar, based on a specified or inferred embedding definition.

    Args:
        object_id (str): The ID of the source object to find similar items for.
        type_name (str): The type name of the source object.
        n_results (int): The maximum number of similar results to return.
            Defaults to 5.
        search_properties (Optional[List[str]]): If provided, the first element
            is used as the specific `embedding_definition_name` to use for both
            the source object's vector retrieval and the target search.
            If None or empty, the first available `EmbeddingDefinition` for the
            `type_name` will be used.

    Returns:
        List[Tuple[ObjectInstance, float]]: A list of tuples, where each
        tuple contains a similar `ObjectInstance` and its similarity score
        (typically distance, where lower is more similar).

    Raises:
        NotConnectedError: If the client is not connected to the database.
        ValueError: If `object_id` is not a valid UUID string.
        SchemaError: If a suitable `EmbeddingDefinition` cannot be found.
        EmbeddingError: If an embedding for the source object cannot be found.
        GrizabellaException: For other underlying database or processing errors.

    """
    if not self._is_connected:
        self._db_manager.connect()

    try:
        source_uuid = uuid.UUID(object_id)
    except ValueError as e:
        msg = f"Invalid UUID string for object_id: '{object_id}'."
        raise ValueError(msg) from e

    embedding_definition_name: Optional[str] = None
    if search_properties and search_properties[0]:
        embedding_definition_name = search_properties[0]
        # Validate this ED exists and is for the correct object type
        ed = self._db_manager.get_embedding_definition(embedding_definition_name)
        if not ed:
            msg = f"Specified EmbeddingDefinition '{embedding_definition_name}' not found."
            raise SchemaError(
                msg,
            )
        if ed.object_type_name != type_name:
            msg = (
                f"Specified EmbeddingDefinition '{embedding_definition_name}' is for object type "
                f"'{ed.object_type_name}', but expected type '{type_name}' for object '{object_id}'."
            )
            raise SchemaError(
                msg,
            )
    else:
        # Find the first available EmbeddingDefinition for the type_name
        all_eds = self._db_manager.list_embedding_definitions()
        for ed in all_eds:
            if ed.object_type_name == type_name:
                embedding_definition_name = ed.name
                break
        if not embedding_definition_name:
            msg = (
                f"No EmbeddingDefinition found for object type '{type_name}'. "
                "Cannot perform similarity search."
            )
            raise SchemaError(
                msg,
            )

    return self._db_manager.find_objects_similar_to_instance(
        source_object_id=source_uuid,
        source_object_type_name=type_name,
        embedding_definition_name=embedding_definition_name,
        n_results=n_results,
    )

upsert_object(obj)

Creates a new object or updates an existing one.

If an object with the same ID already exists, it will be updated with the properties from the provided ObjectInstance. Otherwise, a new object will be created.

Parameters:

Name Type Description Default
obj ObjectInstance

The object instance to create or update. It must include the object type name and its properties. The id field can be provided for updates or will be generated for new objects if not supplied.

required

Returns:

Name Type Description
ObjectInstance ObjectInstance

The created or updated object instance, potentially

ObjectInstance

with a newly assigned ID or updated timestamps.

Raises:

Type Description
GrizabellaException

If the object type does not exist or if there is an error during the upsert operation.

NotConnectedError

If the client is not connected to the database.

Source code in grizabella/api/client.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def upsert_object(self, obj: ObjectInstance) -> ObjectInstance:
    """Creates a new object or updates an existing one.

    If an object with the same ID already exists, it will be updated
    with the properties from the provided `ObjectInstance`. Otherwise, a
    new object will be created.

    Args:
        obj (ObjectInstance): The object instance to create or update.
            It must include the object type name and its properties.
            The `id` field can be provided for updates or will be
            generated for new objects if not supplied.

    Returns:
        ObjectInstance: The created or updated object instance, potentially
        with a newly assigned ID or updated timestamps.

    Raises:
        GrizabellaException: If the object type does not exist or if there
            is an error during the upsert operation.
        NotConnectedError: If the client is not connected to the database.

    """
    return self._db_manager.upsert_object_instance(obj)