Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions dapr/actor/client/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@
"""

from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, Optional, Type
from typing import Any, Callable, Dict, Optional, Type, Union

from dapr.actor.actor_interface import ActorInterface
from dapr.actor.id import ActorId
from dapr.actor.runtime._type_utils import get_dispatchable_attrs_from_interface
from dapr.clients import DaprActorClientBase, DaprActorHttpClient
from dapr.clients.retry import RetryPolicy
from dapr.conf import settings
from dapr.conf import _FromSettings
from dapr.serializers import DefaultJSONSerializer, Serializer

_HTTP_TIMEOUT_FROM_SETTINGS = _FromSettings('DAPR_HTTP_TIMEOUT_SECONDS')

# Actor factory Callable type hint.
ACTOR_FACTORY_CALLBACK = Callable[[ActorInterface, str, str], 'ActorProxy']

Expand All @@ -49,9 +51,11 @@ class ActorProxyFactory(ActorFactoryBase):
def __init__(
self,
message_serializer=DefaultJSONSerializer(),
http_timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS,
http_timeout_seconds: Union[int, None, _FromSettings] = _HTTP_TIMEOUT_FROM_SETTINGS,
retry_policy: Optional[RetryPolicy] = None,
):
if isinstance(http_timeout_seconds, _FromSettings):
http_timeout_seconds = int(http_timeout_seconds.resolve())
# TODO: support serializer for state store later
self._dapr_client = DaprActorHttpClient(
message_serializer, timeout=http_timeout_seconds, retry_policy=retry_policy
Expand Down
14 changes: 9 additions & 5 deletions dapr/actor/runtime/grpc_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import asyncio
import logging
import os
from typing import Awaitable, Callable, Optional, Protocol, Set, Type, TypeVar
from typing import Awaitable, Callable, Optional, Protocol, Set, Type, TypeVar, Union

import grpc.aio # type: ignore
from grpc import StatusCode # type: ignore[attr-defined]
Expand All @@ -43,7 +43,7 @@
from dapr.actor.runtime.runtime import ActorRuntime
from dapr.clients.grpc._channel import create_aio_channel
from dapr.clients.grpc.dapr_actor_grpc_client import DaprActorGrpcClient
from dapr.conf import settings
from dapr.conf import _FromSettings, settings
from dapr.proto import api_service_v1, api_v1
from dapr.serializers import DefaultJSONSerializer, Serializer

Expand All @@ -60,6 +60,7 @@
_RECONNECT_DELAY_SECONDS = 1.0
_DRAIN_TIMEOUT_SECONDS = 5.0
_HANDSHAKE_TIMEOUT_SECONDS = 60.0
_HTTP_TIMEOUT_FROM_SETTINGS = _FromSettings('DAPR_HTTP_TIMEOUT_SECONDS')
_GRPC_AIO_EOF = grpc.aio.EOF # type: ignore[attr-defined]


Expand Down Expand Up @@ -144,7 +145,7 @@ class ActorGrpcHost:
def __init__(
self,
address: Optional[str] = None,
timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS,
timeout_seconds: Union[int, None, _FromSettings] = _HTTP_TIMEOUT_FROM_SETTINGS,
app_port: Optional[int] = None,
):
"""Creates the host.
Expand All @@ -153,14 +154,17 @@ def __init__(
address (str, optional): daprd gRPC address. Defaults to the same
resolution as the Dapr clients (``DAPR_GRPC_ENDPOINT``,
``DAPR_RUNTIME_HOST``/``DAPR_GRPC_PORT``).
timeout_seconds (int): per-call timeout for the actors' outbound
operations (state, reminders, timers, invocations).
timeout_seconds (int, optional): per-call timeout for the actors' outbound
operations (state, reminders, timers, invocations). Defaults to
``DAPR_HTTP_TIMEOUT_SECONDS``, read when the host is created.
app_port (int, optional): port for the empty gRPC app-channel
listener the host starts so daprd accepts the stream. Defaults
to the ``APP_PORT`` env var (set by ``dapr run``). Pass ``0`` to
disable it when you run your own gRPC app server.
"""
self._address = address
if isinstance(timeout_seconds, _FromSettings):
timeout_seconds = int(timeout_seconds.resolve())
self._timeout_seconds = timeout_seconds
self._app_port = self._resolve_app_port(app_port)
self._channel: Optional[grpc.aio.Channel] = None
Expand Down
13 changes: 9 additions & 4 deletions dapr/actor/runtime/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""

import asyncio
from typing import Callable, Dict, List, Optional, Type
from typing import Callable, Dict, List, Optional, Type, Union

from dapr.actor.id import ActorId
from dapr.actor.runtime._type_information import ActorTypeInformation
Expand All @@ -25,9 +25,11 @@
from dapr.clients.base import DaprActorClientBase
from dapr.clients.http.dapr_actor_http_client import DaprActorHttpClient
from dapr.common.reentrancy_context import reentrancy_ctx
from dapr.conf import settings
from dapr.conf import _FromSettings
from dapr.serializers import DefaultJSONSerializer, Serializer

_HTTP_TIMEOUT_FROM_SETTINGS = _FromSettings('DAPR_HTTP_TIMEOUT_SECONDS')


class ActorRuntime:
"""The class that creates instances of :class:`Actor` and
Expand All @@ -45,7 +47,7 @@ async def register_actor(
actor: Type[Actor],
message_serializer: Serializer = DefaultJSONSerializer(),
state_serializer: Serializer = DefaultJSONSerializer(),
http_timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS,
http_timeout_seconds: Union[int, None, _FromSettings] = _HTTP_TIMEOUT_FROM_SETTINGS,
actor_factory: Optional[Callable[['ActorRuntimeContext', ActorId], 'Actor']] = None,
actor_client: Optional[DaprActorClientBase] = None,
) -> None:
Expand All @@ -56,13 +58,16 @@ async def register_actor(
message_serializer (:class:`Serializer`): A serializer that serializes message
between actors.
state_serializer (:class:`Serializer`): Serializer that serializes state values.
http_timeout_seconds (:int:): a configurable timeout value
http_timeout_seconds (int, optional): timeout for the default HTTP actor client.
Defaults to ``DAPR_HTTP_TIMEOUT_SECONDS``, read when the actor is registered.
actor_factory (Callable, optional): factory to create Actor objects.
actor_client (:class:`DaprActorClientBase`, optional): the client used for the
actor's outbound operations (state, reminders, timers, invocations).
Defaults to the HTTP client; :class:`ActorGrpcHost` supplies a gRPC one.
"""
type_info = ActorTypeInformation.create(actor)
if isinstance(http_timeout_seconds, _FromSettings):
http_timeout_seconds = int(http_timeout_seconds.resolve())
if actor_client is None:
actor_client = DaprActorHttpClient(message_serializer, timeout=http_timeout_seconds)
ctx = ActorRuntimeContext(
Expand Down
4 changes: 2 additions & 2 deletions dapr/clients/grpc/dapr_actor_grpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class DaprActorGrpcClient(DaprActorClientBase):

def __init__(
self,
timeout: int = 60,
timeout: Optional[int] = 60,
address: Optional[str] = None,
channel: Optional[grpc.aio.Channel] = None,
message_serializer: Serializer = DefaultJSONSerializer(),
Expand All @@ -59,7 +59,7 @@ def __init__(
"""Creates the gRPC actor client.

Args:
timeout (int): per-call timeout in seconds.
timeout (int, optional): per-call timeout in seconds; None means no timeout.
address (str, optional): Dapr runtime gRPC address; defaults to the
same resolution as the Dapr clients (DAPR_GRPC_ENDPOINT et al).
channel (grpc.aio.Channel, optional): externally owned channel to
Expand Down
2 changes: 1 addition & 1 deletion dapr/clients/http/dapr_actor_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class DaprActorHttpClient(DaprActorClientBase):
def __init__(
self,
message_serializer: 'Serializer',
timeout: int = 60,
timeout: Optional[int] = 60,
headers_callback: Optional[Callable[[], Dict[str, str]]] = None,
retry_policy: Optional[RetryPolicy] = None,
):
Expand Down
14 changes: 10 additions & 4 deletions dapr/clients/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@

import asyncio
import time
from typing import Callable, List, Optional
from typing import Callable, List, Union

from grpc import RpcError, StatusCode # type: ignore

from dapr.conf import settings
from dapr.conf import _FromSettings

_MAX_RETRIES_FROM_SETTINGS = _FromSettings('DAPR_API_MAX_RETRIES')


class RetryPolicy:
"""RetryPolicy holds the retry policy configuration for a gRPC client.

Args:
max_attempts (int): The maximum number of retry attempts.
max_attempts (int, optional): The maximum number of retry attempts. Defaults to
``DAPR_API_MAX_RETRIES``, read when the policy is constructed.
initial_backoff (int): The initial backoff duration.
max_backoff (int): The maximum backoff duration.
backoff_multiplier (float): The backoff multiplier.
Expand All @@ -36,7 +39,7 @@ class RetryPolicy:

def __init__(
self,
max_attempts: Optional[int] = settings.DAPR_API_MAX_RETRIES,
max_attempts: Union[int, None, _FromSettings] = _MAX_RETRIES_FROM_SETTINGS,
Comment thread
CasperGN marked this conversation as resolved.
initial_backoff: int = 1,
max_backoff: int = 20,
backoff_multiplier: float = 1.5,
Expand All @@ -46,6 +49,9 @@ def __init__(
StatusCode.DEADLINE_EXCEEDED,
],
):
if isinstance(max_attempts, _FromSettings):
max_attempts = int(max_attempts.resolve())

if max_attempts is None or max_attempts < -1:
raise ValueError('max_attempts must be greater than or equal to -1')
self.max_attempts = max_attempts
Expand Down
57 changes: 44 additions & 13 deletions dapr/conf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,58 @@
"""

import os
from typing import Any

from dapr.conf import global_settings


class Settings:
def __init__(self):
for setting in dir(global_settings):
default_value = getattr(global_settings, setting)
env_variable = os.environ.get(setting)
if env_variable:
val = (
type(default_value)(env_variable) if default_value is not None else env_variable
)
setattr(self, setting, val)
else:
setattr(self, setting, default_value)
"""Settings resolved from the environment each time they are read.

A value assigned on the instance (``settings.DAPR_HTTP_PORT = 3500``) takes
precedence; otherwise the ``DAPR_*`` environment variable is used when set,
falling back to the default in :mod:`dapr.conf.global_settings`.
"""

def __getattr__(self, name):
if name not in dir(global_settings):
# Only reached when the attribute was not assigned on the instance.
if name.startswith('__') or not hasattr(global_settings, name):
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
return getattr(self, name)
default_value = getattr(global_settings, name)
env_variable = os.environ.get(name)
if not env_variable:
return default_value
if default_value is None:
return env_variable
try:
return type(default_value)(env_variable)
except ValueError as error:
raise ValueError(f'Invalid value for {name}: {env_variable!r}') from error

def __dir__(self):
return sorted(
{*super().__dir__(), *(n for n in dir(global_settings) if not n.startswith('__'))}
)


settings = Settings()


class _FromSettings:
"""Sentinel default that reads a setting when the call is made, not at import.

Lets a parameter tell "omitted" apart from an explicit ``None``.

Args:
name: Name of the setting in :mod:`dapr.conf.global_settings`.
"""

def __init__(self, name: str) -> None:
self.name = name

def __repr__(self) -> str:
return self.name

def resolve(self) -> Any:
"""Returns the current value of the setting."""
return getattr(settings, self.name)
Loading
Loading