prefect-client 3.1.5__py3-none-any.whl → 3.1.6__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 (93) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_internal/compatibility/migration.py +1 -1
  3. prefect/_internal/concurrency/api.py +52 -52
  4. prefect/_internal/concurrency/calls.py +59 -35
  5. prefect/_internal/concurrency/cancellation.py +34 -18
  6. prefect/_internal/concurrency/event_loop.py +7 -6
  7. prefect/_internal/concurrency/threads.py +41 -33
  8. prefect/_internal/concurrency/waiters.py +28 -21
  9. prefect/_internal/pydantic/v1_schema.py +2 -2
  10. prefect/_internal/pydantic/v2_schema.py +10 -9
  11. prefect/_internal/schemas/bases.py +9 -7
  12. prefect/_internal/schemas/validators.py +2 -1
  13. prefect/_version.py +3 -3
  14. prefect/automations.py +53 -47
  15. prefect/blocks/abstract.py +12 -10
  16. prefect/blocks/core.py +4 -2
  17. prefect/cache_policies.py +11 -11
  18. prefect/client/__init__.py +3 -1
  19. prefect/client/base.py +36 -37
  20. prefect/client/cloud.py +26 -19
  21. prefect/client/collections.py +2 -2
  22. prefect/client/orchestration.py +342 -273
  23. prefect/client/schemas/__init__.py +24 -0
  24. prefect/client/schemas/actions.py +123 -116
  25. prefect/client/schemas/objects.py +110 -81
  26. prefect/client/schemas/responses.py +18 -18
  27. prefect/client/schemas/schedules.py +136 -93
  28. prefect/client/subscriptions.py +28 -14
  29. prefect/client/utilities.py +32 -36
  30. prefect/concurrency/asyncio.py +6 -9
  31. prefect/concurrency/sync.py +35 -5
  32. prefect/context.py +39 -31
  33. prefect/deployments/flow_runs.py +3 -5
  34. prefect/docker/__init__.py +1 -1
  35. prefect/events/schemas/events.py +25 -20
  36. prefect/events/utilities.py +1 -2
  37. prefect/filesystems.py +3 -3
  38. prefect/flow_engine.py +61 -21
  39. prefect/flow_runs.py +3 -3
  40. prefect/flows.py +214 -170
  41. prefect/logging/configuration.py +1 -1
  42. prefect/logging/highlighters.py +1 -2
  43. prefect/logging/loggers.py +30 -20
  44. prefect/main.py +17 -24
  45. prefect/runner/runner.py +43 -21
  46. prefect/runner/server.py +30 -32
  47. prefect/runner/submit.py +3 -6
  48. prefect/runner/utils.py +6 -6
  49. prefect/runtime/flow_run.py +7 -0
  50. prefect/settings/constants.py +2 -2
  51. prefect/settings/legacy.py +1 -1
  52. prefect/settings/models/server/events.py +10 -0
  53. prefect/task_engine.py +72 -19
  54. prefect/task_runners.py +2 -2
  55. prefect/tasks.py +46 -33
  56. prefect/telemetry/bootstrap.py +15 -2
  57. prefect/telemetry/run_telemetry.py +107 -0
  58. prefect/transactions.py +14 -14
  59. prefect/types/__init__.py +1 -4
  60. prefect/utilities/_engine.py +96 -0
  61. prefect/utilities/annotations.py +25 -18
  62. prefect/utilities/asyncutils.py +126 -140
  63. prefect/utilities/callables.py +87 -78
  64. prefect/utilities/collections.py +278 -117
  65. prefect/utilities/compat.py +13 -21
  66. prefect/utilities/context.py +6 -5
  67. prefect/utilities/dispatch.py +23 -12
  68. prefect/utilities/dockerutils.py +33 -32
  69. prefect/utilities/engine.py +126 -239
  70. prefect/utilities/filesystem.py +18 -15
  71. prefect/utilities/hashing.py +10 -11
  72. prefect/utilities/importtools.py +40 -27
  73. prefect/utilities/math.py +9 -5
  74. prefect/utilities/names.py +3 -3
  75. prefect/utilities/processutils.py +121 -57
  76. prefect/utilities/pydantic.py +41 -36
  77. prefect/utilities/render_swagger.py +22 -12
  78. prefect/utilities/schema_tools/__init__.py +2 -1
  79. prefect/utilities/schema_tools/hydration.py +50 -43
  80. prefect/utilities/schema_tools/validation.py +52 -42
  81. prefect/utilities/services.py +13 -12
  82. prefect/utilities/templating.py +45 -45
  83. prefect/utilities/text.py +2 -1
  84. prefect/utilities/timeout.py +4 -4
  85. prefect/utilities/urls.py +9 -4
  86. prefect/utilities/visualization.py +46 -24
  87. prefect/variables.py +9 -8
  88. prefect/workers/base.py +15 -8
  89. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/METADATA +4 -2
  90. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/RECORD +93 -91
  91. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/LICENSE +0 -0
  92. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/WHEEL +0 -0
  93. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/top_level.txt +0 -0
@@ -5,14 +5,16 @@ Utilities for working with file systems
5
5
  import os
6
6
  import pathlib
7
7
  import threading
8
+ from collections.abc import Iterable
8
9
  from contextlib import contextmanager
9
10
  from pathlib import Path, PureWindowsPath
10
- from typing import Optional, Union, cast
11
+ from typing import AnyStr, Optional, Union, cast
11
12
 
12
- import fsspec
13
+ # fsspec has no stubs, see https://github.com/fsspec/filesystem_spec/issues/625
14
+ import fsspec # type: ignore
13
15
  import pathspec
14
- from fsspec.core import OpenFile
15
- from fsspec.implementations.local import LocalFileSystem
16
+ from fsspec.core import OpenFile # type: ignore
17
+ from fsspec.implementations.local import LocalFileSystem # type: ignore
16
18
 
17
19
  import prefect
18
20
 
@@ -33,8 +35,10 @@ def create_default_ignore_file(path: str) -> bool:
33
35
 
34
36
 
35
37
  def filter_files(
36
- root: str = ".", ignore_patterns: Optional[list] = None, include_dirs: bool = True
37
- ) -> set:
38
+ root: str = ".",
39
+ ignore_patterns: Optional[Iterable[AnyStr]] = None,
40
+ include_dirs: bool = True,
41
+ ) -> set[str]:
38
42
  """
39
43
  This function accepts a root directory path and a list of file patterns to ignore, and returns
40
44
  a list of files that excludes those that should be ignored.
@@ -51,7 +55,7 @@ def filter_files(
51
55
  return included_files
52
56
 
53
57
 
54
- chdir_lock = threading.Lock()
58
+ chdir_lock: threading.Lock = threading.Lock()
55
59
 
56
60
 
57
61
  def _normalize_path(path: Union[str, Path]) -> str:
@@ -103,33 +107,32 @@ def tmpchdir(path: str):
103
107
  def filename(path: str) -> str:
104
108
  """Extract the file name from a path with remote file system support"""
105
109
  try:
106
- of: OpenFile = cast(OpenFile, fsspec.open(path))
107
- sep = of.fs.sep
110
+ of: OpenFile = cast(OpenFile, fsspec.open(path)) # type: ignore # no typing stubs available
111
+ sep = cast(str, of.fs.sep) # type: ignore # no typing stubs available
108
112
  except (ImportError, AttributeError):
109
113
  sep = "\\" if "\\" in path else "/"
110
114
  return path.split(sep)[-1]
111
115
 
112
116
 
113
- def is_local_path(path: Union[str, pathlib.Path, OpenFile]):
117
+ def is_local_path(path: Union[str, pathlib.Path, OpenFile]) -> bool:
114
118
  """Check if the given path points to a local or remote file system"""
115
119
  if isinstance(path, str):
116
120
  try:
117
- of = fsspec.open(path)
121
+ of = cast(OpenFile, fsspec.open(path)) # type: ignore # no typing stubs available
118
122
  except ImportError:
119
123
  # The path is a remote file system that uses a lib that is not installed
120
124
  return False
121
125
  elif isinstance(path, pathlib.Path):
122
126
  return True
123
- elif isinstance(path, OpenFile):
124
- of = path
125
127
  else:
126
- raise TypeError(f"Invalid path of type {type(path).__name__!r}")
128
+ of = path
127
129
 
128
130
  return isinstance(of.fs, LocalFileSystem)
129
131
 
130
132
 
131
133
  def to_display_path(
132
- path: Union[pathlib.Path, str], relative_to: Union[pathlib.Path, str] = None
134
+ path: Union[pathlib.Path, str],
135
+ relative_to: Optional[Union[pathlib.Path, str]] = None,
133
136
  ) -> str:
134
137
  """
135
138
  Convert a path to a displayable path. The absolute path or relative path to the
@@ -1,21 +1,17 @@
1
1
  import hashlib
2
- import sys
3
2
  from functools import partial
4
3
  from pathlib import Path
5
- from typing import Optional, Union
4
+ from typing import Any, Callable, Optional, Union
6
5
 
7
- import cloudpickle
6
+ import cloudpickle # type: ignore # no stubs available
8
7
 
9
8
  from prefect.exceptions import HashError
10
9
  from prefect.serializers import JSONSerializer
11
10
 
12
- if sys.version_info[:2] >= (3, 9):
13
- _md5 = partial(hashlib.md5, usedforsecurity=False)
14
- else:
15
- _md5 = hashlib.md5
11
+ _md5 = partial(hashlib.md5, usedforsecurity=False)
16
12
 
17
13
 
18
- def stable_hash(*args: Union[str, bytes], hash_algo=_md5) -> str:
14
+ def stable_hash(*args: Union[str, bytes], hash_algo: Callable[..., Any] = _md5) -> str:
19
15
  """Given some arguments, produces a stable 64-bit hash of their contents.
20
16
 
21
17
  Supports bytes and strings. Strings will be UTF-8 encoded.
@@ -35,7 +31,7 @@ def stable_hash(*args: Union[str, bytes], hash_algo=_md5) -> str:
35
31
  return h.hexdigest()
36
32
 
37
33
 
38
- def file_hash(path: str, hash_algo=_md5) -> str:
34
+ def file_hash(path: str, hash_algo: Callable[..., Any] = _md5) -> str:
39
35
  """Given a path to a file, produces a stable hash of the file contents.
40
36
 
41
37
  Args:
@@ -50,7 +46,10 @@ def file_hash(path: str, hash_algo=_md5) -> str:
50
46
 
51
47
 
52
48
  def hash_objects(
53
- *args, hash_algo=_md5, raise_on_failure: bool = False, **kwargs
49
+ *args: Any,
50
+ hash_algo: Callable[..., Any] = _md5,
51
+ raise_on_failure: bool = False,
52
+ **kwargs: Any,
54
53
  ) -> Optional[str]:
55
54
  """
56
55
  Attempt to hash objects by dumping to JSON or serializing with cloudpickle.
@@ -77,7 +76,7 @@ def hash_objects(
77
76
  json_error = str(e)
78
77
 
79
78
  try:
80
- return stable_hash(cloudpickle.dumps((args, kwargs)), hash_algo=hash_algo)
79
+ return stable_hash(cloudpickle.dumps((args, kwargs)), hash_algo=hash_algo) # type: ignore[reportUnknownMemberType]
81
80
  except Exception as e:
82
81
  pickle_error = str(e)
83
82
 
@@ -5,20 +5,23 @@ import os
5
5
  import runpy
6
6
  import sys
7
7
  import warnings
8
+ from collections.abc import Iterable, Sequence
8
9
  from importlib.abc import Loader, MetaPathFinder
9
10
  from importlib.machinery import ModuleSpec
11
+ from io import TextIOWrapper
12
+ from logging import Logger
10
13
  from pathlib import Path
11
14
  from tempfile import NamedTemporaryFile
12
15
  from types import ModuleType
13
- from typing import Any, Callable, Dict, Iterable, NamedTuple, Optional, Union
16
+ from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Optional, Union
14
17
 
15
- import fsspec
18
+ import fsspec # type: ignore # no typing stubs available
16
19
 
17
20
  from prefect.exceptions import ScriptError
18
21
  from prefect.logging.loggers import get_logger
19
22
  from prefect.utilities.filesystem import filename, is_local_path, tmpchdir
20
23
 
21
- logger = get_logger(__name__)
24
+ logger: Logger = get_logger(__name__)
22
25
 
23
26
 
24
27
  def to_qualified_name(obj: Any) -> str:
@@ -70,7 +73,9 @@ def from_qualified_name(name: str) -> Any:
70
73
  return getattr(module, attr_name)
71
74
 
72
75
 
73
- def objects_from_script(path: str, text: Union[str, bytes] = None) -> Dict[str, Any]:
76
+ def objects_from_script(
77
+ path: str, text: Optional[Union[str, bytes]] = None
78
+ ) -> dict[str, Any]:
74
79
  """
75
80
  Run a python script and return all the global variables
76
81
 
@@ -97,7 +102,7 @@ def objects_from_script(path: str, text: Union[str, bytes] = None) -> Dict[str,
97
102
  ScriptError: if the script raises an exception during execution
98
103
  """
99
104
 
100
- def run_script(run_path: str):
105
+ def run_script(run_path: str) -> dict[str, Any]:
101
106
  # Cast to an absolute path before changing directories to ensure relative paths
102
107
  # are not broken
103
108
  abs_run_path = os.path.abspath(run_path)
@@ -120,7 +125,9 @@ def objects_from_script(path: str, text: Union[str, bytes] = None) -> Dict[str,
120
125
  else:
121
126
  if not is_local_path(path):
122
127
  # Remote paths need to be local to run
123
- with fsspec.open(path) as f:
128
+ with fsspec.open(path) as f: # type: ignore # no typing stubs available
129
+ if TYPE_CHECKING:
130
+ assert isinstance(f, TextIOWrapper)
124
131
  contents = f.read()
125
132
  return objects_from_script(path, contents)
126
133
  else:
@@ -156,6 +163,10 @@ def load_script_as_module(path: str) -> ModuleType:
156
163
  # Support explicit relative imports i.e. `from .foo import bar`
157
164
  submodule_search_locations=[parent_path, working_directory],
158
165
  )
166
+ if TYPE_CHECKING:
167
+ assert spec is not None
168
+ assert spec.loader is not None
169
+
159
170
  module = importlib.util.module_from_spec(spec)
160
171
  sys.modules["__prefect_loader__"] = module
161
172
 
@@ -189,7 +200,7 @@ def load_module(module_name: str) -> ModuleType:
189
200
  sys.path.remove(working_directory)
190
201
 
191
202
 
192
- def import_object(import_path: str):
203
+ def import_object(import_path: str) -> Any:
193
204
  """
194
205
  Load an object from an import path.
195
206
 
@@ -228,22 +239,20 @@ class DelayedImportErrorModule(ModuleType):
228
239
  [1]: https://github.com/scientific-python/lazy_loader
229
240
  """
230
241
 
231
- def __init__(self, error_message, help_message, *args, **kwargs):
242
+ def __init__(self, error_message: str, help_message: Optional[str] = None) -> None:
232
243
  self.__error_message = error_message
233
- self.__help_message = (
234
- help_message or "Import errors for this module are only reported when used."
235
- )
236
- super().__init__(*args, **kwargs)
244
+ if not help_message:
245
+ help_message = "Import errors for this module are only reported when used."
246
+ super().__init__("DelayedImportErrorModule", help_message)
237
247
 
238
- def __getattr__(self, attr):
239
- if attr in ("__class__", "__file__", "__help_message"):
240
- super().__getattr__(attr)
241
- else:
242
- raise ModuleNotFoundError(self.__error_message)
248
+ def __getattr__(self, attr: str) -> Any:
249
+ if attr == "__file__": # not set but should result in an attribute error?
250
+ return super().__getattr__(attr)
251
+ raise ModuleNotFoundError(self.__error_message)
243
252
 
244
253
 
245
254
  def lazy_import(
246
- name: str, error_on_import: bool = False, help_message: str = ""
255
+ name: str, error_on_import: bool = False, help_message: Optional[str] = None
247
256
  ) -> ModuleType:
248
257
  """
249
258
  Create a lazily-imported module to use in place of the module of the given name.
@@ -282,13 +291,13 @@ def lazy_import(
282
291
  if error_on_import:
283
292
  raise ModuleNotFoundError(import_error_message)
284
293
 
285
- return DelayedImportErrorModule(
286
- import_error_message, help_message, "DelayedImportErrorModule"
287
- )
294
+ return DelayedImportErrorModule(import_error_message, help_message)
288
295
 
289
296
  module = importlib.util.module_from_spec(spec)
290
297
  sys.modules[name] = module
291
298
 
299
+ if TYPE_CHECKING:
300
+ assert spec.loader is not None
292
301
  loader = importlib.util.LazyLoader(spec.loader)
293
302
  loader.exec_module(module)
294
303
 
@@ -317,13 +326,13 @@ class AliasedModuleFinder(MetaPathFinder):
317
326
 
318
327
  Aliases apply to all modules nested within an alias.
319
328
  """
320
- self.aliases = aliases
329
+ self.aliases: list[AliasedModuleDefinition] = list(aliases)
321
330
 
322
331
  def find_spec(
323
332
  self,
324
333
  fullname: str,
325
- path=None,
326
- target=None,
334
+ path: Optional[Sequence[str]] = None,
335
+ target: Optional[ModuleType] = None,
327
336
  ) -> Optional[ModuleSpec]:
328
337
  """
329
338
  The fullname is the imported path, e.g. "foo.bar". If there is an alias "phi"
@@ -334,6 +343,7 @@ class AliasedModuleFinder(MetaPathFinder):
334
343
  if fullname.startswith(alias):
335
344
  # Retrieve the spec of the real module
336
345
  real_spec = importlib.util.find_spec(fullname.replace(alias, real, 1))
346
+ assert real_spec is not None
337
347
  # Create a new spec for the alias
338
348
  return ModuleSpec(
339
349
  fullname,
@@ -354,7 +364,7 @@ class AliasedModuleLoader(Loader):
354
364
  self.callback = callback
355
365
  self.real_spec = real_spec
356
366
 
357
- def exec_module(self, _: ModuleType) -> None:
367
+ def exec_module(self, module: ModuleType) -> None:
358
368
  root_module = importlib.import_module(self.real_spec.name)
359
369
  if self.callback is not None:
360
370
  self.callback(self.alias)
@@ -363,7 +373,7 @@ class AliasedModuleLoader(Loader):
363
373
 
364
374
  def safe_load_namespace(
365
375
  source_code: str, filepath: Optional[str] = None
366
- ) -> Dict[str, Any]:
376
+ ) -> dict[str, Any]:
367
377
  """
368
378
  Safely load a namespace from source code, optionally handling relative imports.
369
379
 
@@ -380,7 +390,7 @@ def safe_load_namespace(
380
390
  """
381
391
  parsed_code = ast.parse(source_code)
382
392
 
383
- namespace: Dict[str, Any] = {"__name__": "prefect_safe_namespace_loader"}
393
+ namespace: dict[str, Any] = {"__name__": "prefect_safe_namespace_loader"}
384
394
 
385
395
  # Remove the body of the if __name__ == "__main__": block
386
396
  new_body = [node for node in parsed_code.body if not _is_main_block(node)]
@@ -427,6 +437,9 @@ def safe_load_namespace(
427
437
  try:
428
438
  if node.level > 0:
429
439
  # For relative imports, use the parent package to inform the import
440
+ if TYPE_CHECKING:
441
+ assert temp_module is not None
442
+ assert temp_module.__package__ is not None
430
443
  package_parts = temp_module.__package__.split(".")
431
444
  if len(package_parts) < node.level:
432
445
  raise ImportError(
prefect/utilities/math.py CHANGED
@@ -2,7 +2,9 @@ import math
2
2
  import random
3
3
 
4
4
 
5
- def poisson_interval(average_interval, lower=0, upper=1):
5
+ def poisson_interval(
6
+ average_interval: float, lower: float = 0, upper: float = 1
7
+ ) -> float:
6
8
  """
7
9
  Generates an "inter-arrival time" for a Poisson process.
8
10
 
@@ -16,12 +18,12 @@ def poisson_interval(average_interval, lower=0, upper=1):
16
18
  return -math.log(max(1 - random.uniform(lower, upper), 1e-10)) * average_interval
17
19
 
18
20
 
19
- def exponential_cdf(x, average_interval):
21
+ def exponential_cdf(x: float, average_interval: float) -> float:
20
22
  ld = 1 / average_interval
21
23
  return 1 - math.exp(-ld * x)
22
24
 
23
25
 
24
- def lower_clamp_multiple(k):
26
+ def lower_clamp_multiple(k: float) -> float:
25
27
  """
26
28
  Computes a lower clamp multiple that can be used to bound a random variate drawn
27
29
  from an exponential distribution.
@@ -38,7 +40,9 @@ def lower_clamp_multiple(k):
38
40
  return math.log(max(2**k / (2**k - 1), 1e-10), 2)
39
41
 
40
42
 
41
- def clamped_poisson_interval(average_interval, clamping_factor=0.3):
43
+ def clamped_poisson_interval(
44
+ average_interval: float, clamping_factor: float = 0.3
45
+ ) -> float:
42
46
  """
43
47
  Bounds Poisson "inter-arrival times" to a range defined by the clamping factor.
44
48
 
@@ -57,7 +61,7 @@ def clamped_poisson_interval(average_interval, clamping_factor=0.3):
57
61
  return poisson_interval(average_interval, lower_rv, upper_rv)
58
62
 
59
63
 
60
- def bounded_poisson_interval(lower_bound, upper_bound):
64
+ def bounded_poisson_interval(lower_bound: float, upper_bound: float) -> float:
61
65
  """
62
66
  Bounds Poisson "inter-arrival times" to a range.
63
67
 
@@ -1,6 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- import coolname
3
+ import coolname # type: ignore # the version after coolname 2.2.0 should have stubs.
4
4
 
5
5
  OBFUSCATED_PREFIX = "****"
6
6
 
@@ -42,7 +42,7 @@ def generate_slug(n_words: int) -> str:
42
42
  return "-".join(words)
43
43
 
44
44
 
45
- def obfuscate(s: Any, show_tail=False) -> str:
45
+ def obfuscate(s: Any, show_tail: bool = False) -> str:
46
46
  """
47
47
  Obfuscates any data type's string representation. See `obfuscate_string`.
48
48
  """
@@ -52,7 +52,7 @@ def obfuscate(s: Any, show_tail=False) -> str:
52
52
  return obfuscate_string(str(s), show_tail=show_tail)
53
53
 
54
54
 
55
- def obfuscate_string(s: str, show_tail=False) -> str:
55
+ def obfuscate_string(s: str, show_tail: bool = False) -> str:
56
56
  """
57
57
  Obfuscates a string by returning a new string of 8 characters. If the input
58
58
  string is longer than 10 characters and show_tail is True, then up to 4 of