Source code for quantumengine.fields.reference

from typing import Any, Dict, List, Optional, Type, Union

from surrealdb import RecordID

from .base import Field

[docs] class ReferenceField(Field): """Reference to another document. This field type stores references to other documents in the database. It can accept a document instance, an ID string, or a dictionary with an ID. Attributes: document_type: The type of document being referenced """
[docs] def __init__(self, document_type: Type, **kwargs: Any) -> None: """Initialize a new ReferenceField. Args: document_type: The type of document being referenced **kwargs: Additional arguments to pass to the parent class """ self.document_type = document_type super().__init__(**kwargs) self.py_type = Union[Type, str, dict]
[docs] def validate(self, value: Any) -> Any: """Validate the reference value. This method checks if the value is a valid reference to another document. It accepts a document instance, an ID string, a dictionary with an ID, or a RecordID object. Args: value: The value to validate Returns: The validated reference value Raises: TypeError: If the value is not a valid reference ValueError: If the referenced document is not saved """ value = super().validate(value) if value is not None: if not isinstance(value, (self.document_type, str, dict, RecordID)): raise TypeError( f"Expected {self.document_type.__name__}, id string, record dict, or RecordID for field '{self.name}', got {type(value)}") if isinstance(value, self.document_type) and value.id is None: raise ValueError(f"Cannot reference an unsaved {self.document_type.__name__} document") return value
[docs] def to_db(self, value: Any) -> Optional[str]: """Convert Python reference to database representation. This method converts a Python reference (document instance, ID string, dictionary with an ID, or RecordID object) to a database representation. Args: value: The Python reference to convert Returns: The database representation of the reference Raises: ValueError: If the referenced document is not saved """ if value is None: return None # If it's already a record ID string if isinstance(value, str): return value # If it's a RecordID object if isinstance(value, RecordID): return str(value) # If it's a document instance if isinstance(value, self.document_type): if value.id is None: raise ValueError(f"Cannot reference an unsaved {self.document_type.__name__} document") return value.id # If it's a dict (partial reference) if isinstance(value, dict) and value.get('id'): return value['id'] return value
[docs] def from_db(self, value: Any, dereference: bool = False) -> Any: """Convert database reference to Python representation. This method converts a database reference to a Python representation. If the value is already a resolved document (from FETCH), return it as is. If dereference is False, it returns the string reference as is. If dereference is True but value is still a string, fetch the referenced document. Args: value: The database reference to convert dereference: Whether to dereference the reference (default: False) Returns: The Python representation of the reference """ # If value is already a dict (fetched document), convert it to document instance if isinstance(value, dict) and 'id' in value: try: return self.document_type.from_db(value) except Exception: # If conversion fails, return the dict as is return value if isinstance(value, str) and ':' in value: # This is a record ID reference if dereference: # If dereference is True, fetch the referenced document # We need to use get_sync here because from_db is not async try: return self.document_type.objects.get_sync(id=value) except Exception: # If fetching fails, return the ID as is return value else: # If dereference is False, return the ID as is return value elif isinstance(value, RecordID): # This is a RecordID object if dereference: # If dereference is True, fetch the referenced document try: return self.document_type.objects.get_sync(id=str(value)) except Exception: # If fetching fails, return the RecordID as is return value else: # If dereference is False, return the RecordID as is return value return value
[docs] class RelationField(Field): """Field representing a relation between documents. This field type stores relations between documents in the database. It can accept a document instance, an ID string, or a dictionary with an ID. Attributes: to_document: The type of document being related to """
[docs] def __init__(self, to_document: Type, **kwargs: Any) -> None: """Initialize a new RelationField. Args: to_document: The type of document being related to **kwargs: Additional arguments to pass to the parent class """ self.to_document = to_document super().__init__(**kwargs) self.py_type = Union[Type, str, dict]
[docs] def validate(self, value: Any) -> Any: """Validate the relation value. This method checks if the value is a valid relation to another document. It accepts a document instance, an ID string, or a dictionary with an ID. Args: value: The value to validate Returns: The validated relation value Raises: TypeError: If the value is not a valid relation """ value = super().validate(value) if value is not None: if not isinstance(value, (self.to_document, str, dict)): raise TypeError( f"Expected {self.to_document.__name__}, id string, or record dict for field '{self.name}', got {type(value)}") return value
[docs] def to_db(self, value: Any) -> Optional[str]: """Convert Python relation to database representation. This method converts a Python relation (document instance, ID string, or dictionary with an ID) to a database representation. Args: value: The Python relation to convert Returns: The database representation of the relation Raises: ValueError: If the related document is not saved """ if value is None: return None # If it's already a record ID string if isinstance(value, str): if ':' not in value: return f"{self.to_document._get_collection_name()}:{value}" return value # If it's a document instance if isinstance(value, self.to_document): if value.id is None: raise ValueError(f"Cannot relate to an unsaved {self.to_document.__name__} document") # If the ID already includes the collection name, return it as is if isinstance(value.id, str) and ':' in value.id: return value.id # Otherwise, add the collection name return f"{self.to_document._get_collection_name()}:{value.id}" # If it's a dict if isinstance(value, dict) and value.get('id'): id_val = value['id'] # If the ID already includes the collection name, return it as is if isinstance(id_val, str) and ':' in id_val: return id_val # Otherwise, add the collection name return f"{self.to_document._get_collection_name()}:{id_val}" return value
[docs] def from_db(self, value: Any, dereference: bool = False) -> Any: """Convert database relation to Python representation. This method converts a database relation to a Python representation. If the value is already a resolved document (from FETCH), return it as is. If dereference is False, it returns the string reference as is. If dereference is True but value is still a string, fetch the related document. Args: value: The database relation to convert dereference: Whether to dereference the relation (default: False) Returns: The Python representation of the relation """ # If value is already a dict (fetched document), convert it to document instance if isinstance(value, dict) and 'id' in value: try: return self.to_document.from_db(value) except Exception: # If conversion fails, return the dict as is return value if isinstance(value, str) and ':' in value: # This is a record ID reference if dereference: # If dereference is True, fetch the related document # We need to use get_sync here because from_db is not async try: return self.to_document.objects.get_sync(id=value) except Exception: # If fetching fails, return the ID as is return value else: # If dereference is False, return the ID as is return value elif isinstance(value, RecordID): # This is a RecordID object if dereference: # If dereference is True, fetch the related document try: return self.to_document.objects.get_sync(id=str(value)) except Exception: # If fetching fails, return the RecordID as is return value else: # If dereference is False, return the RecordID as is return value return value