Format: text/x-python
⬇ Download File (4.3 KB)
   1  class ConfigValidator:
   2      VALID_STRATEGY_TYPES = {"random", "stratified", "stratified-proportional"}
   3  
   4      def __init__(self, config: dict):
   5          self.config = config
   6  
   7      def validate(self):
   8          self.validate_customer_name()
   9          self.validate_data_sources()
  10          self.validate_filters()
  11          self.validate_sampling_strategy()
  12          self.validate_output()
  13          self.validate_alerting()
  14  
  15      def validate_customer_name(self):
  16          if "customer_name" not in self.config:
  17              raise ValueError("Missing 'customer_name' in config.")
  18          if not isinstance(self.config["customer_name"], str):
  19              raise ValueError("'customer_name' must be a string.")
  20  
  21      def validate_data_sources(self):
  22          if "data_sources" not in self.config:
  23              raise ValueError("Missing 'data_sources' in config.")
  24          
  25          for source in ["matches", "catalog"]:
  26              if source not in self.config["data_sources"]:
  27                  raise ValueError(f"Missing '{source}' in data_sources.")
  28              
  29              source_conf = self.config["data_sources"][source]
  30              if "sql_file" not in source_conf:
  31                  raise ValueError(f"Missing 'sql_file' in {source} data source.")
  32              if "connection" not in source_conf:
  33                  raise ValueError(f"Missing 'connection' details in {source} data source.")
  34              if "secret_name" not in source_conf["connection"]:
  35                  raise ValueError(f"Missing 'secret_name' in {source} connection config.")
  36  
  37      def validate_filters(self):
  38          filters = self.config.get("filters", {})
  39          if not isinstance(filters, dict):
  40              raise ValueError("'filters' section must be a dictionary if provided.")
  41  
  42      def validate_sampling_strategy(self):
  43          if "sampling_strategy" not in self.config:
  44              raise ValueError("Missing 'sampling_strategy' in config.")
  45          
  46          strategy = self.config["sampling_strategy"]
  47          if "type" not in strategy:
  48              raise ValueError("sampling_strategy must include a 'type' field.")
  49          if strategy["type"] not in self.VALID_STRATEGY_TYPES:
  50              raise ValueError(f"sampling_strategy.type must be one of {self.VALID_STRATEGY_TYPES}")
  51  
  52          # Strategy-specific checks
  53          if strategy["type"] == "random":
  54              if "sample_size" not in strategy:
  55                  raise ValueError("random sampling_strategy must have 'sample_size' defined.")
  56  
  57          elif strategy["type"] == "stratified":
  58              if "by" not in strategy:
  59                  raise ValueError("stratified sampling_strategy must have 'by' field.")
  60              # 'ratio' is optional — default to 10/group in Sampler
  61              if "ratio" in strategy:
  62                  if not isinstance(strategy["ratio"], (dict, int)):
  63                      raise ValueError("'ratio' must be an int or dict in stratified strategy.")
  64  
  65          elif strategy["type"] == "stratified-proportional":
  66              if "by" not in strategy:
  67                  raise ValueError("stratified-proportional sampling_strategy must have 'by' field.")
  68              # 'proportion' is optional — default to equal weight in Sampler
  69              if "proportion" in strategy:
  70                  if not isinstance(strategy["proportion"], (dict, float, int)):
  71                      raise ValueError("'proportion' must be a float or dict in stratified-proportional strategy.")
  72  
  73      def validate_output(self):
  74          if "output" not in self.config:
  75              raise ValueError("Missing 'output' section in config.")
  76          
  77          output = self.config["output"]
  78          if "bucket" not in output:
  79              raise ValueError("output must include 'bucket'.")
  80          if "prefix" not in output:
  81              raise ValueError("output must include 'prefix'.")
  82          if "file_format" not in output:
  83              raise ValueError("output must include 'file_format' (csv or parquet).")
  84  
  85      def validate_alerting(self):
  86          alerting = self.config.get("alerting", {})
  87          if alerting and not isinstance(alerting, dict):
  88              raise ValueError("'alerting' must be a dictionary if provided.")
  89          
  90          if alerting:
  91              if "slack_webhook_url" not in alerting and "email_list" not in alerting:
  92                  raise ValueError("alerting must have either 'slack_webhook_url' or 'email_list'.")
  93