Skip to content

Conversation

@WouldYouKindly
Copy link

This PR adds flags to generate only sync or only async stubs.

@WouldYouKindly WouldYouKindly changed the title Add flags to generate only sync or only async stubs DRAFT Add flags to generate only sync or only async stubs Nov 25, 2025
@aidandj
Copy link
Collaborator

aidandj commented Nov 25, 2025

What is the need for this with the last round of changes that generates both?

@WouldYouKindly
Copy link
Author

WouldYouKindly commented Nov 25, 2025

What is the need for this with the last round of changes that generates both?

Two things:

  1. On our codebase, running mypy with generated stubs results in OOM - mypy 1.18.2 consumes 20GB+ of RAM. Probably because of too many generics.
  2. It's nice to be able to copy the types from the generated stubs to the implementations. Sync+Async stubs are quite unwieldy, and I assume most people wouldn't want to see them in their code.

@aidandj
Copy link
Collaborator

aidandj commented Nov 25, 2025

Sounds good, I'll review later today.

Can you update the readme documenting the option, and fix the tests.

@aidandj
Copy link
Collaborator

aidandj commented Nov 25, 2025

@nipunn1313 I know we just talked about expanding options too much, but I've definitely wanted to be able to copy the stubs straight across, and this allows a bit more configuration.

Yesterday I was using ts-proto which has about a billion options, and with documentation it was fine.

I think as long as this has full tests for each option, I'm not opposed to it.

@nipunn1313
Copy link
Owner

This one seems like a reasonable one to do. What is the command line flags you are envisioning and what are the defaults that you envision?

I think onus is high here to have sane defaults (maybe async-only?) - and then have a flag to flip to the other options.

Re: memory usage - I think it may be (separately) worth for you to consider profiling to figure out how to resolve that issue to make the generator more efficient. This feels like it will only be a bandaid for an OOM issue.

@aidandj
Copy link
Collaborator

aidandj commented Nov 25, 2025

I think the default should probably be both still. I think it's a better general default for normal first-time users, and also won't be a breaking change.

2 similar comments
@aidandj
Copy link
Collaborator

aidandj commented Nov 25, 2025

I think the default should probably be both still. I think it's a better general default for normal first-time users, and also won't be a breaking change.

@aidandj
Copy link
Collaborator

aidandj commented Nov 25, 2025

I think the default should probably be both still. I think it's a better general default for normal first-time users, and also won't be a breaking change.

@WouldYouKindly
Copy link
Author

This one seems like a reasonable one to do. What is the command line flags you are envisioning and what are the defaults that you envision?

I think onus is high here to have sane defaults (maybe async-only?) - and then have a flag to flip to the other options.

Re: memory usage - I think it may be (separately) worth for you to consider profiling to figure out how to resolve that issue to make the generator more efficient. This feels like it will only be a bandaid for an OOM issue.

Currently the flags are sync_only/async_only. I agree with Aidan that default should be both - I have no idea if more people need sync or async stubs.

w.r.t. memory usage, I don't think it will be very easy to profile mypy's memory usage, especially given that it has mypyc-compiled parts... I know that the issue is with stubs - as soon as I remove a stub from .pyi file, I stop getting OOM.

@aidandj
Copy link
Collaborator

aidandj commented Nov 26, 2025

This one seems like a reasonable one to do. What is the command line flags you are envisioning and what are the defaults that you envision?

I think onus is high here to have sane defaults (maybe async-only?) - and then have a flag to flip to the other options.

Re: memory usage - I think it may be (separately) worth for you to consider profiling to figure out how to resolve that issue to make the generator more efficient. This feels like it will only be a bandaid for an OOM issue.

Currently the flags are sync_only/async_only. I agree with Aidan that default should be both - I have no idea if more people need sync or async stubs.

w.r.t. memory usage, I don't think it will be very easy to profile mypy's memory usage, especially given that it has mypyc-compiled parts... I know that the issue is with stubs - as soon as I remove a stub from .pyi file, I stop getting OOM.

I've had really good luck with memray and profiling. I think this would be good data to have, Even if we go ahead with the sync/async only stub generation. I know I personally don't have a code base large enough to reproduce this. Maybe you could give us an estimate on the size of your code base?

1 similar comment
@aidandj
Copy link
Collaborator

aidandj commented Nov 26, 2025

This one seems like a reasonable one to do. What is the command line flags you are envisioning and what are the defaults that you envision?

I think onus is high here to have sane defaults (maybe async-only?) - and then have a flag to flip to the other options.

Re: memory usage - I think it may be (separately) worth for you to consider profiling to figure out how to resolve that issue to make the generator more efficient. This feels like it will only be a bandaid for an OOM issue.

Currently the flags are sync_only/async_only. I agree with Aidan that default should be both - I have no idea if more people need sync or async stubs.

w.r.t. memory usage, I don't think it will be very easy to profile mypy's memory usage, especially given that it has mypyc-compiled parts... I know that the issue is with stubs - as soon as I remove a stub from .pyi file, I stop getting OOM.

I've had really good luck with memray and profiling. I think this would be good data to have, Even if we go ahead with the sync/async only stub generation. I know I personally don't have a code base large enough to reproduce this. Maybe you could give us an estimate on the size of your code base?

@WouldYouKindly
Copy link
Author

@aidandj it's really not that big. It's also public: https://github.com/Couchers-org/couchers. Here are the protos: https://github.com/Couchers-org/couchers/tree/develop/app/proto

@WouldYouKindly
Copy link
Author

@aidandj I will try to make a repro a bit later, and submit an issue at least. In the meantime, the tests are fixed and the docs are updated - ready to be reviewed

@aidandj
Copy link
Collaborator

aidandj commented Nov 26, 2025

Hoping to get to it today, otherwise after our holiday weekend

Copy link
Collaborator

@aidandj aidandj left a comment

Choose a reason for hiding this comment

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

Good start. Missing a few things.

  • Typed constructors.
  • Tests are incomplete
    • Please add full tests for the stub usage, at least equivalent to the tests in the existing grpc_usage and async_grpc_usage
  • Incorrect folder names (I realize I started this pattern with the concrete folder, fixed in the below PR)

When I was testing, I made some changes, here is a PR into your current branch: WouldYouKindly#1

BOTH = "BOTH"

@classmethod
def from_parameter(cls, parameter: str) -> GRPCType:
Copy link
Collaborator

Choose a reason for hiding this comment

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

handle error case if someone passes in both options, either error with invalid option, or set both. I'd lean towards error

with self._indent():
wl("'{}',", _build_typevar_name(service.name, method.name))
wl("{}[", self._callable_type(method, is_async=False))
# Write only a sync of an async version
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# Write only a sync of an async version
# Write only a sync or an async version

self.write_self_types(service, True)
wl("]")
wl("")
if self.grpc_type == GRPCType.BOTH and self.grpc_type.supports_async:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if self.grpc_type == GRPCType.BOTH and self.grpc_type.supports_async:
if self.grpc_type.supports_async:

return server
elif aserver:
return aserver
raise RuntimeError(f"Impossible, {self.grpc_type=}") # pragma: no cover
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the purpose of the pragma comment here?

The logic feels a bit confusing here. What about:

server = self._import("grpc", "Server")
aserver = self._import("grpc.aio", "Server")
if self.grpc_type == BOTH:
  return f"{self._import('typing', 'Union')}[{server}, {aserver}]"
elif self.grpc_type == ASYNC:
  return aserver
elif self.grpc_type == SYNC:
  return server
else:
  raise RuntimeError(f"Impossible, {self.grpc_type=}")

testproto.grpc.dummy_pb2.DummyRequest,
testproto.grpc.dummy_pb2.DummyReply,
]
class DummyServiceStub:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing typed constructor, in sync as well

def __init__(channel: grpc.aio.Channel)

run_test.sh Outdated
find proto/testproto/grpc -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_grpc_out=generate_concrete_servicer_stubs:test/generated-concrete

# Generate with sync_only stubs for testing
find proto/testproto/grpc -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_grpc_out=only_sync:test/generated-sync-only
Copy link
Collaborator

Choose a reason for hiding this comment

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

generated_sync_only

run_test.sh Outdated
find proto/testproto/grpc -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_grpc_out=only_sync:test/generated-sync-only

# Generate with async_only stubs for testing
find proto/testproto/grpc -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_grpc_out=only_async:test/generated-async-only
Copy link
Collaborator

Choose a reason for hiding this comment

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

generated_async_only

Rename folders
Update pyproject.toml so pyright works on test code for sync/async only

Signed-off-by: Aidan Jensen <aidandj.github@gmail.com>
Signed-off-by: Aidan Jensen <aidandj.github@gmail.com>
@WouldYouKindly WouldYouKindly force-pushed the sync-async-only branch 2 times, most recently from 4f7cebc to 2080d44 Compare November 27, 2025 11:47
@aidandj
Copy link
Collaborator

aidandj commented Dec 5, 2025

@WouldYouKindly is this still something you'd like to finish working on?

@WouldYouKindly
Copy link
Author

WouldYouKindly commented Dec 9, 2025

@WouldYouKindly is this still something you'd like to finish working on?

Yes, absolutely. I will be a bit freer next week to finish this hopefully.

I think this is useful to have even if the OOM is fixed - I'd imagine most people want to have either sync or async, not both at the same time. Not having overloads also allows one to copy the types from the generated classes into their implementation.

@aidandj
Copy link
Collaborator

aidandj commented Dec 9, 2025

I agree. Let's get that other change fixed first and rebase on it. I'd like to get that one rolled out to unblock people who upgraded.

1 similar comment
@aidandj
Copy link
Collaborator

aidandj commented Dec 9, 2025

I agree. Let's get that other change fixed first and rebase on it. I'd like to get that one rolled out to unblock people who upgraded.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants