:py:mod:`pyab_experiment.codegen.python.python_generator`
=========================================================

.. py:module:: pyab_experiment.codegen.python.python_generator

.. autoapi-nested-parse::

   Module that translates an Abstract Syntax Tree (AST) into executable Python code.

   This module contains the PythonCodeGen class which traverses an ExperimentAST and
   generates corresponding Python code for experiment variant selection. The generated
   code includes conditional logic, group assignments, and deterministic choice functions.



Module Contents
---------------

Classes
~~~~~~~

.. autoapisummary::

   pyab_experiment.codegen.python.python_generator.PythonCodeGen




.. py:class:: PythonCodeGen(experiment_ast: pyab_experiment.language.grammar.ExperimentAST, indentation_char: str = '\t', expose_experiment_variant_function: bool = True)


   Generates Python code from an ExperimentAST representation.

   This class maintains state during AST traversal (like indentation level
   and variable tracking)and provides methods to generate formatted Python
   code for experiment variant selection.

   :param experiment_ast: The AST to translate into Python code
   :type experiment_ast: ExperimentAST
   :param indentation_char: Character used for indentation.
   :type indentation_char: str, optional
   :param Defaults to "   " expose_experiment_variant_function:
   :type Defaults to "   " expose_experiment_variant_function: bool, optional
   :param Whether to expose the variant selection function at the root level.:
   :param Defaults to True:

   .. attribute:: _local_vars

      Tracks local variables used in the generated code

      :type: set

   .. attribute:: _conditional_ids

      Tracks conditional variables referenced in predicates

      :type: set

   .. attribute:: _indent_depth

      Current indentation level during code generation

      :type: int

   .. py:property:: conditional_ids


   .. py:property:: local_vars


   .. py:method:: _generate_conditionals(condition: pyab_experiment.language.grammar.ExperimentConditional | list[pyab_experiment.language.grammar.ExperimentGroup]) -> str

      Generates Python code for conditional statements and group return functions.

      This method traverses the experiment's conditional structure and generates the
      corresponding Python code. It handles:
      1. Conditional statements (if/elif/else) with their predicates and branches
      2. Terminal group definitions that return partial functions for
      variant selection

      :param condition: Either an ExperimentConditional for if/elif/else logic,
                        or a list[ExperimentGroup] for terminal group definitions.
                        - ExperimentConditional contains predicate and branch information
                        - list[ExperimentGroup] contains group definitions and weights

      :returns:

                Generated Python code as a string, including:
                    - For conditionals: if/elif/else statements with their predicates
                    - For groups: partial function definitions for variant selection
      :rtype: str

      :raises RuntimeError: If an unsupported condition type is provided

      Side Effects:
          - Adds conditional ids to self._conditional_ids

      Example generated code:
          # For conditionals:
          if (age >= 18):
              return partial(deterministic_choice, population=['A', 'B'],
              weights=[1, 2])
          else:
              return partial(deterministic_choice, population=['C', 'D'],
              weights=[1, 1])

          # For direct group definitions:
          return partial(deterministic_choice, population=['A', 'B'],
          weights=[1, 1])


   .. py:method:: _generate_exception() -> str


   .. py:method:: _generate_group_return_statement(group_statement: list[pyab_experiment.language.grammar.ExperimentGroup]) -> str

      unwrap the experiment group, into a partial function call
      that applies the splitter logic


   .. py:method:: _generate_op(op: pyab_experiment.language.grammar.LogicalOperatorEnum | pyab_experiment.language.grammar.BooleanOperatorEnum) -> str

      renders legal python operations


   .. py:method:: _generate_predicate(predicate: pyab_experiment.language.grammar.TerminalPredicate | pyab_experiment.language.grammar.RecursivePredicate | None) -> str

      Generates Python code for predicate expressions in conditional statements.

      This method handles three types of predicates:
      1. Terminal predicates: Simple comparisons between two terms (e.g., "age >= 18")
      2. Recursive predicates: Complex boolean expressions combining
      multiple predicates
      3. None: Returns an empty string (used in ELSE conditions)

      :param predicate: The predicate to convert to Python code. Can be:
                        - TerminalPredicate: For simple comparisons
                        - RecursivePredicate: For complex boolean expressions
                        - None: For else conditions

      :returns: A Python expression string representing the predicate logic.
      :rtype: str

      :raises RuntimeError: If an unsupported predicate type is provided.

      Side Effects:
          - Adds conditional ids to self._conditional_ids as it encounters them



   .. py:method:: _generate_term(term: float | int | str | tuple | pyab_experiment.language.grammar.Identifier) -> str

      renders a term


   .. py:method:: generate() -> str

      main method. Does a DFS on the AST rendering python
      code as it traverses the nodes


   .. py:method:: generate_key_definition() -> str

      Generate the composite hash key used for deterministic experiment
      group assignment.

      This method combines the experiment's salt (if provided) with the string
      representation of splitting fields to create a composite key.
      This key is used by the deterministic_choice function to consistently
      assign users to experiment groups.

      The composite key is constructed as follows:
      1. If a salt exists, it's used as a prefix
      2. If splitting fields exist, their string representations are concatenated
      3. If neither exists, returns "None"

      Example outputs:
          - With salt="exp1" and fields=[user_id, country]:
            "'exp1'+''.join(map(str, [user_id, country]))"
          - With only fields=[device_id]:
            "''''.join(map(str, [device_id]))"
          - With no salt or fields:
            "None"

      :returns:

                A Python expression that evaluates to the composite key
                     used for group assignment.
      :rtype: str

      Side Effects:
          - Adds any splitting field variables to self._local_vars


   .. py:method:: indent() -> str

      helper function that renders scope sensitive indentation
      using an internal state to keep track the position in the
      AST traversal


   .. py:method:: render_topline() -> str

      renders the topline of our python code
      i.e. imports, and top line comment



