prefect-client 2.18.0__py3-none-any.whl → 2.18.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. prefect/_internal/schemas/fields.py +31 -12
  2. prefect/automations.py +162 -0
  3. prefect/blocks/core.py +1 -1
  4. prefect/blocks/notifications.py +2 -2
  5. prefect/blocks/system.py +2 -3
  6. prefect/client/orchestration.py +309 -30
  7. prefect/client/schemas/objects.py +11 -8
  8. prefect/client/schemas/sorting.py +9 -0
  9. prefect/client/utilities.py +25 -3
  10. prefect/concurrency/asyncio.py +11 -5
  11. prefect/concurrency/events.py +3 -3
  12. prefect/concurrency/services.py +1 -1
  13. prefect/concurrency/sync.py +9 -5
  14. prefect/deployments/deployments.py +27 -18
  15. prefect/deployments/runner.py +34 -26
  16. prefect/engine.py +3 -1
  17. prefect/events/actions.py +2 -1
  18. prefect/events/cli/automations.py +207 -46
  19. prefect/events/clients.py +53 -20
  20. prefect/events/filters.py +31 -4
  21. prefect/events/instrument.py +40 -40
  22. prefect/events/related.py +2 -1
  23. prefect/events/schemas/automations.py +52 -7
  24. prefect/events/schemas/deployment_triggers.py +16 -228
  25. prefect/events/schemas/events.py +18 -11
  26. prefect/events/schemas/labelling.py +1 -1
  27. prefect/events/utilities.py +1 -1
  28. prefect/events/worker.py +10 -7
  29. prefect/flows.py +42 -24
  30. prefect/input/actions.py +9 -9
  31. prefect/input/run_input.py +51 -37
  32. prefect/new_flow_engine.py +444 -0
  33. prefect/new_task_engine.py +488 -0
  34. prefect/results.py +3 -2
  35. prefect/runner/runner.py +3 -2
  36. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +45 -4
  37. prefect/settings.py +47 -0
  38. prefect/states.py +25 -19
  39. prefect/tasks.py +146 -19
  40. prefect/utilities/asyncutils.py +41 -0
  41. prefect/utilities/engine.py +6 -4
  42. prefect/utilities/schema_tools/validation.py +1 -1
  43. prefect/workers/process.py +2 -1
  44. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/METADATA +1 -1
  45. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/RECORD +48 -46
  46. prefect/concurrency/common.py +0 -0
  47. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/LICENSE +0 -0
  48. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/WHEEL +0 -0
  49. {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from typing import (
12
12
  Optional,
13
13
  Set,
14
14
  Tuple,
15
+ TypeVar,
15
16
  Union,
16
17
  )
17
18
  from uuid import UUID, uuid4
@@ -20,15 +21,19 @@ import certifi
20
21
  import httpcore
21
22
  import httpx
22
23
  import pendulum
24
+ from typing_extensions import ParamSpec
23
25
 
24
26
  from prefect._internal.compatibility.deprecated import (
25
27
  handle_deprecated_infra_overrides_parameter,
26
28
  )
27
29
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
30
+ from prefect.client.schemas import sorting
31
+ from prefect.events import filters
28
32
  from prefect.settings import (
29
33
  PREFECT_API_SERVICES_TRIGGERS_ENABLED,
30
34
  PREFECT_EXPERIMENTAL_EVENTS,
31
35
  )
36
+ from prefect.utilities.asyncutils import run_sync
32
37
 
33
38
  if HAS_PYDANTIC_V2:
34
39
  import pydantic.v1 as pydantic
@@ -151,6 +156,9 @@ if TYPE_CHECKING:
151
156
 
152
157
  from prefect.client.base import ASGIApp, PrefectHttpxClient, app_lifespan_context
153
158
 
159
+ P = ParamSpec("P")
160
+ R = TypeVar("R")
161
+
154
162
 
155
163
  class ServerType(AutoEnum):
156
164
  EPHEMERAL = AutoEnum.auto()
@@ -164,7 +172,9 @@ class ServerType(AutoEnum):
164
172
  return PREFECT_EXPERIMENTAL_EVENTS and PREFECT_API_SERVICES_TRIGGERS_ENABLED
165
173
 
166
174
 
167
- def get_client(httpx_settings: Optional[dict] = None) -> "PrefectClient":
175
+ def get_client(
176
+ httpx_settings: Optional[Dict[str, Any]] = None, sync_client: bool = False
177
+ ) -> "PrefectClient":
168
178
  """
169
179
  Retrieve a HTTP client for communicating with the Prefect REST API.
170
180
 
@@ -174,6 +184,13 @@ def get_client(httpx_settings: Optional[dict] = None) -> "PrefectClient":
174
184
  async with get_client() as client:
175
185
  await client.hello()
176
186
  ```
187
+
188
+ To return a synchronous client, pass sync_client=True:
189
+
190
+ ```python
191
+ with get_client(sync_client=True) as client:
192
+ client.hello()
193
+ ```
177
194
  """
178
195
  ctx = prefect.context.get_settings_context()
179
196
  api = PREFECT_API_URL.value()
@@ -184,11 +201,18 @@ def get_client(httpx_settings: Optional[dict] = None) -> "PrefectClient":
184
201
 
185
202
  api = create_app(ctx.settings, ephemeral=True)
186
203
 
187
- return PrefectClient(
188
- api,
189
- api_key=PREFECT_API_KEY.value(),
190
- httpx_settings=httpx_settings,
191
- )
204
+ if sync_client:
205
+ return SyncPrefectClient(
206
+ api,
207
+ api_key=PREFECT_API_KEY.value(),
208
+ httpx_settings=httpx_settings,
209
+ )
210
+ else:
211
+ return PrefectClient(
212
+ api,
213
+ api_key=PREFECT_API_KEY.value(),
214
+ httpx_settings=httpx_settings,
215
+ )
192
216
 
193
217
 
194
218
  class PrefectClient:
@@ -599,12 +623,12 @@ class PrefectClient:
599
623
  async def create_flow_run(
600
624
  self,
601
625
  flow: "FlowObject",
602
- name: str = None,
626
+ name: Optional[str] = None,
603
627
  parameters: Optional[Dict[str, Any]] = None,
604
628
  context: Optional[Dict[str, Any]] = None,
605
- tags: Iterable[str] = None,
606
- parent_task_run_id: UUID = None,
607
- state: "prefect.states.State" = None,
629
+ tags: Optional[Iterable[str]] = None,
630
+ parent_task_run_id: Optional[UUID] = None,
631
+ state: Optional["prefect.states.State"] = None,
608
632
  ) -> FlowRun:
609
633
  """
610
634
  Create a flow run for a flow.
@@ -2136,21 +2160,23 @@ class PrefectClient:
2136
2160
 
2137
2161
  async def create_task_run(
2138
2162
  self,
2139
- task: "TaskObject",
2163
+ task: "TaskObject[P, R]",
2140
2164
  flow_run_id: Optional[UUID],
2141
2165
  dynamic_key: str,
2142
- name: str = None,
2143
- extra_tags: Iterable[str] = None,
2144
- state: prefect.states.State = None,
2145
- task_inputs: Dict[
2146
- str,
2147
- List[
2148
- Union[
2149
- TaskRunResult,
2150
- Parameter,
2151
- Constant,
2152
- ]
2153
- ],
2166
+ name: Optional[str] = None,
2167
+ extra_tags: Optional[Iterable[str]] = None,
2168
+ state: Optional[prefect.states.State[R]] = None,
2169
+ task_inputs: Optional[
2170
+ Dict[
2171
+ str,
2172
+ List[
2173
+ Union[
2174
+ TaskRunResult,
2175
+ Parameter,
2176
+ Constant,
2177
+ ]
2178
+ ],
2179
+ ]
2154
2180
  ] = None,
2155
2181
  ) -> TaskRun:
2156
2182
  """
@@ -3135,6 +3161,16 @@ class PrefectClient:
3135
3161
 
3136
3162
  return UUID(response.json()["id"])
3137
3163
 
3164
+ async def update_automation(self, automation_id: UUID, automation: AutomationCore):
3165
+ """Updates an automation in Prefect Cloud."""
3166
+ if not self.server_type.supports_automations():
3167
+ self._raise_for_unsupported_automations()
3168
+ response = await self._client.put(
3169
+ f"/automations/{automation_id}",
3170
+ json=automation.dict(json_compatible=True, exclude_unset=True),
3171
+ )
3172
+ response.raise_for_status
3173
+
3138
3174
  async def read_automations(self) -> List[Automation]:
3139
3175
  if not self.server_type.supports_automations():
3140
3176
  self._raise_for_unsupported_automations()
@@ -3144,17 +3180,23 @@ class PrefectClient:
3144
3180
  return pydantic.parse_obj_as(List[Automation], response.json())
3145
3181
 
3146
3182
  async def find_automation(
3147
- self, id_or_name: str, exit_if_not_found: bool = True
3183
+ self, id_or_name: Union[str, UUID], exit_if_not_found: bool = True
3148
3184
  ) -> Optional[Automation]:
3149
- try:
3150
- id = UUID(id_or_name)
3151
- except ValueError:
3152
- id = None
3185
+ if isinstance(id_or_name, str):
3186
+ try:
3187
+ id = UUID(id_or_name)
3188
+ except ValueError:
3189
+ id = None
3190
+ elif isinstance(id_or_name, UUID):
3191
+ id = id_or_name
3153
3192
 
3154
3193
  if id:
3155
- automation = await self.read_automation(id)
3156
- if automation:
3194
+ try:
3195
+ automation = await self.read_automation(id)
3157
3196
  return automation
3197
+ except prefect.exceptions.HTTPStatusError as e:
3198
+ if e.response.status_code == status.HTTP_404_NOT_FOUND:
3199
+ raise prefect.exceptions.ObjectNotFound(http_exc=e) from e
3158
3200
 
3159
3201
  automations = await self.read_automations()
3160
3202
 
@@ -3180,6 +3222,34 @@ class PrefectClient:
3180
3222
  response.raise_for_status()
3181
3223
  return Automation.parse_obj(response.json())
3182
3224
 
3225
+ async def read_automations_by_name(self, name: str) -> List[Automation]:
3226
+ """
3227
+ Query the Prefect API for an automation by name. Only automations matching the provided name will be returned.
3228
+
3229
+ Args:
3230
+ name: the name of the automation to query
3231
+
3232
+ Returns:
3233
+ a list of Automation model representations of the automations
3234
+ """
3235
+ if not self.server_type.supports_automations():
3236
+ self._raise_for_unsupported_automations()
3237
+ automation_filter = filters.AutomationFilter(name=dict(any_=[name]))
3238
+
3239
+ response = await self._client.post(
3240
+ "/automations/filter",
3241
+ json={
3242
+ "sort": sorting.AutomationSort.UPDATED_DESC,
3243
+ "automations": automation_filter.dict(json_compatible=True)
3244
+ if automation_filter
3245
+ else None,
3246
+ },
3247
+ )
3248
+
3249
+ response.raise_for_status()
3250
+
3251
+ return pydantic.parse_obj_as(List[Automation], response.json())
3252
+
3183
3253
  async def pause_automation(self, automation_id: UUID):
3184
3254
  if not self.server_type.supports_automations():
3185
3255
  self._raise_for_unsupported_automations()
@@ -3284,3 +3354,212 @@ class PrefectClient:
3284
3354
 
3285
3355
  def __exit__(self, *_):
3286
3356
  assert False, "This should never be called but must be defined for __enter__"
3357
+
3358
+
3359
+ class SyncPrefectClient:
3360
+ """
3361
+ A synchronous client for interacting with the [Prefect REST API](/api-ref/rest-api/).
3362
+
3363
+ Args:
3364
+ api: the REST API URL or FastAPI application to connect to
3365
+ api_key: An optional API key for authentication.
3366
+ api_version: The API version this client is compatible with.
3367
+ httpx_settings: An optional dictionary of settings to pass to the underlying
3368
+ `httpx.AsyncClient`
3369
+
3370
+ Examples:
3371
+
3372
+ Say hello to a Prefect REST API
3373
+
3374
+ <div class="terminal">
3375
+ ```
3376
+ >>> with get_client(sync_client=True) as client:
3377
+ >>> response = client.hello()
3378
+ >>>
3379
+ >>> print(response.json())
3380
+ 👋
3381
+ ```
3382
+ </div>
3383
+ """
3384
+
3385
+ def __init__(
3386
+ self,
3387
+ api: Union[str, ASGIApp],
3388
+ *,
3389
+ api_key: Optional[str] = None,
3390
+ api_version: Optional[str] = None,
3391
+ httpx_settings: Optional[Dict[str, Any]] = None,
3392
+ ) -> None:
3393
+ self._prefect_client = PrefectClient(
3394
+ api=api,
3395
+ api_key=api_key,
3396
+ api_version=api_version,
3397
+ httpx_settings=httpx_settings,
3398
+ )
3399
+
3400
+ def __enter__(self):
3401
+ run_sync(self._prefect_client.__aenter__())
3402
+ return self
3403
+
3404
+ def __exit__(self, *exc_info):
3405
+ return run_sync(self._prefect_client.__aexit__(*exc_info))
3406
+
3407
+ async def __aenter__(self):
3408
+ raise RuntimeError(
3409
+ "The `SyncPrefectClient` must be entered with a sync context. Use '"
3410
+ "with SyncPrefectClient(...)' not 'async with SyncPrefectClient(...)'"
3411
+ )
3412
+
3413
+ async def __aexit__(self, *_):
3414
+ assert False, "This should never be called but must be defined for __aenter__"
3415
+
3416
+ def hello(self) -> httpx.Response:
3417
+ """
3418
+ Send a GET request to /hello for testing purposes.
3419
+ """
3420
+ return run_sync(self._prefect_client.hello())
3421
+
3422
+ def create_task_run(
3423
+ self,
3424
+ task: "TaskObject[P, R]",
3425
+ flow_run_id: Optional[UUID],
3426
+ dynamic_key: str,
3427
+ name: Optional[str] = None,
3428
+ extra_tags: Optional[Iterable[str]] = None,
3429
+ state: Optional[prefect.states.State[R]] = None,
3430
+ task_inputs: Optional[
3431
+ Dict[
3432
+ str,
3433
+ List[
3434
+ Union[
3435
+ TaskRunResult,
3436
+ Parameter,
3437
+ Constant,
3438
+ ]
3439
+ ],
3440
+ ]
3441
+ ] = None,
3442
+ ) -> TaskRun:
3443
+ """
3444
+ Create a task run
3445
+
3446
+ Args:
3447
+ task: The Task to run
3448
+ flow_run_id: The flow run id with which to associate the task run
3449
+ dynamic_key: A key unique to this particular run of a Task within the flow
3450
+ name: An optional name for the task run
3451
+ extra_tags: an optional list of extra tags to apply to the task run in
3452
+ addition to `task.tags`
3453
+ state: The initial state for the run. If not provided, defaults to
3454
+ `Pending` for now. Should always be a `Scheduled` type.
3455
+ task_inputs: the set of inputs passed to the task
3456
+
3457
+ Returns:
3458
+ The created task run.
3459
+ """
3460
+ return run_sync(
3461
+ self._prefect_client.create_task_run(
3462
+ task=task,
3463
+ flow_run_id=flow_run_id,
3464
+ dynamic_key=dynamic_key,
3465
+ name=name,
3466
+ extra_tags=extra_tags,
3467
+ state=state,
3468
+ task_inputs=task_inputs,
3469
+ )
3470
+ )
3471
+
3472
+ def set_task_run_state(
3473
+ self,
3474
+ task_run_id: UUID,
3475
+ state: prefect.states.State,
3476
+ force: bool = False,
3477
+ ) -> OrchestrationResult:
3478
+ """
3479
+ Set the state of a task run.
3480
+
3481
+ Args:
3482
+ task_run_id: the id of the task run
3483
+ state: the state to set
3484
+ force: if True, disregard orchestration logic when setting the state,
3485
+ forcing the Prefect API to accept the state
3486
+
3487
+ Returns:
3488
+ an OrchestrationResult model representation of state orchestration output
3489
+ """
3490
+ return run_sync(
3491
+ self._prefect_client.set_task_run_state(
3492
+ task_run_id=task_run_id,
3493
+ state=state,
3494
+ force=force,
3495
+ )
3496
+ )
3497
+
3498
+ def create_flow_run(
3499
+ self,
3500
+ flow_id: UUID,
3501
+ parameters: Optional[Dict[str, Any]] = None,
3502
+ context: Optional[Dict[str, Any]] = None,
3503
+ scheduled_start_time: Optional[datetime.datetime] = None,
3504
+ run_name: Optional[str] = None,
3505
+ labels: Optional[List[str]] = None,
3506
+ parameters_json: Optional[str] = None,
3507
+ run_config: Optional[Dict[str, Any]] = None,
3508
+ idempotency_key: Optional[str] = None,
3509
+ ) -> FlowRunResponse:
3510
+ """
3511
+ Create a new flow run.
3512
+
3513
+ Args:
3514
+ - flow_id (UUID): the ID of the flow to create a run for
3515
+ - parameters (Optional[Dict[str, Any]]): a dictionary of parameter values to pass to the flow
3516
+ - context (Optional[Dict[str, Any]]): a dictionary of context values to pass to the flow
3517
+ - scheduled_start_time (Optional[datetime.datetime]): the scheduled start time for the flow run
3518
+ - run_name (Optional[str]): a name to assign to the flow run
3519
+ - labels (Optional[List[str]]): a list of labels to assign to the flow run
3520
+ - parameters_json (Optional[str]): a JSON string of parameter values to pass to the flow
3521
+ - run_config (Optional[Dict[str, Any]]): a dictionary of run configuration options
3522
+ - idempotency_key (Optional[str]): a key to ensure idempotency when creating the flow run
3523
+
3524
+ Returns:
3525
+ - FlowRunResponse: the created flow run
3526
+ """
3527
+ return run_sync(
3528
+ self._prefect_client.create_flow_run(
3529
+ flow_id=flow_id,
3530
+ parameters=parameters,
3531
+ context=context,
3532
+ scheduled_start_time=scheduled_start_time,
3533
+ run_name=run_name,
3534
+ labels=labels,
3535
+ parameters_json=parameters_json,
3536
+ run_config=run_config,
3537
+ idempotency_key=idempotency_key,
3538
+ )
3539
+ )
3540
+
3541
+ async def set_flow_run_state(
3542
+ self,
3543
+ flow_run_id: UUID,
3544
+ state: "prefect.states.State",
3545
+ force: bool = False,
3546
+ ) -> OrchestrationResult:
3547
+ """
3548
+ Set the state of a flow run.
3549
+
3550
+ Args:
3551
+ flow_run_id: the id of the flow run
3552
+ state: the state to set
3553
+ force: if True, disregard orchestration logic when setting the state,
3554
+ forcing the Prefect API to accept the state
3555
+
3556
+ Returns:
3557
+ an OrchestrationResult model representation of state orchestration output
3558
+ """
3559
+ return run_sync(
3560
+ self._prefect_client.set_flow_run_state(
3561
+ flow_run_id=flow_run_id,
3562
+ state=state,
3563
+ force=force,
3564
+ )
3565
+ )
@@ -53,6 +53,7 @@ if TYPE_CHECKING:
53
53
  from prefect.deprecated.data_documents import DataDocument
54
54
  from prefect.results import BaseResult
55
55
 
56
+
56
57
  R = TypeVar("R")
57
58
 
58
59
 
@@ -120,20 +121,20 @@ class WorkQueueStatus(AutoEnum):
120
121
 
121
122
 
122
123
  class StateDetails(PrefectBaseModel):
123
- flow_run_id: UUID = None
124
- task_run_id: UUID = None
124
+ flow_run_id: Optional[UUID] = None
125
+ task_run_id: Optional[UUID] = None
125
126
  # for task runs that represent subflows, the subflow's run ID
126
- child_flow_run_id: UUID = None
127
+ child_flow_run_id: Optional[UUID] = None
127
128
  scheduled_time: DateTimeTZ = None
128
- cache_key: str = None
129
+ cache_key: Optional[str] = None
129
130
  cache_expiration: DateTimeTZ = None
130
131
  untrackable_result: bool = False
131
132
  pause_timeout: DateTimeTZ = None
132
133
  pause_reschedule: bool = False
133
- pause_key: str = None
134
+ pause_key: Optional[str] = None
134
135
  run_input_keyset: Optional[Dict[str, str]] = None
135
- refresh_cache: bool = None
136
- retriable: bool = None
136
+ refresh_cache: Optional[bool] = None
137
+ retriable: Optional[bool] = None
137
138
  transition_id: Optional[UUID] = None
138
139
  task_parameters_id: Optional[UUID] = None
139
140
 
@@ -160,7 +161,9 @@ class State(ObjectBaseModel, Generic[R]):
160
161
  def result(self: "State[R]", raise_on_failure: bool = False) -> Union[R, Exception]:
161
162
  ...
162
163
 
163
- def result(self, raise_on_failure: bool = True, fetch: Optional[bool] = None):
164
+ def result(
165
+ self, raise_on_failure: bool = True, fetch: Optional[bool] = None
166
+ ) -> Union[R, Exception]:
164
167
  """
165
168
  Retrieve the result attached to this state.
166
169
 
@@ -27,6 +27,15 @@ class TaskRunSort(AutoEnum):
27
27
  END_TIME_DESC = AutoEnum.auto()
28
28
 
29
29
 
30
+ class AutomationSort(AutoEnum):
31
+ """Defines automation sorting options."""
32
+
33
+ CREATED_DESC = AutoEnum.auto()
34
+ UPDATED_DESC = AutoEnum.auto()
35
+ NAME_ASC = AutoEnum.auto()
36
+ NAME_DESC = AutoEnum.auto()
37
+
38
+
30
39
  class LogSort(AutoEnum):
31
40
  """Defines log sorting options."""
32
41
 
@@ -6,14 +6,25 @@ Utilities for working with clients.
6
6
  # circular imports for decorators such as `inject_client` which are widely used.
7
7
 
8
8
  from functools import wraps
9
- from typing import TYPE_CHECKING, Any, Callable, Coroutine, Optional, Tuple, cast
10
-
11
- from typing_extensions import ParamSpec
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ Awaitable,
13
+ Callable,
14
+ Coroutine,
15
+ Optional,
16
+ Tuple,
17
+ TypeVar,
18
+ cast,
19
+ )
20
+
21
+ from typing_extensions import Concatenate, ParamSpec
12
22
 
13
23
  if TYPE_CHECKING:
14
24
  from prefect.client.orchestration import PrefectClient
15
25
 
16
26
  P = ParamSpec("P")
27
+ R = TypeVar("R")
17
28
 
18
29
 
19
30
  def get_or_create_client(
@@ -52,6 +63,17 @@ def get_or_create_client(
52
63
  return get_httpx_client(), False
53
64
 
54
65
 
66
+ def client_injector(
67
+ func: Callable[Concatenate["PrefectClient", P], Awaitable[R]],
68
+ ) -> Callable[P, Awaitable[R]]:
69
+ @wraps(func)
70
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
71
+ client, _ = get_or_create_client()
72
+ return await func(client, *args, **kwargs)
73
+
74
+ return wrapper
75
+
76
+
55
77
  def inject_client(
56
78
  fn: Callable[P, Coroutine[Any, Any, Any]],
57
79
  ) -> Callable[P, Coroutine[Any, Any, Any]]:
@@ -1,10 +1,16 @@
1
1
  import asyncio
2
2
  from contextlib import asynccontextmanager
3
- from typing import List, Literal, Union
3
+ from typing import List, Literal, Union, cast
4
4
 
5
5
  import httpx
6
6
  import pendulum
7
7
 
8
+ try:
9
+ from pendulum import Interval
10
+ except ImportError:
11
+ # pendulum < 3
12
+ from pendulum.period import Period as Interval # type: ignore
13
+
8
14
  from prefect import get_client
9
15
  from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
10
16
 
@@ -30,10 +36,10 @@ async def concurrency(names: Union[str, List[str]], occupy: int = 1):
30
36
  try:
31
37
  yield
32
38
  finally:
33
- occupancy_seconds: float = (
34
- pendulum.now("UTC") - acquisition_time
35
- ).total_seconds()
36
- await _release_concurrency_slots(names, occupy, occupancy_seconds)
39
+ occupancy_period = cast(Interval, (pendulum.now("UTC") - acquisition_time))
40
+ await _release_concurrency_slots(
41
+ names, occupy, occupancy_period.total_seconds()
42
+ )
37
43
  _emit_concurrency_release_events(limits, occupy, emitted_events)
38
44
 
39
45
 
@@ -12,11 +12,11 @@ def _emit_concurrency_event(
12
12
  slots: int,
13
13
  follows: Union[Event, None] = None,
14
14
  ) -> Union[Event, None]:
15
- resource = {
15
+ resource: Dict[str, str] = {
16
16
  "prefect.resource.id": f"prefect.concurrency-limit.{primary_limit.id}",
17
17
  "prefect.resource.name": primary_limit.name,
18
- "slots-acquired": slots,
19
- "limit": primary_limit.limit,
18
+ "slots-acquired": str(slots),
19
+ "limit": str(primary_limit.limit),
20
20
  }
21
21
 
22
22
  related = [
@@ -64,7 +64,7 @@ class ConcurrencySlotAcquisitionService(QueueService):
64
64
  raise RuntimeError("Cannot put items in a stopped service instance.")
65
65
 
66
66
  logger.debug("Service %r enqueuing item %r", self, item)
67
- future = concurrent.futures.Future()
67
+ future: concurrent.futures.Future = concurrent.futures.Future()
68
68
 
69
69
  occupy, mode = item
70
70
  self._queue.put_nowait((occupy, mode, future))
@@ -1,8 +1,14 @@
1
1
  from contextlib import contextmanager
2
- from typing import List, Union
2
+ from typing import List, Union, cast
3
3
 
4
4
  import pendulum
5
5
 
6
+ try:
7
+ from pendulum import Interval
8
+ except ImportError:
9
+ # pendulum < 3
10
+ from pendulum.period import Period as Interval # type: ignore
11
+
6
12
  from prefect._internal.concurrency.api import create_call, from_sync
7
13
  from prefect._internal.concurrency.event_loop import get_running_loop
8
14
  from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
@@ -30,11 +36,9 @@ def concurrency(names: Union[str, List[str]], occupy: int = 1):
30
36
  try:
31
37
  yield
32
38
  finally:
33
- occupancy_seconds: float = (
34
- pendulum.now("UTC") - acquisition_time
35
- ).total_seconds()
39
+ occupancy_period = cast(Interval, pendulum.now("UTC") - acquisition_time)
36
40
  _call_async_function_from_sync(
37
- _release_concurrency_slots, names, occupy, occupancy_seconds
41
+ _release_concurrency_slots, names, occupy, occupancy_period.total_seconds()
38
42
  )
39
43
  _emit_concurrency_release_events(limits, occupy, emitted_events)
40
44