Solver as a Service
Sign Up Now →Sign Up Now

Milestone 9: generate and use the Python SDK

Preview

Cross-tier clients come from OpenAPI through helixor-sdk-gen; do not copy the request models or hand-write a second HTTP client.

Dump the contract from source so generation does not depend on a running server:

PYTHONPATH=helixor-solver-server/src:helixor-solvers/src:helixor-core/src \
  python helixor-solver-server/scripts/dump_solver_openapi.py \
  --output solver-server.openapi.json

Create helix-sdk.yml:

package_name: helixor-solver-client
package_version: 0.1.0
languages: [python]
base_url: https://solver.helixor.dev
plugins: [operation_contracts]
diagnostics:
  fail_on_error: true
  emit_coverage: true

Generate locally or use the hosted generator:

python -m pip install helixor-sdk-gen

# Reproducible local/air-gapped generation.
helix-sdk-gen generate helix-sdk.yml solver-server.openapi.json \
  --out generated/solver --local

python -m pip install -e generated/solver/python

The generated names below come from the current FastAPI operation IDs. Treat operation-contracts.json as the inspectable map when an operation is renamed:

import os

from helixor_solver_client import ApiClient, models
from helixor_solver_client.errors import ApiHttpException

client = ApiClient(
    base_url=os.environ.get("SOLVER_URL", "https://solver.helixor.dev"),
    headers={
        "Authorization": f"Bearer {os.environ['SOLVER_TOKEN']}",
    },
    timeout=120.0,
)

catalog = client.list_manifests_api_v1_manifests_get()
retail_manifest = client.get_manifest_api_v1_manifests_problem_type_get(
    "retail_rota"
)

problem = {
    "employees": [
        {
            "id": "alice",
            "skills": ["sales", "key_holder"],
            "max_hours": 16,
            "acceptable_shift_ids": ["mon-open"],
        },
        {
            "id": "ben",
            "skills": ["sales"],
            "max_hours": 16,
            "acceptable_shift_ids": ["mon-close"],
        },
    ],
    "shifts": [
        {
            "id": "mon-open",
            "day": 0,
            "duration_hours": 8,
            "required_skill": "key_holder",
            "required_staff": 1,
        },
        {
            "id": "mon-close",
            "day": 0,
            "duration_hours": 8,
            "required_skill": "sales",
            "required_staff": 1,
        },
    ],
}

try:
    response = client.solve_api_v1_solve_problem_type_post(
        "retail_rota",
        models.ApiSolveRequest(
            problem=problem,
            solver={},
            options={"engine": "benders_dw"},
        ),
    )
except ApiHttpException as exc:
    # Preserve the typed server envelope; do not replace it with a default plan.
    print(exc.parsed_error)
    raise

print(response["status"])
print(response.get("result", {}).get("solution"))

Note

The current solver OpenAPI does not declare its authentication security scheme even though the service enforces authentication. Consequently the generated client accepts explicit headers rather than a generated bearer_token argument. That OpenAPI gap should be fixed at the service and regenerated, not worked around with a second client implementation.