Services#

OrangeQS Juice services are fully customizable Python programs running within the OrangeQS Juice framework. By default, OrangeQS Juice ships with the IPythonService type which runs an IPython kernel, with a customizable module to run on startup. You can extend this functionality by implementing a class based on the Service class, of which IPython service is an example.

By implementing a Python program as an OrangeQS Juice service you benefit from all the built-in functionality of OrangeQS Juice, like centralized logging, monitoring, service discovery, etc.

Example: HTTP server#

This example shows how to build an OrangeQS Juice service that runs a HTTP server.

Any OrangeQS Juice service needs to implement the base class Service, which has a constructor and the Service.start method. Let’s start by defining an HTTPService class that can be used as an OrangeQS Juice service.

from orangeqs.juice.service import Service

class HTTPService(Service):
    """An OrangeQS Juice service that runs a HTTP server"""

    def __init__(self, service_name: str):
        super().__init__(service_name)
        # TODO: Initialize HTTP server

    def start(self):
        # TODO: Start HTTP server

For this example we will use a HTTP server based on the Tornado framework. We define the request handler for our HTTP application:

import tornado

class HTTPServiceHandler(tornado.web.RequestHandler):
    def initialize(self, service_name):
        self.service_name = service_name

    def get(self):
        self.write(f"Hello from {self.service_name}")

Let’s now add the HTTP application to our custom HTTPService class.

import asyncio

from orangeqs.juice.service import Service

class HTTPService(Service):
    """An OrangeQS Juice service that runs a HTTP server"""

    def __init__(self, service_name: str):
        super().__init__(service_name)
        # Initialize the HTTP application
        self.app = tornado.web.Application([
            (r"/", HTTPServiceHandler)
        ])

    async def serve():
        """Serve HTTP request asynchronously forever."""
        self.app.listen(8000)
        await asyncio.Event().wait()

    def start(self):
        """
        Start serving HTTP requests using the `self.serve()` coroutine.

        This function never returns and runs forever.
        """
        asyncio.run(self.serve())

The final step is to configure an OrangeQS Juice service to use this class. For this you need to point your service to the HTTPService you have just created. You can do this by adding the following section to your OrangeQS Juice orchestrator configuration.

[orchestration.services.http_service]
# Entrypoint follows the format `path.to.your.module:ClassName`
entrypoint = "juice_extension_example:HTTPService"

Reporting service info#

Every Service automatically records information about itself on startup, queryable via get_service_info(), including historical lookups. Service calls two hooks automatically as part of __init__, which you can override if your service needs to:

  • _setup_service_specifics() — perform any setup your service needs. Called before service info is gathered and stored, so any data you want to report (see below) should be ready by the time it returns. Does nothing by default.

  • _service_info_kwargs() — supply extra values for the fields store_service_info accepts (shell_port, iopub_port, stdin_port, control_port, hb_port, ip, init_module). Returns nothing extra by default.

IPythonService is the built-in example of overriding both — it sets up the embedded kernel in _setup_service_specifics, then reports the kernel’s ports and init module via _service_info_kwargs.

You don’t need to override either hook unless your service has its own setup to perform, or data matching one of those specific fields to report.

To look up this info, either the current state or a historical snapshot from a specific point in time:

from datetime import datetime
from orangeqs.juice import Client

client = Client()

client.get_service_info("my-service")  # current info
client.get_service_info("my-service", timestamp=datetime(2026, 3, 1))  # historical info

A timestamp lookup does not need to match a snapshot exactly - it returns the most recent snapshot at or before that timestamp, i.e. whatever the service was actually running at that point in time.

A run ID also has a direct connection to this lookup: it already encodes a timestamp in its own format (see Juice Identifiers and Run IDs), so you can parse it directly and use that timestamp instead of tracking one down separately. Since the lookup finds the closest snapshot at or before that moment, you’ll get exactly what was running when that run started, even though a snapshot was almost certainly never taken at that precise instant:

from orangeqs.juice import identifiers, Client

_, service, timestamp = identifiers.parse_id(run_id)
info = Client().get_service_info(service, timestamp=timestamp)

As a next step when creating a service, you would likely want to learn how to use Juice’s communication framework