Source code for src.configuration

"""
Load environment variables and validation of project configurations from user
"""

import random
import asyncio
from string import Template
from typing import List
import environ
from dotenv import load_dotenv
import loguru
import discord
from discord.ext.commands import Bot as DcBot
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from .tetue_generic.generic_requests import GenReqConfiguration
from .tetue_generic.watcher import WatcherConfiguration
from .db_classes import (
    DbConfiguration,
    GAME,
    StoryType,
    TALE,
    EVENT,
    CHARACTER,
    GameStatus,
    StartCondition,
    INSPIRATIONALWORD,
    GENRE,
)


load_dotenv("default.env")
load_dotenv("files/.env", override=True)


[docs] class DelimitedTemplate(Template): """This class allow the creation of a template with a user defined separator. The package is there to define templates for texts and then substitute them with certain values. Args: Template (_type_): Basic class that is inherited """ delimiter = "#"
[docs] class CharacterContext: """ Class to specify the character context for processing. """ def __init__(self): self.available_character: List[CHARACTER] = [] self.selected_character_id: int = 0 self.selected_character: CHARACTER = None
[docs] async def input_valid_character(self) -> bool: """ Checks if character are available for selection. Returns: bool: Character available """ if len(self.available_character) <= 0: return False return True
[docs] class GenreContext: """ Class to specify the genre context for processing. """ def __init__(self): self.available_genre: List[GENRE] = [] self.selected_genre_id: int = 0 self.selected_genre: GENRE = None
[docs] async def input_valid_genre(self) -> bool: """ Checks if genre are available for selection. Returns: bool: Genre available """ if len(self.available_genre) <= 0: return False return True
[docs] class UserContext: """ Class to specify the user context and input data for processing. """ def __init__(self): self.user_dc_id: str = "0" self.available_chars: List[CHARACTER] = [] self.selected_char: int = 0
[docs] async def input_valid_char(self) -> bool: """ Checks if character are available for selection. Returns: bool: Character available """ if len(self.available_chars) <= 0: return False return True
[docs] class GameContext: """ Class to specify the game context and input data for processing. """ def __init__(self): self.available_games: List[GAME] = [] self.selected_game_id: int = 0 self.selected_game: GAME = None self.new_game_status: GameStatus = None self.start: GameStartContext = GameStartContext()
[docs] async def input_valid_game(self) -> bool: """ Checks if games are available for selection. Returns: bool: Games available """ if len(self.available_games) <= 0: return False return True
[docs] async def request_game_start(self) -> bool: """ Checks if a game start is requested, based on the selected game status. Returns: bool: Game start is requested. """ return ( self.selected_game.status is GameStatus.CREATED and self.new_game_status is GameStatus.RUNNING )
[docs] class StoryContext: """ Class to specify the story context and input data for processing. """ def __init__(self): self.story_type: StoryType = None self.fiction_prompt: str = "" self.tale: TALE = None self.event: EVENT = None self.character: list[CHARACTER] = [] self.start = StoryStartContext()
[docs] def events_available(self) -> bool: """ Checks if events are available. Returns: bool: Events available """ if len(self.tale.genre.events) <= 0: return False return True
[docs] async def get_random_event_weighted(self): """ Function selecting a random event based on the weights defined in the database for the tale's genre. """ weights = [element.chance for element in self.tale.genre.events] self.event = random.choices( # pylint: disable=no-member self.tale.genre.events, weights=weights, k=1 )[0]
[docs] async def get_random_insp_word_weighted(self) -> INSPIRATIONALWORD: """ Function selecting a random inspirational word based on the weights defined in the database for the tale's genre. """ weights = [element.chance for element in self.tale.genre.inspirational_words] return random.choices( # pylint: disable=no-member self.tale.genre.inspirational_words, weights=weights, k=1 )[0]
[docs] def insp_words_not_available(self) -> bool: """ Function checks Returns: bool: _description_ """ return len(self.tale.genre.inspirational_words) <= 0
[docs] async def get_fiction_prompt(self) -> str: """ This function create the fiction prompt based on saved attributes and input from user. Returns: str: Fiction prompt """ if self.fiction_prompt: return self.fiction_prompt return (await self.get_random_insp_word_weighted()).text
[docs] class StoryStartContext: """ Class to specify the story start context and input data for processing. """ def __init__(self): self.condition: StartCondition = None self.city: str = "" self.prompt: str = ""
[docs] class GameStartContext: """ Class to specify the game start context and input data for processing. """ def __init__(self): self.selected_genre: int = 0 self.game_name: str = "" self.game_description: str = "" self.selected_user: List[discord.member.Member] = []
[docs] class ProcessInput: """ Combined class to hold all context and input data for processing. """ def __init__(self): self.user_context = UserContext() self.game_context = GameContext() self.story_context = StoryContext()
[docs] @environ.config(prefix="DC") class DcConfiguration: """ Configuration model for the discord """ bot_token: str = environ.var(converter=str) historian_role_id: int = environ.var(0, converter=int) storyteller_role_id: int = environ.var(0, converter=int) everyone_role_id: int = environ.var(0, converter=int)
[docs] @environ.config(prefix="TT") class EnvConfiguration: """ Configuration class for the entire application, grouping all sub-configurations. """ api_key = environ.var("ollama", converter=str) base_url = environ.var("localhost:11434/v1", converter=str) model = environ.var("llama3.2:3b", converter=str) gen_req = environ.group(GenReqConfiguration) watcher = environ.group(WatcherConfiguration) db = environ.group(DbConfiguration) dc = environ.group(DcConfiguration)
[docs] class Configuration: """ Genral configuration class for the entire application. Combines all sub-configurations and initializes the database engine and session. """ def __init__(self, config: EnvConfiguration): self.dc_bot = DcBot self.env = config self.engine = create_async_engine(config.db.db_url) self.session = async_sessionmaker(bind=self.engine, expire_on_commit=False) self.write_lock = asyncio.Lock() # pylint: disable=not-callable self.logger: loguru._logger.Logger = None