.. _custom-service-reference: Creating Your Own service ========================= This tutorial will continue from where :ref:`first-service-reference` left off. There we created a basic service using the ``mvi init`` command. >>> mvi init project_name [my_project]: my_first_mvi_project And this created three files and a directory. >>> ls ./my_first_mvi_project -a . .. .s2i README.md requirements.txt service.py Where `service.py` contains the service source code. Let's look at how a typical `service.py` file might look. Setup ----- As usual, the first step is to import packages:: import logging from mvi.mvi import MviService, notify, Severity Only :py:class:`~mvi.mvi.MviService` is actually required, but we recommend to import and use the standard python `logging `_ package. :py:func:`~mvi.mvi.notify` and :py:class:`~mvi.mvi.Severity` are used for the notification functionality in MVI. If any external packages are used, they must be specified in the `requirements.txt` file. That way they will be installed the service is deployed. .. note:: It is highly recommended to **pin** your requirements to specific versions when in a production environment, for example `numpy==1.19.4` Next we initialize a :py:class:`~mvi.mvi.MviService` object and a logger:: mvi = MviService() logger = logging.getLogger(__name__) The :py:class:`~mvi.mvi.MviService` object helps us set up entrypoints for the service, add parameters and start the service. The logger is just a regular python logging object, which MVI natively supports. The logs from a service can be read from the dashboard or using ``mvi logs ``. Defining parameters ------------------- It is possible to define variables that automatically get exposed to the API and can be freely changed from outside a running service:: mvi.add_parameter("greeting_phrase", "Hello") To use the parameter in the code you do:: greeting_phrase = mvi.get_parameter("greeting_phrase") This way you can control the behaviour of your running services without having to make any code changes. We recommend using this for e.g. tuning parameters. Creating an Entrypoint ---------------------- To define an entrypoint for a service we use the :py:meth:`~mvi.mvi.MviService.entrypoint` decorator:: @mvi.entrypoint def hello(name: str) -> str: greeting_phrase = mvi.get_parameter("greeting_phrase") logger.info(f"Greeting someone with the name: {name}") return f"{greeting_phrase} {name}" This will automatically expose the :py:func:`hello` function to the API. We strongly recommend that you use type hints in your MVI entrypoint functions. That way, you will get type verification in your API and the auto-generated documentation will show the expected data types. Please take a look at :ref:`sdk-typing-reference` for a more detailed guide on how typing is handled in MVI. Notifications ------------- Notifications is another feature of MVI. When a notification is raised it can be viewed on the dashboard at http://your-host or sent to an email address. To add a notification we use the :py:func:`~mvi.mvi.notify` function. They can be placed in a conditional statement to send a notification if it's true. With notifications added, the :py:func:`hello` function now looks like this:: @mvi.entrypoint def hello(name: str) -> str: greeting_phrase = mvi.get_parameter("greeting_phrase") if name == "World": notify( msg="Someone is trying to greet the World, too time consuming. Skipping!", severity=Severity.WARNING, ) return "Greeting failed" logger.info(f"Greeting someone with the name: {name}") return f"{greeting_phrase} {name}" Here we want to stop the user from greeting the world, because that would take a lot of time. So if the name is "World" we send a notification. Starting the Service -------------------- The last thing we have to do is to ensure the service runs once it is deployed:: mvi.run() Full Code --------- The code we just looked at is the same code that is generated by ``mvi init`` by default. It is fewer than 25 lines of code to create a fully functional service complete with logging, notifications and configurable parameters. All together it looks like this:: import logging from mvi.mvi import MviService, notify, Severity mvi = MviService() logger = logging.getLogger(__name__) mvi.add_parameter("greeting_phrase", "Hello") @mvi.entrypoint def hello(name: str) -> str: greeting_phrase = mvi.get_parameter("greeting_phrase") if name == "World": notify( msg="Someone is trying to greet the World, too time consuming. Skipping!", severity=Severity.WARNING, ) return "Greeting failed" logger.info(f"Greeting someone with the name: {name}") return f"{greeting_phrase} {name}" mvi.run() Deploying the Service --------------------- As a reminder from :ref:`first-service-reference` we will deploy our new service. >>> mvi deploy hello 1.0.0 ./my_first_mvi_project/ Deploying service... Service deployed successfully MAIN NAME VERSION STATUS RUNNING ------ ------ --------- -------- ----------------------------------- * hello 1.0.0 running Running (since 2020-11-23 10:29:01) What's Next? ------------ Now you have seen the different components of the SDK and you should be ready to create your own service. The next step could be to take a look at the manager :ref:`dashboard-reference`, or the :ref:`sdk-reference` documentation.