From 1110fb5cb71c7b5ebb19ceaa695b9f48cc283823 Mon Sep 17 00:00:00 2001 From: hatchet-temporary Date: Wed, 18 Dec 2024 20:13:04 -0500 Subject: [PATCH 1/9] feat: exclude autogenerated stuff --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 69380b67..b28150df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,9 @@ files = [ "hatchet_sdk/context/worker_context.py", "hatchet_sdk/clients/dispatcher/dispatcher.py", ] +exclude = [ + "hatchet_sdk/clients/rest", +] follow_imports = "silent" disable_error_code = ["unused-coroutine"] explicit_package_bases = true From 3bb1d41975a6dd84a844a8f5c9ff59883271f20a Mon Sep 17 00:00:00 2001 From: hatchet-temporary Date: Wed, 18 Dec 2024 21:23:33 -0500 Subject: [PATCH 2/9] fix: tons more mypy issues --- conftest.py | 6 +- hatchet_sdk/__init__.py | 5 +- hatchet_sdk/client.py | 10 +- hatchet_sdk/clients/admin.py | 92 ++++++++++-------- .../clients/dispatcher/action_listener.py | 24 ++--- hatchet_sdk/clients/event_ts.py | 8 +- hatchet_sdk/clients/events.py | 16 ++-- hatchet_sdk/clients/rest_client.py | 72 +++++++------- hatchet_sdk/clients/run_event_listener.py | 48 ++++++---- hatchet_sdk/clients/workflow_listener.py | 24 +++-- hatchet_sdk/connection.py | 6 +- hatchet_sdk/features/cron.py | 6 +- hatchet_sdk/labels.py | 9 +- hatchet_sdk/loader.py | 95 +++++++++---------- hatchet_sdk/metadata.py | 4 +- hatchet_sdk/rate_limit.py | 10 +- hatchet_sdk/token.py | 11 ++- hatchet_sdk/utils/serialization.py | 4 +- hatchet_sdk/utils/tracing.py | 5 +- hatchet_sdk/v2/callable.py | 2 +- hatchet_sdk/v2/concurrency.py | 4 +- hatchet_sdk/v2/hatchet.py | 9 +- hatchet_sdk/worker/action_listener_process.py | 28 +++--- hatchet_sdk/worker/runner/run_loop_manager.py | 14 +-- .../worker/runner/utils/capture_logs.py | 8 +- .../runner/utils/error_with_traceback.py | 2 +- pyproject.toml | 23 ++--- 27 files changed, 279 insertions(+), 266 deletions(-) diff --git a/conftest.py b/conftest.py index 2aff5cd3..e5b462ab 100644 --- a/conftest.py +++ b/conftest.py @@ -3,7 +3,7 @@ import time from io import BytesIO from threading import Thread -from typing import AsyncGenerator, Callable, cast +from typing import AsyncGenerator, Callable, Generator, cast import psutil import pytest @@ -23,7 +23,9 @@ def hatchet() -> Hatchet: @pytest.fixture() -def worker(request: pytest.FixtureRequest): +def worker( + request: pytest.FixtureRequest, +) -> Generator[subprocess.Popen[bytes], None, None]: example = cast(str, request.param) command = ["poetry", "run", example] diff --git a/hatchet_sdk/__init__.py b/hatchet_sdk/__init__.py index 3162c25c..fc81cced 100644 --- a/hatchet_sdk/__init__.py +++ b/hatchet_sdk/__init__.py @@ -137,8 +137,9 @@ from .clients.run_event_listener import StepRunEventType, WorkflowRunEventType from .context.context import Context from .context.worker_context import WorkerContext -from .hatchet import ClientConfig, Hatchet, concurrency, on_failure_step, step, workflow -from .worker import Worker, WorkerStartOptions, WorkerStatus +from .hatchet import Hatchet, concurrency, on_failure_step, step, workflow +from .loader import ClientConfig +from .worker.worker import Worker, WorkerStartOptions, WorkerStatus from .workflow import ConcurrencyExpression __all__ = [ diff --git a/hatchet_sdk/client.py b/hatchet_sdk/client.py index 45dfd394..8c0c42ec 100644 --- a/hatchet_sdk/client.py +++ b/hatchet_sdk/client.py @@ -30,7 +30,7 @@ def from_environment( defaults: ClientConfig = ClientConfig(), debug: bool = False, *opts_functions: Callable[[ClientConfig], None], - ): + ) -> "Client": try: loop = asyncio.get_running_loop() except RuntimeError: @@ -48,7 +48,7 @@ def from_config( cls, config: ClientConfig = ClientConfig(), debug: bool = False, - ): + ) -> "Client": try: loop = asyncio.get_running_loop() except RuntimeError: @@ -89,7 +89,7 @@ def __init__( rest_client: RestApi, config: ClientConfig, debug: bool = False, - ): + ) -> None: try: loop = asyncio.get_running_loop() except RuntimeError: @@ -107,8 +107,8 @@ def __init__( self.debug = debug -def with_host_port(host: str, port: int): - def with_host_port_impl(config: ClientConfig): +def with_host_port(host: str, port: int) -> Callable[[ClientConfig], None]: + def with_host_port_impl(config: ClientConfig) -> None: config.host = host config.port = port diff --git a/hatchet_sdk/clients/admin.py b/hatchet_sdk/clients/admin.py index 02fdeb56..80c83429 100644 --- a/hatchet_sdk/clients/admin.py +++ b/hatchet_sdk/clients/admin.py @@ -1,6 +1,6 @@ import json from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, TypedDict, TypeVar, Union +from typing import Any, Callable, Optional, TypedDict, TypeVar, Union, cast import grpc from google.protobuf import timestamp_pb2 @@ -37,7 +37,7 @@ from ..workflow import WorkflowMeta -def new_admin(config: ClientConfig): +def new_admin(config: ClientConfig) -> "AdminClient": return AdminClient(config) @@ -50,21 +50,21 @@ class ScheduleTriggerWorkflowOptions(TypedDict, total=False): class ChildTriggerWorkflowOptions(TypedDict, total=False): - additional_metadata: Dict[str, str] | None = None - sticky: bool | None = None + additional_metadata: dict[str, str] | None + sticky: bool | None class ChildWorkflowRunDict(TypedDict, total=False): workflow_name: str input: Any options: ChildTriggerWorkflowOptions - key: str | None = None + key: str | None class TriggerWorkflowOptions(ScheduleTriggerWorkflowOptions, total=False): - additional_metadata: Dict[str, str] | None = None - desired_worker_id: str | None = None - namespace: str | None = None + additional_metadata: dict[str, str | bytes] | None + desired_worker_id: str | None + namespace: str | None class WorkflowRunDict(TypedDict, total=False): @@ -83,8 +83,8 @@ class AdminClientBase: pooled_workflow_listener: PooledWorkflowRunListener | None = None def _prepare_workflow_request( - self, workflow_name: str, input: any, options: TriggerWorkflowOptions = None - ): + self, workflow_name: str, input: Any, options: TriggerWorkflowOptions = None + ) -> TriggerWorkflowRequest: try: payload_data = json.dumps(input) @@ -110,7 +110,7 @@ def _prepare_put_workflow_request( name: str, workflow: CreateWorkflowVersionOpts | WorkflowMeta, overrides: CreateWorkflowVersionOpts | None = None, - ): + ) -> PutWorkflowRequest: try: opts: CreateWorkflowVersionOpts @@ -133,10 +133,10 @@ def _prepare_put_workflow_request( def _prepare_schedule_workflow_request( self, name: str, - schedules: List[Union[datetime, timestamp_pb2.Timestamp]], - input={}, - options: ScheduleTriggerWorkflowOptions = None, - ): + schedules: list[Union[datetime, timestamp_pb2.Timestamp]], + input: dict[str, Any] = {}, + options: ScheduleTriggerWorkflowOptions | None = None, + ) -> ScheduleWorkflowRequest: timestamp_schedules = [] for schedule in schedules: if isinstance(schedule, datetime): @@ -176,7 +176,7 @@ def __init__(self, config: ClientConfig): async def run( self, function: Union[str, Callable[[Any], T]], - input: any, + input: Any, options: TriggerWorkflowOptions = None, ) -> "RunRef[T]": workflow_name = function @@ -192,7 +192,10 @@ async def run( @tenacity_retry async def run_workflow( - self, workflow_name: str, input: any, options: TriggerWorkflowOptions = None + self, + workflow_name: str, + input: Any, + options: TriggerWorkflowOptions | None = None, ) -> WorkflowRunRef: ctx = parse_carrier_from_metadata( (options or {}).get("additional_metadata", {}) @@ -227,7 +230,9 @@ async def run_workflow( ) span.set_attributes( flatten( - options["additional_metadata"], parent_key="", separator="." + options["additional_metadata"] or {}, + parent_key="", + separator=".", ) ) @@ -263,7 +268,7 @@ async def run_workflows( self, workflows: list[WorkflowRunDict], options: TriggerWorkflowOptions | None = None, - ) -> List[WorkflowRunRef]: + ) -> list[WorkflowRunRef]: if len(workflows) == 0: raise ValueError("No workflows to run") try: @@ -285,7 +290,7 @@ async def run_workflows( for workflow in workflows: workflow_name = workflow["workflow_name"] input_data = workflow["input"] - options = workflow["options"] + options = workflow["options"] or {} if namespace != "" and not workflow_name.startswith(self.namespace): workflow_name = f"{namespace}{workflow_name}" @@ -327,9 +332,12 @@ async def put_workflow( try: opts = self._prepare_put_workflow_request(name, workflow, overrides) - return await self.aio_client.PutWorkflow( - opts, - metadata=get_metadata(self.token), + return cast( + WorkflowVersion, + await self.aio_client.PutWorkflow( + opts, + metadata=get_metadata(self.token), + ), ) except grpc.RpcError as e: raise ValueError(f"Could not put workflow: {e}") @@ -340,7 +348,7 @@ async def put_rate_limit( key: str, limit: int, duration: RateLimitDuration = RateLimitDuration.SECOND, - ): + ) -> None: try: await self.aio_client.PutRateLimit( PutRateLimitRequest( @@ -357,9 +365,9 @@ async def put_rate_limit( async def schedule_workflow( self, name: str, - schedules: List[Union[datetime, timestamp_pb2.Timestamp]], - input={}, - options: ScheduleTriggerWorkflowOptions = None, + schedules: list[Union[datetime, timestamp_pb2.Timestamp]], + input: dict[str, Any] = {}, + options: ScheduleTriggerWorkflowOptions | None = None, ) -> WorkflowVersion: try: namespace = self.namespace @@ -379,9 +387,12 @@ async def schedule_workflow( name, schedules, input, options ) - return await self.aio_client.ScheduleWorkflow( - request, - metadata=get_metadata(self.token), + return cast( + WorkflowVersion, + await self.aio_client.ScheduleWorkflow( + request, + metadata=get_metadata(self.token), + ), ) except grpc.RpcError as e: if e.code() == grpc.StatusCode.ALREADY_EXISTS: @@ -426,7 +437,7 @@ def put_rate_limit( key: str, limit: int, duration: Union[RateLimitDuration.Value, str] = RateLimitDuration.SECOND, - ): + ) -> None: try: self.client.PutRateLimit( PutRateLimitRequest( @@ -443,9 +454,9 @@ def put_rate_limit( def schedule_workflow( self, name: str, - schedules: List[Union[datetime, timestamp_pb2.Timestamp]], - input={}, - options: ScheduleTriggerWorkflowOptions = None, + schedules: list[Union[datetime, timestamp_pb2.Timestamp]], + input: dict[str, Any] = {}, + options: ScheduleTriggerWorkflowOptions | None = None, ) -> WorkflowVersion: try: namespace = self.namespace @@ -479,7 +490,10 @@ def schedule_workflow( ## TODO: `any` type hint should come from `typing` @tenacity_retry def run_workflow( - self, workflow_name: str, input: any, options: TriggerWorkflowOptions = None + self, + workflow_name: str, + input: Any, + options: TriggerWorkflowOptions | None = None, ) -> WorkflowRunRef: ctx = parse_carrier_from_metadata( (options or {}).get("additional_metadata", {}) @@ -549,7 +563,9 @@ def run_workflow( @tenacity_retry def run_workflows( - self, workflows: List[WorkflowRunDict], options: TriggerWorkflowOptions = None + self, + workflows: list[WorkflowRunDict], + options: TriggerWorkflowOptions | None = None, ) -> list[WorkflowRunRef]: workflow_run_requests: TriggerWorkflowRequest = [] try: @@ -603,8 +619,8 @@ def run_workflows( def run( self, function: Union[str, Callable[[Any], T]], - input: any, - options: TriggerWorkflowOptions = None, + input: Any, + options: TriggerWorkflowOptions | None = None, ) -> "RunRef[T]": workflow_name = function diff --git a/hatchet_sdk/clients/dispatcher/action_listener.py b/hatchet_sdk/clients/dispatcher/action_listener.py index fc2887bd..06a079cd 100644 --- a/hatchet_sdk/clients/dispatcher/action_listener.py +++ b/hatchet_sdk/clients/dispatcher/action_listener.py @@ -46,7 +46,7 @@ class GetActionListenerRequest: labels: dict[str, WorkerLabels] = field(init=False) - def __post_init__(self): + def __post_init__(self) -> None: self.labels = {} for key, value in self._labels.items(): @@ -77,7 +77,7 @@ class Action: child_workflow_key: str | None = None parent_workflow_run_id: str | None = None - def __post_init__(self): + def __post_init__(self) -> None: if isinstance(self.additional_metadata, str) and self.additional_metadata != "": try: self.additional_metadata = json.loads(self.additional_metadata) @@ -137,15 +137,15 @@ class ActionListener: missed_heartbeats: int = field(default=0, init=False) - def __post_init__(self): + def __post_init__(self) -> None: self.client = DispatcherStub(new_conn(self.config)) self.aio_client = DispatcherStub(new_conn(self.config, True)) self.token = self.config.token - def is_healthy(self): + def is_healthy(self) -> bool: return self.last_heartbeat_succeeded - async def heartbeat(self): + async def heartbeat(self) -> None: # send a heartbeat every 4 seconds heartbeat_delay = 4 @@ -205,7 +205,7 @@ async def heartbeat(self): break await asyncio.sleep(heartbeat_delay) - async def start_heartbeater(self): + async def start_heartbeater(self) -> None: if self.heartbeat_task is not None: return @@ -219,7 +219,7 @@ async def start_heartbeater(self): raise e self.heartbeat_task = loop.create_task(self.heartbeat()) - def __aiter__(self): + def __aiter__(self) -> AsyncGenerator[Action, None]: return self._generator() async def _generator(self) -> AsyncGenerator[Action, None]: @@ -323,14 +323,14 @@ async def _generator(self) -> AsyncGenerator[Action, None]: self.retries = self.retries + 1 - def parse_action_payload(self, payload: str): + def parse_action_payload(self, payload: str) -> dict[str, Any]: try: payload_data = json.loads(payload) except json.JSONDecodeError as e: raise ValueError(f"Error decoding payload: {e}") return payload_data - def map_action_type(self, action_type): + def map_action_type(self, action_type: ActionType) -> int: if action_type == ActionType.START_STEP_RUN: return START_STEP_RUN elif action_type == ActionType.CANCEL_STEP_RUN: @@ -341,7 +341,7 @@ def map_action_type(self, action_type): # logger.error(f"Unknown action type: {action_type}") return None - async def get_listen_client(self): + async def get_listen_client(self) -> Any: current_time = int(time.time()) if ( @@ -392,7 +392,7 @@ async def get_listen_client(self): return listener - def cleanup(self): + def cleanup(self) -> None: self.run_heartbeat = False self.heartbeat_task.cancel() @@ -404,7 +404,7 @@ def cleanup(self): if self.interrupt: self.interrupt.set() - def unregister(self): + def unregister(self) -> Any: self.run_heartbeat = False self.heartbeat_task.cancel() diff --git a/hatchet_sdk/clients/event_ts.py b/hatchet_sdk/clients/event_ts.py index 1d3c3978..7add1a79 100644 --- a/hatchet_sdk/clients/event_ts.py +++ b/hatchet_sdk/clients/event_ts.py @@ -7,20 +7,20 @@ class Event_ts(asyncio.Event): Event_ts is a subclass of asyncio.Event that allows for thread-safe setting and clearing of the event. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) if self._loop is None: self._loop = asyncio.get_event_loop() - def set(self): + def set(self) -> None: if not self._loop.is_closed(): self._loop.call_soon_threadsafe(super().set) - def clear(self): + def clear(self) -> None: self._loop.call_soon_threadsafe(super().clear) -async def read_with_interrupt(listener: Any, interrupt: Event_ts): +async def read_with_interrupt(listener: Any, interrupt: Event_ts) -> Any: try: result = await listener.read() return result diff --git a/hatchet_sdk/clients/events.py b/hatchet_sdk/clients/events.py index e188d386..8a35f698 100644 --- a/hatchet_sdk/clients/events.py +++ b/hatchet_sdk/clients/events.py @@ -28,14 +28,14 @@ from ..metadata import get_metadata -def new_event(conn, config: ClientConfig): +def new_event(conn: grpc.Channel, config: ClientConfig) -> "EventClient": return EventClient( client=EventsServiceStub(conn), config=config, ) -def proto_timestamp_now(): +def proto_timestamp_now() -> timestamp_pb2.Timestamp: t = datetime.datetime.now().timestamp() seconds = int(t) nanos = int(t % 1 * 1e9) @@ -44,12 +44,12 @@ def proto_timestamp_now(): class PushEventOptions(TypedDict, total=False): - additional_metadata: Dict[str, str] | None = None - namespace: str | None = None + additional_metadata: dict[str, str] | None + namespace: str | None class BulkPushEventOptions(TypedDict, total=False): - namespace: str | None = None + namespace: str | None class BulkPushEventWithMetadata(TypedDict, total=False): @@ -134,7 +134,7 @@ def push(self, event_key, payload, options: PushEventOptions = None) -> Event: def bulk_push( self, events: List[BulkPushEventWithMetadata], - options: BulkPushEventOptions = None, + options: BulkPushEventOptions | None = None, ) -> List[Event]: namespace = self.namespace bulk_push_correlation_id = uuid4() @@ -196,7 +196,7 @@ def bulk_push( except grpc.RpcError as e: raise ValueError(f"gRPC error: {e}") - def log(self, message: str, step_run_id: str): + def log(self, message: str, step_run_id: str) -> None: try: request = PutLogRequest( stepRunId=step_run_id, @@ -208,7 +208,7 @@ def log(self, message: str, step_run_id: str): except Exception as e: raise ValueError(f"Error logging: {e}") - def stream(self, data: str | bytes, step_run_id: str): + def stream(self, data: str | bytes, step_run_id: str) -> None: try: if isinstance(data, str): data_bytes = data.encode("utf-8") diff --git a/hatchet_sdk/clients/rest_client.py b/hatchet_sdk/clients/rest_client.py index dbfa5c6c..91b4cca0 100644 --- a/hatchet_sdk/clients/rest_client.py +++ b/hatchet_sdk/clients/rest_client.py @@ -2,7 +2,7 @@ import atexit import datetime import threading -from typing import Any, Coroutine, List +from typing import Any, Coroutine, List, TypeVar from pydantic import StrictInt @@ -78,49 +78,49 @@ def __init__(self, host: str, api_key: str, tenant_id: str): ) self._api_client = None - self._workflow_api = None - self._workflow_run_api = None - self._step_run_api = None - self._event_api = None - self._log_api = None + self._workflow_api: WorkflowApi | None = None + self._workflow_run_api: WorkflowRunApi | None = None + self._step_run_api: StepRunApi | None = None + self._event_api: EventApi | None = None + self._log_api: LogApi | None = None @property - def api_client(self): + def api_client(self) -> ApiClient: if self._api_client is None: self._api_client = ApiClient(configuration=self.config) return self._api_client @property - def workflow_api(self): + def workflow_api(self) -> WorkflowApi: if self._workflow_api is None: self._workflow_api = WorkflowApi(self.api_client) return self._workflow_api @property - def workflow_run_api(self): + def workflow_run_api(self) -> WorkflowRunApi: if self._workflow_run_api is None: self._workflow_run_api = WorkflowRunApi(self.api_client) return self._workflow_run_api @property - def step_run_api(self): + def step_run_api(self) -> StepRunApi: if self._step_run_api is None: self._step_run_api = StepRunApi(self.api_client) return self._step_run_api @property - def event_api(self): + def event_api(self) -> EventApi: if self._event_api is None: self._event_api = EventApi(self.api_client) return self._event_api @property - def log_api(self): + def log_api(self) -> LogApi: if self._log_api is None: self._log_api = LogApi(self.api_client) return self._log_api - async def close(self): + async def close(self) -> None: # Ensure the aiohttp client session is closed if self._api_client is not None: await self._api_client.close() @@ -231,7 +231,7 @@ async def cron_create( expression: str, input: dict[str, Any], additional_metadata: dict[str, str], - ): + ) -> CronWorkflows: return await self.workflow_run_api.cron_workflow_trigger_create( tenant=self.tenant_id, workflow=workflow_name, @@ -243,7 +243,7 @@ async def cron_create( ), ) - async def cron_delete(self, cron_trigger_id: str): + async def cron_delete(self, cron_trigger_id: str) -> None: return await self.workflow_api.workflow_cron_delete( tenant=self.tenant_id, cron_workflow=cron_trigger_id, @@ -257,7 +257,7 @@ async def cron_list( additional_metadata: list[str] | None = None, order_by_field: CronWorkflowsOrderByField | None = None, order_by_direction: WorkflowRunOrderByDirection | None = None, - ): + ) -> CronWorkflows: return await self.workflow_api.cron_workflow_list( tenant=self.tenant_id, offset=offset, @@ -268,7 +268,7 @@ async def cron_list( order_by_direction=order_by_direction, ) - async def cron_get(self, cron_trigger_id: str): + async def cron_get(self, cron_trigger_id: str) -> CronWorkflows: return await self.workflow_api.workflow_cron_get( tenant=self.tenant_id, cron_workflow=cron_trigger_id, @@ -280,7 +280,7 @@ async def schedule_create( trigger_at: datetime.datetime, input: dict[str, Any], additional_metadata: dict[str, str], - ): + ) -> ScheduledWorkflows: return await self.workflow_run_api.scheduled_workflow_run_create( tenant=self.tenant_id, workflow=name, @@ -291,7 +291,7 @@ async def schedule_create( ), ) - async def schedule_delete(self, scheduled_trigger_id: str): + async def schedule_delete(self, scheduled_trigger_id: str) -> None: return await self.workflow_api.workflow_scheduled_delete( tenant=self.tenant_id, scheduled_workflow_run=scheduled_trigger_id, @@ -307,7 +307,7 @@ async def schedule_list( parent_step_run_id: str | None = None, order_by_field: ScheduledWorkflowsOrderByField | None = None, order_by_direction: WorkflowRunOrderByDirection | None = None, - ): + ) -> ScheduledWorkflows: return await self.workflow_api.workflow_scheduled_list( tenant=self.tenant_id, offset=offset, @@ -320,7 +320,7 @@ async def schedule_list( order_by_direction=order_by_direction, ) - async def schedule_get(self, scheduled_trigger_id: str): + async def schedule_get(self, scheduled_trigger_id: str) -> ScheduledWorkflows: return await self.workflow_api.workflow_scheduled_get( tenant=self.tenant_id, scheduled_workflow_run=scheduled_trigger_id, @@ -381,8 +381,11 @@ async def events_replay(self, event_ids: list[str] | EventList) -> EventList: ) +T = TypeVar("T") + + class RestApi: - def __init__(self, host: str, api_key: str, tenant_id: str): + def __init__(self, host: str, api_key: str, tenant_id: str) -> None: self._loop = asyncio.new_event_loop() self._thread = threading.Thread(target=self._run_event_loop, daemon=True) self._thread.start() @@ -393,7 +396,7 @@ def __init__(self, host: str, api_key: str, tenant_id: str): # Register the cleanup method to be called on exit atexit.register(self._cleanup) - def _cleanup(self): + def _cleanup(self) -> None: """ Stop the running thread and clean up the event loop. """ @@ -401,18 +404,17 @@ def _cleanup(self): self._loop.call_soon_threadsafe(self._loop.stop) self._thread.join() - def _run_event_loop(self): + def _run_event_loop(self) -> None: """ Run the asyncio event loop in a separate thread. """ asyncio.set_event_loop(self._loop) self._loop.run_forever() - def _run_coroutine(self, coro) -> Any: - """ - Execute a coroutine in the event loop and return the result. - """ + def _run_coroutine(self, coro: Coroutine[Any, Any, T]) -> T: + """Run a coroutine and return its result.""" future = asyncio.run_coroutine_threadsafe(coro, self._loop) + return future.result() def workflow_list(self) -> WorkflowList: @@ -494,7 +496,7 @@ def cron_create( ) ) - def cron_delete(self, cron_trigger_id: str): + def cron_delete(self, cron_trigger_id: str) -> None: return self._run_coroutine(self.aio.cron_delete(cron_trigger_id)) def cron_list( @@ -505,7 +507,7 @@ def cron_list( additional_metadata: list[str] | None = None, order_by_field: CronWorkflowsOrderByField | None = None, order_by_direction: WorkflowRunOrderByDirection | None = None, - ): + ) -> CronWorkflows: return self._run_coroutine( self.aio.cron_list( offset, @@ -517,7 +519,7 @@ def cron_list( ) ) - def cron_get(self, cron_trigger_id: str): + def cron_get(self, cron_trigger_id: str) -> CronWorkflows: return self._run_coroutine(self.aio.cron_get(cron_trigger_id)) def schedule_create( @@ -526,14 +528,14 @@ def schedule_create( trigger_at: datetime.datetime, input: dict[str, Any], additional_metadata: dict[str, str], - ): + ) -> ScheduledWorkflows: return self._run_coroutine( self.aio.schedule_create( workflow_name, trigger_at, input, additional_metadata ) ) - def schedule_delete(self, scheduled_trigger_id: str): + def schedule_delete(self, scheduled_trigger_id: str) -> None: return self._run_coroutine(self.aio.schedule_delete(scheduled_trigger_id)) def schedule_list( @@ -544,7 +546,7 @@ def schedule_list( additional_metadata: list[str] | None = None, order_by_field: CronWorkflowsOrderByField | None = None, order_by_direction: WorkflowRunOrderByDirection | None = None, - ): + ) -> ScheduledWorkflows: return self._run_coroutine( self.aio.schedule_list( offset, @@ -556,7 +558,7 @@ def schedule_list( ) ) - def schedule_get(self, scheduled_trigger_id: str): + def schedule_get(self, scheduled_trigger_id: str) -> ScheduledWorkflows: return self._run_coroutine(self.aio.schedule_get(scheduled_trigger_id)) def list_logs( diff --git a/hatchet_sdk/clients/run_event_listener.py b/hatchet_sdk/clients/run_event_listener.py index b5db6a74..11a45596 100644 --- a/hatchet_sdk/clients/run_event_listener.py +++ b/hatchet_sdk/clients/run_event_listener.py @@ -1,8 +1,8 @@ import asyncio import json -from typing import AsyncGenerator +from typing import AsyncGenerator, Callable, Generator -import grpc +import grpc # type: ignore[import-untyped] from hatchet_sdk.connection import new_conn from hatchet_sdk.contracts.dispatcher_pb2 import ( @@ -57,27 +57,31 @@ class WorkflowRunEventType: class StepRunEvent: - def __init__(self, type: StepRunEventType, payload: str): + def __init__(self, type: str, payload: str): + if type not in StepRunEventType.__dict__.keys(): + raise ValueError(f"Invalid type: {type}") + self.type = type self.payload = payload -def new_listener(config: ClientConfig): +def new_listener(config: ClientConfig) -> "RunEventListenerClient": return RunEventListenerClient(config=config) class RunEventListener: + workflow_run_id: str | None = None + additional_meta_kv: tuple[str, str] | None = None - workflow_run_id: str = None - additional_meta_kv: tuple[str, str] = None - - def __init__(self, client: DispatcherStub, token: str): + def __init__(self, client: DispatcherStub, token: str) -> None: self.client = client self.stop_signal = False self.token = token @classmethod - def for_run_id(cls, workflow_run_id: str, client: DispatcherStub, token: str): + def for_run_id( + cls, workflow_run_id: str, client: DispatcherStub, token: str + ) -> "RunEventListener": listener = RunEventListener(client, token) listener.workflow_run_id = workflow_run_id return listener @@ -85,21 +89,21 @@ def for_run_id(cls, workflow_run_id: str, client: DispatcherStub, token: str): @classmethod def for_additional_meta( cls, key: str, value: str, client: DispatcherStub, token: str - ): + ) -> "RunEventListener": listener = RunEventListener(client, token) listener.additional_meta_kv = (key, value) return listener - def abort(self): + def abort(self) -> None: self.stop_signal = True - def __aiter__(self): + def __aiter__(self) -> AsyncGenerator[StepRunEvent, None]: return self._generator() - async def __anext__(self): + async def __anext__(self) -> StepRunEvent: return await self._generator().__anext__() - def __iter__(self): + def __iter__(self) -> Generator[StepRunEvent, None, None]: try: loop = asyncio.get_event_loop() except RuntimeError as e: @@ -194,7 +198,7 @@ async def _generator(self) -> AsyncGenerator[StepRunEvent, None]: break # Raise StopAsyncIteration to properly end the generator - async def retry_subscribe(self): + async def retry_subscribe(self) -> grpc.aio.StreamStreamCall: retries = 0 while retries < DEFAULT_ACTION_LISTENER_RETRY_COUNT: @@ -228,15 +232,15 @@ async def retry_subscribe(self): class RunEventListenerClient: - def __init__(self, config: ClientConfig): + def __init__(self, config: ClientConfig) -> None: self.token = config.token self.config = config - self.client: DispatcherStub = None + self.client: DispatcherStub | None = None - def stream_by_run_id(self, workflow_run_id: str): + def stream_by_run_id(self, workflow_run_id: str) -> RunEventListener: return self.stream(workflow_run_id) - def stream(self, workflow_run_id: str): + def stream(self, workflow_run_id: str) -> RunEventListener: if not isinstance(workflow_run_id, str) and hasattr(workflow_run_id, "__str__"): workflow_run_id = str(workflow_run_id) @@ -246,14 +250,16 @@ def stream(self, workflow_run_id: str): return RunEventListener.for_run_id(workflow_run_id, self.client, self.token) - def stream_by_additional_metadata(self, key: str, value: str): + def stream_by_additional_metadata(self, key: str, value: str) -> RunEventListener: if not self.client: aio_conn = new_conn(self.config, True) self.client = DispatcherStub(aio_conn) return RunEventListener.for_additional_meta(key, value, self.client, self.token) - async def on(self, workflow_run_id: str, handler: callable = None): + async def on( + self, workflow_run_id: str, handler: Callable[[StepRunEvent], None] + ) -> None: async for event in self.stream(workflow_run_id): # call the handler if provided if handler: diff --git a/hatchet_sdk/clients/workflow_listener.py b/hatchet_sdk/clients/workflow_listener.py index b1131587..5b0d5a64 100644 --- a/hatchet_sdk/clients/workflow_listener.py +++ b/hatchet_sdk/clients/workflow_listener.py @@ -1,7 +1,7 @@ import asyncio import json from collections.abc import AsyncIterator -from typing import AsyncGenerator +from typing import Any, AsyncGenerator import grpc from grpc._cython import cygrpc @@ -31,7 +31,7 @@ def __init__(self, id: int, workflow_run_id: str): self.workflow_run_id = workflow_run_id self.queue: asyncio.Queue[WorkflowRunEvent | None] = asyncio.Queue() - async def __aiter__(self): + async def __aiter__(self) -> "_Subscription": return self async def __anext__(self) -> WorkflowRunEvent: @@ -45,10 +45,10 @@ async def get(self) -> WorkflowRunEvent: return event - async def put(self, item: WorkflowRunEvent): + async def put(self, item: WorkflowRunEvent) -> None: await self.queue.put(item) - async def close(self): + async def close(self) -> None: await self.queue.put(None) @@ -80,7 +80,7 @@ def __init__(self, config: ClientConfig): self.token = config.token self.config = config - async def _interrupter(self): + async def _interrupter(self) -> None: """ _interrupter runs in a separate thread and interrupts the listener according to a configurable duration. """ @@ -89,7 +89,7 @@ async def _interrupter(self): if self.interrupt is not None: self.interrupt.set() - async def _init_producer(self): + async def _init_producer(self) -> None: try: if not self.listener: while True: @@ -178,7 +178,7 @@ async def _request(self) -> AsyncIterator[SubscribeToWorkflowRunsRequest]: yield request self.requests.task_done() - def cleanup_subscription(self, subscription_id: int): + def cleanup_subscription(self, subscription_id: int) -> None: workflow_run_id = self.subscriptionsToWorkflows[subscription_id] if workflow_run_id in self.workflowsToSubscriptions: @@ -187,7 +187,7 @@ def cleanup_subscription(self, subscription_id: int): del self.subscriptionsToWorkflows[subscription_id] del self.events[subscription_id] - async def subscribe(self, workflow_run_id: str): + async def subscribe(self, workflow_run_id: str) -> WorkflowRunEvent: init_producer: asyncio.Task = None try: # create a new subscription id, place a mutex on the counter @@ -224,7 +224,7 @@ async def subscribe(self, workflow_run_id: str): finally: self.cleanup_subscription(subscription_id) - async def result(self, workflow_run_id: str): + async def result(self, workflow_run_id: str) -> dict[str, Any]: from hatchet_sdk.clients.admin import DedupeViolationErr event = await self.subscribe(workflow_run_id) @@ -240,15 +240,13 @@ async def result(self, workflow_run_id: str): else: raise Exception(f"Workflow Errors: {errors}") - results = { + return { result.stepReadableId: json.loads(result.output) for result in event.results if result.output } - return results - - async def _retry_subscribe(self): + async def _retry_subscribe(self) -> AsyncIterator[SubscribeToWorkflowRunsRequest]: retries = 0 while retries < DEFAULT_WORKFLOW_LISTENER_RETRY_COUNT: diff --git a/hatchet_sdk/connection.py b/hatchet_sdk/connection.py index 185395e4..86efc34b 100644 --- a/hatchet_sdk/connection.py +++ b/hatchet_sdk/connection.py @@ -1,13 +1,15 @@ import os from typing import TYPE_CHECKING, Any -import grpc +import grpc # type: ignore[import-untyped] if TYPE_CHECKING: from hatchet_sdk.loader import ClientConfig -def new_conn(config: "ClientConfig", aio=False): +def new_conn( + config: "ClientConfig", aio: bool = False +) -> grpc.Channel | grpc.aio.Channel: credentials: grpc.ChannelCredentials | None = None diff --git a/hatchet_sdk/features/cron.py b/hatchet_sdk/features/cron.py index c54e5b3b..159d7f65 100644 --- a/hatchet_sdk/features/cron.py +++ b/hatchet_sdk/features/cron.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Any, Union from pydantic import BaseModel, field_validator @@ -24,7 +24,7 @@ class CreateCronTriggerInput(BaseModel): """ expression: str = None - input: dict = {} + input: dict[str, Any] = {} additional_metadata: dict[str, str] = {} @field_validator("expression") @@ -86,7 +86,7 @@ def create( workflow_name: str, cron_name: str, expression: str, - input: dict, + input: dict[str, Any], additional_metadata: dict[str, str], ) -> CronWorkflows: """ diff --git a/hatchet_sdk/labels.py b/hatchet_sdk/labels.py index 646c666d..9cc64804 100644 --- a/hatchet_sdk/labels.py +++ b/hatchet_sdk/labels.py @@ -3,8 +3,7 @@ class DesiredWorkerLabel(TypedDict, total=False): value: str | int - required: bool | None = None - weight: int | None = None - comparator: int | None = ( - None # _ClassVar[WorkerLabelComparator] TODO figure out type - ) + required: bool | None + weight: int | None + # _ClassVar[WorkerLabelComparator] TODO figure out type + comparator: int | None diff --git a/hatchet_sdk/loader.py b/hatchet_sdk/loader.py index d754c2ae..af498d2b 100644 --- a/hatchet_sdk/loader.py +++ b/hatchet_sdk/loader.py @@ -1,7 +1,6 @@ -import json import os from logging import Logger, getLogger -from typing import Dict, Optional +from typing import Any, Optional, cast import yaml @@ -25,18 +24,16 @@ def __init__( class ClientConfig: - logInterceptor: Logger - def __init__( self, - tenant_id: str = None, - tls_config: ClientTLSConfig = None, - token: str = None, + tenant_id: str | None = None, + tls_config: ClientTLSConfig | None = None, + token: str | None = None, host_port: str = "localhost:7070", server_url: str = "https://app.dev.hatchet-tools.com", - namespace: str = None, - listener_v2_timeout: int = None, - logger: Logger = None, + namespace: str | None = None, + listener_v2_timeout: int | None = None, + logger: Logger | None = None, grpc_max_recv_message_length: int = 4 * 1024 * 1024, # 4MB grpc_max_send_message_length: int = 4 * 1024 * 1024, # 4MB otel_exporter_oltp_endpoint: str | None = None, @@ -82,14 +79,14 @@ def __init__(self, directory: str): def load_client_config(self, defaults: ClientConfig) -> ClientConfig: config_file_path = os.path.join(self.directory, "client.yaml") - config_data: object = {"tls": {}} + config_data: dict[str, Any] = {"tls": {}} # determine if client.yaml exists if os.path.exists(config_file_path): with open(config_file_path, "r") as file: config_data = yaml.safe_load(file) - def get_config_value(key, env_var): + def get_config_value(key: str, env_var: str) -> str | None: if key in config_data: return config_data[key] @@ -102,10 +99,12 @@ def get_config_value(key, env_var): tenant_id = get_config_value("tenantId", "HATCHET_CLIENT_TENANT_ID") token = get_config_value("token", "HATCHET_CLIENT_TOKEN") - listener_v2_timeout = get_config_value( + _listener_v2_timeout = get_config_value( "listener_v2_timeout", "HATCHET_CLIENT_LISTENER_V2_TIMEOUT" ) - listener_v2_timeout = int(listener_v2_timeout) if listener_v2_timeout else None + listener_v2_timeout = ( + int(_listener_v2_timeout) if _listener_v2_timeout else None + ) if not token: raise ValueError( @@ -115,20 +114,27 @@ def get_config_value(key, env_var): host_port = get_config_value("hostPort", "HATCHET_CLIENT_HOST_PORT") server_url: str | None = None - grpc_max_recv_message_length = get_config_value( - "grpc_max_recv_message_length", - "HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH", - ) - grpc_max_send_message_length = get_config_value( - "grpc_max_send_message_length", - "HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH", + grpc_max_recv_message_length = ( + int(_grpc_max_recv_message_length) + if ( + _grpc_max_recv_message_length := get_config_value( + "grpc_max_recv_message_length", + "HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH", + ) + ) + else None ) - if grpc_max_recv_message_length: - grpc_max_recv_message_length = int(grpc_max_recv_message_length) - - if grpc_max_send_message_length: - grpc_max_send_message_length = int(grpc_max_send_message_length) + grpc_max_send_message_length = ( + int(_grpc_max_send_message_length) + if ( + _grpc_max_send_message_length := get_config_value( + "grpc_max_send_message_length", + "HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH", + ) + ) + else None + ) if not host_port: # extract host and port from token @@ -203,36 +209,27 @@ def get_config_value(key, env_var): worker_healthcheck_enabled=worker_healthcheck_enabled, ) - def _load_tls_config(self, tls_data: Dict, host_port) -> ClientTLSConfig: + def _load_tls_config( + self, tls_data: dict[str, Any], host_port: str + ) -> ClientTLSConfig: tls_strategy = ( - tls_data["tlsStrategy"] - if "tlsStrategy" in tls_data - else self._get_env_var("HATCHET_CLIENT_TLS_STRATEGY") + cast(str | None, tls_data.get("tlsStrategy")) + or self._get_env_var("HATCHET_CLIENT_TLS_STRATEGY") + or "tls" ) - if not tls_strategy: - tls_strategy = "tls" - - cert_file = ( - tls_data["tlsCertFile"] - if "tlsCertFile" in tls_data - else self._get_env_var("HATCHET_CLIENT_TLS_CERT_FILE") + cert_file = tls_data.get("tlsCertFile") or self._get_env_var( + "HATCHET_CLIENT_TLS_CERT_FILE" ) - key_file = ( - tls_data["tlsKeyFile"] - if "tlsKeyFile" in tls_data - else self._get_env_var("HATCHET_CLIENT_TLS_KEY_FILE") + key_file = tls_data.get("tlsKeyFile") or self._get_env_var( + "HATCHET_CLIENT_TLS_KEY_FILE" ) - ca_file = ( - tls_data["tlsRootCAFile"] - if "tlsRootCAFile" in tls_data - else self._get_env_var("HATCHET_CLIENT_TLS_ROOT_CA_FILE") + ca_file = tls_data.get("tlsRootCAFile") or self._get_env_var( + "HATCHET_CLIENT_TLS_ROOT_CA_FILE" ) - server_name = ( - tls_data["tlsServerName"] - if "tlsServerName" in tls_data - else self._get_env_var("HATCHET_CLIENT_TLS_SERVER_NAME") + server_name = tls_data.get("tlsServerName") or self._get_env_var( + "HATCHET_CLIENT_TLS_SERVER_NAME" ) # if server_name is not set, use the host from the host_port diff --git a/hatchet_sdk/metadata.py b/hatchet_sdk/metadata.py index 38a31b8b..9a88744e 100644 --- a/hatchet_sdk/metadata.py +++ b/hatchet_sdk/metadata.py @@ -1,2 +1,2 @@ -def get_metadata(token: str): - return [("authorization", "bearer " + token)] +def get_metadata(token: str | None) -> list[tuple[str, str]]: + return [("authorization", "bearer " + (token or ""))] diff --git a/hatchet_sdk/rate_limit.py b/hatchet_sdk/rate_limit.py index 0d7b9143..30d39814 100644 --- a/hatchet_sdk/rate_limit.py +++ b/hatchet_sdk/rate_limit.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Union -from celpy import CELEvalError, Environment +from celpy import CELEvalError, Environment # type: ignore[import-untyped] from hatchet_sdk.contracts.workflows_pb2 import CreateStepRateLimit @@ -69,11 +69,11 @@ class RateLimit: dynamic_key: Union[str, None] = None units: Union[int, str] = 1 limit: Union[int, str, None] = None - duration: RateLimitDuration = RateLimitDuration.MINUTE + duration: RateLimitDuration = RateLimitDuration.MINUTE # type: ignore[assignment] - _req: CreateStepRateLimit = None + _req: CreateStepRateLimit | None = None - def __post_init__(self): + def __post_init__(self) -> None: # juggle the key and key_expr fields key = self.static_key key_expression = self.dynamic_key @@ -122,5 +122,5 @@ def __post_init__(self): units=units, units_expr=units_expression, limit_values_expr=limit_expression, - duration=self.duration, + duration=self.duration, # type: ignore[arg-type] ) diff --git a/hatchet_sdk/token.py b/hatchet_sdk/token.py index 313a6671..c74f0043 100644 --- a/hatchet_sdk/token.py +++ b/hatchet_sdk/token.py @@ -1,20 +1,22 @@ import base64 import json +from typing import cast -def get_tenant_id_from_jwt(token: str) -> str: +## TODO: Narrow `None` out of the return type if possible. +def get_tenant_id_from_jwt(token: str) -> str | None: claims = extract_claims_from_jwt(token) return claims.get("sub") -def get_addresses_from_jwt(token: str) -> (str, str): +def get_addresses_from_jwt(token: str) -> tuple[str | None, str | None]: claims = extract_claims_from_jwt(token) return claims.get("server_url"), claims.get("grpc_broadcast_address") -def extract_claims_from_jwt(token: str): +def extract_claims_from_jwt(token: str) -> dict[str, str]: parts = token.split(".") if len(parts) != 3: raise ValueError("Invalid token format") @@ -22,6 +24,5 @@ def extract_claims_from_jwt(token: str): claims_part = parts[1] claims_part += "=" * ((4 - len(claims_part) % 4) % 4) # Padding for base64 decoding claims_data = base64.urlsafe_b64decode(claims_part) - claims = json.loads(claims_data) - return claims + return cast(dict[str, str], json.loads(claims_data)) diff --git a/hatchet_sdk/utils/serialization.py b/hatchet_sdk/utils/serialization.py index 7eb1d13a..046f92a9 100644 --- a/hatchet_sdk/utils/serialization.py +++ b/hatchet_sdk/utils/serialization.py @@ -1,7 +1,9 @@ from typing import Any -def flatten(xs: dict[str, Any], parent_key: str, separator: str) -> dict[str, Any]: +def flatten( + xs: dict[str, Any] | None, parent_key: str, separator: str +) -> dict[str, Any]: if not xs: return {} diff --git a/hatchet_sdk/utils/tracing.py b/hatchet_sdk/utils/tracing.py index afc398f7..4a8711a1 100644 --- a/hatchet_sdk/utils/tracing.py +++ b/hatchet_sdk/utils/tracing.py @@ -51,8 +51,11 @@ def create_carrier() -> dict[str, str]: def inject_carrier_into_metadata( - metadata: dict[Any, Any], carrier: dict[str, str] + metadata: dict[Any, Any] | None, carrier: dict[str, str] ) -> dict[Any, Any]: + if metadata is None: + metadata = {} + if carrier: metadata[OTEL_CARRIER_KEY] = carrier diff --git a/hatchet_sdk/v2/callable.py b/hatchet_sdk/v2/callable.py index 097a7d87..64a75fa9 100644 --- a/hatchet_sdk/v2/callable.py +++ b/hatchet_sdk/v2/callable.py @@ -13,7 +13,7 @@ from hatchet_sdk.clients.admin import ChildTriggerWorkflowOptions from hatchet_sdk.context.context import Context -from hatchet_sdk.contracts.workflows_pb2 import ( # type: ignore[attr-defined] +from hatchet_sdk.contracts.workflows_pb2 import ( CreateStepRateLimit, CreateWorkflowJobOpts, CreateWorkflowStepOpts, diff --git a/hatchet_sdk/v2/concurrency.py b/hatchet_sdk/v2/concurrency.py index 73d9e3b4..717e92f0 100644 --- a/hatchet_sdk/v2/concurrency.py +++ b/hatchet_sdk/v2/concurrency.py @@ -1,9 +1,7 @@ from typing import Any, Callable from hatchet_sdk.context.context import Context -from hatchet_sdk.contracts.workflows_pb2 import ( # type: ignore[attr-defined] - ConcurrencyLimitStrategy, -) +from hatchet_sdk.contracts.workflows_pb2 import ConcurrencyLimitStrategy class ConcurrencyFunction: diff --git a/hatchet_sdk/v2/hatchet.py b/hatchet_sdk/v2/hatchet.py index 4dd3faf0..d802d639 100644 --- a/hatchet_sdk/v2/hatchet.py +++ b/hatchet_sdk/v2/hatchet.py @@ -2,10 +2,7 @@ from hatchet_sdk import Worker from hatchet_sdk.context.context import Context -from hatchet_sdk.contracts.workflows_pb2 import ( # type: ignore[attr-defined] - ConcurrencyLimitStrategy, - StickyStrategy, -) +from hatchet_sdk.contracts.workflows_pb2 import ConcurrencyLimitStrategy, StickyStrategy from hatchet_sdk.hatchet import Hatchet as HatchetV1 from hatchet_sdk.hatchet import workflow from hatchet_sdk.labels import DesiredWorkerLabel @@ -63,7 +60,7 @@ def durable( version: str = "", timeout: str = "60m", schedule_timeout: str = "5m", - sticky: StickyStrategy = None, + sticky: StickyStrategy | None = None, retries: int = 0, rate_limits: list[RateLimit] | None = None, desired_worker_labels: dict[str, DesiredWorkerLabel] = {}, @@ -209,7 +206,7 @@ def wrapper(func: HatchetCallable[T]) -> HatchetCallable[T]: def worker( self, name: str, max_runs: int | None = None, labels: dict[str, str | int] = {} - ): + ) -> Worker: worker = Worker( name=name, max_runs=max_runs, diff --git a/hatchet_sdk/worker/action_listener_process.py b/hatchet_sdk/worker/action_listener_process.py index 08508607..fb9ef6cd 100644 --- a/hatchet_sdk/worker/action_listener_process.py +++ b/hatchet_sdk/worker/action_listener_process.py @@ -41,7 +41,7 @@ class ActionEvent: ) -def noop_handler(): +def noop_handler() -> None: pass @@ -66,7 +66,7 @@ class WorkerActionListenerProcess: running_step_runs: Mapping[str, float] = field(init=False, default_factory=dict) - def __post_init__(self): + def __post_init__(self) -> None: if self.debug: logger.setLevel(logging.DEBUG) @@ -77,7 +77,7 @@ def __post_init__(self): signal.SIGQUIT, lambda: asyncio.create_task(self.exit_gracefully()) ) - async def start(self, retry_attempt=0): + async def start(self, retry_attempt: int = 0) -> None: if retry_attempt > 5: logger.error("could not start action listener") return @@ -108,11 +108,11 @@ async def start(self, retry_attempt=0): self.blocked_main_loop = asyncio.create_task(self.start_blocked_main_loop()) # TODO move event methods to separate class - async def _get_event(self): + async def _get_event(self) -> ActionEvent: loop = asyncio.get_running_loop() return await loop.run_in_executor(None, self.event_queue.get) - async def start_event_send_loop(self): + async def start_event_send_loop(self) -> None: while True: event: ActionEvent = await self._get_event() if event == STOP_LOOP: @@ -122,7 +122,7 @@ async def start_event_send_loop(self): logger.debug(f"tx: event: {event.action.action_id}/{event.type}") asyncio.create_task(self.send_event(event)) - async def start_blocked_main_loop(self): + async def start_blocked_main_loop(self) -> None: threshold = 1 while not self.killing: count = 0 @@ -135,7 +135,7 @@ async def start_blocked_main_loop(self): logger.warning(f"{BLOCKED_THREAD_WARNING}: Waiting Steps {count}") await asyncio.sleep(1) - async def send_event(self, event: ActionEvent, retry_attempt: int = 1): + async def send_event(self, event: ActionEvent, retry_attempt: int = 1) -> None: try: match event.action.action_type: # FIXME: all events sent from an execution of a function are of type ActionType.START_STEP_RUN since @@ -185,10 +185,10 @@ async def send_event(self, event: ActionEvent, retry_attempt: int = 1): await exp_backoff_sleep(retry_attempt, 1) await self.send_event(event, retry_attempt + 1) - def now(self): + def now(self) -> float: return time.time() - async def start_action_loop(self): + async def start_action_loop(self) -> None: try: async for action in self.listener: if action is None: @@ -241,7 +241,7 @@ async def start_action_loop(self): if not self.killing: await self.exit_gracefully(skip_unregister=True) - async def cleanup(self): + async def cleanup(self) -> None: self.killing = True if self.listener is not None: @@ -249,7 +249,7 @@ async def cleanup(self): self.event_queue.put(STOP_LOOP) - async def exit_gracefully(self, skip_unregister=False): + async def exit_gracefully(self, skip_unregister: bool = False) -> None: if self.killing: return @@ -262,13 +262,13 @@ async def exit_gracefully(self, skip_unregister=False): logger.info("action listener closed") - def exit_forcefully(self): + def exit_forcefully(self) -> None: asyncio.run(self.cleanup()) logger.debug("forcefully closing listener...") -def worker_action_listener_process(*args, **kwargs): - async def run(): +def worker_action_listener_process(*args, **kwargs) -> None: + async def run() -> None: process = WorkerActionListenerProcess(*args, **kwargs) await process.start() # Keep the process running diff --git a/hatchet_sdk/worker/runner/run_loop_manager.py b/hatchet_sdk/worker/runner/run_loop_manager.py index 27ed788c..5c5496a7 100644 --- a/hatchet_sdk/worker/runner/run_loop_manager.py +++ b/hatchet_sdk/worker/runner/run_loop_manager.py @@ -2,7 +2,7 @@ import logging from dataclasses import dataclass, field from multiprocessing import Queue -from typing import Callable, TypeVar +from typing import Any, Callable, TypeVar from hatchet_sdk import Context from hatchet_sdk.client import Client, new_client_raw @@ -25,8 +25,8 @@ class WorkerActionRunLoopManager: validator_registry: dict[str, WorkflowValidator] max_runs: int | None config: ClientConfig - action_queue: Queue - event_queue: Queue + action_queue: Queue[Any] + event_queue: Queue[Any] loop: asyncio.AbstractEventLoop handle_kill: bool = True debug: bool = False @@ -37,16 +37,16 @@ class WorkerActionRunLoopManager: killing: bool = field(init=False, default=False) runner: Runner = field(init=False, default=None) - def __post_init__(self): + def __post_init__(self) -> None: if self.debug: logger.setLevel(logging.DEBUG) self.client = new_client_raw(self.config, self.debug) self.start() - def start(self, retry_count=1): + def start(self, retry_count: int = 1) -> None: k = self.loop.create_task(self.async_start(retry_count)) - async def async_start(self, retry_count=1): + async def async_start(self, retry_count: int = 1) -> None: await capture_logs( self.client.logInterceptor, self.client.event, @@ -91,7 +91,7 @@ async def _start_action_loop(self) -> None: self.runner.run(action) logger.debug("action runner loop stopped") - async def _get_action(self): + async def _get_action(self) -> Any: return await self.loop.run_in_executor(None, self.action_queue.get) async def exit_gracefully(self) -> None: diff --git a/hatchet_sdk/worker/runner/utils/capture_logs.py b/hatchet_sdk/worker/runner/utils/capture_logs.py index 08c57de8..bd1b013b 100644 --- a/hatchet_sdk/worker/runner/utils/capture_logs.py +++ b/hatchet_sdk/worker/runner/utils/capture_logs.py @@ -32,12 +32,12 @@ def filter(self, record): class CustomLogHandler(logging.StreamHandler): - def __init__(self, event_client: EventClient, stream=None): + def __init__(self, event_client: EventClient, stream=None) -> None: super().__init__(stream) self.logger_thread_pool = ThreadPoolExecutor(max_workers=1) self.event_client = event_client - def _log(self, line: str, step_run_id: str | None): + def _log(self, line: str, step_run_id: str | None) -> None: try: if not step_run_id: return @@ -46,7 +46,7 @@ def _log(self, line: str, step_run_id: str | None): except Exception as e: logger.error(f"Error logging: {e}") - def emit(self, record): + def emit(self, record) -> None: super().emit(record) log_entry = self.format(record) @@ -57,7 +57,7 @@ def capture_logs( logger: logging.Logger, event_client: EventClient, func: Coroutine[Any, Any, Any], -): +) -> Coroutine[Any, Any, Any]: @functools.wraps(func) async def wrapper(*args, **kwargs): if not logger: diff --git a/hatchet_sdk/worker/runner/utils/error_with_traceback.py b/hatchet_sdk/worker/runner/utils/error_with_traceback.py index 9c09602f..88c9f078 100644 --- a/hatchet_sdk/worker/runner/utils/error_with_traceback.py +++ b/hatchet_sdk/worker/runner/utils/error_with_traceback.py @@ -1,6 +1,6 @@ import traceback -def errorWithTraceback(message: str, e: Exception): +def error_with_traceback(message: str, e: Exception) -> str: trace = "".join(traceback.format_exception(type(e), e, e.__traceback__)) return f"{message}\n{trace}" diff --git a/pyproject.toml b/pyproject.toml index b28150df..6947fd09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,8 @@ mypy = "^1.14.0" types-protobuf = "^5.28.3.20241030" black = "^24.10.0" isort = "^5.13.2" +types-pyyaml = "^6.0.12.20240917" +types-psutil = "^6.1.0.20241102" [tool.poetry.group.test.dependencies] pytest-timeout = "^2.3.1" @@ -78,24 +80,11 @@ extend_exclude = "hatchet_sdk/contracts/" [tool.mypy] strict = true -files = [ - "hatchet_sdk/hatchet.py", - "hatchet_sdk/worker/worker.py", - "hatchet_sdk/context/context.py", - "hatchet_sdk/worker/runner/runner.py", - "hatchet_sdk/workflow.py", - "hatchet_sdk/utils/serialization.py", - "hatchet_sdk/utils/tracing.py", - "hatchet_sdk/utils/types.py", - "hatchet_sdk/utils/backoff.py", - "examples/**/*.py", - "hatchet_sdk/clients/rest/models/workflow_list.py", - "hatchet_sdk/clients/rest/models/workflow_run.py", - "hatchet_sdk/context/worker_context.py", - "hatchet_sdk/clients/dispatcher/dispatcher.py", -] +files = "." exclude = [ - "hatchet_sdk/clients/rest", + "hatchet_sdk/clients/rest/.*", + ".*.pyi$", + "hatchet_sdk/contracts/.*" ] follow_imports = "silent" disable_error_code = ["unused-coroutine"] From 592d2167567425c9028f7d528b1b0ec365a3f633 Mon Sep 17 00:00:00 2001 From: hatchet-temporary Date: Wed, 18 Dec 2024 21:24:52 -0500 Subject: [PATCH 3/9] fix: unwind --- hatchet_sdk/worker/runner/utils/error_with_traceback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hatchet_sdk/worker/runner/utils/error_with_traceback.py b/hatchet_sdk/worker/runner/utils/error_with_traceback.py index 88c9f078..6aff1cb6 100644 --- a/hatchet_sdk/worker/runner/utils/error_with_traceback.py +++ b/hatchet_sdk/worker/runner/utils/error_with_traceback.py @@ -1,6 +1,6 @@ import traceback -def error_with_traceback(message: str, e: Exception) -> str: +def errorWithTraceback(message: str, e: Exception) -> str: trace = "".join(traceback.format_exception(type(e), e, e.__traceback__)) return f"{message}\n{trace}" From f56dc16c4adf4cb95249113039d58d184e81bf84 Mon Sep 17 00:00:00 2001 From: hatchet-temporary Date: Thu, 2 Jan 2025 10:37:40 -0500 Subject: [PATCH 4/9] fix: more cleanup --- examples/bulk_fanout/bulk_trigger.py | 28 +++++++++++----------- hatchet_sdk/clients/admin.py | 35 ++++++++++++++++------------ hatchet_sdk/hatchet.py | 3 +-- hatchet_sdk/loader.py | 2 +- hatchet_sdk/token.py | 8 +++---- pyproject.toml | 3 ++- 6 files changed, 41 insertions(+), 38 deletions(-) diff --git a/examples/bulk_fanout/bulk_trigger.py b/examples/bulk_fanout/bulk_trigger.py index d0606673..98c8f8d1 100644 --- a/examples/bulk_fanout/bulk_trigger.py +++ b/examples/bulk_fanout/bulk_trigger.py @@ -7,7 +7,7 @@ from dotenv import load_dotenv from hatchet_sdk import new_client -from hatchet_sdk.clients.admin import TriggerWorkflowOptions +from hatchet_sdk.clients.admin import TriggerWorkflowOptions, WorkflowRunDict from hatchet_sdk.clients.rest.models.workflow_run import WorkflowRun from hatchet_sdk.clients.run_event_listener import StepRunEventType @@ -16,22 +16,20 @@ async def main() -> None: load_dotenv() hatchet = new_client() - workflowRuns: list[dict[str, Any]] = [] - # we are going to run the BulkParent workflow 20 which will trigger the Child workflows n times for each n in range(20) - for i in range(20): - workflowRuns.append( - { - "workflow_name": "BulkParent", - "input": {"n": i}, - "options": { - "additional_metadata": { - "bulk-trigger": i, - "hello-{i}": "earth-{i}", - }, - }, - } + workflowRuns = [ + WorkflowRunDict( + workflow_name="BulkParent", + input={"n": i}, + options=TriggerWorkflowOptions( + additional_metadata={ + "bulk-trigger": str(i), + f"hello-{i}": f"earth-{i}", + } + ), ) + for i in range(20) + ] workflowRunRefs = hatchet.admin.run_workflows( workflowRuns, diff --git a/hatchet_sdk/clients/admin.py b/hatchet_sdk/clients/admin.py index 80c83429..0e6c9090 100644 --- a/hatchet_sdk/clients/admin.py +++ b/hatchet_sdk/clients/admin.py @@ -83,7 +83,10 @@ class AdminClientBase: pooled_workflow_listener: PooledWorkflowRunListener | None = None def _prepare_workflow_request( - self, workflow_name: str, input: Any, options: TriggerWorkflowOptions = None + self, + workflow_name: str, + input: Any, + options: TriggerWorkflowOptions | None = None, ) -> TriggerWorkflowRequest: try: payload_data = json.dumps(input) @@ -476,9 +479,12 @@ def schedule_workflow( name, schedules, input, options ) - return self.client.ScheduleWorkflow( - request, - metadata=get_metadata(self.token), + return cast( + WorkflowVersion, + self.client.ScheduleWorkflow( + request, + metadata=get_metadata(self.token), + ), ) except grpc.RpcError as e: if e.code() == grpc.StatusCode.ALREADY_EXISTS: @@ -518,7 +524,7 @@ def run_workflow( and "namespace" in options and options["namespace"] is not None ): - namespace = options.pop("namespace") + namespace = cast(str, options.pop("namespace")) if options is not None and "additional_metadata" in options: options["additional_metadata"] = inject_carrier_into_metadata( @@ -567,7 +573,7 @@ def run_workflows( workflows: list[WorkflowRunDict], options: TriggerWorkflowOptions | None = None, ) -> list[WorkflowRunRef]: - workflow_run_requests: TriggerWorkflowRequest = [] + workflow_run_requests: list[TriggerWorkflowRequest] = [] try: if not self.pooled_workflow_listener: self.pooled_workflow_listener = PooledWorkflowRunListener(self.config) @@ -591,13 +597,11 @@ def run_workflows( workflow_name = f"{namespace}{workflow_name}" # Prepare and trigger workflow for each workflow name and input - request = self._prepare_workflow_request( - workflow_name, input_data, options + workflow_run_requests.append( + self._prepare_workflow_request(workflow_name, input_data, options) ) - workflow_run_requests.append(request) - - request = BulkTriggerWorkflowRequest(workflows=workflow_run_requests) + request = BulkTriggerWorkflowRequest(workflows=workflow_run_requests) resp: BulkTriggerWorkflowResponse = self.client.BulkTriggerWorkflow( request, @@ -622,10 +626,11 @@ def run( input: Any, options: TriggerWorkflowOptions | None = None, ) -> "RunRef[T]": - workflow_name = function - - if not isinstance(function, str): - workflow_name = function.function_name + workflow_name = ( + cast(str, getattr(function, "function_name")) + if not isinstance(function, str) + else function + ) wrr = self.run_workflow(workflow_name, input, options) diff --git a/hatchet_sdk/hatchet.py b/hatchet_sdk/hatchet.py index bf0e9089..62c50d96 100644 --- a/hatchet_sdk/hatchet.py +++ b/hatchet_sdk/hatchet.py @@ -69,13 +69,12 @@ def inner(cls: Type[TWorkflow]) -> WorkflowMeta: setattr(cls, "sticky", sticky) setattr(cls, "default_priority", default_priority) setattr(cls, "concurrency_expression", concurrency) + setattr(cls, "input_validator", input_validator) # Define a new class with the same name and bases as the original, but # with WorkflowMeta as its metaclass ## TODO: Figure out how to type this metaclass correctly - setattr(cls, "input_validator", input_validator) - return WorkflowMeta(name, cls.__bases__, dict(cls.__dict__)) return inner diff --git a/hatchet_sdk/loader.py b/hatchet_sdk/loader.py index af498d2b..9e8ba6d3 100644 --- a/hatchet_sdk/loader.py +++ b/hatchet_sdk/loader.py @@ -88,7 +88,7 @@ def load_client_config(self, defaults: ClientConfig) -> ClientConfig: def get_config_value(key: str, env_var: str) -> str | None: if key in config_data: - return config_data[key] + return cast(str, config_data[key]) if self._get_env_var(env_var) is not None: return self._get_env_var(env_var) diff --git a/hatchet_sdk/token.py b/hatchet_sdk/token.py index c74f0043..21d329ea 100644 --- a/hatchet_sdk/token.py +++ b/hatchet_sdk/token.py @@ -4,16 +4,16 @@ ## TODO: Narrow `None` out of the return type if possible. -def get_tenant_id_from_jwt(token: str) -> str | None: +def get_tenant_id_from_jwt(token: str) -> str: claims = extract_claims_from_jwt(token) - return claims.get("sub") + return claims["sub"] -def get_addresses_from_jwt(token: str) -> tuple[str | None, str | None]: +def get_addresses_from_jwt(token: str) -> tuple[str, str]: claims = extract_claims_from_jwt(token) - return claims.get("server_url"), claims.get("grpc_broadcast_address") + return claims["server_url"], claims["grpc_broadcast_address"] def extract_claims_from_jwt(token: str) -> dict[str, str]: diff --git a/pyproject.toml b/pyproject.toml index 6947fd09..4cefb88f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,8 @@ files = "." exclude = [ "hatchet_sdk/clients/rest/.*", ".*.pyi$", - "hatchet_sdk/contracts/.*" + "hatchet_sdk/contracts/.*", + "hatchet_sdk/v2" ] follow_imports = "silent" disable_error_code = ["unused-coroutine"] From 0531897a3b62d3090d0cab7126c2f70c87351a41 Mon Sep 17 00:00:00 2001 From: hatchet-temporary Date: Thu, 2 Jan 2025 11:07:07 -0500 Subject: [PATCH 5/9] fix: more --- hatchet_sdk/clients/admin.py | 16 ++++----- hatchet_sdk/clients/event_ts.py | 2 ++ hatchet_sdk/clients/rest_client.py | 4 +-- hatchet_sdk/clients/run_event_listener.py | 2 +- hatchet_sdk/clients/workflow_listener.py | 1 - hatchet_sdk/connection.py | 27 ++++++++------ hatchet_sdk/features/cron.py | 35 ++++++++++++------- hatchet_sdk/features/scheduled.py | 9 +++-- hatchet_sdk/worker/action_listener_process.py | 2 +- .../worker/runner/utils/capture_logs.py | 4 +-- pyproject.toml | 1 + 11 files changed, 61 insertions(+), 42 deletions(-) diff --git a/hatchet_sdk/clients/admin.py b/hatchet_sdk/clients/admin.py index 0e6c9090..4a909317 100644 --- a/hatchet_sdk/clients/admin.py +++ b/hatchet_sdk/clients/admin.py @@ -64,7 +64,6 @@ class ChildWorkflowRunDict(TypedDict, total=False): class TriggerWorkflowOptions(ScheduleTriggerWorkflowOptions, total=False): additional_metadata: dict[str, str | bytes] | None desired_worker_id: str | None - namespace: str | None class WorkflowRunDict(TypedDict, total=False): @@ -180,12 +179,13 @@ async def run( self, function: Union[str, Callable[[Any], T]], input: Any, - options: TriggerWorkflowOptions = None, + options: TriggerWorkflowOptions | None = None, ) -> "RunRef[T]": - workflow_name = function - - if not isinstance(function, str): - workflow_name = function.function_name + workflow_name = ( + cast(str, getattr(function, "function_name")) + if not isinstance(function, str) + else function + ) wrr = await self.run_workflow(workflow_name, input, options) @@ -222,7 +222,7 @@ async def run_workflow( and "namespace" in options and options["namespace"] is not None ): - namespace = options.pop("namespace") + namespace = cast(str, options.pop("namespace")) if namespace != "" and not workflow_name.startswith(self.namespace): workflow_name = f"{namespace}{workflow_name}" @@ -288,7 +288,7 @@ async def run_workflows( namespace = options["namespace"] del options["namespace"] - workflow_run_requests: TriggerWorkflowRequest = [] + workflow_run_requests: list[TriggerWorkflowRequest] = [] for workflow in workflows: workflow_name = workflow["workflow_name"] diff --git a/hatchet_sdk/clients/event_ts.py b/hatchet_sdk/clients/event_ts.py index 7add1a79..3b559fa3 100644 --- a/hatchet_sdk/clients/event_ts.py +++ b/hatchet_sdk/clients/event_ts.py @@ -9,6 +9,8 @@ class Event_ts(asyncio.Event): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) + self._loop: asyncio.AbstractEventLoop + if self._loop is None: self._loop = asyncio.get_event_loop() diff --git a/hatchet_sdk/clients/rest_client.py b/hatchet_sdk/clients/rest_client.py index 91b4cca0..b357ab0e 100644 --- a/hatchet_sdk/clients/rest_client.py +++ b/hatchet_sdk/clients/rest_client.py @@ -184,7 +184,7 @@ async def workflow_run_replay( return await self.workflow_run_api.workflow_run_update_replay( tenant=self.tenant_id, replay_workflow_runs_request=ReplayWorkflowRunsRequest( - workflow_run_ids=workflow_run_ids, + workflowRunIds=workflow_run_ids, ), ) @@ -287,7 +287,7 @@ async def schedule_create( create_schedule_workflow_trigger_request=ScheduleWorkflowRunRequest( triggerAt=trigger_at, input=input, - additional_metadata=additional_metadata, + additionalMetadata=additional_metadata, ), ) diff --git a/hatchet_sdk/clients/run_event_listener.py b/hatchet_sdk/clients/run_event_listener.py index 11a45596..7a479798 100644 --- a/hatchet_sdk/clients/run_event_listener.py +++ b/hatchet_sdk/clients/run_event_listener.py @@ -2,7 +2,7 @@ import json from typing import AsyncGenerator, Callable, Generator -import grpc # type: ignore[import-untyped] +import grpc from hatchet_sdk.connection import new_conn from hatchet_sdk.contracts.dispatcher_pb2 import ( diff --git a/hatchet_sdk/clients/workflow_listener.py b/hatchet_sdk/clients/workflow_listener.py index 5b0d5a64..35cb3af8 100644 --- a/hatchet_sdk/clients/workflow_listener.py +++ b/hatchet_sdk/clients/workflow_listener.py @@ -188,7 +188,6 @@ def cleanup_subscription(self, subscription_id: int) -> None: del self.events[subscription_id] async def subscribe(self, workflow_run_id: str) -> WorkflowRunEvent: - init_producer: asyncio.Task = None try: # create a new subscription id, place a mutex on the counter await self.subscription_counter_lock.acquire() diff --git a/hatchet_sdk/connection.py b/hatchet_sdk/connection.py index 86efc34b..a3723cce 100644 --- a/hatchet_sdk/connection.py +++ b/hatchet_sdk/connection.py @@ -1,7 +1,7 @@ import os -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast -import grpc # type: ignore[import-untyped] +import grpc if TYPE_CHECKING: from hatchet_sdk.loader import ClientConfig @@ -34,7 +34,7 @@ def new_conn( start = grpc if not aio else grpc.aio - channel_options = [ + channel_options: list[tuple[str, str | int]] = [ ("grpc.max_send_message_length", config.grpc_max_send_message_length), ("grpc.max_receive_message_length", config.grpc_max_recv_message_length), ("grpc.keepalive_time_ms", 10 * 1000), @@ -49,18 +49,23 @@ def new_conn( os.environ["GRPC_ENABLE_FORK_SUPPORT"] = "False" if config.tls_config.tls_strategy == "none": - conn = start.insecure_channel( - target=config.host_port, - options=channel_options, + return cast( + grpc.Channel, + start.insecure_channel( + target=config.host_port, + options=channel_options, + ), ) else: channel_options.append( ("grpc.ssl_target_name_override", config.tls_config.server_name) ) - conn = start.secure_channel( - target=config.host_port, - credentials=credentials, - options=channel_options, + return cast( + grpc.Channel, + start.secure_channel( + target=config.host_port, + credentials=credentials, + options=channel_options, + ), ) - return conn diff --git a/hatchet_sdk/features/cron.py b/hatchet_sdk/features/cron.py index 159d7f65..04e0dfd2 100644 --- a/hatchet_sdk/features/cron.py +++ b/hatchet_sdk/features/cron.py @@ -1,6 +1,6 @@ from typing import Any, Union -from pydantic import BaseModel, field_validator +from pydantic import BaseModel, ValidationError, field_validator from hatchet_sdk.client import Client from hatchet_sdk.clients.rest.models.cron_workflows import CronWorkflows @@ -23,12 +23,12 @@ class CreateCronTriggerInput(BaseModel): additional_metadata (dict[str, str]): Additional metadata associated with the cron trigger (e.g. {"key1": "value1", "key2": "value2"}). """ - expression: str = None + expression: str input: dict[str, Any] = {} additional_metadata: dict[str, str] = {} @field_validator("expression") - def validate_cron_expression(cls, v): + def validate_cron_expression(cls, v: Any) -> str: """ Validates the cron expression to ensure it adheres to the expected format. @@ -42,11 +42,14 @@ def validate_cron_expression(cls, v): str: The validated cron expression. """ if not v: - raise ValueError("Cron expression is required") + raise ValidationError("Cron expression is required") + + if not isinstance(v, str): + raise ValidationError("Cron expression must be a string") parts = v.split() if len(parts) != 5: - raise ValueError( + raise ValidationError( "Cron expression must have 5 parts: minute hour day month weekday" ) @@ -55,7 +58,7 @@ def validate_cron_expression(cls, v): part == "*" or part.replace("*/", "").replace("-", "").replace(",", "").isdigit() ): - raise ValueError(f"Invalid cron expression part: {part}") + raise ValidationError(f"Invalid cron expression part: {part}") return v @@ -121,9 +124,12 @@ def delete(self, cron_trigger: Union[str, CronWorkflows]) -> None: Args: cron_trigger (Union[str, CronWorkflows]): The cron trigger ID or CronWorkflows instance to delete. """ - id_ = cron_trigger - if isinstance(cron_trigger, CronWorkflows): - id_ = cron_trigger.metadata.id + id_ = ( + cron_trigger.metadata.id + if isinstance(cron_trigger, CronWorkflows) + else cron_trigger + ) + self._client.rest.cron_delete(id_) def list( @@ -134,7 +140,7 @@ def list( additional_metadata: list[str] | None = None, order_by_field: CronWorkflowsOrderByField | None = None, order_by_direction: WorkflowRunOrderByDirection | None = None, - ) -> CronWorkflowsList: + ) -> CronWorkflows: """ Retrieves a list of all workflow cron triggers matching the criteria. @@ -280,7 +286,10 @@ async def get(self, cron_trigger: Union[str, CronWorkflows]) -> CronWorkflows: Returns: CronWorkflows: The requested cron workflow instance. """ - id_ = cron_trigger - if isinstance(cron_trigger, CronWorkflows): - id_ = cron_trigger.metadata.id + id_ = ( + cron_trigger.metadata.id + if isinstance(cron_trigger, CronWorkflows) + else cron_trigger + ) + return await self._client.rest.aio.cron_get(id_) diff --git a/hatchet_sdk/features/scheduled.py b/hatchet_sdk/features/scheduled.py index 45af2609..76cf36de 100644 --- a/hatchet_sdk/features/scheduled.py +++ b/hatchet_sdk/features/scheduled.py @@ -242,7 +242,10 @@ async def get( Returns: ScheduledWorkflows: The requested scheduled workflow instance. """ - id_ = scheduled - if isinstance(scheduled, ScheduledWorkflows): - id_ = scheduled.metadata.id + id_ = ( + scheduled.metadata.id + if isinstance(scheduled, ScheduledWorkflows) + else scheduled + ) + return await self._client.rest.aio.schedule_get(id_) diff --git a/hatchet_sdk/worker/action_listener_process.py b/hatchet_sdk/worker/action_listener_process.py index fb9ef6cd..960c5a7c 100644 --- a/hatchet_sdk/worker/action_listener_process.py +++ b/hatchet_sdk/worker/action_listener_process.py @@ -267,7 +267,7 @@ def exit_forcefully(self) -> None: logger.debug("forcefully closing listener...") -def worker_action_listener_process(*args, **kwargs) -> None: +def worker_action_listener_process(*args: Any, **kwargs: Any) -> None: async def run() -> None: process = WorkerActionListenerProcess(*args, **kwargs) await process.start() diff --git a/hatchet_sdk/worker/runner/utils/capture_logs.py b/hatchet_sdk/worker/runner/utils/capture_logs.py index bd1b013b..f07881b4 100644 --- a/hatchet_sdk/worker/runner/utils/capture_logs.py +++ b/hatchet_sdk/worker/runner/utils/capture_logs.py @@ -5,8 +5,8 @@ from io import StringIO from typing import Any, Coroutine -from hatchet_sdk import logger from hatchet_sdk.clients.events import EventClient +from hatchet_sdk.logger import logger wr: contextvars.ContextVar[str | None] = contextvars.ContextVar( "workflow_run_id", default=None @@ -25,7 +25,7 @@ def copy_context_vars(ctx_vars, func, *args, **kwargs): class InjectingFilter(logging.Filter): # For some reason, only the InjectingFilter has access to the contextvars method sr.get(), # otherwise we would use emit within the CustomLogHandler - def filter(self, record): + def filter(self, record) -> bool: record.workflow_run_id = wr.get() record.step_run_id = sr.get() return True diff --git a/pyproject.toml b/pyproject.toml index 4cefb88f..ddf30c2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ black = "^24.10.0" isort = "^5.13.2" types-pyyaml = "^6.0.12.20240917" types-psutil = "^6.1.0.20241102" +grpc-stubs = "^1.53.0.5" [tool.poetry.group.test.dependencies] pytest-timeout = "^2.3.1" From c13faa8c04aeb6fd9ad96032ecdb82baf9682957 Mon Sep 17 00:00:00 2001 From: hatchet-temporary Date: Thu, 2 Jan 2025 11:20:35 -0500 Subject: [PATCH 6/9] fix: more --- hatchet_sdk/clients/admin.py | 5 ++--- .../clients/dispatcher/action_listener.py | 2 +- hatchet_sdk/clients/events.py | 14 ++++++++------ hatchet_sdk/clients/workflow_listener.py | 4 ++-- hatchet_sdk/features/scheduled.py | 18 ++++++++++++------ hatchet_sdk/worker/action_listener_process.py | 12 ++++++------ hatchet_sdk/workflow_run.py | 2 +- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/hatchet_sdk/clients/admin.py b/hatchet_sdk/clients/admin.py index 4a909317..43e7c2d2 100644 --- a/hatchet_sdk/clients/admin.py +++ b/hatchet_sdk/clients/admin.py @@ -299,10 +299,9 @@ async def run_workflows( workflow_name = f"{namespace}{workflow_name}" # Prepare and trigger workflow for each workflow name and input - request = self._prepare_workflow_request( - workflow_name, input_data, options + workflow_run_requests.append( + self._prepare_workflow_request(workflow_name, input_data, options) ) - workflow_run_requests.append(request) request = BulkTriggerWorkflowRequest(workflows=workflow_run_requests) diff --git a/hatchet_sdk/clients/dispatcher/action_listener.py b/hatchet_sdk/clients/dispatcher/action_listener.py index 06a079cd..ea446131 100644 --- a/hatchet_sdk/clients/dispatcher/action_listener.py +++ b/hatchet_sdk/clients/dispatcher/action_listener.py @@ -222,7 +222,7 @@ async def start_heartbeater(self) -> None: def __aiter__(self) -> AsyncGenerator[Action, None]: return self._generator() - async def _generator(self) -> AsyncGenerator[Action, None]: + async def _generator(self) -> AsyncGenerator[Action | None, None]: listener = None while not self.stop_signal: diff --git a/hatchet_sdk/clients/events.py b/hatchet_sdk/clients/events.py index 8a35f698..37105d16 100644 --- a/hatchet_sdk/clients/events.py +++ b/hatchet_sdk/clients/events.py @@ -1,7 +1,7 @@ import asyncio import datetime import json -from typing import Any, Dict, List, Optional, TypedDict +from typing import Any, Dict, List, Optional, TypedDict, cast from uuid import uuid4 import grpc @@ -74,13 +74,15 @@ async def async_push( async def async_bulk_push( self, - events: List[BulkPushEventWithMetadata], - options: Optional[BulkPushEventOptions] = None, + events: list[BulkPushEventWithMetadata], + options: BulkPushEventOptions | None = None, ) -> List[Event]: return await asyncio.to_thread(self.bulk_push, events=events, options=options) @tenacity_retry - def push(self, event_key, payload, options: PushEventOptions = None) -> Event: + def push( + self, event_key, payload, options: PushEventOptions | None = None + ) -> Event: ctx = parse_carrier_from_metadata( (options or {}).get("additional_metadata", {}) ) @@ -96,7 +98,7 @@ def push(self, event_key, payload, options: PushEventOptions = None) -> Event: and "namespace" in options and options["namespace"] is not None ): - namespace = options.pop("namespace") + namespace = cast(str, options.pop("namespace")) namespaced_event_key = namespace + event_key @@ -147,7 +149,7 @@ def bulk_push( and "namespace" in options and options["namespace"] is not None ): - namespace = options.pop("namespace") + namespace = cast(str, options.pop("namespace")) bulk_events = [] for event in events: diff --git a/hatchet_sdk/clients/workflow_listener.py b/hatchet_sdk/clients/workflow_listener.py index 35cb3af8..48db7b98 100644 --- a/hatchet_sdk/clients/workflow_listener.py +++ b/hatchet_sdk/clients/workflow_listener.py @@ -65,14 +65,14 @@ class PooledWorkflowRunListener: requests: asyncio.Queue[SubscribeToWorkflowRunsRequest] = asyncio.Queue() listener: AsyncGenerator[WorkflowRunEvent, None] = None - listener_task: asyncio.Task = None + listener_task: asyncio.Task[Any] = None curr_requester: int = 0 # events have keys of the format workflow_run_id + subscription_id events: dict[int, _Subscription] = {} - interrupter: asyncio.Task = None + interrupter: asyncio.Task[Any] = None def __init__(self, config: ClientConfig): conn = new_conn(config, True) diff --git a/hatchet_sdk/features/scheduled.py b/hatchet_sdk/features/scheduled.py index 76cf36de..106d217f 100644 --- a/hatchet_sdk/features/scheduled.py +++ b/hatchet_sdk/features/scheduled.py @@ -138,9 +138,12 @@ def get(self, scheduled: Union[str, ScheduledWorkflows]) -> ScheduledWorkflows: Returns: ScheduledWorkflows: The requested scheduled workflow instance. """ - id_ = scheduled - if isinstance(scheduled, ScheduledWorkflows): - id_ = scheduled.metadata.id + id_ = ( + scheduled.metadata.id + if isinstance(scheduled, ScheduledWorkflows) + else scheduled + ) + return self._client.rest.schedule_get(id_) @@ -193,9 +196,12 @@ async def delete(self, scheduled: Union[str, ScheduledWorkflows]) -> None: Args: scheduled (Union[str, ScheduledWorkflows]): The scheduled workflow trigger ID or ScheduledWorkflows instance to delete. """ - id_ = scheduled - if isinstance(scheduled, ScheduledWorkflows): - id_ = scheduled.metadata.id + id_ = ( + scheduled.metadata.id + if isinstance(scheduled, ScheduledWorkflows) + else scheduled + ) + await self._client.rest.aio.schedule_delete(id_) async def list( diff --git a/hatchet_sdk/worker/action_listener_process.py b/hatchet_sdk/worker/action_listener_process.py index 960c5a7c..06646898 100644 --- a/hatchet_sdk/worker/action_listener_process.py +++ b/hatchet_sdk/worker/action_listener_process.py @@ -48,21 +48,21 @@ def noop_handler() -> None: @dataclass class WorkerActionListenerProcess: name: str - actions: List[str] + actions: list[str] max_runs: int config: ClientConfig - action_queue: Queue - event_queue: Queue + action_queue: Queue[Any] + event_queue: Queue[Any] handle_kill: bool = True debug: bool = False - labels: dict = field(default_factory=dict) + labels: dict[str, str | int] = field(default_factory=dict) listener: ActionListener = field(init=False, default=None) killing: bool = field(init=False, default=False) - action_loop_task: asyncio.Task = field(init=False, default=None) - event_send_loop_task: asyncio.Task = field(init=False, default=None) + action_loop_task: asyncio.Task[Any] = field(init=False, default=None) + event_send_loop_task: asyncio.Task[Any] = field(init=False, default=None) running_step_runs: Mapping[str, float] = field(init=False, default_factory=dict) diff --git a/hatchet_sdk/workflow_run.py b/hatchet_sdk/workflow_run.py index 51a23821..ae2629b9 100644 --- a/hatchet_sdk/workflow_run.py +++ b/hatchet_sdk/workflow_run.py @@ -31,7 +31,7 @@ def stream(self) -> RunEventListener: def result(self) -> Coroutine: return self.workflow_listener.result(self.workflow_run_id) - def sync_result(self) -> dict: + def sync_result(self) -> dict[str, Any]: loop = get_active_event_loop() if loop is None: with EventLoopThread() as loop: From e9e304270bd13aaf893c1bc9688cb9ba7bb9cd21 Mon Sep 17 00:00:00 2001 From: hatchet-temporary Date: Thu, 2 Jan 2025 12:16:53 -0500 Subject: [PATCH 7/9] fix: couple more --- hatchet_sdk/clients/admin.py | 2 ++ hatchet_sdk/clients/run_event_listener.py | 16 +++++++++++----- hatchet_sdk/clients/workflow_listener.py | 2 ++ hatchet_sdk/features/cron.py | 22 ++++++++++++++-------- hatchet_sdk/features/scheduled.py | 9 ++++++--- hatchet_sdk/workflow_run.py | 4 ++-- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/hatchet_sdk/clients/admin.py b/hatchet_sdk/clients/admin.py index 43e7c2d2..bcf3aa9c 100644 --- a/hatchet_sdk/clients/admin.py +++ b/hatchet_sdk/clients/admin.py @@ -87,6 +87,8 @@ def _prepare_workflow_request( input: Any, options: TriggerWorkflowOptions | None = None, ) -> TriggerWorkflowRequest: + options = options or {} + try: payload_data = json.dumps(input) diff --git a/hatchet_sdk/clients/run_event_listener.py b/hatchet_sdk/clients/run_event_listener.py index 7a479798..f631e978 100644 --- a/hatchet_sdk/clients/run_event_listener.py +++ b/hatchet_sdk/clients/run_event_listener.py @@ -1,8 +1,9 @@ import asyncio import json -from typing import AsyncGenerator, Callable, Generator +from typing import AsyncGenerator, Callable, Generator, cast import grpc +import grpc.aio from hatchet_sdk.connection import new_conn from hatchet_sdk.contracts.dispatcher_pb2 import ( @@ -207,11 +208,14 @@ async def retry_subscribe(self) -> grpc.aio.StreamStreamCall: await asyncio.sleep(DEFAULT_ACTION_LISTENER_RETRY_INTERVAL) if self.workflow_run_id is not None: - return self.client.SubscribeToWorkflowEvents( - SubscribeToWorkflowEventsRequest( - workflowRunId=self.workflow_run_id, + return cast( + grpc.aio.StreamStreamCall, + self.client.SubscribeToWorkflowEvents( + SubscribeToWorkflowEventsRequest( + workflowRunId=self.workflow_run_id, + ), + metadata=get_metadata(self.token), ), - metadata=get_metadata(self.token), ) elif self.additional_meta_kv is not None: return self.client.SubscribeToWorkflowEvents( @@ -230,6 +234,8 @@ async def retry_subscribe(self) -> grpc.aio.StreamStreamCall: else: raise ValueError(f"gRPC error: {e}") + raise ValueError("Failed to subscribe to events") + class RunEventListenerClient: def __init__(self, config: ClientConfig) -> None: diff --git a/hatchet_sdk/clients/workflow_listener.py b/hatchet_sdk/clients/workflow_listener.py index 48db7b98..5dac9546 100644 --- a/hatchet_sdk/clients/workflow_listener.py +++ b/hatchet_sdk/clients/workflow_listener.py @@ -268,3 +268,5 @@ async def _retry_subscribe(self) -> AsyncIterator[SubscribeToWorkflowRunsRequest retries = retries + 1 else: raise ValueError(f"gRPC error: {e}") + + raise ValueError("Failed to connect to workflow run listener") diff --git a/hatchet_sdk/features/cron.py b/hatchet_sdk/features/cron.py index 04e0dfd2..d7383fbb 100644 --- a/hatchet_sdk/features/cron.py +++ b/hatchet_sdk/features/cron.py @@ -174,9 +174,12 @@ def get(self, cron_trigger: Union[str, CronWorkflows]) -> CronWorkflows: Returns: CronWorkflows: The requested cron workflow instance. """ - id_ = cron_trigger - if isinstance(cron_trigger, CronWorkflows): - id_ = cron_trigger.metadata.id + id_ = ( + cron_trigger.metadata.id + if isinstance(cron_trigger, CronWorkflows) + else cron_trigger + ) + return self._client.rest.cron_get(id_) @@ -204,7 +207,7 @@ async def create( workflow_name: str, cron_name: str, expression: str, - input: dict, + input: dict[str, Any], additional_metadata: dict[str, str], ) -> CronWorkflows: """ @@ -239,9 +242,12 @@ async def delete(self, cron_trigger: Union[str, CronWorkflows]) -> None: Args: cron_trigger (Union[str, CronWorkflows]): The cron trigger ID or CronWorkflows instance to delete. """ - id_ = cron_trigger - if isinstance(cron_trigger, CronWorkflows): - id_ = cron_trigger.metadata.id + id_ = ( + cron_trigger.metadata.id + if isinstance(cron_trigger, CronWorkflows) + else cron_trigger + ) + await self._client.rest.aio.cron_delete(id_) async def list( @@ -252,7 +258,7 @@ async def list( additional_metadata: list[str] | None = None, order_by_field: CronWorkflowsOrderByField | None = None, order_by_direction: WorkflowRunOrderByDirection | None = None, - ) -> CronWorkflowsList: + ) -> CronWorkflows: """ Asynchronously retrieves a list of all workflow cron triggers matching the criteria. diff --git a/hatchet_sdk/features/scheduled.py b/hatchet_sdk/features/scheduled.py index 106d217f..73df4f90 100644 --- a/hatchet_sdk/features/scheduled.py +++ b/hatchet_sdk/features/scheduled.py @@ -91,9 +91,12 @@ def delete(self, scheduled: Union[str, ScheduledWorkflows]) -> None: Args: scheduled (Union[str, ScheduledWorkflows]): The scheduled workflow trigger ID or ScheduledWorkflows instance to delete. """ - id_ = scheduled - if isinstance(scheduled, ScheduledWorkflows): - id_ = scheduled.metadata.id + id_ = ( + scheduled.metadata.id + if isinstance(scheduled, ScheduledWorkflows) + else scheduled + ) + self._client.rest.schedule_delete(id_) def list( diff --git a/hatchet_sdk/workflow_run.py b/hatchet_sdk/workflow_run.py index ae2629b9..648dc9e4 100644 --- a/hatchet_sdk/workflow_run.py +++ b/hatchet_sdk/workflow_run.py @@ -22,13 +22,13 @@ def __init__( self.workflow_listener = workflow_listener self.workflow_run_event_listener = workflow_run_event_listener - def __str__(self): + def __str__(self) -> str: return self.workflow_run_id def stream(self) -> RunEventListener: return self.workflow_run_event_listener.stream(self.workflow_run_id) - def result(self) -> Coroutine: + def result(self) -> Coroutine[]: return self.workflow_listener.result(self.workflow_run_id) def sync_result(self) -> dict[str, Any]: From 71dbfc4da5ec7f23a44705620728741b75a8ec35 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 9 Jan 2025 16:43:18 -0500 Subject: [PATCH 8/9] chore: lockfile --- poetry.lock | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 603caef7..b07d1aef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,7 @@ version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, @@ -17,6 +18,7 @@ version = "3.11.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8"}, {file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5"}, @@ -115,6 +117,7 @@ version = "2.9.1" description = "Simple retry client for aiohttp" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54"}, {file = "aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1"}, @@ -129,6 +132,7 @@ version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, @@ -143,6 +147,7 @@ version = "0.5.2" description = "Generator-based operators for asynchronous iteration" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "aiostream-0.5.2-py3-none-any.whl", hash = "sha256:054660370be9d37f6fe3ece3851009240416bd082e469fd90cc8673d3818cf71"}, {file = "aiostream-0.5.2.tar.gz", hash = "sha256:b71b519a2d66c38f0872403ab86417955b77352f08d9ad02ad46fc3926b389f4"}, @@ -157,6 +162,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -168,6 +174,8 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -179,6 +187,7 @@ version = "24.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, @@ -198,6 +207,7 @@ version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, @@ -212,6 +222,7 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["lint"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -258,6 +269,7 @@ version = "0.1.5" description = "Pure Python CEL Implementation" optional = false python-versions = ">=3.7, <4" +groups = ["main"] files = [ {file = "cel-python-0.1.5.tar.gz", hash = "sha256:d3911bb046bc3ed12792bd88ab453f72d98c66923b72a2fa016bcdffd96e2f98"}, {file = "cel_python-0.1.5-py3-none-any.whl", hash = "sha256:ac81fab8ba08b633700a45d84905be2863529c6a32935c9da7ef53fc06844f1a"}, @@ -278,6 +290,7 @@ version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, @@ -289,6 +302,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -390,6 +404,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["lint"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -404,10 +419,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev", "lint", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "sys_platform == \"win32\"", dev = "sys_platform == \"win32\"", lint = "platform_system == \"Windows\"", test = "sys_platform == \"win32\""} [[package]] name = "deprecated" @@ -415,6 +432,7 @@ version = "1.2.15" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] files = [ {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, @@ -432,6 +450,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev", "test"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -446,6 +466,7 @@ version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, @@ -547,6 +568,7 @@ version = "1.66.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, @@ -558,12 +580,28 @@ protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +[[package]] +name = "grpc-stubs" +version = "1.53.0.5" +description = "Mypy stubs for gRPC" +optional = false +python-versions = ">=3.6" +groups = ["lint"] +files = [ + {file = "grpc-stubs-1.53.0.5.tar.gz", hash = "sha256:3e1b642775cbc3e0c6332cfcedfccb022176db87e518757bef3a1241397be406"}, + {file = "grpc_stubs-1.53.0.5-py3-none-any.whl", hash = "sha256:04183fb65a1b166a1febb9627e3d9647d3926ccc2dfe049fe7b6af243428dbe1"}, +] + +[package.dependencies] +grpcio = "*" + [[package]] name = "grpcio" version = "1.69.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" +groups = ["main", "lint"] files = [ {file = "grpcio-1.69.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:2060ca95a8db295ae828d0fc1c7f38fb26ccd5edf9aa51a0f44251f5da332e97"}, {file = "grpcio-1.69.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2e52e107261fd8fa8fa457fe44bfadb904ae869d87c1280bf60f93ecd3e79278"}, @@ -631,6 +669,7 @@ version = "1.69.0" description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "grpcio_tools-1.69.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:8c210630faa581c3bd08953dac4ad21a7f49862f3b92d69686e9b436d2f1265d"}, {file = "grpcio_tools-1.69.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:09b66ea279fcdaebae4ec34b1baf7577af3b14322738aa980c1c33cfea71f7d7"}, @@ -700,6 +739,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -714,6 +754,7 @@ version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, @@ -737,6 +778,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev", "test"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -748,6 +790,7 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["lint"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -762,6 +805,7 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -773,6 +817,7 @@ version = "0.12.0" description = "a modern parsing library" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "lark-parser-0.12.0.tar.gz", hash = "sha256:15967db1f1214013dca65b1180745047b9be457d73da224fcda3d9dd4e96a138"}, {file = "lark_parser-0.12.0-py2.py3-none-any.whl", hash = "sha256:0eaf30cb5ba787fe404d73a7d6e61df97b21d5a63ac26c5008c78a494373c675"}, @@ -789,6 +834,7 @@ version = "0.7.3" description = "Python logging made (stupidly) simple" optional = false python-versions = "<4.0,>=3.5" +groups = ["main"] files = [ {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, @@ -807,6 +853,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -911,6 +958,7 @@ version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, @@ -970,6 +1018,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["lint"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -981,6 +1030,7 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -992,6 +1042,7 @@ version = "1.29.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_api-1.29.0-py3-none-any.whl", hash = "sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8"}, {file = "opentelemetry_api-1.29.0.tar.gz", hash = "sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf"}, @@ -1007,6 +1058,7 @@ version = "0.50b0" description = "OpenTelemetry Python Distro" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_distro-0.50b0-py3-none-any.whl", hash = "sha256:5fa2e2a99a047ea477fab53e73fb8088b907bda141e8440745b92eb2a84d74aa"}, {file = "opentelemetry_distro-0.50b0.tar.gz", hash = "sha256:3e059e00f53553ebd646d1162d1d3edf5d7c6d3ceafd54a49e74c90dc1c39a7d"}, @@ -1026,6 +1078,7 @@ version = "1.29.0" description = "OpenTelemetry Collector Exporters" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp-1.29.0-py3-none-any.whl", hash = "sha256:b8da6e20f5b0ffe604154b1e16a407eade17ce310c42fb85bb4e1246fc3688ad"}, {file = "opentelemetry_exporter_otlp-1.29.0.tar.gz", hash = "sha256:ee7dfcccbb5e87ad9b389908452e10b7beeab55f70a83f41ce5b8c4efbde6544"}, @@ -1041,6 +1094,7 @@ version = "1.29.0" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.29.0-py3-none-any.whl", hash = "sha256:a9d7376c06b4da9cf350677bcddb9618ed4b8255c3f6476975f5e38274ecd3aa"}, {file = "opentelemetry_exporter_otlp_proto_common-1.29.0.tar.gz", hash = "sha256:e7c39b5dbd1b78fe199e40ddfe477e6983cb61aa74ba836df09c3869a3e3e163"}, @@ -1055,6 +1109,7 @@ version = "1.29.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_grpc-1.29.0-py3-none-any.whl", hash = "sha256:5a2a3a741a2543ed162676cf3eefc2b4150e6f4f0a193187afb0d0e65039c69c"}, {file = "opentelemetry_exporter_otlp_proto_grpc-1.29.0.tar.gz", hash = "sha256:3d324d07d64574d72ed178698de3d717f62a059a93b6b7685ee3e303384e73ea"}, @@ -1075,6 +1130,7 @@ version = "1.29.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.29.0-py3-none-any.whl", hash = "sha256:b228bdc0f0cfab82eeea834a7f0ffdd2a258b26aa33d89fb426c29e8e934d9d0"}, {file = "opentelemetry_exporter_otlp_proto_http-1.29.0.tar.gz", hash = "sha256:b10d174e3189716f49d386d66361fbcf6f2b9ad81e05404acdee3f65c8214204"}, @@ -1095,6 +1151,7 @@ version = "0.50b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_instrumentation-0.50b0-py3-none-any.whl", hash = "sha256:b8f9fc8812de36e1c6dffa5bfc6224df258841fb387b6dfe5df15099daa10630"}, {file = "opentelemetry_instrumentation-0.50b0.tar.gz", hash = "sha256:7d98af72de8dec5323e5202e46122e5f908592b22c6d24733aad619f07d82979"}, @@ -1112,6 +1169,7 @@ version = "1.29.0" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_proto-1.29.0-py3-none-any.whl", hash = "sha256:495069c6f5495cbf732501cdcd3b7f60fda2b9d3d4255706ca99b7ca8dec53ff"}, {file = "opentelemetry_proto-1.29.0.tar.gz", hash = "sha256:3c136aa293782e9b44978c738fff72877a4b78b5d21a64e879898db7b2d93e5d"}, @@ -1126,6 +1184,7 @@ version = "1.29.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_sdk-1.29.0-py3-none-any.whl", hash = "sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a"}, {file = "opentelemetry_sdk-1.29.0.tar.gz", hash = "sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643"}, @@ -1142,6 +1201,7 @@ version = "0.50b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "opentelemetry_semantic_conventions-0.50b0-py3-none-any.whl", hash = "sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e"}, {file = "opentelemetry_semantic_conventions-0.50b0.tar.gz", hash = "sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38"}, @@ -1157,6 +1217,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "lint", "test"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1168,6 +1229,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1179,6 +1241,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1195,6 +1258,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev", "test"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1210,6 +1274,7 @@ version = "0.21.1" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301"}, {file = "prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb"}, @@ -1224,6 +1289,7 @@ version = "0.2.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, @@ -1315,6 +1381,7 @@ version = "5.29.2" description = "" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "protobuf-5.29.2-cp310-abi3-win32.whl", hash = "sha256:c12ba8249f5624300cf51c3d0bfe5be71a60c63e4dcf51ffe9a68771d958c851"}, {file = "protobuf-5.29.2-cp310-abi3-win_amd64.whl", hash = "sha256:842de6d9241134a973aab719ab42b008a18a90f9f07f06ba480df268f86432f9"}, @@ -1335,6 +1402,7 @@ version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["dev"] files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, @@ -1365,6 +1433,7 @@ version = "2.10.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, @@ -1385,6 +1454,7 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -1497,6 +1567,7 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev", "test"] files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -1519,6 +1590,7 @@ version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, @@ -1537,6 +1609,7 @@ version = "2.3.1" description = "pytest plugin to abort hanging tests" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9"}, {file = "pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e"}, @@ -1551,6 +1624,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1565,6 +1639,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -1579,6 +1654,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1641,6 +1717,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1662,6 +1739,7 @@ version = "75.7.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "setuptools-75.7.0-py3-none-any.whl", hash = "sha256:84fb203f278ebcf5cd08f97d3fb96d3fbed4b629d500b29ad60d11e00769b183"}, {file = "setuptools-75.7.0.tar.gz", hash = "sha256:886ff7b16cd342f1d1defc16fc98c9ce3fde69e087a4e1983d7ab634e5f41f4f"}, @@ -1682,6 +1760,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1693,6 +1772,7 @@ version = "9.0.0" description = "Retry code until it succeeds" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, @@ -1708,6 +1788,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev", "lint", "test"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1749,17 +1831,43 @@ version = "5.29.1.20241207" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" +groups = ["lint"] files = [ {file = "types_protobuf-5.29.1.20241207-py3-none-any.whl", hash = "sha256:92893c42083e9b718c678badc0af7a9a1307b92afe1599e5cba5f3d35b668b2f"}, {file = "types_protobuf-5.29.1.20241207.tar.gz", hash = "sha256:2ebcadb8ab3ef2e3e2f067e0882906d64ba0dc65fc5b0fd7a8b692315b4a0be9"}, ] +[[package]] +name = "types-psutil" +version = "6.1.0.20241221" +description = "Typing stubs for psutil" +optional = false +python-versions = ">=3.8" +groups = ["lint"] +files = [ + {file = "types_psutil-6.1.0.20241221-py3-none-any.whl", hash = "sha256:8498dbe13285a9ba7d4b2fa934c569cc380efc74e3dacdb34ae16d2cdf389ec3"}, + {file = "types_psutil-6.1.0.20241221.tar.gz", hash = "sha256:600f5a36bd5e0eb8887f0e3f3ff2cf154d90690ad8123c8a707bba4ab94d3185"}, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20241230" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.8" +groups = ["lint"] +files = [ + {file = "types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6"}, + {file = "types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "lint"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1771,6 +1879,7 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, @@ -1788,6 +1897,8 @@ version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, @@ -1802,6 +1913,7 @@ version = "1.17.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, @@ -1876,6 +1988,7 @@ version = "1.18.3" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, @@ -1972,6 +2085,7 @@ version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, @@ -1986,6 +2100,6 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", type = ["pytest-mypy"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.10" -content-hash = "414d63b255f80d13260cb3a9ecce29f782af46280bba79395554595a47c42f05" +content-hash = "614a756216751096b0a859ab9649755c984a90697f639ccdfa12afd7aa70d51b" From be8af4637397ca739f7f3da7678bf64c9a63a891 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 9 Jan 2025 16:50:53 -0500 Subject: [PATCH 9/9] fix: couple more --- hatchet_sdk/clients/admin.py | 6 +++--- hatchet_sdk/clients/dispatcher/action_listener.py | 1 - hatchet_sdk/clients/events.py | 10 ++++++++-- hatchet_sdk/workflow_run.py | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/hatchet_sdk/clients/admin.py b/hatchet_sdk/clients/admin.py index bcf3aa9c..f5d18202 100644 --- a/hatchet_sdk/clients/admin.py +++ b/hatchet_sdk/clients/admin.py @@ -171,7 +171,7 @@ class AdminClientAioImpl(AdminClientBase): def __init__(self, config: ClientConfig): aio_conn = new_conn(config, True) self.config = config - self.aio_client = WorkflowServiceStub(aio_conn) + self.aio_client = WorkflowServiceStub(aio_conn) # type: ignore[no-untyped-call] self.token = config.token self.listener_client = new_listener(config) self.namespace = config.namespace @@ -409,7 +409,7 @@ class AdminClient(AdminClientBase): def __init__(self, config: ClientConfig): conn = new_conn(config) self.config = config - self.client = WorkflowServiceStub(conn) + self.client = WorkflowServiceStub(conn) # type: ignore[no-untyped-call] self.aio = AdminClientAioImpl(config) self.token = config.token self.listener_client = new_listener(config) @@ -440,7 +440,7 @@ def put_rate_limit( self, key: str, limit: int, - duration: Union[RateLimitDuration.Value, str] = RateLimitDuration.SECOND, + duration: Union[RateLimitDuration.Value, str] = RateLimitDuration.SECOND, # type: ignore[arg-type] ) -> None: try: self.client.PutRateLimit( diff --git a/hatchet_sdk/clients/dispatcher/action_listener.py b/hatchet_sdk/clients/dispatcher/action_listener.py index ea446131..9b3f5c63 100644 --- a/hatchet_sdk/clients/dispatcher/action_listener.py +++ b/hatchet_sdk/clients/dispatcher/action_listener.py @@ -260,7 +260,6 @@ async def _generator(self) -> AsyncGenerator[Action | None, None]: break self.retries = 0 - assigned_action: AssignedAction # Process the received action action_type = self.map_action_type(assigned_action.actionType) diff --git a/hatchet_sdk/clients/events.py b/hatchet_sdk/clients/events.py index 37105d16..1f959208 100644 --- a/hatchet_sdk/clients/events.py +++ b/hatchet_sdk/clients/events.py @@ -66,7 +66,10 @@ def __init__(self, client: EventsServiceStub, config: ClientConfig): self.otel_tracer = create_tracer(config=config) async def async_push( - self, event_key, payload, options: Optional[PushEventOptions] = None + self, + event_key: str, + payload: dict[str, Any], + options: Optional[PushEventOptions] = None, ) -> Event: return await asyncio.to_thread( self.push, event_key=event_key, payload=payload, options=options @@ -81,7 +84,10 @@ async def async_bulk_push( @tenacity_retry def push( - self, event_key, payload, options: PushEventOptions | None = None + self, + event_key: str, + payload: dict[str, Any], + options: PushEventOptions | None = None, ) -> Event: ctx = parse_carrier_from_metadata( (options or {}).get("additional_metadata", {}) diff --git a/hatchet_sdk/workflow_run.py b/hatchet_sdk/workflow_run.py index 648dc9e4..2b10011f 100644 --- a/hatchet_sdk/workflow_run.py +++ b/hatchet_sdk/workflow_run.py @@ -28,7 +28,7 @@ def __str__(self) -> str: def stream(self) -> RunEventListener: return self.workflow_run_event_listener.stream(self.workflow_run_id) - def result(self) -> Coroutine[]: + def result(self) -> Coroutine[None, None, dict[str, Any]]: return self.workflow_listener.result(self.workflow_run_id) def sync_result(self) -> dict[str, Any]: