Source code for quantumengine.fields.specialized

import re
import uuid
import decimal
from decimal import Decimal
from typing import Any, Dict, List, Optional, Pattern, Type, Union

from .base import Field
from .scalar import StringField, NumberField
from ..exceptions import ValidationError

[docs] class BytesField(Field): """Bytes field type. This field type stores binary data as byte arrays and provides validation and conversion between Python bytes objects and SurrealDB bytes format. """
[docs] def __init__(self, **kwargs: Any) -> None: """Initialize a new BytesField. Args: **kwargs: Additional arguments to pass to the parent class """ super().__init__(**kwargs) self.py_type = bytes
[docs] def validate(self, value: Any) -> Optional[bytes]: """Validate the bytes value. This method checks if the value is a valid bytes object or can be converted to bytes. Args: value: The value to validate Returns: The validated bytes value Raises: TypeError: If the value cannot be converted to bytes """ value = super().validate(value) if value is not None: if isinstance(value, bytes): return value if isinstance(value, str): try: return value.encode('utf-8') except UnicodeEncodeError: pass raise TypeError(f"Expected bytes for field '{self.name}', got {type(value)}") return value
[docs] def to_db(self, value: Any) -> Optional[str]: """Convert Python bytes to database representation. This method converts a Python bytes object to a SurrealDB bytes format for storage in the database. Args: value: The Python bytes to convert Returns: The SurrealDB bytes format for the database """ if value is None: return None if isinstance(value, bytes): # Convert bytes to SurrealDB bytes format # SurrealDB uses <bytes>"base64_encoded_string" format import base64 encoded = base64.b64encode(value).decode('ascii') return f'<bytes>"{encoded}"' if isinstance(value, str) and value.startswith('<bytes>"') and value.endswith('"'): # If it's already in SurrealDB bytes format, return as is return value raise TypeError(f"Cannot convert {type(value)} to bytes")
[docs] def from_db(self, value: Any) -> Optional[bytes]: """Convert database value to Python bytes. This method converts a SurrealDB bytes format from the database to a Python bytes object. Args: value: The database value to convert Returns: The Python bytes object """ if value is not None: if isinstance(value, bytes): return value if isinstance(value, str) and value.startswith('<bytes>"') and value.endswith('"'): # Extract the base64-encoded string from <bytes>"..." format import base64 encoded = value[8:-1] # Remove <bytes>" and " return base64.b64decode(encoded) return value
[docs] class RegexField(Field): """Regular expression field type. This field type stores regular expressions and provides validation and conversion between Python regex objects and SurrealDB regex format. """
[docs] def __init__(self, **kwargs: Any) -> None: """Initialize a new RegexField. Args: **kwargs: Additional arguments to pass to the parent class """ super().__init__(**kwargs) self.py_type = Pattern
[docs] def validate(self, value: Any) -> Optional[Pattern]: """Validate the regex value. This method checks if the value is a valid regex pattern or can be compiled into a regex pattern. Args: value: The value to validate Returns: The validated regex pattern Raises: TypeError: If the value cannot be converted to a regex pattern ValueError: If the regex pattern is invalid """ value = super().validate(value) if value is not None: if isinstance(value, Pattern): return value if isinstance(value, str): try: return re.compile(value) except re.error as e: raise ValueError(f"Invalid regex pattern for field '{self.name}': {str(e)}") raise TypeError(f"Expected regex pattern for field '{self.name}', got {type(value)}") return value
[docs] def to_db(self, value: Any) -> Optional[str]: """Convert Python regex to database representation. This method converts a Python regex pattern to a SurrealDB regex format for storage in the database. Args: value: The Python regex pattern to convert Returns: The SurrealDB regex format for the database """ if value is None: return None if isinstance(value, Pattern): # Convert regex pattern to SurrealDB regex format # SurrealDB uses /pattern/flags format pattern = value.pattern flags = "" if value.flags & re.IGNORECASE: flags += "i" if value.flags & re.MULTILINE: flags += "m" if value.flags & re.DOTALL: flags += "s" return f"/{pattern}/{flags}" if isinstance(value, str): # If it's already a string, assume it's in the correct format return value raise TypeError(f"Cannot convert {type(value)} to regex")
[docs] def from_db(self, value: Any) -> Optional[Pattern]: """Convert database value to Python regex. This method converts a SurrealDB regex format from the database to a Python regex pattern. Args: value: The database value to convert Returns: The Python regex pattern """ if value is not None: if isinstance(value, Pattern): return value if isinstance(value, str) and value.startswith('/') and '/' in value[1:]: # Parse /pattern/flags format last_slash = value.rindex('/') pattern = value[1:last_slash] flags_str = value[last_slash + 1:] flags = 0 if 'i' in flags_str: flags |= re.IGNORECASE if 'm' in flags_str: flags |= re.MULTILINE if 's' in flags_str: flags |= re.DOTALL return re.compile(pattern, flags) return value
[docs] class DecimalField(NumberField): """Decimal field type. This field type stores decimal values with arbitrary precision using Python's Decimal class. It provides validation to ensure the value is a valid decimal."""
[docs] def __init__(self, **kwargs: Any) -> None: """Initialize a new DecimalField. Args: **kwargs: Additional arguments to pass to the parent class """ super().__init__(**kwargs) self.py_type = Decimal
[docs] def validate(self, value: Any) -> Optional[Decimal]: """Validate the decimal value. This method checks if the value is a valid decimal or can be converted to a decimal. Args: value: The value to validate Returns: The validated decimal value Raises: TypeError: If the value cannot be converted to a decimal """ value = super().validate(value) if value is not None: if isinstance(value, Decimal): return value try: return Decimal(str(value)) except (TypeError, ValueError, decimal.InvalidOperation): raise TypeError(f"Expected decimal for field '{self.name}', got {type(value)}") return value
def _to_db_backend_specific(self, value: Any, backend: str) -> Optional[Union[float, str]]: """Backend-specific decimal conversion logic. Args: value: The Python Decimal to convert backend: The backend type Returns: Backend-appropriate decimal representation """ if value is not None: if isinstance(value, Decimal): if backend == 'clickhouse': # ClickHouse has excellent high-precision decimal support # Convert to string to preserve full precision return str(value) elif backend == 'surrealdb': # SurrealDB typically uses float representation return float(value) else: # Default to float for other backends return float(value) try: decimal_value = Decimal(str(value)) if backend == 'clickhouse': return str(decimal_value) else: return float(decimal_value) except (TypeError, ValueError, decimal.InvalidOperation): pass return value def _from_db_backend_specific(self, value: Any, backend: str) -> Optional[Decimal]: """Backend-specific decimal conversion logic. Args: value: The database value to convert backend: The backend type Returns: Python Decimal object """ if value is not None: try: if backend == 'clickhouse': # ClickHouse may return high-precision strings or floats return Decimal(str(value)) elif backend == 'surrealdb': # SurrealDB typically returns floats return Decimal(str(value)) else: # Default behavior for other backends return Decimal(str(value)) except (TypeError, ValueError, decimal.InvalidOperation): pass return value
[docs] class UUIDField(Field): """UUID field type. This field type stores UUID values and provides validation and conversion between Python UUID objects and SurrealDB string format. Example: >>> class User(Document): ... id = UUIDField(default=uuid.uuid4) """
[docs] def __init__(self, **kwargs: Any) -> None: """Initialize a new UUIDField. Args: **kwargs: Additional arguments to pass to the parent class """ super().__init__(**kwargs) self.py_type = uuid.UUID
[docs] def validate(self, value: Any) -> Optional[uuid.UUID]: """Validate the UUID value. This method checks if the value is a valid UUID or can be converted to a UUID. Args: value: The value to validate Returns: The validated UUID value Raises: TypeError: If the value cannot be converted to a UUID ValueError: If the UUID format is invalid """ value = super().validate(value) if value is not None: if isinstance(value, uuid.UUID): return value try: return uuid.UUID(str(value)) except (TypeError, ValueError) as e: raise ValueError(f"Invalid UUID format for field '{self.name}': {str(e)}") return value
[docs] def to_db(self, value: Any) -> Optional[str]: """Convert Python UUID to database representation. This method converts a Python UUID object to a string for storage in the database. Args: value: The Python UUID to convert Returns: The string representation for the database """ if value is not None: if isinstance(value, uuid.UUID): return str(value) try: return str(uuid.UUID(str(value))) except (TypeError, ValueError): pass return value
[docs] def from_db(self, value: Any) -> Optional[uuid.UUID]: """Convert database value to Python UUID. This method converts a value from the database to a Python UUID object. Args: value: The database value to convert Returns: The Python UUID object """ if value is not None: try: return uuid.UUID(str(value)) except (TypeError, ValueError): pass return value
[docs] class LiteralField(Field): """Field for union/enum-like values. Allows a field to accept multiple different types or specific values, similar to a union or enum type in other languages. Example: class Product(Document): status = LiteralField(["active", "discontinued", "out_of_stock"]) id_or_name = LiteralField([IntField(), StringField()]) """
[docs] def __init__(self, allowed_values: List[Any], **kwargs: Any) -> None: """Initialize a new LiteralField. Args: allowed_values: List of allowed values or field types **kwargs: Additional arguments to pass to the parent class """ self.allowed_values = allowed_values self.allowed_fields = [v for v in allowed_values if isinstance(v, Field)] self.allowed_literals = [v for v in allowed_values if not isinstance(v, Field)] super().__init__(**kwargs) self.py_type = Union[tuple(f.py_type for f in self.allowed_fields)] if self.allowed_fields else Any
[docs] def validate(self, value: Any) -> Any: """Validate that the value is one of the allowed values or types. Args: value: The value to validate Returns: The validated value Raises: ValidationError: If the value is not one of the allowed values or types """ value = super().validate(value) if value is None: return None # Check if the value is one of the allowed literals if value in self.allowed_literals: return value # Try to validate with each allowed field type for field in self.allowed_fields: try: return field.validate(value) except (TypeError, ValueError): continue # If we get here, the value is not valid if self.allowed_literals: literals_str = ", ".join(repr(v) for v in self.allowed_literals) error_msg = f"Value for field '{self.name}' must be one of: {literals_str}" if self.allowed_fields: field_types = ", ".join(f.__class__.__name__ for f in self.allowed_fields) error_msg += f" or a valid {field_types}" else: field_types = ", ".join(f.__class__.__name__ for f in self.allowed_fields) error_msg = f"Value for field '{self.name}' must be a valid {field_types}" raise ValidationError(error_msg)
[docs] def to_db(self, value: Any) -> Any: """Convert Python value to database representation. This method converts a Python value to a database representation by using the appropriate field type if the value is not a literal. Args: value: The Python value to convert Returns: The database representation of the value """ if value is None: return None # If it's a literal, return as is if value in self.allowed_literals: return value # Try to convert with each allowed field type for field in self.allowed_fields: try: field.validate(value) # Validate first to ensure it's the right type return field.to_db(value) except (TypeError, ValueError): continue return value
[docs] def from_db(self, value: Any) -> Any: """Convert database value to Python representation. This method converts a database value to a Python representation by using the appropriate field type if the value is not a literal. Args: value: The database value to convert Returns: The Python representation of the value """ if value is None: return None # If it's a literal, return as is if value in self.allowed_literals: return value # Try to convert with each allowed field type for field in self.allowed_fields: try: return field.from_db(value) except (TypeError, ValueError): continue return value
[docs] class EmailField(StringField): """Email field type. This field type stores email addresses and provides validation to ensure the value is a valid email address. Example: >>> class User(Document): ... email = EmailField(required=True) """
[docs] def __init__(self, **kwargs: Any) -> None: """Initialize a new EmailField. Args: **kwargs: Additional arguments to pass to the parent class """ # Add a regex pattern to validate email addresses kwargs['regex'] = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' super().__init__(**kwargs)
[docs] def validate(self, value: Any) -> Optional[str]: """Validate the email address. This method checks if the value is a valid email address. Args: value: The value to validate Returns: The validated email address Raises: ValueError: If the email address is invalid """ value = super().validate(value) if value is not None: # Additional validation specific to email addresses if '@' not in value: raise ValueError(f"Invalid email address for field '{self.name}': missing @ symbol") if value.count('@') > 1: raise ValueError(f"Invalid email address for field '{self.name}': multiple @ symbols") local, domain = value.split('@') if not local: raise ValueError(f"Invalid email address for field '{self.name}': empty local part") if not domain: raise ValueError(f"Invalid email address for field '{self.name}': empty domain part") if '.' not in domain: raise ValueError(f"Invalid email address for field '{self.name}': invalid domain") return value
[docs] class URLField(StringField): """URL field type. This field type stores URLs and provides validation to ensure the value is a valid URL. Example: >>> class Website(Document): ... url = URLField(required=True) """
[docs] def __init__(self, **kwargs: Any) -> None: """Initialize a new URLField. Args: **kwargs: Additional arguments to pass to the parent class """ # Add a regex pattern to validate URLs kwargs['regex'] = r'^(https?|ftp)://[^\s/$.?#].[^\s]*$' super().__init__(**kwargs)
[docs] def validate(self, value: Any) -> Optional[str]: """Validate the URL. This method checks if the value is a valid URL. Args: value: The value to validate Returns: The validated URL Raises: ValueError: If the URL is invalid """ value = super().validate(value) if value is not None: # Additional validation specific to URLs if not value.startswith(('http://', 'https://', 'ftp://')): raise ValueError(f"Invalid URL for field '{self.name}': must start with http://, https://, or ftp://") return value
[docs] class IPAddressField(StringField): """IP address field type. This field type stores IP addresses and provides validation to ensure the value is a valid IPv4 or IPv6 address. Example: >>> class Server(Document): ... ip_address = IPAddressField(required=True) ... ip_v4 = IPAddressField(ipv4_only=True) ... ip_v6 = IPAddressField(ipv6_only=True) """
[docs] def __init__(self, ipv4_only: bool = False, ipv6_only: bool = False, version: str = None, **kwargs: Any) -> None: """Initialize a new IPAddressField. Args: ipv4_only: Whether to only allow IPv4 addresses ipv6_only: Whether to only allow IPv6 addresses version: IP version to validate ('ipv4', 'ipv6', or 'both') **kwargs: Additional arguments to pass to the parent class """ # Handle version parameter for backward compatibility if version is not None: version = version.lower() if version not in ('ipv4', 'ipv6', 'both'): raise ValueError("version must be 'ipv4', 'ipv6', or 'both'") ipv4_only = (version == 'ipv4') ipv6_only = (version == 'ipv6') self.ipv4_only = ipv4_only self.ipv6_only = ipv6_only if ipv4_only and ipv6_only: raise ValueError("Cannot set both ipv4_only and ipv6_only to True") # Remove version from kwargs to avoid passing it to the parent class # This prevents it from being included in the schema definition if 'version' in kwargs: del kwargs['version'] super().__init__(**kwargs)
[docs] def validate(self, value: Any) -> Optional[str]: """Validate the IP address. This method checks if the value is a valid IP address. Args: value: The value to validate Returns: The validated IP address Raises: ValueError: If the IP address is invalid """ value = super().validate(value) if value is not None: # Validate IPv4 address if self.ipv4_only or not self.ipv6_only: ipv4_pattern = r'^(\d{1,3}\.){3}\d{1,3}$' if re.match(ipv4_pattern, value): # Check that each octet is in the valid range octets = value.split('.') if all(0 <= int(octet) <= 255 for octet in octets): return value if self.ipv4_only: raise ValueError(f"Invalid IPv4 address for field '{self.name}': octets must be between 0 and 255") # Validate IPv6 address if self.ipv6_only or not self.ipv4_only: try: # Use socket.inet_pton to validate IPv6 address import socket socket.inet_pton(socket.AF_INET6, value) return value except (socket.error, ValueError): if self.ipv6_only: raise ValueError(f"Invalid IPv6 address for field '{self.name}'") # If we get here, the value is not a valid IP address if self.ipv4_only: raise ValueError(f"Invalid IPv4 address for field '{self.name}'") elif self.ipv6_only: raise ValueError(f"Invalid IPv6 address for field '{self.name}'") else: raise ValueError(f"Invalid IP address for field '{self.name}'") return value
[docs] class SlugField(StringField): """Slug field type. This field type stores slugs (URL-friendly strings) and provides validation to ensure the value is a valid slug. Example: >>> class Article(Document): ... slug = SlugField(required=True) """
[docs] def __init__(self, **kwargs: Any) -> None: """Initialize a new SlugField. Args: **kwargs: Additional arguments to pass to the parent class """ # Add a regex pattern to validate slugs kwargs['regex'] = r'^[a-z0-9]+(?:-[a-z0-9]+)*$' super().__init__(**kwargs)
[docs] def validate(self, value: Any) -> Optional[str]: """Validate the slug. This method checks if the value is a valid slug. Args: value: The value to validate Returns: The validated slug Raises: ValueError: If the slug is invalid """ value = super().validate(value) if value is not None: # Additional validation specific to slugs if not value: raise ValueError(f"Slug for field '{self.name}' cannot be empty") if value.startswith('-') or value.endswith('-'): raise ValueError(f"Slug for field '{self.name}' cannot start or end with a hyphen") if '--' in value: raise ValueError(f"Slug for field '{self.name}' cannot contain consecutive hyphens") return value
[docs] class ChoiceField(Field): """Choice field type. This field type stores values from a predefined set of choices and provides validation to ensure the value is one of the allowed choices. Example: >>> class Product(Document): ... status = ChoiceField(choices=['active', 'inactive', 'discontinued']) """
[docs] def __init__(self, choices: List[Union[str, tuple]], **kwargs: Any) -> None: """Initialize a new ChoiceField. Args: choices: List of allowed choices. Each choice can be a string or a tuple of (value, display_name). **kwargs: Additional arguments to pass to the parent class """ self.choices = choices self.values = [c[0] if isinstance(c, tuple) else c for c in choices] super().__init__(**kwargs) self.py_type = str
[docs] def validate(self, value: Any) -> Optional[str]: """Validate the choice value. This method checks if the value is one of the allowed choices. Args: value: The value to validate Returns: The validated choice value Raises: ValueError: If the value is not one of the allowed choices """ value = super().validate(value) if value is not None and value not in self.values: choices_str = ", ".join(repr(v) for v in self.values) raise ValueError(f"Value for field '{self.name}' must be one of: {choices_str}") return value