add_custom_storage(storage: UserStorageService)
Parameter | Type | Required | Description |
storage | UserStorageService | Yes | Custom user storage implementation. |
Type | Description |
PageSenseSDKOptions | Returns the same SDK options instance to support method chaining. |
from pagesense import PageSenseSDKOptions# Create your custom UserStorageService implementationcustom_storage = MyUserStorageService()sdk_options = (PageSenseSDKOptions().add_custom_storage(custom_storage))
import osimport jsonimport loggingimport asynciofrom pagesense.storage.userStorageService import UserStorageService"""Implements the UserStorageService interface that writes the user profile details to a local JSON fileto store all the users' experiment to variation mappings. This is a simple, file-based approach thatcan be replaced with other persistent storage mechanisms like databases, Redis, or cloud storage."""class FileUserStorageService(UserStorageService):USER_DATA_FILE_NAME = 'user_storage.json'USER_LOG_FILE_NAME = 'user_storage.log'def __init__(self, storage_directory):"""Initialize a new FileUserStorageService instance.Args:storageDirectory: Path where the user storage & log files will be created.Returns:None"""# Call the Super Constructorsuper().__init__()self.logger = Noneself.storage_directory = Noneself.user_data_file_path = Noneself.user_log_file_path = None# Create and verify the storage directory.self.create_storage_directory(storage_directory)# Create the user storage file if missing.self.create_user_data_file()# Initialize the logger after storage setup is complete.self.initialize_logger()# Log the successful setup.self.logger.info('FileUserStorageService initialized successfully.', extra={"storageDirectory": self.storage_directory,"userDataFilePath": self.user_data_file_path,"userLogFilePath": self.user_log_file_path})def create_storage_directory(self, storage_directory):"""Create and validate the storage directory.Args:storageDirectory: Path where the user storage & log files will be stored.Returns:None"""# If directory does not exist, attempt to create it recursively.if not os.path.exists(storage_directory):try:os.makedirs(storage_directory, exist_ok=True)except:raise Exception(f"Failed to create the storage directory: {storage_directory}")# Ensure the directory is writable to avoid runtime failures.if not os.access(storage_directory, os.W_OK):raise Exception(f"Storage directory is not writable: {storage_directory}")# Define the directory and the file paths for internal use.self.storage_directory = storage_directoryself.user_data_file_path = os.path.join(storage_directory, self.USER_DATA_FILE_NAME)self.user_log_file_path = os.path.join(storage_directory, self.USER_LOG_FILE_NAME)def create_user_data_file(self):"""Create the user storage JSON file if it does not exist.Args:NoneReturns:None"""# Check if the user storage file exists.if not os.path.exists(self.user_data_file_path):try:with open(self.user_data_file_path, 'w') as f:json.dump({}, f, indent=2)except:raise Exception(f"Failed to create the user data file at: {self.user_data_file_path}")def initialize_logger(self):"""Initialize the logger for file-based logging.Args:NoneReturns:None"""# Ensure the log file existsif not os.path.exists(self.user_log_file_path):with open(self.user_log_file_path, 'w'):pass# Configure the loggerself.logger = logging.getLogger("FileUserStorageService")self.logger.setLevel(logging.DEBUG)file_handler = logging.FileHandler(self.user_log_file_path)file_handler.setLevel(logging.DEBUG)formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')file_handler.setFormatter(formatter)self.logger.addHandler(file_handler)# Log successful logger creationself.logger.info('Logger initialized successfully.', extra={"logFile": self.user_log_file_path})def look_up(self, user_id):"""Look up the experiment–variation mapping for a user.Args:userId: Unique user identifier.Returns:JSON string of user mappings or None"""self.logger.info('Looking up user data.', extra={"userId": user_id})# Verify that the user storage file exists.if not os.path.exists(self.user_data_file_path):self.logger.warning('User data file not found.', extra={"userId": user_id})return None# Attempt to read the file contents.with open(self.user_data_file_path, 'r') as f:file_contents = f.read()# Decode JSON safelydecoded_data = self.safe_json_decode(file_contents)# Verify JSON structureif not decoded_data or not isinstance(decoded_data, dict):self.logger.warning('Invalid JSON format.', extra={"path": self.user_data_file_path})return Noneif user_id not in decoded_data:self.logger.info('No mappings found.', extra={"userId": user_id})return Noneself.logger.info('Mappings retrieved.', extra={"userId": user_id})return json.dumps(decoded_data[user_id])def save(self, user_profile):"""Save or update user experiment–variation mapping.Args:userProfile: UserProfile instance.Returns:None"""user_id = user_profile.get_user_id()self.logger.info('Saving user data.', extra={"userId": user_id})try:# Decode incoming user profile JSONuser_profile_data = self.safe_json_decode(user_profile.get_user_profile_json_string())# Validate dataif not user_profile_data or not isinstance(user_profile_data, dict):self.logger.error('Invalid user profile JSON.', extra={"userId": user_id})return# Load existing datatry:with open(self.user_data_file_path, 'r') as f:file_contents = f.read()except:file_contents = '{}'existing_data = self.safe_json_decode(file_contents) or {}# Update user dataexisting_data[user_id] = user_profile_data# Write updated datawith open(self.user_data_file_path, 'w') as f:json.dump(existing_data, f, indent=2)self.logger.info('User data saved.', extra={"userId": user_id,"file": self.user_data_file_path})except Exception as err:self.logger.error('Failed to save user data.', extra={"userId": user_id,"path": self.user_data_file_path,"message": str(err)})def safe_json_decode(self, json_string):"""Safely decode a JSON string.Args:jsonString: JSON string to decode.Returns:Decoded object or None"""# Validate inputif not json_string or not json_string.strip():return Nonetry:return json.loads(json_string)except Exception as err:if self.logger:self.logger.warning('JSON decoding failed.', extra={"errorMessage": str(err)})return None