prefect-client 3.2.13__py3-none-any.whl → 3.2.15__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 (39) hide show
  1. prefect/_build_info.py +3 -3
  2. prefect/_experimental/bundles.py +7 -1
  3. prefect/_internal/concurrency/services.py +13 -3
  4. prefect/cache_policies.py +31 -2
  5. prefect/client/orchestration/_flow_runs/client.py +34 -4
  6. prefect/client/schemas/actions.py +14 -1
  7. prefect/client/schemas/objects.py +18 -0
  8. prefect/deployments/runner.py +1 -9
  9. prefect/docker/docker_image.py +2 -1
  10. prefect/flow_engine.py +11 -5
  11. prefect/flow_runs.py +1 -1
  12. prefect/flows.py +27 -9
  13. prefect/locking/memory.py +16 -8
  14. prefect/logging/__init__.py +1 -1
  15. prefect/logging/configuration.py +6 -4
  16. prefect/logging/formatters.py +3 -3
  17. prefect/logging/handlers.py +37 -26
  18. prefect/results.py +9 -3
  19. prefect/runner/__init__.py +2 -0
  20. prefect/runner/runner.py +1 -1
  21. prefect/runner/server.py +12 -7
  22. prefect/runner/storage.py +37 -37
  23. prefect/runner/submit.py +36 -25
  24. prefect/runner/utils.py +9 -5
  25. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +4 -4
  26. prefect/server/api/flow_runs.py +21 -0
  27. prefect/server/api/task_runs.py +52 -1
  28. prefect/settings/models/tasks.py +5 -0
  29. prefect/task_engine.py +18 -24
  30. prefect/tasks.py +31 -8
  31. prefect/transactions.py +5 -0
  32. prefect/utilities/callables.py +2 -0
  33. prefect/utilities/engine.py +2 -2
  34. prefect/utilities/importtools.py +6 -9
  35. prefect/workers/base.py +9 -4
  36. {prefect_client-3.2.13.dist-info → prefect_client-3.2.15.dist-info}/METADATA +3 -2
  37. {prefect_client-3.2.13.dist-info → prefect_client-3.2.15.dist-info}/RECORD +39 -39
  38. {prefect_client-3.2.13.dist-info → prefect_client-3.2.15.dist-info}/WHEEL +0 -0
  39. {prefect_client-3.2.13.dist-info → prefect_client-3.2.15.dist-info}/licenses/LICENSE +0 -0
prefect/runner/utils.py CHANGED
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from copy import deepcopy
2
- from typing import Any
4
+ from typing import Any, Hashable
3
5
 
4
6
  from fastapi import FastAPI
5
7
  from fastapi.openapi.utils import get_openapi
@@ -8,7 +10,7 @@ from prefect import __version__ as PREFECT_VERSION
8
10
 
9
11
 
10
12
  def inject_schemas_into_openapi(
11
- webserver: FastAPI, schemas_to_inject: dict[str, Any]
13
+ webserver: FastAPI, schemas_to_inject: dict[Hashable, Any]
12
14
  ) -> dict[str, Any]:
13
15
  """
14
16
  Augments the webserver's OpenAPI schema with additional schemas from deployments / flows / tasks.
@@ -29,7 +31,7 @@ def inject_schemas_into_openapi(
29
31
 
30
32
 
31
33
  def merge_definitions(
32
- injected_schemas: dict[str, Any], openapi_schema: dict[str, Any]
34
+ injected_schemas: dict[Hashable, Any], openapi_schema: dict[str, Any]
33
35
  ) -> dict[str, Any]:
34
36
  """
35
37
  Integrates definitions from injected schemas into the OpenAPI components.
@@ -51,7 +53,9 @@ def merge_definitions(
51
53
  return openapi_schema_copy
52
54
 
53
55
 
54
- def update_refs_in_schema(schema_item: Any, new_ref: str) -> None:
56
+ def update_refs_in_schema(
57
+ schema_item: dict[str, Any] | list[Any], new_ref: str
58
+ ) -> None:
55
59
  """
56
60
  Recursively replaces `$ref` with a new reference base in a schema item.
57
61
 
@@ -64,7 +68,7 @@ def update_refs_in_schema(schema_item: Any, new_ref: str) -> None:
64
68
  schema_item["$ref"] = schema_item["$ref"].replace("#/definitions/", new_ref)
65
69
  for value in schema_item.values():
66
70
  update_refs_in_schema(value, new_ref)
67
- elif isinstance(schema_item, list):
71
+ elif isinstance(schema_item, list): # pyright: ignore[reportUnnecessaryIsInstance]
68
72
  for item in schema_item:
69
73
  update_refs_in_schema(item, new_ref)
70
74
 
@@ -559,7 +559,7 @@
559
559
  "description": "To use any private container registry with a username and password, choose DockerRegistry. To use a private Azure Container Registry with a managed identity, choose ACRManagedIdentity.",
560
560
  "anyOf": [
561
561
  {
562
- "$ref": "#/definitions/DockerRegistry"
562
+ "$ref": "#/definitions/DockerRegistryCredentials"
563
563
  },
564
564
  {
565
565
  "$ref": "#/definitions/ACRManagedIdentity"
@@ -637,8 +637,8 @@
637
637
  "subscription_id"
638
638
  ],
639
639
  "definitions": {
640
- "DockerRegistry": {
641
- "title": "DockerRegistry",
640
+ "DockerRegistryCredentials": {
641
+ "title": "DockerRegistryCredentials",
642
642
  "description": "Connects to a Docker registry.\n\nRequires a Docker Engine to be connectable.",
643
643
  "type": "object",
644
644
  "properties": {
@@ -671,7 +671,7 @@
671
671
  "password",
672
672
  "registry_url"
673
673
  ],
674
- "block_type_slug": "docker-registry",
674
+ "block_type_slug": "docker-registry-credentials",
675
675
  "secret_fields": [
676
676
  "password"
677
677
  ],
@@ -29,6 +29,7 @@ import prefect.server.schemas as schemas
29
29
  from prefect.logging import get_logger
30
30
  from prefect.server.api.run_history import run_history
31
31
  from prefect.server.api.validation import validate_job_variables_for_deployment_flow_run
32
+ from prefect.server.api.workers import WorkerLookups
32
33
  from prefect.server.database import PrefectDBInterface, provide_database_interface
33
34
  from prefect.server.exceptions import FlowRunGraphTooLarge
34
35
  from prefect.server.models.flow_runs import (
@@ -68,6 +69,7 @@ async def create_flow_run(
68
69
  orchestration_dependencies.provide_flow_orchestration_parameters
69
70
  ),
70
71
  api_version: str = Depends(dependencies.provide_request_api_version),
72
+ worker_lookups: WorkerLookups = Depends(WorkerLookups),
71
73
  ) -> schemas.responses.FlowRunResponse:
72
74
  """
73
75
  Create a flow run. If a flow run with the same flow_id and
@@ -91,6 +93,25 @@ async def create_flow_run(
91
93
  right_now = now("UTC")
92
94
 
93
95
  async with db.session_context(begin_transaction=True) as session:
96
+ if flow_run.work_pool_name:
97
+ if flow_run.work_queue_name:
98
+ work_queue_id = await worker_lookups._get_work_queue_id_from_name(
99
+ session=session,
100
+ work_pool_name=flow_run.work_pool_name,
101
+ work_queue_name=flow_run.work_queue_name,
102
+ )
103
+ else:
104
+ work_queue_id = (
105
+ await worker_lookups._get_default_work_queue_id_from_work_pool_name(
106
+ session=session,
107
+ work_pool_name=flow_run.work_pool_name,
108
+ )
109
+ )
110
+ else:
111
+ work_queue_id = None
112
+
113
+ flow_run_object.work_queue_id = work_queue_id
114
+
94
115
  model = await models.flow_runs.create_flow_run(
95
116
  session=session,
96
117
  flow_run=flow_run_object,
@@ -16,6 +16,7 @@ from fastapi import (
16
16
  WebSocket,
17
17
  status,
18
18
  )
19
+ from fastapi.responses import ORJSONResponse
19
20
  from starlette.websockets import WebSocketDisconnect
20
21
 
21
22
  import prefect.server.api.dependencies as dependencies
@@ -27,7 +28,10 @@ from prefect.server.database import PrefectDBInterface, provide_database_interfa
27
28
  from prefect.server.orchestration import dependencies as orchestration_dependencies
28
29
  from prefect.server.orchestration.core_policy import CoreTaskPolicy
29
30
  from prefect.server.orchestration.policies import TaskRunOrchestrationPolicy
30
- from prefect.server.schemas.responses import OrchestrationResult
31
+ from prefect.server.schemas.responses import (
32
+ OrchestrationResult,
33
+ TaskRunPaginationResponse,
34
+ )
31
35
  from prefect.server.task_queue import MultiQueue, TaskQueue
32
36
  from prefect.server.utilities import subscriptions
33
37
  from prefect.server.utilities.server import PrefectRouter
@@ -214,6 +218,53 @@ async def read_task_runs(
214
218
  )
215
219
 
216
220
 
221
+ @router.post("/paginate", response_class=ORJSONResponse)
222
+ async def paginate_task_runs(
223
+ sort: schemas.sorting.TaskRunSort = Body(schemas.sorting.TaskRunSort.ID_DESC),
224
+ limit: int = dependencies.LimitBody(),
225
+ page: int = Body(1, ge=1),
226
+ flows: Optional[schemas.filters.FlowFilter] = None,
227
+ flow_runs: Optional[schemas.filters.FlowRunFilter] = None,
228
+ task_runs: Optional[schemas.filters.TaskRunFilter] = None,
229
+ deployments: Optional[schemas.filters.DeploymentFilter] = None,
230
+ db: PrefectDBInterface = Depends(provide_database_interface),
231
+ ) -> TaskRunPaginationResponse:
232
+ """
233
+ Pagination query for task runs.
234
+ """
235
+ offset = (page - 1) * limit
236
+
237
+ async with db.session_context() as session:
238
+ runs = await models.task_runs.read_task_runs(
239
+ session=session,
240
+ flow_filter=flows,
241
+ flow_run_filter=flow_runs,
242
+ task_run_filter=task_runs,
243
+ deployment_filter=deployments,
244
+ offset=offset,
245
+ limit=limit,
246
+ sort=sort,
247
+ )
248
+
249
+ total_count = await models.task_runs.count_task_runs(
250
+ session=session,
251
+ flow_filter=flows,
252
+ flow_run_filter=flow_runs,
253
+ task_run_filter=task_runs,
254
+ deployment_filter=deployments,
255
+ )
256
+
257
+ return TaskRunPaginationResponse.model_validate(
258
+ dict(
259
+ results=runs,
260
+ count=total_count,
261
+ limit=limit,
262
+ pages=(total_count + limit - 1) // limit,
263
+ page=page,
264
+ )
265
+ )
266
+
267
+
217
268
  @router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
218
269
  async def delete_task_run(
219
270
  task_run_id: UUID = Path(..., description="The task run id", alias="id"),
@@ -57,6 +57,11 @@ class TasksSettings(PrefectBaseSettings):
57
57
  description="If `True`, enables a refresh of cached results: re-executing the task will refresh the cached results.",
58
58
  )
59
59
 
60
+ default_no_cache: bool = Field(
61
+ default=False,
62
+ description="If `True`, sets the default cache policy on all tasks to `NO_CACHE`.",
63
+ )
64
+
60
65
  default_retries: int = Field(
61
66
  default=0,
62
67
  ge=0,
prefect/task_engine.py CHANGED
@@ -755,11 +755,14 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
755
755
  if self._telemetry.span
756
756
  else nullcontext()
757
757
  ):
758
- self.begin_run()
759
- try:
760
- yield
761
- finally:
762
- self.call_hooks()
758
+ # Acquire a concurrency slot for each tag, but only if a limit
759
+ # matching the tag already exists.
760
+ with concurrency(list(self.task_run.tags), self.task_run.id):
761
+ self.begin_run()
762
+ try:
763
+ yield
764
+ finally:
765
+ self.call_hooks()
763
766
 
764
767
  @contextmanager
765
768
  def transaction_context(self) -> Generator[Transaction, None, None]:
@@ -820,13 +823,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
820
823
  if transaction.is_committed():
821
824
  result = transaction.read()
822
825
  else:
823
- if self.task_run.tags:
824
- # Acquire a concurrency slot for each tag, but only if a limit
825
- # matching the tag already exists.
826
- with concurrency(list(self.task_run.tags), self.task_run.id):
827
- result = call_with_parameters(self.task.fn, parameters)
828
- else:
829
- result = call_with_parameters(self.task.fn, parameters)
826
+ result = call_with_parameters(self.task.fn, parameters)
830
827
  self.handle_success(result, transaction=transaction)
831
828
  return result
832
829
 
@@ -1288,11 +1285,14 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1288
1285
  if self._telemetry.span
1289
1286
  else nullcontext()
1290
1287
  ):
1291
- await self.begin_run()
1292
- try:
1293
- yield
1294
- finally:
1295
- await self.call_hooks()
1288
+ # Acquire a concurrency slot for each tag, but only if a limit
1289
+ # matching the tag already exists.
1290
+ async with aconcurrency(list(self.task_run.tags), self.task_run.id):
1291
+ await self.begin_run()
1292
+ try:
1293
+ yield
1294
+ finally:
1295
+ await self.call_hooks()
1296
1296
 
1297
1297
  @asynccontextmanager
1298
1298
  async def transaction_context(self) -> AsyncGenerator[Transaction, None]:
@@ -1352,13 +1352,7 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1352
1352
  if transaction.is_committed():
1353
1353
  result = transaction.read()
1354
1354
  else:
1355
- if self.task_run and self.task_run.tags:
1356
- # Acquire a concurrency slot for each tag, but only if a limit
1357
- # matching the tag already exists.
1358
- async with aconcurrency(list(self.task_run.tags), self.task_run.id):
1359
- result = await call_with_parameters(self.task.fn, parameters)
1360
- else:
1361
- result = await call_with_parameters(self.task.fn, parameters)
1355
+ result = await call_with_parameters(self.task.fn, parameters)
1362
1356
  await self.handle_success(result, transaction=transaction)
1363
1357
  return result
1364
1358
 
prefect/tasks.py CHANGED
@@ -1,8 +1,10 @@
1
1
  """
2
2
  Module containing the base workflow task class and decorator - for most use cases, using the [`@task` decorator][prefect.tasks.task] is preferred.
3
3
  """
4
+
4
5
  # This file requires type-checking with pyright because mypy does not yet support PEP612
5
6
  # See https://github.com/python/mypy/issues/8645
7
+ from __future__ import annotations
6
8
 
7
9
  import asyncio
8
10
  import datetime
@@ -314,7 +316,7 @@ class Task(Generic[P, R]):
314
316
  # exactly in the @task decorator
315
317
  def __init__(
316
318
  self,
317
- fn: Callable[P, R],
319
+ fn: Callable[P, R] | "classmethod[Any, P, R]" | "staticmethod[P, R]",
318
320
  name: Optional[str] = None,
319
321
  description: Optional[str] = None,
320
322
  tags: Optional[Iterable[str]] = None,
@@ -378,6 +380,13 @@ class Task(Generic[P, R]):
378
380
  " my_task():\n\tpass"
379
381
  )
380
382
 
383
+ if isinstance(fn, classmethod):
384
+ fn = cast(Callable[P, R], fn.__func__)
385
+
386
+ if isinstance(fn, staticmethod):
387
+ fn = cast(Callable[P, R], fn.__func__)
388
+ setattr(fn, "__prefect_static__", True)
389
+
381
390
  if not callable(fn):
382
391
  raise TypeError("'fn' must be callable")
383
392
 
@@ -422,6 +431,11 @@ class Task(Generic[P, R]):
422
431
 
423
432
  self.task_key: str = _generate_task_key(self.fn)
424
433
 
434
+ # determine cache and result configuration
435
+ settings = get_current_settings()
436
+ if settings.tasks.default_no_cache and cache_policy is NotSet:
437
+ cache_policy = NO_CACHE
438
+
425
439
  if cache_policy is not NotSet and cache_key_fn is not None:
426
440
  logger.warning(
427
441
  f"Both `cache_policy` and `cache_key_fn` are set on task {self}. `cache_key_fn` will be used."
@@ -458,7 +472,7 @@ class Task(Generic[P, R]):
458
472
  )
459
473
  elif cache_policy is NotSet and result_storage_key is None:
460
474
  self.cache_policy = DEFAULT
461
- elif result_storage_key:
475
+ elif cache_policy != NO_CACHE and result_storage_key:
462
476
  # TODO: handle this situation with double storage
463
477
  self.cache_policy = None
464
478
  else:
@@ -468,7 +482,6 @@ class Task(Generic[P, R]):
468
482
  # TODO: We can instantiate a `TaskRunPolicy` and add Pydantic bound checks to
469
483
  # validate that the user passes positive numbers here
470
484
 
471
- settings = get_current_settings()
472
485
  self.retries: int = (
473
486
  retries if retries is not None else settings.tasks.default_retries
474
487
  )
@@ -532,6 +545,14 @@ class Task(Generic[P, R]):
532
545
  def ismethod(self) -> bool:
533
546
  return hasattr(self.fn, "__prefect_self__")
534
547
 
548
+ @property
549
+ def isclassmethod(self) -> bool:
550
+ return hasattr(self.fn, "__prefect_cls__")
551
+
552
+ @property
553
+ def isstaticmethod(self) -> bool:
554
+ return getattr(self.fn, "__prefect_static__", False)
555
+
535
556
  def __get__(self, instance: Any, owner: Any) -> "Task[P, R]":
536
557
  """
537
558
  Implement the descriptor protocol so that the task can be used as an instance method.
@@ -539,10 +560,15 @@ class Task(Generic[P, R]):
539
560
  an argument. We return a copy of the task with that instance bound to the task's function.
540
561
  """
541
562
 
542
- # if no instance is provided, it's being accessed on the class
543
- if instance is None:
563
+ if self.isstaticmethod:
544
564
  return self
545
565
 
566
+ # wrapped function is a classmethod
567
+ if not instance:
568
+ bound_task = copy(self)
569
+ setattr(bound_task.fn, "__prefect_cls__", owner)
570
+ return bound_task
571
+
546
572
  # if the task is being accessed on an instance, bind the instance to the __prefect_self__ attribute
547
573
  # of the task's function. This will allow it to be automatically added to the task's parameters
548
574
  else:
@@ -1847,9 +1873,6 @@ def task(
1847
1873
  """
1848
1874
 
1849
1875
  if __fn:
1850
- if isinstance(__fn, (classmethod, staticmethod)):
1851
- method_decorator = type(__fn).__name__
1852
- raise TypeError(f"@{method_decorator} should be applied on top of @task")
1853
1876
  return Task(
1854
1877
  fn=__fn,
1855
1878
  name=name,
prefect/transactions.py CHANGED
@@ -23,6 +23,7 @@ from prefect.exceptions import (
23
23
  MissingContextError,
24
24
  SerializationError,
25
25
  )
26
+ from prefect.filesystems import NullFileSystem
26
27
  from prefect.logging.loggers import LoggingAdapter, get_logger, get_run_logger
27
28
  from prefect.results import (
28
29
  ResultRecord,
@@ -453,6 +454,10 @@ def transaction(
453
454
  if key and not store:
454
455
  store = get_result_store()
455
456
 
457
+ # Avoid inheriting a NullFileSystem for metadata_storage from a flow's result store
458
+ if store and isinstance(store.metadata_storage, NullFileSystem):
459
+ store = store.model_copy(update={"metadata_storage": None})
460
+
456
461
  try:
457
462
  _logger: Union[logging.Logger, LoggingAdapter] = logger or get_run_logger()
458
463
  except MissingContextError:
@@ -63,6 +63,8 @@ def get_call_parameters(
63
63
  """
64
64
  if hasattr(fn, "__prefect_self__"):
65
65
  call_args = (getattr(fn, "__prefect_self__"), *call_args)
66
+ if hasattr(fn, "__prefect_cls__"):
67
+ call_args = (getattr(fn, "__prefect_cls__"), *call_args)
66
68
 
67
69
  try:
68
70
  bound_signature = inspect.signature(fn).bind(*call_args, **call_kwargs)
@@ -63,7 +63,7 @@ engine_logger: Logger = get_logger("engine")
63
63
  T = TypeVar("T")
64
64
 
65
65
 
66
- async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> set[TaskRunInput]:
66
+ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> set[TaskRunResult]:
67
67
  """
68
68
  This function recurses through an expression to generate a set of any discernible
69
69
  task run inputs it finds in the data structure. It produces a set of all inputs
@@ -76,7 +76,7 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> set[TaskRun
76
76
  """
77
77
  # TODO: This function needs to be updated to detect parameters and constants
78
78
 
79
- inputs: set[TaskRunInput] = set()
79
+ inputs: set[TaskRunResult] = set()
80
80
 
81
81
  def add_futures_and_states_to_inputs(obj: Any) -> None:
82
82
  if isinstance(obj, PrefectFuture):
@@ -93,8 +93,11 @@ def load_script_as_module(path: str) -> ModuleType:
93
93
  parent_path = str(Path(path).resolve().parent)
94
94
  working_directory = os.getcwd()
95
95
 
96
- # Generate unique module name for thread safety
97
- module_name = f"__prefect_loader_{id(path)}__"
96
+ module_name = os.path.splitext(Path(path).name)[0]
97
+
98
+ # fall back in case of filenames with the same names as modules
99
+ if module_name in sys.modules:
100
+ module_name = f"__prefect_loader_{id(path)}__"
98
101
 
99
102
  spec = importlib.util.spec_from_file_location(
100
103
  module_name,
@@ -112,15 +115,9 @@ def load_script_as_module(path: str) -> ModuleType:
112
115
  with _get_sys_path_lock():
113
116
  sys.path.insert(0, working_directory)
114
117
  sys.path.insert(0, parent_path)
115
- try:
116
- spec.loader.exec_module(module)
117
- finally:
118
- sys.path.remove(parent_path)
119
- sys.path.remove(working_directory)
118
+ spec.loader.exec_module(module)
120
119
  except Exception as exc:
121
120
  raise ScriptError(user_exc=exc, path=path) from exc
122
- finally:
123
- sys.modules.pop(module_name)
124
121
 
125
122
  return module
126
123
 
prefect/workers/base.py CHANGED
@@ -60,6 +60,7 @@ from prefect.settings import (
60
60
  get_current_settings,
61
61
  )
62
62
  from prefect.states import (
63
+ Cancelled,
63
64
  Crashed,
64
65
  Pending,
65
66
  exception_to_failed_state,
@@ -1249,10 +1250,14 @@ class BaseWorker(abc.ABC, Generic[C, V, R]):
1249
1250
  ) -> None:
1250
1251
  state_updates = state_updates or {}
1251
1252
  state_updates.setdefault("name", "Cancelled")
1252
- state_updates.setdefault("type", StateType.CANCELLED)
1253
- if TYPE_CHECKING:
1254
- assert flow_run.state
1255
- state = flow_run.state.model_copy(update=state_updates)
1253
+
1254
+ if flow_run.state:
1255
+ state_updates.setdefault("type", StateType.CANCELLED)
1256
+ state = flow_run.state.model_copy(update=state_updates)
1257
+ else:
1258
+ # Unexpectedly when flow run does not have a state, create a new one
1259
+ # does not need to explicitly set the type
1260
+ state = Cancelled(**state_updates)
1256
1261
 
1257
1262
  await self.client.set_flow_run_state(flow_run.id, state, force=True)
1258
1263
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prefect-client
3
- Version: 3.2.13
3
+ Version: 3.2.15
4
4
  Summary: Workflow orchestration and management.
5
5
  Project-URL: Changelog, https://github.com/PrefectHQ/prefect/releases
6
6
  Project-URL: Documentation, https://docs.prefect.io
@@ -48,13 +48,14 @@ Requires-Dist: pydantic-settings>2.2.1
48
48
  Requires-Dist: python-dateutil<3.0.0,>=2.8.2
49
49
  Requires-Dist: python-slugify<9.0,>=5.0
50
50
  Requires-Dist: python-socks[asyncio]<3.0,>=2.5.3
51
+ Requires-Dist: pytz<2026,>=2021.1
51
52
  Requires-Dist: pyyaml<7.0.0,>=5.4.1
52
53
  Requires-Dist: rfc3339-validator<0.2.0,>=0.1.4
53
54
  Requires-Dist: rich<14.0,>=11.0
54
55
  Requires-Dist: ruamel-yaml>=0.17.0
55
56
  Requires-Dist: sniffio<2.0.0,>=1.3.0
56
57
  Requires-Dist: toml>=0.10.0
57
- Requires-Dist: typing-extensions<5.0.0,>=4.5.0
58
+ Requires-Dist: typing-extensions<5.0.0,>=4.10.0
58
59
  Requires-Dist: ujson<6.0.0,>=5.8.0
59
60
  Requires-Dist: uvicorn!=0.29.0,>=0.14.0
60
61
  Requires-Dist: websockets<16.0,>=13.0