.. _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 # doctest: +SKIP project_name [my_project]: my_first_mvi_project And this created three files and two directories. >>> ls ./my_first_mvi_project -a # doctest: +SKIP . .. .s2i/ README.md requirements.txt service.py tests/ 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. We also initialize a logger so we can get information about our services for debugging: .. testcode:: import logging from mvi import service from mvi.communication import Severity, notify logger = logging.getLogger(__name__) Only :py:obj:`service` is actually required, but we recommend to import and use the standard python `logging `_ package. :py:func:`~mvi.communication.notify` and :py:class:`~mvi.communication.Severity` in the :py:mod:`mvi.communication` module 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` The :py:obj:`service` 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 name version``. .. _custom-service_defining_parameters-reference: Defining parameters ------------------- It is possible to define parameters that automatically get exposed to the API and can be freely changed from outside a running service .. testcode:: service.add_parameter("greeting_phrase", "Hello") Get the value of a parameter with .. testcode:: greeting_phrase = service.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. These parameters can be changed by sending a HTTP POST request, for instance via pythons `requests `_ library or the docs of the service, see :ref:`autodocs-reference`. Example of changing the ``greeting_phrase`` parameter for this service:: json_data = {"greeting_phrase": "Bonjour"} response = requests.post( "http:://your-host/services/name_version/parameters/greeting_phrase", json=json_data, headers={"Authorization": f"Bearer {TOKEN}"}) Creating an Entrypoint ---------------------- To define an entrypoint for a service we use the :py:obj:`~mvi.service.entrypoint` decorator .. testcode:: @service.entrypoint def hello(name: str) -> str: greeting_phrase = service.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. .. note:: MVI entrypoints should have JSON-compatible data as input and output. Note that e.g. ``numpy.ndarray`` and ``pandas.DataFrame`` are not JSON-compatible and must be converted to lists or dictionaries. Read :ref:`sdk-typing-non-json-reference` on how to make it easier to use such data types. 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.communication.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 .. testcode:: @service.entrypoint def hello(name: str) -> str: greeting_phrase = service.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 .. testcode:: if __name__ == '__main__': service.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 .. testcode:: import logging from mvi import service from mvi.communication import Severity, notify logger = logging.getLogger(__name__) service.add_parameter("greeting_phrase", "Hello") @service.entrypoint def hello(name: str) -> str: greeting_phrase = service.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}" if __name__ == '__main__': service.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/ # doctest: +SKIP 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.