diff --git a/docs/source/api_reference.rst b/docs/source/api_reference.rst index a187a51f..31f693ef 100644 --- a/docs/source/api_reference.rst +++ b/docs/source/api_reference.rst @@ -56,6 +56,22 @@ Storing Configs wrapper.default_to_config +************************************** +Creating and Launching Jobs with Hydra +************************************** + +hydra-zen provides users the ability to launch a Hydra job via a +Python function instead of from a commandline interface. + +.. currentmodule:: hydra_zen + +.. autosummary:: + :toctree: generated/ + + zen + launch + wrapper.Zen + Instantiating and Resolving Configs *********************************** @@ -111,22 +127,6 @@ hydra_zen.typing DataclassOptions ZenConvert -************************************** -Creating and Launching Jobs with Hydra -************************************** - -hydra-zen provides users the ability to launch a Hydra job via a -Python function instead of from a commandline interface. - -.. currentmodule:: hydra_zen - -.. autosummary:: - :toctree: generated/ - - launch - zen - wrapper.Zen - .. _valid-types: diff --git a/docs/source/how_to/configuring_experiments.rst b/docs/source/how_to/configuring_experiments.rst index 2ea66655..50f2a630 100644 --- a/docs/source/how_to/configuring_experiments.rst +++ b/docs/source/how_to/configuring_experiments.rst @@ -101,7 +101,7 @@ The application can then be executed using: Creating Configurations for Particular "Experiments" ==================================================== -Suppose that we frequently want to run our application using the following two configurations (which we will refer to as `aplite` and `nglite`, respectively) +Suppose that we frequently want to run our application using the following two configurations, which we will refer to as `aplite` and `nglite`, respectively. .. code-block:: console :caption: Manually running the so-called `aplite` configuration @@ -131,7 +131,7 @@ Our objective is to be able run these experiments more concisely, as: .. code-block:: console - $ python my_app.py +experiment= + $ python my_app.py +experiment= To do this we implement new experiment configurations that: @@ -182,7 +182,7 @@ Now the configuration for either "experiment" can be specified by-name from the .. tab-set:: - .. tab-item:: aplit + .. tab-item:: aplite .. code-block:: console :caption: 4 Running the `aplite` experiment diff --git a/docs/source/tutorials/add_cli.rst b/docs/source/tutorials/add_cli.rst index 8248e6d6..773f73a4 100644 --- a/docs/source/tutorials/add_cli.rst +++ b/docs/source/tutorials/add_cli.rst @@ -18,43 +18,56 @@ Add a Command Line Interface to Our Application In this tutorial we will update our project so that it can be configured and launched from a command line interface (CLI), using Hydra. +In the last section of the tutorial, we + +- Defined a task function +- Created a config for that task function +- Ran the task function via `hydra_zen.launch(Config, task_function, overrides=<...>)` + +In this section, we need to add our config to Hydra's config-store; this enables Hydra to generate a CLI from this config. Modifying Our Project ===================== Open ``my_app.py`` in your editor. We will make the following modifications to it: -1. Use :func:`hydra_zen.store` (`docs `_) to generate for our task function and to store it in Hydra's global config store. +1. Use :class:`hydra_zen.ZenStore` to store our config locally. 2. Add a ``__main__`` clause to our ``my_app.py`` script so that the script runs our task function. -3. Use :func:`hydra_zen.zen` to wrap the task function and to generate the CLI. +3. Within ``__main__`` populate Hydra's global config store so that Hydra can generate a CLI using our configs. +4. Use :func:`hydra_zen.zen` to wrap the task function and to generate the CLI. Modify your script to match this: .. code-block:: python :caption: Contents of my_app.py: - from hydra_zen import store, zen + from hydra_zen import builds, zen, ZenStore - # 1) `hydra_zen.store generates a config for our task function - # and stores it locally under the entry-name "my_app" - @store(name="my_app") + # The same task function as before def task_function(player1: str, player2: str): - # write the log with the names with open("player_log.txt", "w") as f: f.write("Game session log:\n") f.write(f"Player 1: {player1}\n" f"Player 2: {player2}") return player1, player2 - - # 2) Executing `python my_app.py [...]` will run our task function + + Config = builds(task_function, populate_full_signature=True) + + # 1) Create a local config store and store our config + store = ZenStore() + store(Config, name="my_app") + + # 2) Adding our __main__ clause to our script. + # Executing `python my_app.py [...]` will generate a CLI for our running + # our task function if __name__ == "__main__": # 3) We need to add the configs from our local store to Hydra's - # global config store + # global config store store.add_to_hydra_store() - # 4) Our zen-wrapped task function is used to generate - # the CLI, and to specify which config we want to use - # to configure the app by default + # 4) hydra_main generates a CLI based off of the config + # stored under the name "my_app", and will run + # `task_function` zen(task_function).hydra_main(config_name="my_app", version_base="1.1", config_path=None, @@ -135,6 +148,42 @@ VoilĂ ! As demonstrated, our simple application can now be configured and launch command line. It should be noted that we can still launch our app from a Python console, using :func:`~hydra_zen.launch`, as we did :ref:`in the previous tutorial `. + +Streamlining Our Code +===================== + +:class:`hydra_zen.ZenStore` has :ref:`auto-config capabilities ` and it +can be used as a decorator. This enables us to both create and store a config for our task function in a single line. + +.. code-block:: python + :caption: Streamlined version of my_app.py: + + from hydra_zen import zen, ZenStore + + store = ZenStore() + + @store(name="my_app") + def task_function(player1: str, player2: str): + with open("player_log.txt", "w") as f: + f.write("Game session log:\n") + f.write(f"Player 1: {player1}\n" f"Player 2: {player2}") + + return player1, player2 + + if __name__ == "__main__": + store.add_to_hydra_store() + zen(task_function).hydra_main(config_name="my_app", + version_base="1.1", + config_path=None, + ) + +Here, applying ``@store("my_app")`` to ``task_function`` is equivalent to + +.. code-block:: python + + store(builds(task_function, populate_full_signature=True), name="my_app") + + Reference Documentation ======================= Want a deeper understanding of how hydra-zen and Hydra work? diff --git a/docs/source/tutorials/basic_app.rst b/docs/source/tutorials/basic_app.rst index 60618e0d..b9572c85 100644 --- a/docs/source/tutorials/basic_app.rst +++ b/docs/source/tutorials/basic_app.rst @@ -21,7 +21,8 @@ Create and Launch a Basic Application with Hydra ================================================ In this tutorial we will create a basic application that we can configure and launch -using Hydra. Although this project will be trivial, we will be introduced to the +using Hydra, within a Python program (the next section of the tutorial will introduce a +CLI). Although this project will be trivial, we will be introduced to the overarching design that is used for any Hydra-based project, as well to the core functionality provided by hydra-zen. @@ -71,6 +72,9 @@ Hydra-based application: 1. A "config", which defines the configurable interface of our application. 2. A task function, which accepts the populated config, and whose body specifies the code that will be executed when our application is launched. +We will then use `hydra_zen.launch(Config, task_function, overrides=<...>)` to +configure and run our task-function from within a python program. + Once we create this typical Hydra app, we will iterate on it and simplify things by using how hydra-zen's :func:`hydra_zen.zen` wrapper. Writing the Application @@ -87,6 +91,9 @@ this file. from hydra_zen import make_config, instantiate Config = make_config("player1", "player2") + # A dataclass-type with fields + # - player1: Any + # - player2: Any def task_function(cfg): # cfg: Config @@ -232,8 +239,6 @@ In the final section, let's see how we can simplify some of our code using speci Simplifying Things with :func:`hydra_zen.zen` --------------------------------------------- -.. note:: This part of the tutorial requires ``hydra-zen v0.9.0`` or later to be installed. - We can simplify our task function, removing Hydra-specific logic from it, by using :func:`hydra_zen.zen`. Let's update our task function in `my_app.py` to be a simple function whose signature determines the fields that will be extracted and instantiated from our config. @@ -254,9 +259,14 @@ Then we can use :func:`hydra_zen.builds`, instead of :func:`hydra_zen.make_confi return player1, player2 - # auto-populates the fields of our configs based on the signature of - # `task_function` Config = builds(new_task_function, populate_full_signature=True) + # `builds` auto-populates the fields of our config based on the + # signature of `task_function` + # + # `Config` is dataclass-type whose fields are + # - player1: str + # - player2: str + # - _target_: str = "my_app.task_function" Wrapping this function as diff --git a/docs/source/tutorials/config_groups.rst b/docs/source/tutorials/config_groups.rst index 1bd7dc26..20cdc3db 100644 --- a/docs/source/tutorials/config_groups.rst +++ b/docs/source/tutorials/config_groups.rst @@ -201,13 +201,7 @@ over. Modify your ``my_app.py`` script to match the following code. if __name__ == "__main__": - # We need to add the configs from our local store to Hydra's - # global config store store.add_to_hydra_store() - - # Our zen-wrapped task function is used to generate - # the CLI, and to specify which config we want to use - # to configure the app by default zen(task_function).hydra_main(config_name="my_app", version_base="1.1", config_path=".",