quicopt.client¶
client ¶
quicopt.client — talk to the Quicopt service over HTTP.
Encode a model (quicopt.wire), POST it, read the result back. Standard library
only (urllib): sending adds no dependency, like the rest of the package. The
request body is the encoded model; the response is the service's
result JSON (status / objective / solution / a ready-to-print
display / …). The first keyless call mints an API key, returned in the
X-Quicopt-Api-Key response header; it is cached at
$XDG_CACHE_HOME/quicopt/free_key and replayed as Authorization: Bearer on
every later call — including from a later process, so one caller keeps one key
rather than minting a fresh one per run.
Two entry points mirror the two service endpoints:
- :meth:
Client.solve— POST/v1/solve, block for the result (synchronous). - :meth:
Client.submit— POST/v1/jobs, return a :class:Jobto poll.
A non-2xx response raises :class:QuicoptError, carrying the service's stable
reason code and the framed display text.
Example
Maximize 3x + 5y subject to x + 2y ≤ 5, with 0 ≤ x ≤ 4 and y
binary:
import pulp
from quicopt import Client
prob = pulp.LpProblem("mix", pulp.LpMaximize)
x = pulp.LpVariable("x", lowBound=0, upBound=4)
y = pulp.LpVariable("y", cat="Binary")
prob += 3 * x + 5 * y
prob += x + 2 * y <= 5
result = Client().solve(prob) # read, encode, POST, wait
print(result.status, result.objective) # optimal 14.0
print(result.solution) # {'x': 3.0, 'y': 1.0}
print(result.model_class) # milp
print(result.display) # the service's own summary
The model is passed as it stands — Pyomo, MathOpt or PuLP; the matching
importer is chosen by type, so nothing here changes with the library, and a
:class:~quicopt.ir.Program or already-encoded bytes are accepted too. No
solver runs locally and none need be installed. Which kind of model it was is
the service's finding, reported back as model_class, not something to
declare.
For a long solve, :meth:Client.submit returns immediately with a
:class:Job and :meth:Job.result waits on it:
KEY_PATH_ENV
module-attribute
¶
Environment variable overriding where the free key is cached. Point it at durable storage in an environment whose home directory does not survive the run (CI, containers, Colab), where the default location is wiped between sessions and every run would otherwise mint a fresh key.
DEFAULT_BASE_URL
module-attribute
¶
The public Quicopt free-tier endpoint a :class:Client targets when no
base_url is given. Mirrors the Julia client's DEFAULT_BASE_URL so both
clients reach the same server out of the box.
Result
dataclass
¶
Result(
job_id: str,
status: str,
objective: Optional[float],
feasible: Optional[bool],
solution: Dict[str, float],
solve_time_seconds: float,
solver_data: Dict[str, Any],
display: str,
)
A finished solve, parsed from the service's result JSON. objective and
feasible are None where the outcome leaves them undefined — a model
with nothing to feasibility-check, or a run that found no solution.
display is the framed, ready-to-print summary the service renders the same
way for every kind of model.
model_class
property
¶
The kind of model the service recognised (LP/MILP/QUBO/…).
Returns:
| Type | Description |
|---|---|
Optional[str]
|
The |
Optional[str]
|
service did not report one. |
QuicoptError ¶
Bases: Exception
A non-2xx service response. reason is the service's stable snake_case
code (size_exceeded, unsupported_model, quota_exhausted, …),
display the framed message to print, status_code the HTTP status.
Source code in src/quicopt/client.py
Client ¶
Client(
base_url: str = DEFAULT_BASE_URL,
api_key: Optional[str] = None,
*,
timeout: float = 60.0,
key_path: Optional[Union[str, Path]] = None,
cache: bool = True
)
A connection to a Quicopt service at base_url — the public free tier
(:data:DEFAULT_BASE_URL) unless another URL is given. Holds the API key: pass
a known one, or let the first keyless call mint one — which is then cached on
disk and reused by later runs, so one caller keeps one key.
Bind a client to a service endpoint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
The service base URL; a trailing slash is stripped. Defaults
to :data: |
DEFAULT_BASE_URL
|
api_key
|
Optional[str]
|
A known API key, or |
None
|
timeout
|
float
|
Per-request socket timeout, in seconds. |
60.0
|
key_path
|
Optional[Union[str, Path]]
|
Where to cache the free key. Defaults to
:func: |
None
|
cache
|
bool
|
Set |
True
|
Source code in src/quicopt/client.py
solve ¶
solve(
model: _Model,
*,
project: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
gzip: bool = False
) -> Result
Solve model synchronously, blocking until the result returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
_Model
|
A model from a modeling library (Pyomo, OR-Tools MathOpt, PuLP)
— read and encoded here — or a |
required |
project
|
Optional[str]
|
Optional project tag for the call, so calls on one key can be invoiced per project. Sent as a query param, not baked into the model. |
None
|
config
|
Optional[Dict[str, Any]]
|
Optional service parameters, sent as the query string. |
None
|
gzip
|
bool
|
If |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
Result |
Result
|
The finished solve. |
Raises:
| Type | Description |
|---|---|
QuicoptError
|
On a non-2xx response. |
Source code in src/quicopt/client.py
submit ¶
submit(
model: _Model,
*,
project: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
gzip: bool = False
) -> "Job"
Submit model for asynchronous solving and return a handle to poll.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
_Model
|
A Pyomo/MathOpt/PuLP model, a |
required |
project
|
Optional[str]
|
Optional project tag for the call (per-project invoicing), sent as a query param, not baked into the model. |
None
|
config
|
Optional[Dict[str, Any]]
|
Optional service parameters, sent as the query string. |
None
|
gzip
|
bool
|
If |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
Job |
'Job'
|
A handle to the queued job; call :meth: |
Raises:
| Type | Description |
|---|---|
QuicoptError
|
On a non-2xx response. |
Source code in src/quicopt/client.py
Job
dataclass
¶
Job(client: Client, job_id: str)
A handle to an async job. :meth:result polls until it finishes.
status ¶
Fetch the job's metadata and framed log_tail.
Returns:
| Name | Type | Description |
|---|---|---|
dict |
Dict[str, Any]
|
The job state ( |
Dict[str, Any]
|
log tail, as returned by the service. |
Source code in src/quicopt/client.py
result ¶
result(
*,
wait: bool = True,
timeout: float = 120.0,
poll: float = 0.5
) -> Result
Fetch the job's result, optionally polling until it is ready.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wait
|
bool
|
If |
True
|
timeout
|
float
|
Maximum time to poll, in seconds, before giving up. |
120.0
|
poll
|
float
|
Delay between polls, in seconds. |
0.5
|
Returns:
| Name | Type | Description |
|---|---|---|
Result |
Result
|
The finished solve. |
Raises:
| Type | Description |
|---|---|
QuicoptError
|
If |
Source code in src/quicopt/client.py
log ¶
Fetch the job's plain-text log.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The framed log view on success, or the error text on failure. |
delete ¶
Delete the job and its stored blob/result/log on the server.
Returns:
| Type | Description |
|---|---|
None
|
None. |