Skip to content

Advanced Patterns

Advanced usage patterns for production IVCAP lambda services.

Model Loading with Readiness Check

Load a machine-learning model at startup and prevent the health check from succeeding until loading is complete:

import time
from ivcap_lambda import ivcap_lambda, ToolOptions, start_lambda_server, logging_init
from ivcap_service import Service, ServiceContact, with_schema, getLogger

logging_init()
logger = getLogger("ml_service")

service = Service(
    name="ML Inference Service",
    contact=ServiceContact(name="ML Team", email="ml@example.com"),
)

# ── Model loading ─────────────────────────────────────────────────────────────

_model = None
_model_ready = False

def is_ready() -> bool:
    return _model_ready

def load_model():
    global _model, _model_ready
    logger.info("Loading model…")
    time.sleep(2)  # simulate load
    _model = {"type": "mock"}
    _model_ready = True
    logger.info("Model ready")


@with_schema("urn:example:schema:predict.request.1")
class PredictRequest(BaseModel):
    text: str = Field(..., description="Text to classify.")


@with_schema("urn:example:schema:predict.1")
class PredictResult(BaseModel):
    label: str = Field(..., description="Predicted label.")
    confidence: float = Field(..., description="Confidence score (0–1).", ge=0, le=1)


@ivcap_lambda(
    "/predict",
    opts=ToolOptions(tags=["ML"], is_ready=is_ready, max_wait_time=30.0),
)
def predict(req: PredictRequest) -> PredictResult:
    """Classify text using the loaded model

    Returns a label and confidence score for the provided text.
    The service will report 503 until the model is fully loaded.
    """
    # Use the pre-loaded model
    label = "positive" if len(req.text) % 2 == 0 else "negative"
    return PredictResult(label=label, confidence=0.95)


# Load model at module level, before start_lambda_server
load_model()

if __name__ == "__main__":
    start_lambda_server(service)

Custom CLI Arguments

Add service-specific command-line flags:

import argparse
import os
from ivcap_lambda import start_lambda_server

def parse_args(parser: argparse.ArgumentParser) -> argparse.Namespace:
    parser.add_argument(
        "--model-path",
        type=str,
        default=os.environ.get("MODEL_PATH", "model.pkl"),
        help="Path to the model file",
    )
    parser.add_argument(
        "--cache-ttl",
        type=int,
        default=int(os.environ.get("CACHE_TTL", "3600")),
        help="Job result cache TTL in seconds",
    )
    args = parser.parse_args()
    # Propagate to environment for later use
    os.environ["MODEL_PATH"] = args.model_path
    os.environ["CACHE_TTL"] = str(args.cache_ttl)
    return args

if __name__ == "__main__":
    start_lambda_server(service, custom_args=parse_args)

Shared Execution Context

Pass a shared context object to every tool invocation:

from ivcap_lambda import ivcap_lambda, ToolOptions
from ivcap_lambda.executor import ExecutionContext

class DatabaseContext(ExecutionContext):
    def __init__(self, db_url: str):
        self.db = connect(db_url)

db_context = DatabaseContext(db_url="sqlite:///my.db")

@ivcap_lambda(
    "/lookup",
    opts=ToolOptions(tags=["Data"]),
    context=db_context,
)
def lookup(req: LookupRequest, ctx: DatabaseContext) -> LookupResult:
    """Look up a record in the database"""
    record = ctx.db.query(req.id)
    return LookupResult(data=record)

Registering Routes Programmatically

Use add_tool_api_route instead of the decorator when you want to register tools in a loop or factory:

from ivcap_lambda.builder import add_tool_api_route, ToolOptions
from ivcap_lambda.server import get_fast_app

app = get_fast_app()

for tool_name, handler, request_cls, result_cls in tool_definitions:
    add_tool_api_route(
        app,
        f"/{tool_name}",
        handler,
        opts=ToolOptions(tags=[tool_name.title()]),
    )

Streaming Progress with Fine-Grained Events

Emit detailed progress events for long-running batch operations:

from ivcap_service.events import GenericEvent

@ivcap_lambda("/batch-process")
def batch_process(req: BatchRequest, jobCtxt: JobContext) -> BatchResult:
    """Process a batch of items"""
    total = len(req.items)
    results = []

    with jobCtxt.report.step("process", f"Processing {total} items") as step:
        for i, item in enumerate(req.items):
            result = process_item(item)
            results.append(result)

            # Emit progress every 10 items
            if (i + 1) % 10 == 0 or (i + 1) == total:
                step.info(GenericEvent(
                    name="batch-progress",
                    options={
                        "processed": i + 1,
                        "total": total,
                        "pct": round((i + 1) / total * 100, 1),
                    },
                ))
        step.finished(f"Processed {total} items")

    return BatchResult(results=results, count=total)

Using Secrets

Access platform secrets via SecretMgrClient (from ivcap_service):

from ivcap_service.secret import SecretMgrClient

_secret_client = SecretMgrClient()

@ivcap_lambda("/call-api")
def call_api(req: ApiRequest, jobCtxt: JobContext) -> ApiResult:
    """Call an external API using a stored secret"""
    api_key = _secret_client.get_secret("external-api-key")
    response = requests.get(
        req.url,
        headers={"Authorization": f"Bearer {api_key}"},
    )
    return ApiResult(status=response.status_code, body=response.text)

See Also