Milestone 9: generate and use the Python SDK
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.