Skip to content

remove extra or unnecessary parameters in Python gRPC api - #1654

Open
tmckayus wants to merge 3 commits into
NVIDIA:mainfrom
tmckayus:grpc-client-get-result
Open

remove extra or unnecessary parameters in Python gRPC api#1654
tmckayus wants to merge 3 commits into
NVIDIA:mainfrom
tmckayus:grpc-client-get-result

Conversation

@tmckayus

@tmckayus tmckayus commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

On review of the Python gRPC api, we have a few unnecessary parameters that can be removed:

  1. "is_mip" on result is not necessary, can and should be learned from the server. Not only is it unnecessary, actually, but the implementation is seriously flawed. The is_mip value is currently being tracked in the client itself, not by data downloaded, and this parameter is being set by the client. This means that the same client can't be used for mixed submission of LPs and MIPs in a series of submissions and then retrieved later. Also, the same job can't be submitted by one client and retrieved by another unless the job type is known. This change adds a new get_result API to support the Python client which determines the job type from the download.

  2. the "callback" parameter on start_incumbent_stream is not needed because incumbent callbacks are registered through "settings" as with a local solve, and in fact at least one registration in "settings" is needed for the server to send incumbent solutions at all. So "callback" is unnecessary and confusing. Remove it from the API, the internal plumbing to support it will be removed later.

@tmckayus tmckayus added this to the 26.08 milestone Aug 2, 2026
@tmckayus tmckayus self-assigned this Aug 2, 2026
@tmckayus
tmckayus requested review from a team as code owners August 2, 2026 00:07
@tmckayus tmckayus added the non-breaking Introduces a non-breaking change label Aug 2, 2026
@tmckayus tmckayus added the improvement Improves an existing functionality label Aug 2, 2026
@tmckayus
tmckayus requested review from Bubullzz and Kh4ster August 2, 2026 00:07
@tmckayus tmckayus added the bug Something isn't working label Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The C++ and Python gRPC clients now use server-reported result types for unified LP/MIP retrieval. Tests cover unary and chunked downloads. Python incumbent streaming now requires settings-based callbacks.

Changes

Unified result handling

Layer / File(s) Summary
Unified result contracts
cpp/src/grpc/client/grpc_client.hpp, cpp/include/cuopt/grpc/cython_grpc_client.hpp, cpp/src/grpc/client/cython_grpc_client.cpp, python/cuopt/cuopt/grpc/linear_programming/grpc_client.pxd
Added remote_result_t and get_result. Removed the caller-supplied is_mip parameter.
Server-selected result retrieval
cpp/src/grpc/client/grpc_client.cpp
Unified retrieval handles unary and chunked downloads and populates the matching LP or MIP solution.
Result conversion and validation
cpp/src/grpc/client/cython_grpc_client.cpp, cpp/tests/linear_programming/grpc/grpc_client_test.cpp
Python conversion uses the server-selected result. Tests cover unary and chunked LP and MIP retrieval.

Python client stream API

Layer / File(s) Summary
Client result state cleanup
python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx
Removed local MIP job tracking from submission, deletion, and result handling.
Settings-based incumbent delivery
python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx
Incumbent streaming now requires settings with a MIP callback and delivers incumbent events directly.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • NVIDIA/cuopt#1653: Changes the same Python gRPC client result and streaming behavior.

Suggested labels: improvement

Suggested reviewers: kh4ster, ramakrishnap-nv, bubullzz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the removal of unnecessary parameters from the Python gRPC API.
Description check ✅ Passed The description directly explains the removed parameters, server-based result detection, and callback handling changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx (1)

322-332: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Two public Client methods drop parameters without a deprecation shim.

Both result() and start_incumbent_stream() remove a previously accepted keyword argument outright instead of deprecating it. The shared root cause is the same: the review guide requires that breaking Cython/Python signature changes normally preserve compatibility with a DeprecationWarning.

  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx#L322-L332: add a deprecated, ignored is_mip=None keyword to result() that emits a DeprecationWarning when explicitly set, instead of raising TypeError on old call sites.
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx#L511-L517: add a deprecated callback=None keyword to start_incumbent_stream() that emits a DeprecationWarning directing callers to register callbacks through settings, instead of raising TypeError on old call sites.

As per path instructions: "Cython/public Python signature changes are breaking API changes and should normally preserve compatibility with a DeprecationWarning."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx` around lines 322
- 332, Update python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx:322-332
in Client.result to accept an ignored is_mip=None keyword and emit a
DeprecationWarning only when it is explicitly provided; update
python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx:511-517 in
Client.start_incumbent_stream to accept callback=None and emit a
DeprecationWarning directing callers to register callbacks through settings.
Preserve the current behavior otherwise.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx`:
- Around line 535-543: Update the settings validation around get_mip_callbacks
so it requires at least one callback that is an instance of GetSolutionCallback,
rather than accepting any non-null callback. Revise the GrpcError message to
state that a GetSolutionCallback is required for incumbent forwarding, while
preserving the existing settings-required validation.

---

Nitpick comments:
In `@python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx`:
- Around line 322-332: Update
python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx:322-332 in
Client.result to accept an ignored is_mip=None keyword and emit a
DeprecationWarning only when it is explicitly provided; update
python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx:511-517 in
Client.start_incumbent_stream to accept callback=None and emit a
DeprecationWarning directing callers to register callbacks through settings.
Preserve the current behavior otherwise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: bb84c960-d402-4e64-9308-9486803cd95b

📥 Commits

Reviewing files that changed from the base of the PR and between a291a93 and d61440b.

📒 Files selected for processing (8)
  • cpp/include/cuopt/grpc/cython_grpc_client.hpp
  • cpp/src/grpc/client/cython_grpc_client.cpp
  • cpp/src/grpc/client/grpc_client.cpp
  • cpp/src/grpc/client/grpc_client.hpp
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp
  • cpp/tests/linear_programming/grpc/grpc_integration_test.cpp
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pxd
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx

Comment thread python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx Outdated
…cumbents

Add grpc_client_t::get_result alongside the existing get_lp_result /
get_mip_result APIs (internals unchanged for now). Wire the Python async
client result() path through get_result so LP vs MIP comes from the server,
and require SolverSettings mip callbacks for start_incumbent_stream.
Add mock client tests so the unified get_result API is exercised for
both problem types and both download modes.
@tmckayus
tmckayus force-pushed the grpc-client-get-result branch from d61440b to dc593dc Compare August 2, 2026 00:36
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

CI Test Summary

✅ All 31 test job(s) passed.

@tmckayus tmckayus removed the improvement Improves an existing functionality label Aug 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
cpp/tests/linear_programming/grpc/grpc_client_test.cpp (1)

943-947: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the decoded solution vector, not only the objective. All four new tests feed known solution values through the mock but check only the objective value and the pointer identity. The shared root cause is that no test validates the decoded vector, so a decoding defect in the unary or chunked path passes undetected while the objective still matches.

  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp#L943-L947: assert the LP primal solution equals {1.5, 2.5}.
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp#L979-L983: assert the MIP solution equals {1.0, 0.0}.
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp#L1047-L1051: assert the chunk-decoded LP primal solution equals {1.5, 2.5}.
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp#L1115-L1119: assert the chunk-decoded MIP solution equals {1.0, 0.0}.

The chunked sites matter most. They exercise the raw byte reinterpretation in download_chunked_result, where an element-size or offset defect changes the vector but leaves the header objective intact.

As per path instructions: "Numerical correctness validation (not just 'runs without error')".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/tests/linear_programming/grpc/grpc_client_test.cpp` around lines 943 -
947, Extend the assertions in
cpp/tests/linear_programming/grpc/grpc_client_test.cpp at lines 943-947,
979-983, 1047-1051, and 1115-1119 to validate the decoded solution vectors: LP
cases must equal {1.5, 2.5} and MIP cases must equal {1.0, 0.0}; retain the
existing objective and pointer assertions, including for the chunk-decoded
paths.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/tests/linear_programming/grpc/grpc_client_test.cpp`:
- Around line 941-948: Add a regression test near the existing unified unary
get_result tests, named GetResultUnified_UnaryMissingSolution, that mocks a
completed CheckStatus response and a successful GetResult response with neither
solution field populated. Call client_->get_result for the missing-solution job
and assert success is false, both lp_solution and mip_solution are null, and
error_message is non-empty.

---

Nitpick comments:
In `@cpp/tests/linear_programming/grpc/grpc_client_test.cpp`:
- Around line 943-947: Extend the assertions in
cpp/tests/linear_programming/grpc/grpc_client_test.cpp at lines 943-947,
979-983, 1047-1051, and 1115-1119 to validate the decoded solution vectors: LP
cases must equal {1.5, 2.5} and MIP cases must equal {1.0, 0.0}; retain the
existing objective and pointer assertions, including for the chunk-decoded
paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1e13f231-83cf-4798-a5b4-dff43c68fc7c

📥 Commits

Reviewing files that changed from the base of the PR and between d61440b and dc593dc.

📒 Files selected for processing (7)
  • cpp/include/cuopt/grpc/cython_grpc_client.hpp
  • cpp/src/grpc/client/cython_grpc_client.cpp
  • cpp/src/grpc/client/grpc_client.cpp
  • cpp/src/grpc/client/grpc_client.hpp
  • cpp/tests/linear_programming/grpc/grpc_client_test.cpp
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pxd
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx
🚧 Files skipped from review as they are similar to previous changes (5)
  • cpp/include/cuopt/grpc/cython_grpc_client.hpp
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pxd
  • cpp/src/grpc/client/cython_grpc_client.cpp
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx
  • cpp/src/grpc/client/grpc_client.cpp

Comment on lines +941 to +948
auto result = client_->get_result<int32_t, double>("unified-lp-unary");

EXPECT_TRUE(result.success) << result.error_message;
EXPECT_FALSE(result.is_mip);
ASSERT_NE(result.lp_solution, nullptr);
EXPECT_EQ(result.mip_solution, nullptr);
EXPECT_NEAR(result.lp_solution->get_objective_value(), -464.753, 0.01);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a test for a unary response that carries no solution.

The new tests cover only success paths. get_result has a reachable error path: a unary ResultResponse with neither lp_solution nor mip_solution set. That input falls through the is_mip check into the LP branch and returns "GetResult succeeded but no LP solution in response". No test pins this behavior. Without it, a later refactor can return success == true with both solution pointers null, and grpc_python_client_t::result in cpp/src/grpc/client/cython_grpc_client.cpp then dereferences a null lp_solution.

Add one test that returns an empty ResultResponse and asserts success == false plus both pointers null.

💚 Proposed test
TEST_F(GrpcClientTest, GetResultUnified_UnaryMissingSolution)
{
  EXPECT_CALL(*mock_stub_, CheckStatus(_, _, _))
    .WillOnce([](grpc::ClientContext*,
                 const cuopt::remote::StatusRequest&,
                 cuopt::remote::StatusResponse* resp) {
      resp->set_job_status(cuopt::remote::COMPLETED);
      resp->set_result_size_bytes(64);
      resp->set_max_message_bytes(256 * 1024 * 1024);
      return grpc::Status::OK;
    });

  EXPECT_CALL(*mock_stub_, GetResult(_, _, _))
    .WillOnce([](grpc::ClientContext*,
                 const cuopt::remote::GetResultRequest&,
                 cuopt::remote::ResultResponse* resp) {
      resp->set_status(cuopt::remote::SUCCESS);
      return grpc::Status::OK;
    });

  auto result = client_->get_result<int32_t, double>("unified-missing");

  EXPECT_FALSE(result.success);
  EXPECT_EQ(result.lp_solution, nullptr);
  EXPECT_EQ(result.mip_solution, nullptr);
  EXPECT_FALSE(result.error_message.empty());
}

As per path instructions: "Edge cases: empty, infeasible, unbounded, degenerate, singleton problems" and "When a bug fix lands, a regression test should cover the specific case".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/tests/linear_programming/grpc/grpc_client_test.cpp` around lines 941 -
948, Add a regression test near the existing unified unary get_result tests,
named GetResultUnified_UnaryMissingSolution, that mocks a completed CheckStatus
response and a successful GetResult response with neither solution field
populated. Call client_->get_result for the missing-solution job and assert
success is false, both lp_solution and mip_solution are null, and error_message
is non-empty.

Source: Path instructions

This limits the changes. Internals will be cleaned up later

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx (1)

167-171: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not use TypeError to detect callback arity.

If the callback raises TypeError inside its four-argument body, this code invokes it again with three arguments. The retry can duplicate side effects and replace the original error with an arity error. Keep the temporary compatibility path, but select the callback arity before invocation or use separate adapters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx` around lines 167
- 171, Update _call_incumbent_callback so it no longer catches TypeError from
callback execution to determine arity. Preserve the temporary support for both
callback signatures by determining the callable’s accepted arity before
invocation or by using separate adapters, and ensure errors raised inside the
callback propagate without retrying or duplicating side effects.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx`:
- Around line 167-171: Update _call_incumbent_callback so it no longer catches
TypeError from callback execution to determine arity. Preserve the temporary
support for both callback signatures by determining the callable’s accepted
arity before invocation or by using separate adapters, and ensure errors raised
inside the callback propagate without retrying or duplicating side effects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c5fe0b83-ac1e-4aef-a686-b7f14ef94752

📥 Commits

Reviewing files that changed from the base of the PR and between dc593dc and da6dd3e.

📒 Files selected for processing (1)
  • python/cuopt/cuopt/grpc/linear_programming/grpc_client.pyx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant