Skip to content

Error Handling

This guide covers the common exceptions in Seeknal and demonstrates best practices for handling them in your applications.

Overview

Seeknal provides a set of custom exceptions to help you identify and handle specific error conditions. All exceptions can be imported from seeknal.exceptions:

from seeknal.exceptions import (
    # Project errors
    ProjectNotSetError,
    ProjectNotFoundError,
    # Entity errors
    EntityNotFoundError,
    EntityNotSavedError,
    # Feature store errors
    FeatureGroupNotFoundError,
    FeatureServingNotFoundError,
    # Validation errors
    InvalidIdentifierError,
    InvalidPathError,
)

Exception Categories

Project Exceptions

ProjectNotSetError

Raised when an operation requires a project context, but no project has been set.

from seeknal import Project
from seeknal.exceptions import ProjectNotSetError

# Example: Handling missing project context
try:
    # Operations that require a project will fail if none is set
    from seeknal.context import check_project_id
    check_project_id()
except ProjectNotSetError as e:
    print(f"Error: {e}")
    # Solution: Set up a project first
    project = Project(name="my_project", description="My feature store")
    project.get_or_create()

Best Practice: Always initialize your project at the start of your application:

from seeknal import Project

def initialize_seeknal():
    """Initialize Seeknal with the required project context."""
    project = Project(name="my_feature_store")
    project = project.get_or_create()
    return project

# Call at application startup
project = initialize_seeknal()

ProjectNotFoundError

Raised when attempting to access a project that doesn't exist in the database.

from seeknal import Project
from seeknal.exceptions import ProjectNotFoundError

def get_project_safely(project_id: int):
    """Safely retrieve a project by ID with error handling."""
    try:
        return Project.get_by_id(project_id)
    except ProjectNotFoundError:
        print(f"Project with ID {project_id} was not found.")
        return None

# Usage
project = get_project_safely(999)  # Returns None if not found

Entity Exceptions

EntityNotSavedError

Raised when attempting to perform operations on an entity that hasn't been persisted.

from seeknal import Entity
from seeknal.exceptions import EntityNotSavedError

def update_entity_safely(entity: Entity, **updates):
    """Update an entity with proper error handling."""
    try:
        entity.update(**updates)
    except EntityNotSavedError:
        print("Entity must be saved before updating.")
        print("Call entity.get_or_create() first.")
        # Optionally, save it automatically
        entity.get_or_create()
        entity.update(**updates)

# Usage
entity = Entity(name="customer", join_keys=["customer_id"])
# This will handle the case where entity hasn't been saved
update_entity_safely(entity, description="Customer entity for retail")

Best Practice: Always call get_or_create() before performing operations:

from seeknal import Entity

# Correct pattern
entity = Entity(
    name="customer",
    join_keys=["customer_id"],
    pii_keys=["email", "phone"],
    description="Customer entity"
)
entity = entity.get_or_create()  # Always call this first
entity.update(description="Updated description")  # Now safe to update

EntityNotFoundError

Raised when an entity cannot be found in the feature store.

from seeknal import Entity
from seeknal.exceptions import EntityNotFoundError

def get_or_create_entity(name: str, join_keys: list) -> Entity:
    """Get an existing entity or create a new one."""
    entity = Entity(name=name, join_keys=join_keys)
    entity = entity.get_or_create()
    return entity

# The get_or_create() pattern handles both cases automatically
customer_entity = get_or_create_entity("customer", ["customer_id"])

Feature Store Exceptions

FeatureGroupNotFoundError

Raised when a feature group cannot be found in the feature store.

from seeknal.exceptions import FeatureGroupNotFoundError

def load_feature_group(name: str):
    """Load a feature group with error handling."""
    try:
        # Feature group loading logic here
        pass
    except FeatureGroupNotFoundError:
        print(f"Feature group '{name}' not found.")
        print("Make sure the feature group has been created and saved.")
        return None

FeatureServingNotFoundError

Raised when a feature serving configuration cannot be found.

from seeknal.exceptions import FeatureServingNotFoundError

def get_feature_serving(name: str):
    """Get feature serving configuration with error handling."""
    try:
        # Feature serving lookup logic here
        pass
    except FeatureServingNotFoundError:
        print(f"Feature serving '{name}' not found.")
        print("Please ensure the feature serving has been configured.")
        return None

Validation Exceptions

InvalidIdentifierError

Raised when a SQL identifier (table name, column name, database name) contains invalid characters or exceeds length limits.

from seeknal.validation import validate_table_name, validate_column_name
from seeknal.exceptions import InvalidIdentifierError

def create_table_safely(table_name: str, columns: list):
    """Create a table with validated identifiers."""
    try:
        # Validate table name
        validated_name = validate_table_name(table_name)

        # Validate column names
        for col in columns:
            validate_column_name(col)

        print(f"Creating table: {validated_name}")
        # Proceed with table creation...

    except InvalidIdentifierError as e:
        print(f"Invalid identifier: {e}")
        print("Identifiers must:")
        print("  - Start with a letter or underscore")
        print("  - Contain only alphanumeric characters and underscores")
        print("  - Be no longer than 128 characters")

# Valid usage
create_table_safely("user_features", ["user_id", "feature_1", "feature_2"])

# Invalid usage - will raise error
create_table_safely("user-features", ["user_id"])  # Hyphens not allowed
create_table_safely("123_table", ["user_id"])  # Cannot start with number

Common Invalid Identifier Patterns:

from seeknal.validation import validate_sql_identifier
from seeknal.exceptions import InvalidIdentifierError

# Examples of invalid identifiers
invalid_identifiers = [
    "",              # Empty string
    "123table",      # Starts with number
    "table-name",    # Contains hyphen
    "table name",    # Contains space
    "table;drop",    # Contains semicolon (SQL injection attempt)
]

for identifier in invalid_identifiers:
    try:
        validate_sql_identifier(identifier)
    except InvalidIdentifierError as e:
        print(f"Invalid: '{identifier}' - {e}")

InvalidPathError

Raised when a file path contains potentially dangerous characters that could be used for SQL injection.

from seeknal.validation import validate_file_path
from seeknal.exceptions import InvalidPathError

def load_data_safely(file_path: str):
    """Load data from a file path with validation."""
    try:
        validated_path = validate_file_path(file_path)
        print(f"Loading data from: {validated_path}")
        # Proceed with file loading...

    except InvalidPathError as e:
        print(f"Invalid file path: {e}")
        print("File paths cannot contain SQL injection characters like:")
        print("  ', \", ;, --, /*, */")

# Valid usage
load_data_safely("/data/features/customer_data.parquet")

# Invalid usage - will raise error
load_data_safely("/data/features'; DROP TABLE users; --")  # SQL injection attempt

Comprehensive Error Handling Pattern

Here's a complete example showing how to handle multiple exception types in a production application:

from seeknal import Project, Entity
from seeknal.exceptions import (
    ProjectNotSetError,
    ProjectNotFoundError,
    EntityNotFoundError,
    EntityNotSavedError,
    InvalidIdentifierError,
    InvalidPathError,
)
from seeknal.validation import validate_table_name

class SeeknalClient:
    """A client wrapper with comprehensive error handling."""

    def __init__(self, project_name: str):
        self.project = None
        self.entities = {}
        self._initialize_project(project_name)

    def _initialize_project(self, project_name: str):
        """Initialize the Seeknal project."""
        try:
            validate_table_name(project_name)
            self.project = Project(name=project_name)
            self.project = self.project.get_or_create()
            print(f"Project '{project_name}' initialized successfully.")
        except InvalidIdentifierError as e:
            raise ValueError(f"Invalid project name: {e}")

    def create_entity(
        self,
        name: str,
        join_keys: list,
        description: str = None
    ) -> Entity:
        """Create or retrieve an entity with error handling."""
        try:
            # Validate the entity name
            validate_table_name(name)

            # Create and save the entity
            entity = Entity(
                name=name,
                join_keys=join_keys,
                description=description
            )
            entity = entity.get_or_create()
            self.entities[name] = entity
            return entity

        except InvalidIdentifierError as e:
            print(f"Invalid entity name: {e}")
            raise
        except Exception as e:
            print(f"Unexpected error creating entity: {e}")
            raise

    def update_entity(self, name: str, **updates) -> Entity:
        """Update an entity with proper error handling."""
        try:
            if name not in self.entities:
                raise EntityNotFoundError(f"Entity '{name}' not in cache")

            entity = self.entities[name]
            entity.update(**updates)
            return entity

        except EntityNotSavedError:
            print(f"Entity '{name}' hasn't been saved. Saving now...")
            entity = self.entities[name].get_or_create()
            entity.update(**updates)
            return entity
        except EntityNotFoundError as e:
            print(f"Entity not found: {e}")
            raise


# Usage
try:
    client = SeeknalClient("my_feature_store")

    # Create entities
    customer = client.create_entity(
        name="customer",
        join_keys=["customer_id"],
        description="Customer entity"
    )

    # Update entity
    client.update_entity("customer", description="Updated customer entity")

except ValueError as e:
    print(f"Configuration error: {e}")
except (EntityNotFoundError, EntityNotSavedError) as e:
    print(f"Entity error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

Logging Errors

For production applications, use Python's logging module:

import logging
from seeknal.exceptions import (
    ProjectNotSetError,
    EntityNotFoundError,
    InvalidIdentifierError,
)

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("seeknal_app")

def process_with_logging():
    """Example with proper logging."""
    try:
        # Your Seeknal operations here
        pass
    except ProjectNotSetError as e:
        logger.error("Project not set: %s", e)
        raise
    except EntityNotFoundError as e:
        logger.warning("Entity not found: %s", e)
        return None
    except InvalidIdentifierError as e:
        logger.error("Validation error: %s", e)
        raise ValueError(f"Invalid input: {e}")
    except Exception as e:
        logger.exception("Unexpected error occurred")
        raise

Summary

Exception When Raised How to Handle
ProjectNotSetError No project context set Initialize project with Project(...).get_or_create()
ProjectNotFoundError Project doesn't exist Use get_or_create() or verify project ID
EntityNotSavedError Entity not persisted Call entity.get_or_create() first
EntityNotFoundError Entity doesn't exist Use get_or_create() pattern
FeatureGroupNotFoundError Feature group missing Verify feature group is created and saved
FeatureServingNotFoundError Feature serving missing Verify serving configuration exists
InvalidIdentifierError Invalid SQL identifier Use alphanumeric characters and underscores only
InvalidPathError Dangerous file path Remove SQL injection characters from paths