Skip to content

Use _LazyImport for grpcio package#5954

Merged
HideakiImamura merged 1 commit intooptuna:masterfrom
c-bata:lazy-import-grpcio
Jan 31, 2025
Merged

Use _LazyImport for grpcio package#5954
HideakiImamura merged 1 commit intooptuna:masterfrom
c-bata:lazy-import-grpcio

Conversation

@c-bata
Copy link
Copy Markdown
Member

@c-bata c-bata commented Jan 31, 2025

Motivation

Resolves #5947

Description of the changes

The approach of this PR is very similar with #5915, but avoid defining the inner class.

Here is a high-level overview of the changes.

Before:

from optuna.storages._grpc.grpc_imports import _imports

if _imports.is_successful():
    from optuna.storages._grpc.grpc_imports import StorageServiceServicer
else:
    class StorageServiceServicer:  # type: ignore
        pass


class OptunaStorageProxyService(StorageServiceServicer):
    ...

def make_server(
    storage: BaseStorage, host: str, port: int, thread_pool: ThreadPoolExecutor | None = None
) -> grpc.Server:
    server = grpc.server(thread_pool or ThreadPoolExecutor(max_workers=10))
    api_pb2_grpc.add_StorageServiceServicer_to_server(
        OptunaStorageProxyService(storage), server
    )  # type: ignore
    server.add_insecure_port(f"{host}:{port}")
    return server

After:

api_pb2_grpc = _LazyImport("optuna.storages._grpc.auto_generated.api_pb2_grpc")
grpc_servicer = _LazyImport("optuna.storages._grpc.servicer")

def make_server(
    storage: BaseStorage, host: str, port: int, thread_pool: ThreadPoolExecutor | None = None
) -> grpc.Server:
    server = grpc.server(thread_pool or ThreadPoolExecutor(max_workers=10))
    api_pb2_grpc.add_StorageServiceServicer_to_server(
        grpc_servicer.OptunaStorageProxyService(storage), server
    )  # type: ignore
    server.add_insecure_port(f"{host}:{port}")
    return server

@c-bata c-bata force-pushed the lazy-import-grpcio branch from 6afa96e to bc29f94 Compare January 31, 2025 02:54
Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com>
@c-bata c-bata force-pushed the lazy-import-grpcio branch from bc29f94 to a24a13b Compare January 31, 2025 02:55
@HideakiImamura HideakiImamura self-assigned this Jan 31, 2025
@c-bata
Copy link
Copy Markdown
Member Author

c-bata commented Jan 31, 2025

I confirmed that this PR and #5915 resolves the issue reported at #5947.

$ pip freeze | grep grpcio
grpcio==1.67.1
grpcio-tools==1.68.1
$ python3
>>> import optuna
>>> optuna.storages.GrpcStorageProxy()
<stdin>:1: ExperimentalWarning: GrpcStorageProxy is experimental (supported from v4.2.0). The interface can change in the future.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/cbata/go/src/github.com/optuna/optuna/optuna/_experimental.py", line 123, in wrapped_init
    _original_init(self, *args, **kwargs)
  File "/home/cbata/go/src/github.com/optuna/optuna/optuna/storages/_grpc/client.py", line 69, in __init__
    self._stub = api_pb2_grpc.StorageServiceStub(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/cbata/go/src/github.com/optuna/optuna/optuna/_imports.py", line 130, in __getattr__
    return getattr(self._load(), item)
                   ^^^^^^^^^^^^
  File "/home/cbata/go/src/github.com/optuna/optuna/optuna/_imports.py", line 125, in _load
    module = importlib.import_module(self._name)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/cbata/.pyenv/versions/3.11.7/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/cbata/go/src/github.com/optuna/optuna/optuna/storages/_grpc/auto_generated/api_pb2_grpc.py", line 19, in <module>
    raise RuntimeError(
RuntimeError: The grpc package installed is at version 1.67.1, but the generated code in api_pb2_grpc.py depends on grpcio>=1.68.1. Please upgrade your grpc module to grpcio>=1.68.1 or downgrade your generated code using grpcio-tools<=1.67.1.
>>> 
$ pip freeze | grep grpcio
grpcio==1.70.0
grpcio-tools==1.68.1
$ python3
>>> import optuna
>>> optuna.storages.GrpcStorageProxy()
<stdin>:1: ExperimentalWarning: GrpcStorageProxy is experimental (supported from v4.2.0). The interface can change in the future.
<optuna.storages._grpc.client.GrpcStorageProxy object at 0x75551360a810>

@c-bata c-bata marked this pull request as ready for review January 31, 2025 05:29
@c-bata c-bata added the bug Issue/PR about behavior that is broken. Not for typos/examples/CI/test but for Optuna itself. label Jan 31, 2025
@nabenabe0928 nabenabe0928 self-assigned this Jan 31, 2025
Copy link
Copy Markdown
Contributor

@nabenabe0928 nabenabe0928 left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Copy Markdown
Member

@HideakiImamura HideakiImamura left a comment

Choose a reason for hiding this comment

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

Looks very good. I confirmed that the current version pass the optuna import w/o grpcio and raises an error with old grpcio and without it. I also confirmed that the difference between the previous server.py and the current servicer.py is reasonable as follows.

3d2
< from concurrent.futures import ThreadPoolExecutor
6a6
> from typing import TYPE_CHECKING
9c9
< from optuna._experimental import experimental_func
---
> from optuna._imports import _LazyImport
14d13
< from optuna.storages._grpc.grpc_imports import _imports
20,25c19,20
< if _imports.is_successful():
<     from optuna.storages._grpc.grpc_imports import api_pb2
<     from optuna.storages._grpc.grpc_imports import api_pb2_grpc
<     from optuna.storages._grpc.grpc_imports import grpc
<     from optuna.storages._grpc.grpc_imports import StorageServiceServicer
< else:
---
> if TYPE_CHECKING:
>     import grpc
27,28c22,27
<     class StorageServiceServicer:  # type: ignore
<         pass
---
>     from optuna.storages._grpc.auto_generated import api_pb2
>     from optuna.storages._grpc.auto_generated import api_pb2_grpc
> else:
>     api_pb2 = _LazyImport("optuna.storages._grpc.auto_generated.api_pb2")
>     api_pb2_grpc = _LazyImport("optuna.storages._grpc.auto_generated.api_pb2_grpc")
>     grpc = _LazyImport("grpc")
35c34
< class OptunaStorageProxyService(StorageServiceServicer):
---
> class OptunaStorageProxyService(api_pb2_grpc.StorageServiceServicer):
37d35
<         _imports.check()
431,483d428
< 
< 
< def make_server(
<     storage: BaseStorage, host: str, port: int, thread_pool: ThreadPoolExecutor | None = None
< ) -> grpc.Server:
<     server = grpc.server(thread_pool or ThreadPoolExecutor(max_workers=10))
<     api_pb2_grpc.add_StorageServiceServicer_to_server(
<         OptunaStorageProxyService(storage), server
<     )  # type: ignore
<     server.add_insecure_port(f"{host}:{port}")
<     return server
< 
< 
< @experimental_func("4.2.0")
< def run_grpc_proxy_server(
<     storage: BaseStorage,
<     *,
<     host: str = "localhost",
<     port: int = 13000,
<     thread_pool: ThreadPoolExecutor | None = None,
< ) -> None:
<     """Run a gRPC server for the given storage URL, host, and port.
< 
<     Example:
< 
<         Run this server with the following way:
< 
<         .. code::
< 
<             from optuna.storages import run_grpc_proxy_server
<             from optuna.storages import get_storage
< 
<             storage = get_storage("mysql+pymysql://<user>:<pass>@<host>/<dbname>[?<options>]")
<             run_grpc_proxy_server(storage, host="localhost", port=13000)
< 
<         Please refer to the client class :class:`~optuna.storages.GrpcStorageProxy` for
<         the client usage. Please use :func:`~optuna.storages.get_storage` instead of
<         :class:`~optuna.storages.RDBStorage` since ``RDBStorage`` by itself does not use cache in
<         process and it may cause significant slowdown.
< 
<     Args:
<         storage: A storage object to proxy.
<         host: Hostname to listen on.
<         port: Port to listen on.
<         thread_pool:
<             Thread pool to use for the server. If :obj:`None`, a default thread pool
<             with 10 workers will be used.
<     """
<     server = make_server(storage, host, port, thread_pool)
<     server.start()
<     _logger.info(f"Server started at {host}:{port}")
<     _logger.info("Listening...")
<     server.wait_for_termination()

@HideakiImamura HideakiImamura merged commit a1df0ca into optuna:master Jan 31, 2025
@c-bata c-bata added this to the v4.3.0 milestone Jan 31, 2025
HideakiImamura added a commit to HideakiImamura/optuna that referenced this pull request Feb 12, 2025
Use `_LazyImport` for grpcio package
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Issue/PR about behavior that is broken. Not for typos/examples/CI/test but for Optuna itself.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

grpcio version messed up by/with tensorboard and/or ahead of conda-forge

3 participants