import hashlib
import inspect
import logging
from graphviz import Digraph
from app.core.config import configs
logger = logging.getLogger(__name__)
[docs]
class BaseStrategy:
"""
Base class for strategies, providing a framework for creating and managing
strategy configurations.
Attributes:
strategy_name (str): The name of the strategy.
strategy_description (str): A description of the strategy.
strategy_name_slug (str): A slugified version of the strategy name.
strategy_version (str): The version of the strategy.
variable_basic_points (int): The basic points variable for the
strategy.
variable_bonus_points (int): The bonus points variable for the
strategy.
hash_version (str): The hash of the calculate_points method for
versioning.
debug (bool): Flag to enable or disable debug printing.
"""
[docs]
def __init__(
self,
strategy_name=None,
strategy_description=None,
strategy_name_slug=None,
strategy_version="0.0.1",
variable_basic_points=1,
variable_bonus_points=1,
):
"""
Initializes the BaseStrategy with the provided attributes.
Args:
strategy_name (str, optional): The name of the strategy.
strategy_description (str, optional): A description of the
strategy.
strategy_name_slug (str, optional): A slugified version of the
strategy name.
strategy_version (str, optional): The version of the strategy.
Default is "0.0.1".
variable_basic_points (int, optional): The basic points variable
for the strategy. Default is 1.
variable_bonus_points (int, optional): The bonus points variable
for the strategy. Default is 1.
"""
# Debug tracing (debug_print) follows the environment: ON in dev,
# OFF everywhere else. Concrete strategies no longer hardcode this to
# True, which previously emitted ANSI-coloured debug lines to stdout
# in production. Tests override .debug per-instance as needed.
self.debug = configs.ENV == "dev"
self.strategy_name = strategy_name
self.strategy_description = strategy_description
self.strategy_name_slug = strategy_name_slug
self.strategy_version = strategy_version
self.variable_basic_points = variable_basic_points
self.variable_bonus_points = variable_bonus_points
self.hash_version = self._generate_hash_of_calculate_points()
def _generate_hash_of_calculate_points(self):
"""
Generates a SHA-256 hash of the source code of the calculate_points
method. This hash is used for versioning the strategy.
Returns:
str: The SHA-256 hash of the calculate_points method.
"""
source_code = inspect.getsource(self.calculate_points)
hash_object = hashlib.sha256(source_code.encode())
return hash_object.hexdigest()
[docs]
def debug_print(self, *args):
"""
Emits debug information if debug mode is enabled.
Args:
*args: The arguments to log.
"""
if self.debug:
logger.debug(" ".join(str(a) for a in args))
[docs]
def get_strategy_id(self):
"""
Retrieves the strategy ID, which is the class name. This id is the
filename of the strategy.
Returns:
str: The strategy ID.
"""
return self.__class__.__name__
[docs]
def get_strategy_name(self):
"""
Retrieves the strategy name.
Returns:
str: The strategy name.
"""
return self.strategy_name
[docs]
def get_strategy_description(self):
"""
Retrieves the strategy description.
Returns:
str: The strategy description.
"""
return self.strategy_description
[docs]
def get_strategy_name_slug(self):
"""
Retrieves the slugified strategy name.
Returns:
str: The slugified strategy name.
"""
return self.strategy_name_slug
[docs]
def get_strategy_version(self):
"""
Retrieves the strategy version.
Returns:
str: The strategy version.
"""
return self.strategy_version
[docs]
def get_variable_basic_points(self):
"""
Retrieves the basic points variable.
Returns:
int: The basic points variable.
"""
return self.variable_basic_points
[docs]
def get_variable_bonus_points(self):
"""
Retrieves the bonus points variable.
Returns:
int: The bonus points variable.
"""
return self.variable_bonus_points
[docs]
def set_variables(self, new_variables):
"""
Sets multiple variables at once.
Args:
new_variables (dict): A dictionary of variable names and values.
Returns:
list: A list of variable names that were changed.
"""
variables_changed = []
for new_variable, new_value in new_variables.items():
if hasattr(self, new_variable):
setattr(self, new_variable, new_value)
variables_changed.append(new_variable)
return variables_changed
[docs]
def get_variables(self):
"""
Retrieves all variables whose name starts with ``variable_``.
Returns:
dict: A dictionary of variable names and their values.
"""
return {k: v for k, v in self.__dict__.items() if k.startswith("variable_")}
[docs]
def get_variable(self, variable_name):
"""
Retrieves the value of a specific variable.
Args:
variable_name (str): The name of the variable.
Returns:
The value of the variable if it exists, otherwise None.
"""
if hasattr(self, variable_name):
return getattr(self, variable_name)
return None
[docs]
def set_variable(self, variable_name, variable_value):
"""
Sets the value of a specific variable.
Args:
variable_name (str): The name of the variable.
variable_value: The new value of the variable.
Returns:
bool: True if the variable was set, otherwise False.
"""
if hasattr(self, variable_name):
setattr(self, variable_name, variable_value)
return True
return False
[docs]
def get_strategy(self):
"""
Retrieves the strategy details including name, description, slug,
version, and variables.
Returns:
dict: A dictionary containing the strategy details.
"""
return {
"name": self.get_strategy_name(),
"description": self.get_strategy_description(),
"name_slug": self.get_strategy_name_slug(),
"version": self.get_strategy_version(),
"variables": self.get_variables(),
"hash_version": self.hash_version,
}
[docs]
async def calculate_points(self, data=None):
"""
Calculates the points for the strategy.
Returns:
int: The basic points variable.
"""
return self.get_variable_basic_points()
[docs]
def simulate_strategy(self, data=None):
"""
Simulates the strategy for testing purposes.
Returns:
dict: A dictionary containing the simulated points.
"""
return None
[docs]
def generate_logic_graph(self, format="png"):
"""
Generates a logic graph for the strategy.
Returns:
str: The logic graph as a string.
"""
dot = Digraph(comment="Points Calculation Logic", format=format)
dot.node("A", "No logic graph available")
return dot