anod.action
===========

.. py:module:: anod.action


Attributes
----------

.. autoapisummary::

   anod.action.Choice


Classes
-------

.. autoapisummary::

   anod.action.Action
   anod.action.Root
   anod.action.GetSource
   anod.action.Download
   anod.action.DownloadSource
   anod.action.InstallSource
   anod.action.CreateSource
   anod.action.CreateSources
   anod.action.Checkout
   anod.action.AnodAction
   anod.action.Build
   anod.action.Test
   anod.action.Install
   anod.action.DownloadBinary
   anod.action.Upload
   anod.action.UploadComponent
   anod.action.UploadBinaryComponent
   anod.action.UploadSourceComponent
   anod.action.UploadSource
   anod.action.Decision
   anod.action.CreateSourceOrDownload
   anod.action.BuildOrDownload


Package Contents
----------------

.. py:data:: Choice

.. py:class:: Action(uid: str, data: Any)

   Action object.

   Action objects are used as node in DAG produced by anod scheduler. Only
   child classes are used directly.


   .. py:attribute:: __slots__
      :value: ('uid', 'data')



   .. py:attribute:: uid


   .. py:attribute:: data


   .. py:property:: run_method
      :type: str



.. py:class:: Root

   Bases: :py:obj:`Action`


   Root action.

   This action does not correspond to any real activity. It's only used as
   the root of the DAG and can be "executed" only once all the scheduled
   Actions are completed.


   .. py:attribute:: __slots__
      :value: ('uid', 'data')



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


.. py:class:: GetSource(builder: e3.anod.package.SourceBuilder)

   Bases: :py:obj:`Action`


   GetSource Action.

   This action means that we need to retrieve a given source package. In the
   anod DAG context it can either have one child node: DownloadSource (read
   access to a store) or provide the choice between DownloadSource or
   CreateSource using a Decision node.


   .. py:attribute:: __slots__
      :value: ('uid', 'builder')



   .. py:attribute:: builder


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


.. py:class:: Download(uid: str, data: Any)

   Bases: :py:obj:`Action`


   General root class for all download actions.


.. py:class:: DownloadSource(builder: e3.anod.package.SourceBuilder)

   Bases: :py:obj:`Download`


   DownloadSource Action.

   This action means the we need to download from the store a given
   source. DownloadSource is always a leaf of the DAG.


   .. py:attribute:: __slots__
      :value: ('uid', 'builder')



   .. py:attribute:: builder


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


.. py:class:: InstallSource(uid: str, spec: e3.anod.spec.Anod, source: e3.anod.package.Source)

   Bases: :py:obj:`Action`


   InstallSource Action.

   This means that a source should be installed. Child action is always
   a GetSource Action.


   .. py:attribute:: __slots__
      :value: ('uid', 'spec', 'source')



   .. py:attribute:: spec


   .. py:attribute:: source


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


.. py:class:: CreateSource(anod_instance: e3.anod.spec.Anod, source_name: str)

   Bases: :py:obj:`Action`


   CreateSource Action.

   This means that we need to assemble the source package from a repositories
   checkouts. CreateSource has at least one Checkout child node.


   .. py:attribute:: __slots__
      :value: ('uid', 'anod_instance', 'source_name')



   .. py:attribute:: anod_instance


   .. py:attribute:: source_name


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


.. py:class:: CreateSources(anod_instance: e3.anod.spec.Anod)

   Bases: :py:obj:`Action`


   CreateSources Action.

   This action does not correspond to any real activity. It's only
   used to group all CreateSource action corresponding to the same
   anod spec.


   .. py:attribute:: __slots__
      :value: ('uid', 'anod_instance')



   .. py:attribute:: anod_instance


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


.. py:class:: Checkout(repo_name: str, repo_data: dict[str, Any])

   Bases: :py:obj:`Action`


   Checkout Action.

   This means that we need to perform the checkout/update of a given
   repository.


   .. py:attribute:: __slots__
      :value: ('uid', 'repo_name', 'repo_data')



   .. py:attribute:: repo_name


   .. py:attribute:: repo_data


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


.. py:class:: AnodAction(anod_instance: e3.anod.spec.Anod)

   Bases: :py:obj:`Action`


   AnodAction Action.

   Correspond to an Anod primitive call. Only subclasses should be used.


   .. py:attribute:: __slots__
      :value: ('uid', 'anod_instance')



   .. py:attribute:: anod_instance


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


.. py:class:: Build(anod_instance: e3.anod.spec.Anod)

   Bases: :py:obj:`AnodAction`


   Anod build primitive.


.. py:class:: Test(anod_instance: e3.anod.spec.Anod)

   Bases: :py:obj:`AnodAction`


   Anod test primitive.


.. py:class:: Install(anod_instance: e3.anod.spec.Anod)

   Bases: :py:obj:`AnodAction`


   Anod install primitive.


.. py:class:: DownloadBinary(data: e3.anod.spec.Anod)

   Bases: :py:obj:`Download`


   DownloadBinary Action.

   Download a binary package from the store.


   .. py:attribute:: __slots__
      :value: ('uid', 'data')



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


.. py:class:: Upload(uid: str, data: Any)

   Bases: :py:obj:`Action`


   General root class for all upload actions.


.. py:class:: UploadComponent(data: e3.anod.spec.Anod)

   Bases: :py:obj:`Upload`


   UploadComponent Action.

   Upload a component to the store.


   .. py:attribute:: __slots__
      :value: ('uid', 'data', 'anod_instance')



   .. py:attribute:: str_prefix
      :value: ''



   .. py:attribute:: anod_instance


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


.. py:class:: UploadBinaryComponent(data: e3.anod.spec.Anod)

   Bases: :py:obj:`UploadComponent`


   Upload binary component.


   .. py:attribute:: str_prefix
      :value: 'binary package'



.. py:class:: UploadSourceComponent(data: e3.anod.spec.Anod)

   Bases: :py:obj:`UploadComponent`


   Upload source only component.


   .. py:attribute:: str_prefix
      :value: 'source metadata'



.. py:class:: UploadSource(anod_instance: e3.anod.spec.Anod, source_name: str)

   Bases: :py:obj:`Upload`


   Upload a source package.


   .. py:attribute:: __slots__
      :value: ('uid', 'anod_instance', 'source_name')



   .. py:attribute:: anod_instance


   .. py:attribute:: source_name


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

      Return string representation.



.. py:class:: Decision(root: Action, left: Action, right: Action, choice: Choice | None = None)

   Bases: :py:obj:`Action`


   Decision Action.

   Decision nodes correspond to Action nodes where the end result
   can be obtained using multiple methods. For instance, sources
   can be obtained either by downloading them from the store, but
   also by getting them from repositories.  Each child of Decision nodes
   represents one way to obtain the desired end result (a "choice").

   The current implementation only supports 2 choices, called
   "left" and "right".

   The DAG we first create when reading the plan includes these Decision
   nodes. Then, as part of scheduling the plan, we create a new DAG where
   all Decision nodes are removed, and only one child of each Decision
   is chosen by the scheduler, then taking the place of its Decision
   node in the DAG. Therefore, once scheduling of the DAG is complete,
   there should no longer be any Decision node left.

   It important to know that this class does not actually decide
   which choice to make. It just records some information about
   the plan, and the associated specs. It then uses that information
   to determine if a choice has actually been made, be it implicitly
   or explicitly.

   :ivar initiator: The UID of the parent node of the decision.
   :vartype initiator: str
   :ivar left_action: The first possible choice for our decision.
   :vartype left_action: Action.
   :ivar right_action: The second possible choice for our decision.
   :vartype right_action: Action.
   :ivar choice: If not None, the choice made by the plan itself.
       It can be Decision.LEFT, meaning that the plan contains
       an entry to perform the self.left_action; If set to
       Decision.RIGHT, it means the plan contains an entry to
       perform the self.right_action; and if set to BOTH, it means
       the plan contains entries to perform both self.left_action
       and self.right_action (which may or may not be an issue).
   :vartype choice: Decision.LEFT | Decision.RIGHT | Decision.BOTH
   :ivar expected_choice: If not None, the choice implicitly made
       by the way the specs involved in the decision are written.
       In practice, this attribute records the constraints we have
       in terms of the choices in the plan which are valid.
   :ivar triggers: A list of actions that, if present in a DAG
       being created while scheduling the plan (from which this
       Decision originates), causes a specific choice to be expected
       for this decision.  Each element of this list consists of a tuple
       with the uid of the Action triggering the choice, the expected
       choice, and a plan_line (if the action comes from the plan,
       otherwise None).
   :vartype triggers: list[(str, LEFT | RIGHT, str | None)]
   :ivar decision_maker: If not None, the plan_line where an entry
       in the plan is performing an action corresponding to one of
       our choices.
   :ivar: str


   .. py:attribute:: LEFT
      :type:  Final
      :value: 0



   .. py:attribute:: RIGHT
      :type:  Final
      :value: 1



   .. py:attribute:: BOTH
      :type:  Final
      :value: 2



   .. py:attribute:: initiator


   .. py:attribute:: choice
      :value: None



   .. py:attribute:: expected_choice
      :type:  Choice | None
      :value: None



   .. py:attribute:: left_action


   .. py:attribute:: right_action


   .. py:attribute:: triggers
      :type:  list[tuple[str, Choice, str]]
      :value: []



   .. py:attribute:: decision_maker
      :type:  str | None
      :value: None



   .. py:property:: left
      :type: str


      return self.left_action.uid (this is a convenience property).



   .. py:property:: right
      :type: str


      return self.right_action.uid (this is a convenience property).



   .. py:method:: add_trigger(trigger: Action, decision: Choice, plan_line: str) -> None

      Add a trigger to self.triggers.

      See the description of the "triggers" attribute for more
      information as to what these triggers are used for.

      :param trigger: The action which, when scheduled, causes
          the given decision (which is actually more aptly
          described as a choice) to be recorded as the expected
          choice for our Decision.
      :param decision: The expected choice when the trigger Action is
          scheduled.
      :param plan_line: plan line associated with this action



   .. py:method:: apply_triggers(dag: e3.collection.dag.DAG) -> None

      Apply triggers to the given dag.

      :param dag: a dag of scheduled actions



   .. py:method:: get_decision() -> str | None

      Return uid of the choice made by the plan, or None.

      This function returns the choice made by the plan, if any.
      This means that if the plan doesn't include an action
      that implements any of the choices, we return None.
      Similarly, if the plan has actions implementing more than
      one of the choices, the plan hasn't made any decision
      because the decision is ambiguous.

      And finally, if the plan made a choice, but that choice
      contradicts this decision's expected_choice, then the decision
      is incorrect, and we also return None in that case.

      :return: None if the decision cannot be made; otherwise,
          return uid of the choice that was made.



   .. py:method:: get_expected_decision() -> str | None

      Get expected decision.

      :return: uid of the expected action or None if no specific decision
          is expected.



   .. py:method:: set_decision(which: Choice, decision_maker: str | None) -> None

      Record a choice made by an action in a plan.

      This method should be caused when an entry in our plan
      performs an action corresponding to one of the choices
      for our decision. This method records which of the choices
      that action performs.

      :param which: Decision.LEFT or Decision.RIGHT
      :param decision_maker: Record who the decision maker is.
          This is typically the plan line of the action performing
          the choice being recorded.



   .. py:method:: description(decision: Choice) -> str
      :classmethod:

      :abstractmethod:


      Return a description of the decision (actually a choice).

      :param decision: The decision (actually a choice).
      :return: A description of the given parameter (named "decision",
          but actually a choice).



   .. py:method:: suggest_plan_fix(choice: Choice) -> str | None

      Suggest a plan line that would fix the conflict.

      :param choice: Decision.LEFT or Decision.RIGHT

      :return: a line to add to the plan or None if no fix
           can be proposed



.. py:class:: CreateSourceOrDownload(root: Action, left: CreateSource, right: DownloadSource)

   Bases: :py:obj:`Decision`


   Decision between creating or downloading a source package.


   .. py:attribute:: CREATE
      :type:  Final
      :value: 0



   .. py:attribute:: DOWNLOAD
      :type:  Final
      :value: 1



   .. py:method:: description(decision: Choice) -> str
      :classmethod:


      Return a description of the decision (actually a choice).

      :param decision: The decision (actually a choice).
      :return: A description of the given parameter (named "decision",
          but actually a choice).



.. py:class:: BuildOrDownload(root: Install, left: Build, right: DownloadBinary)

   Bases: :py:obj:`Decision`


   Decision between building or downloading a component.


   .. py:attribute:: BUILD
      :type:  Final
      :value: 0



   .. py:attribute:: INSTALL
      :type:  Final
      :value: 1



   .. py:method:: description(decision: Choice) -> str
      :classmethod:


      Return a description of the decision (actually a choice).

      :param decision: The decision (actually a choice).
      :return: A description of the given parameter (named "decision",
          but actually a choice).



   .. py:method:: suggest_plan_fix(choice: Choice) -> str

      Suggest a plan line that would fix the conflict.

      :param choice: Decision.LEFT or Decision.RIGHT

      :return: a line to add to the plan or None if no fix
           can be proposed



