diff --git a/sphinx-docs/source/cookbook/console_applications/create-application-from-scratch/2-creating-the-application.rst b/sphinx-docs/source/cookbook/console_applications/create-application-from-scratch/2-creating-the-application.rst index e24b6811..ed1effa2 100644 --- a/sphinx-docs/source/cookbook/console_applications/create-application-from-scratch/2-creating-the-application.rst +++ b/sphinx-docs/source/cookbook/console_applications/create-application-from-scratch/2-creating-the-application.rst @@ -24,7 +24,6 @@ First, let's add some imports that our application depends on. from injector import inject from Ligare.programming.application import ApplicationBase, ApplicationBuilder - from Ligare.programming.config import Config from typing_extensions import override We're going to write an application that displays a message stored in a configuration file. @@ -32,48 +31,118 @@ We're going to write an application that displays a message stored in a configur To do this, we need to do a few things: 1. Create an ``Application`` class -2. Create a ``Config`` class +2. Create a ``Config`` class and file 3. Configure the application Creating the Application Class ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -All Ligare applications are extensions of ``ApplicationBase``. These classes must implement a ``run`` method, which can use the Ligare dependency injection system. - -For our console application, we're going to print a message from an instance of a ``Config`` class. +All Ligare applications are extensions of ``ApplicationBase``. These classes must implement a +``run`` method, which can use the Ligare dependency injection system. .. code-block:: python class Application(ApplicationBase): - @inject @override - def run(self, config: "AppConfig"): - print(config.message) + def run(self): + print("Hello world!") input("\nPress anything to exit. ") Here, we have created a class called ``Application`` that extends ``ApplicationBase``. -We then implement the run method with a single parameter named ``config``. The use of -``@inject`` ensures that the run method receives an instance of ``AppConfig`` as the -value of ``config``. - -There is no need to instantiate ``Application``, no need to call ``run``, and no need -to pass in the value of ``config`` ourselves - Ligare will do this for us. -To set up Ligare to run our application, we will use ``ApplicationBuilder``. +Although we could run our application by instantiating ``Application`` and calling ``run``, +we would lose out on what Ligare care do for us. To take advantage of that functionality, +we will use ``ApplicationBuilder``. .. code-block:: python builder = ApplicationBuilder(Application) This gives us a builder for our ``Application`` class with which we can configure the -application, and then start it. While there are a variety of options we can set, -this guide will only go over ``use_configuration``. +application, and then start it. + +This is the minimum required to set up our application. We can now "build" it, +and then start it. + + +.. code-block:: python + + application = builder.build() + + if __name__ == "__main__": + application.run() + +Our completed application looks like this. + +.. code-block:: python + + from injector import inject + from Ligare.programming.application import ApplicationBase, ApplicationBuilder + from typing_extensions import override + + class Application(ApplicationBase): + @inject + @override + def run(self): + print("Hello, world!") + input("\nPress anything to exit. ") + + builder = ApplicationBuilder(Application) + + application = builder.build() + + if __name__ == "__main__": + application.run() + +When we run the application, we see the expected output. + +.. code-block:: shell-session + + user@: my-ligare-app $ python -m app + Hello, world! + + Press anything to exit. + +Creating the Config Class +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Now let's see how to create the ``Config`` class and make better use of Ligare. + +Here we will see: + +* How to use Ligare's dependency injection system +* How to set up application configuration with a file +* How to use configuration values from a configuration file + +Start by importing Ligare's base ``Config`` class. + +.. code-block:: python + + from Ligare.programming.config import Config + +To create a ``Config`` class for our application, we need to extend ``Config`` +and add any fields to it that we expect to be in a configuration file, and +that we want the application to be able to read. .. code-block:: python class AppConfig(Config): message: str +Because we want to display a message from a configuration file, we just add +a "message" field to our class. We will not set this value directly; this class +is a Pydantic dataclass whose values Ligare will set from a TOML file. + +Registering the Config Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Ligare to know about this class, and to tell Ligare where our configuration +file is, we use the ``use_configuration`` method from the ``ApplicationBuilder`` +instance to "register" the class with Ligare's dependency injection system. +This must occur before ``builder.build()`` is called. + +.. code-block:: python + builder.use_configuration( lambda config_builder: config_builder \ .with_config_type(AppConfig) \ @@ -85,56 +154,60 @@ That parameter type is itself an ``ApplicationConfigBuilder`` that is used to se options specific to the configuration of a Ligare application. `It's builders all the way down! `_ -First we create our ``Config`` class, ``AppConfig``. This class is a Pydantic dataclass -whose values Ligare will set from a TOML file. +Using the Config Class +~~~~~~~~~~~~~~~~~~~~~~ -Then, within the function passed to ``use_configuration``, we set both the type of our -config class, and the path to the TOML file. +Now we need to adjust our run method. -This is the minimum required to set up our application. We can now "build" it, -and then start it. +.. code-block:: python + class Application(ApplicationBase): + @inject # 1 + @override + def run(self, config: "AppConfig"): # 2 + print(config.message) # 3 + input("\nPress anything to exit. ") -.. code-block:: python +There are three notable changes here: - application = builder.build() +1. The ``@inject`` decorator was added to the run method +2. The ``config`` parameter was added to the run method +3. The ``config`` object is used to print a field value called ``message`` - if __name__ == "__main__": - application.run() +Ligare supports the use of `Dependency Injection `_ +so you can use certain objects throughout your application without having to instantiate them yourself. +In this case, ``run(config)`` is called automatically by Ligare. Because the ``@inject`` decorator is present (#1), +the value of ``config`` is created automatically, and it is passed into your run method. +We then need to specify what the type of the parameter is (#2); in this case, the type is ``AppConfig``. +This is accomplished with Python's `type annotation `_ system, +by writing ``config: "AppConfig"``. +Finally, we use the value of ``config`` to access the field ``message``, and pass that into ``print``. (#3) -.. First, ``Ligare.web`` uses a `builder `_ to set up an instance of your application during runtime. -.. This allows us to `build` our application using specific functions to configure desired beavhiors and features. -.. -.. .. seealso:: -.. -.. A `builder `_ is a pattern that helps programmers configure an instance of some other `type`. -.. For this guide, it helps us configure an instance of ``FlaskApp``, which is the -.. class name for the type of application we're creating. -.. -.. -.. The class we need for this is :obj:`ApplicationBuilder[T] `. We also need the application type. For this example, we're using `FlaskApp `_. -.. -.. .. seealso:: -.. -.. A "generic" class is a way to specify `generic` functionality for a range of more specific types. -.. Here, we use the generic class :obj:`ApplicationBuilder[T] ` to configure `generic` aspects of -.. ``FlaskApp``. -.. Here, the "generic type" is named ``T`` and is how we reference the more specific type that -.. it represents. -.. -.. .. note:: -.. -.. :obj:`ApplicationBuilder[T] ` supports one other type of class called ``Flask``. -.. Review `this guide `_ to see how to create a ``Flask`` application. +With these changes, your application should resemble the Ligare example CLI application. .. literalinclude:: ../../../../../examples/cli-app/app/__main__.py :language: python :caption: :example:`cli-app/app/__main__.py` -Now we have something we can run. Go ahead and run this code. +Creating the Config File +^^^^^^^^^^^^^^^^^^^^^^^^ + +There is one last thing to do, which is to create the configuration file and store our message in it. + +.. code-block:: shell-session + + user@: my-ligare-app $ cat > app/config.toml <