prefect-client 3.0.1__py3-none-any.whl → 3.0.3__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 (48) hide show
  1. prefect/_internal/compatibility/deprecated.py +1 -1
  2. prefect/blocks/core.py +5 -4
  3. prefect/blocks/notifications.py +21 -0
  4. prefect/blocks/webhook.py +17 -1
  5. prefect/cache_policies.py +98 -28
  6. prefect/client/orchestration.py +42 -20
  7. prefect/client/schemas/actions.py +10 -2
  8. prefect/client/schemas/filters.py +4 -2
  9. prefect/client/schemas/objects.py +48 -6
  10. prefect/client/schemas/responses.py +15 -1
  11. prefect/client/types/flexible_schedule_list.py +1 -1
  12. prefect/concurrency/asyncio.py +45 -6
  13. prefect/concurrency/services.py +1 -1
  14. prefect/concurrency/sync.py +21 -27
  15. prefect/concurrency/v1/asyncio.py +3 -0
  16. prefect/concurrency/v1/sync.py +4 -5
  17. prefect/context.py +6 -6
  18. prefect/deployments/runner.py +43 -5
  19. prefect/events/actions.py +6 -0
  20. prefect/flow_engine.py +12 -4
  21. prefect/flows.py +15 -11
  22. prefect/locking/filesystem.py +243 -0
  23. prefect/logging/handlers.py +0 -2
  24. prefect/logging/loggers.py +0 -18
  25. prefect/logging/logging.yml +1 -0
  26. prefect/main.py +19 -5
  27. prefect/plugins.py +9 -1
  28. prefect/records/base.py +12 -0
  29. prefect/records/filesystem.py +6 -2
  30. prefect/records/memory.py +6 -0
  31. prefect/records/result_store.py +6 -0
  32. prefect/results.py +192 -29
  33. prefect/runner/runner.py +74 -6
  34. prefect/settings.py +31 -1
  35. prefect/states.py +34 -17
  36. prefect/task_engine.py +58 -43
  37. prefect/transactions.py +113 -52
  38. prefect/utilities/asyncutils.py +7 -0
  39. prefect/utilities/collections.py +3 -2
  40. prefect/utilities/engine.py +20 -9
  41. prefect/utilities/importtools.py +1 -0
  42. prefect/utilities/urls.py +70 -12
  43. prefect/workers/base.py +10 -8
  44. {prefect_client-3.0.1.dist-info → prefect_client-3.0.3.dist-info}/METADATA +1 -1
  45. {prefect_client-3.0.1.dist-info → prefect_client-3.0.3.dist-info}/RECORD +48 -47
  46. {prefect_client-3.0.1.dist-info → prefect_client-3.0.3.dist-info}/LICENSE +0 -0
  47. {prefect_client-3.0.1.dist-info → prefect_client-3.0.3.dist-info}/WHEEL +0 -0
  48. {prefect_client-3.0.1.dist-info → prefect_client-3.0.3.dist-info}/top_level.txt +0 -0
prefect/flows.py CHANGED
@@ -51,8 +51,8 @@ from prefect._internal.concurrency.api import create_call, from_async
51
51
  from prefect.blocks.core import Block
52
52
  from prefect.client.orchestration import get_client
53
53
  from prefect.client.schemas.actions import DeploymentScheduleCreate
54
+ from prefect.client.schemas.objects import ConcurrencyLimitConfig, FlowRun
54
55
  from prefect.client.schemas.objects import Flow as FlowSchema
55
- from prefect.client.schemas.objects import FlowRun
56
56
  from prefect.client.utilities import client_injector
57
57
  from prefect.docker.docker_image import DockerImage
58
58
  from prefect.events import DeploymentTriggerTypes, TriggerTypes
@@ -258,11 +258,11 @@ class Flow(Generic[P, R]):
258
258
  if not callable(fn):
259
259
  raise TypeError("'fn' must be callable")
260
260
 
261
- # Validate name if given
262
- if name:
263
- _raise_on_name_with_banned_characters(name)
264
-
265
- self.name = name or fn.__name__.replace("_", "-")
261
+ self.name = name or fn.__name__.replace("_", "-").replace(
262
+ "<lambda>",
263
+ "unknown-lambda", # prefect API will not accept "<" or ">" in flow names
264
+ )
265
+ _raise_on_name_with_banned_characters(self.name)
266
266
 
267
267
  if flow_run_name is not None:
268
268
  if not isinstance(flow_run_name, str) and not callable(flow_run_name):
@@ -643,7 +643,7 @@ class Flow(Generic[P, R]):
643
643
  rrule: Optional[Union[Iterable[str], str]] = None,
644
644
  paused: Optional[bool] = None,
645
645
  schedules: Optional["FlexibleScheduleList"] = None,
646
- concurrency_limit: Optional[int] = None,
646
+ concurrency_limit: Optional[Union[int, ConcurrencyLimitConfig, None]] = None,
647
647
  parameters: Optional[dict] = None,
648
648
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
649
649
  description: Optional[str] = None,
@@ -715,6 +715,7 @@ class Flow(Generic[P, R]):
715
715
  storage=self._storage,
716
716
  entrypoint=self._entrypoint,
717
717
  name=name,
718
+ flow_name=self.name,
718
719
  interval=interval,
719
720
  cron=cron,
720
721
  rrule=rrule,
@@ -733,7 +734,7 @@ class Flow(Generic[P, R]):
733
734
  ) # type: ignore # TODO: remove sync_compatible
734
735
  else:
735
736
  return RunnerDeployment.from_flow(
736
- self,
737
+ flow=self,
737
738
  name=name,
738
739
  interval=interval,
739
740
  cron=cron,
@@ -798,6 +799,7 @@ class Flow(Generic[P, R]):
798
799
  rrule: Optional[Union[Iterable[str], str]] = None,
799
800
  paused: Optional[bool] = None,
800
801
  schedules: Optional["FlexibleScheduleList"] = None,
802
+ global_limit: Optional[Union[int, ConcurrencyLimitConfig, None]] = None,
801
803
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
802
804
  parameters: Optional[dict] = None,
803
805
  description: Optional[str] = None,
@@ -827,6 +829,7 @@ class Flow(Generic[P, R]):
827
829
  paused: Whether or not to set this deployment as paused.
828
830
  schedules: A list of schedule objects defining when to execute runs of this deployment.
829
831
  Used to define multiple schedules or additional scheduling options like `timezone`.
832
+ global_limit: The maximum number of concurrent runs allowed across all served flow instances associated with the same deployment.
830
833
  parameters: A dictionary of default parameter values to pass to runs of this deployment.
831
834
  description: A description for the created deployment. Defaults to the flow's
832
835
  description if not provided.
@@ -838,7 +841,7 @@ class Flow(Generic[P, R]):
838
841
  pause_on_shutdown: If True, provided schedule will be paused when the serve function is stopped.
839
842
  If False, the schedules will continue running.
840
843
  print_starting_message: Whether or not to print the starting message when flow is served.
841
- limit: The maximum number of runs that can be executed concurrently.
844
+ limit: The maximum number of runs that can be executed concurrently by the created runner; only applies to this served flow. To apply a limit across multiple served flows, use `global_limit`.
842
845
  webserver: Whether or not to start a monitoring webserver for this flow.
843
846
  entrypoint_type: Type of entrypoint to use for the deployment. When using a module path
844
847
  entrypoint, ensure that the module will be importable in the execution environment.
@@ -890,6 +893,7 @@ class Flow(Generic[P, R]):
890
893
  rrule=rrule,
891
894
  paused=paused,
892
895
  schedules=schedules,
896
+ concurrency_limit=global_limit,
893
897
  parameters=parameters,
894
898
  description=description,
895
899
  tags=tags,
@@ -1057,7 +1061,7 @@ class Flow(Generic[P, R]):
1057
1061
  rrule: Optional[str] = None,
1058
1062
  paused: Optional[bool] = None,
1059
1063
  schedules: Optional[List[DeploymentScheduleCreate]] = None,
1060
- concurrency_limit: Optional[int] = None,
1064
+ concurrency_limit: Optional[Union[int, ConcurrencyLimitConfig, None]] = None,
1061
1065
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
1062
1066
  parameters: Optional[dict] = None,
1063
1067
  description: Optional[str] = None,
@@ -1622,7 +1626,7 @@ def flow(
1622
1626
  )
1623
1627
 
1624
1628
 
1625
- def _raise_on_name_with_banned_characters(name: str) -> str:
1629
+ def _raise_on_name_with_banned_characters(name: Optional[str]) -> Optional[str]:
1626
1630
  """
1627
1631
  Raise an InvalidNameError if the given name contains any invalid
1628
1632
  characters.
@@ -0,0 +1,243 @@
1
+ import time
2
+ from pathlib import Path
3
+ from typing import Dict, Optional
4
+
5
+ import anyio
6
+ import pendulum
7
+ import pydantic_core
8
+ from typing_extensions import TypedDict
9
+
10
+ from prefect.logging.loggers import get_logger
11
+
12
+ from .protocol import LockManager
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ class _LockInfo(TypedDict):
18
+ """
19
+ A dictionary containing information about a lock.
20
+
21
+ Attributes:
22
+ holder: The holder of the lock.
23
+ expiration: Datetime when the lock expires.
24
+ path: Path to the lock file.
25
+ """
26
+
27
+ holder: str
28
+ expiration: Optional[pendulum.DateTime]
29
+ path: Path
30
+
31
+
32
+ class FileSystemLockManager(LockManager):
33
+ """
34
+ A lock manager that implements locking using local files.
35
+
36
+ Attributes:
37
+ lock_files_directory: the directory where lock files are stored
38
+ """
39
+
40
+ def __init__(self, lock_files_directory: Path):
41
+ self.lock_files_directory = lock_files_directory.expanduser().resolve()
42
+ self._locks: Dict[str, _LockInfo] = {}
43
+
44
+ def _ensure_lock_files_directory_exists(self):
45
+ self.lock_files_directory.mkdir(parents=True, exist_ok=True)
46
+
47
+ def _lock_path_for_key(self, key: str) -> Path:
48
+ if (lock_info := self._locks.get(key)) is not None:
49
+ return lock_info["path"]
50
+ return self.lock_files_directory.joinpath(key).with_suffix(".lock")
51
+
52
+ def _get_lock_info(self, key: str, use_cache=True) -> Optional[_LockInfo]:
53
+ if use_cache:
54
+ if (lock_info := self._locks.get(key)) is not None:
55
+ return lock_info
56
+
57
+ lock_path = self._lock_path_for_key(key)
58
+
59
+ try:
60
+ with open(lock_path, "rb") as lock_file:
61
+ lock_info = pydantic_core.from_json(lock_file.read())
62
+ lock_info["path"] = lock_path
63
+ expiration = lock_info.get("expiration")
64
+ lock_info["expiration"] = (
65
+ pendulum.parse(expiration) if expiration is not None else None
66
+ )
67
+ self._locks[key] = lock_info
68
+ return lock_info
69
+ except FileNotFoundError:
70
+ return None
71
+
72
+ async def _aget_lock_info(
73
+ self, key: str, use_cache: bool = True
74
+ ) -> Optional[_LockInfo]:
75
+ if use_cache:
76
+ if (lock_info := self._locks.get(key)) is not None:
77
+ return lock_info
78
+
79
+ lock_path = self._lock_path_for_key(key)
80
+
81
+ try:
82
+ lock_info_bytes = await anyio.Path(lock_path).read_bytes()
83
+ lock_info = pydantic_core.from_json(lock_info_bytes)
84
+ lock_info["path"] = lock_path
85
+ expiration = lock_info.get("expiration")
86
+ lock_info["expiration"] = (
87
+ pendulum.parse(expiration) if expiration is not None else None
88
+ )
89
+ self._locks[key] = lock_info
90
+ return lock_info
91
+ except FileNotFoundError:
92
+ return None
93
+
94
+ def acquire_lock(
95
+ self,
96
+ key: str,
97
+ holder: str,
98
+ acquire_timeout: Optional[float] = None,
99
+ hold_timeout: Optional[float] = None,
100
+ ) -> bool:
101
+ self._ensure_lock_files_directory_exists()
102
+ lock_path = self._lock_path_for_key(key)
103
+
104
+ if self.is_locked(key) and not self.is_lock_holder(key, holder):
105
+ lock_free = self.wait_for_lock(key, acquire_timeout)
106
+ if not lock_free:
107
+ return False
108
+
109
+ try:
110
+ Path(lock_path).touch(exist_ok=False)
111
+ except FileExistsError:
112
+ if not self.is_lock_holder(key, holder):
113
+ logger.debug(
114
+ f"Another actor acquired the lock for record with key {key}. Trying again."
115
+ )
116
+ return self.acquire_lock(key, holder, acquire_timeout, hold_timeout)
117
+ expiration = (
118
+ pendulum.now("utc") + pendulum.duration(seconds=hold_timeout)
119
+ if hold_timeout is not None
120
+ else None
121
+ )
122
+
123
+ with open(Path(lock_path), "wb") as lock_file:
124
+ lock_file.write(
125
+ pydantic_core.to_json(
126
+ {
127
+ "holder": holder,
128
+ "expiration": str(expiration)
129
+ if expiration is not None
130
+ else None,
131
+ },
132
+ )
133
+ )
134
+
135
+ self._locks[key] = {
136
+ "holder": holder,
137
+ "expiration": expiration,
138
+ "path": lock_path,
139
+ }
140
+
141
+ return True
142
+
143
+ async def aacquire_lock(
144
+ self,
145
+ key: str,
146
+ holder: str,
147
+ acquire_timeout: Optional[float] = None,
148
+ hold_timeout: Optional[float] = None,
149
+ ) -> bool:
150
+ await anyio.Path(self.lock_files_directory).mkdir(parents=True, exist_ok=True)
151
+ lock_path = self._lock_path_for_key(key)
152
+
153
+ if self.is_locked(key) and not self.is_lock_holder(key, holder):
154
+ lock_free = await self.await_for_lock(key, acquire_timeout)
155
+ if not lock_free:
156
+ return False
157
+
158
+ try:
159
+ await anyio.Path(lock_path).touch(exist_ok=False)
160
+ except FileExistsError:
161
+ if not self.is_lock_holder(key, holder):
162
+ logger.debug(
163
+ f"Another actor acquired the lock for record with key {key}. Trying again."
164
+ )
165
+ return self.acquire_lock(key, holder, acquire_timeout, hold_timeout)
166
+ expiration = (
167
+ pendulum.now("utc") + pendulum.duration(seconds=hold_timeout)
168
+ if hold_timeout is not None
169
+ else None
170
+ )
171
+
172
+ async with await anyio.Path(lock_path).open("wb") as lock_file:
173
+ await lock_file.write(
174
+ pydantic_core.to_json(
175
+ {
176
+ "holder": holder,
177
+ "expiration": str(expiration)
178
+ if expiration is not None
179
+ else None,
180
+ },
181
+ )
182
+ )
183
+
184
+ self._locks[key] = {
185
+ "holder": holder,
186
+ "expiration": expiration,
187
+ "path": lock_path,
188
+ }
189
+
190
+ return True
191
+
192
+ def release_lock(self, key: str, holder: str) -> None:
193
+ lock_path = self._lock_path_for_key(key)
194
+ if not self.is_locked(key):
195
+ ValueError(f"No lock for transaction with key {key}")
196
+ if self.is_lock_holder(key, holder):
197
+ Path(lock_path).unlink(missing_ok=True)
198
+ self._locks.pop(key, None)
199
+ else:
200
+ raise ValueError(f"No lock held by {holder} for transaction with key {key}")
201
+
202
+ def is_locked(self, key: str, use_cache: bool = False) -> bool:
203
+ if (lock_info := self._get_lock_info(key, use_cache=use_cache)) is None:
204
+ return False
205
+
206
+ if (expiration := lock_info.get("expiration")) is None:
207
+ return True
208
+
209
+ expired = expiration < pendulum.now("utc")
210
+ if expired:
211
+ Path(lock_info["path"]).unlink()
212
+ self._locks.pop(key, None)
213
+ return False
214
+ else:
215
+ return True
216
+
217
+ def is_lock_holder(self, key: str, holder: str) -> bool:
218
+ if not self.is_locked(key):
219
+ return False
220
+
221
+ if not self.is_locked(key):
222
+ return False
223
+ if (lock_info := self._get_lock_info(key)) is None:
224
+ return False
225
+ return lock_info["holder"] == holder
226
+
227
+ def wait_for_lock(self, key: str, timeout: Optional[float] = None) -> bool:
228
+ seconds_waited = 0
229
+ while self.is_locked(key, use_cache=False):
230
+ if timeout and seconds_waited >= timeout:
231
+ return False
232
+ seconds_waited += 0.1
233
+ time.sleep(0.1)
234
+ return True
235
+
236
+ async def await_for_lock(self, key: str, timeout: Optional[float] = None) -> bool:
237
+ seconds_waited = 0
238
+ while self.is_locked(key, use_cache=False):
239
+ if timeout and seconds_waited >= timeout:
240
+ return False
241
+ seconds_waited += 0.1
242
+ await anyio.sleep(0.1)
243
+ return True
@@ -138,8 +138,6 @@ class APILogHandler(logging.Handler):
138
138
  return # Respect the global settings toggle
139
139
  if not getattr(record, "send_to_api", True):
140
140
  return # Do not send records that have opted out
141
- if not getattr(record, "send_to_orion", True):
142
- return # Backwards compatibility
143
141
 
144
142
  log = self.prepare(record)
145
143
  APILogWorker.instance().send(log)
@@ -1,7 +1,6 @@
1
1
  import io
2
2
  import logging
3
3
  import sys
4
- import warnings
5
4
  from builtins import print
6
5
  from contextlib import contextmanager
7
6
  from functools import lru_cache
@@ -34,23 +33,6 @@ class PrefectLogAdapter(logging.LoggerAdapter):
34
33
 
35
34
  def process(self, msg, kwargs):
36
35
  kwargs["extra"] = {**(self.extra or {}), **(kwargs.get("extra") or {})}
37
-
38
- from prefect._internal.compatibility.deprecated import (
39
- PrefectDeprecationWarning,
40
- generate_deprecation_message,
41
- )
42
-
43
- if "send_to_orion" in kwargs["extra"]:
44
- warnings.warn(
45
- generate_deprecation_message(
46
- 'The "send_to_orion" option',
47
- start_date="May 2023",
48
- help='Use "send_to_api" instead.',
49
- ),
50
- PrefectDeprecationWarning,
51
- stacklevel=4,
52
- )
53
-
54
36
  return (msg, kwargs)
55
37
 
56
38
  def getChild(
@@ -77,6 +77,7 @@ loggers:
77
77
  prefect.extra:
78
78
  level: "${PREFECT_LOGGING_LEVEL}"
79
79
  handlers: [api]
80
+ propagate: false
80
81
 
81
82
  prefect.flow_runs:
82
83
  level: NOTSET
prefect/main.py CHANGED
@@ -7,7 +7,7 @@ from prefect.transactions import Transaction
7
7
  from prefect.tasks import task, Task
8
8
  from prefect.context import tags
9
9
  from prefect.utilities.annotations import unmapped, allow_failure
10
- from prefect.results import BaseResult
10
+ from prefect.results import BaseResult, ResultRecordMetadata
11
11
  from prefect.flow_runs import pause_flow_run, resume_flow_run, suspend_flow_run
12
12
  from prefect.client.orchestration import get_client, PrefectClient
13
13
  from prefect.client.cloud import get_cloud_client, CloudClient
@@ -26,12 +26,26 @@ import prefect.context
26
26
  import prefect.client.schemas
27
27
 
28
28
  prefect.context.FlowRunContext.model_rebuild(
29
- _types_namespace={"Flow": Flow, "BaseResult": BaseResult}
29
+ _types_namespace={
30
+ "Flow": Flow,
31
+ "BaseResult": BaseResult,
32
+ "ResultRecordMetadata": ResultRecordMetadata,
33
+ }
34
+ )
35
+ prefect.context.TaskRunContext.model_rebuild(
36
+ _types_namespace={"Task": Task, "BaseResult": BaseResult}
37
+ )
38
+ prefect.client.schemas.State.model_rebuild(
39
+ _types_namespace={
40
+ "BaseResult": BaseResult,
41
+ "ResultRecordMetadata": ResultRecordMetadata,
42
+ }
30
43
  )
31
- prefect.context.TaskRunContext.model_rebuild(_types_namespace={"Task": Task})
32
- prefect.client.schemas.State.model_rebuild(_types_namespace={"BaseResult": BaseResult})
33
44
  prefect.client.schemas.StateCreate.model_rebuild(
34
- _types_namespace={"BaseResult": BaseResult}
45
+ _types_namespace={
46
+ "BaseResult": BaseResult,
47
+ "ResultRecordMetadata": ResultRecordMetadata,
48
+ }
35
49
  )
36
50
  Transaction.model_rebuild()
37
51
 
prefect/plugins.py CHANGED
@@ -14,6 +14,8 @@ from typing import Any, Dict, Union
14
14
  import prefect.settings
15
15
  from prefect.utilities.compat import EntryPoints, entry_points
16
16
 
17
+ COLLECTIONS: Union[None, Dict[str, Union[ModuleType, Exception]]] = None
18
+
17
19
 
18
20
  def safe_load_entrypoints(entrypoints: EntryPoints) -> Dict[str, Union[Exception, Any]]:
19
21
  """
@@ -38,11 +40,16 @@ def safe_load_entrypoints(entrypoints: EntryPoints) -> Dict[str, Union[Exception
38
40
  return results
39
41
 
40
42
 
41
- def load_prefect_collections() -> Dict[str, ModuleType]:
43
+ def load_prefect_collections() -> Dict[str, Union[ModuleType, Exception]]:
42
44
  """
43
45
  Load all Prefect collections that define an entrypoint in the group
44
46
  `prefect.collections`.
45
47
  """
48
+ global COLLECTIONS
49
+
50
+ if COLLECTIONS is not None:
51
+ return COLLECTIONS
52
+
46
53
  collection_entrypoints: EntryPoints = entry_points(group="prefect.collections")
47
54
  collections = safe_load_entrypoints(collection_entrypoints)
48
55
 
@@ -61,4 +68,5 @@ def load_prefect_collections() -> Dict[str, ModuleType]:
61
68
  if prefect.settings.PREFECT_DEBUG_MODE:
62
69
  print(f"Loaded collection {name!r}.")
63
70
 
71
+ COLLECTIONS = collections
64
72
  return collections
prefect/records/base.py CHANGED
@@ -6,11 +6,18 @@ from contextlib import contextmanager
6
6
  from dataclasses import dataclass
7
7
  from typing import TYPE_CHECKING, Optional
8
8
 
9
+ from prefect._internal.compatibility import deprecated
10
+
9
11
  if TYPE_CHECKING:
10
12
  from prefect.results import BaseResult
11
13
  from prefect.transactions import IsolationLevel
12
14
 
13
15
 
16
+ @deprecated.deprecated_class(
17
+ start_date="Sep 2024",
18
+ end_date="Nov 2024",
19
+ help="Use `ResultRecord` instead to represent a result and its associated metadata.",
20
+ )
14
21
  @dataclass
15
22
  class TransactionRecord:
16
23
  """
@@ -21,6 +28,11 @@ class TransactionRecord:
21
28
  result: "BaseResult"
22
29
 
23
30
 
31
+ @deprecated.deprecated_class(
32
+ start_date="Sep 2024",
33
+ end_date="Nov 2024",
34
+ help="Use `ResultStore` and provide a `WritableFileSystem` for `metadata_storage` instead.",
35
+ )
24
36
  class RecordStore(abc.ABC):
25
37
  @abc.abstractmethod
26
38
  def read(
@@ -6,6 +6,7 @@ from typing import Dict, Optional
6
6
  import pendulum
7
7
  from typing_extensions import TypedDict
8
8
 
9
+ from prefect._internal.compatibility import deprecated
9
10
  from prefect.logging.loggers import get_logger
10
11
  from prefect.records.base import RecordStore, TransactionRecord
11
12
  from prefect.results import BaseResult
@@ -29,6 +30,11 @@ class _LockInfo(TypedDict):
29
30
  path: Path
30
31
 
31
32
 
33
+ @deprecated.deprecated_class(
34
+ start_date="Sep 2024",
35
+ end_date="Nov 2024",
36
+ help="Use `ResultStore` with a `LocalFileSystem` for `metadata_storage` and a `FileSystemLockManager` instead.",
37
+ )
32
38
  class FileSystemRecordStore(RecordStore):
33
39
  """
34
40
  A record store that stores data on the local filesystem.
@@ -56,7 +62,6 @@ class FileSystemRecordStore(RecordStore):
56
62
  def _get_lock_info(self, key: str, use_cache=True) -> Optional[_LockInfo]:
57
63
  if use_cache:
58
64
  if (lock_info := self._locks.get(key)) is not None:
59
- print("Got lock info from cache")
60
65
  return lock_info
61
66
 
62
67
  lock_path = self._lock_path_for_key(key)
@@ -70,7 +75,6 @@ class FileSystemRecordStore(RecordStore):
70
75
  pendulum.parse(expiration) if expiration is not None else None
71
76
  )
72
77
  self._locks[key] = lock_info
73
- print("Got lock info from file")
74
78
  return lock_info
75
79
  except FileNotFoundError:
76
80
  return None
prefect/records/memory.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import threading
2
2
  from typing import Dict, Optional, TypedDict
3
3
 
4
+ from prefect._internal.compatibility import deprecated
4
5
  from prefect.results import BaseResult
5
6
  from prefect.transactions import IsolationLevel
6
7
 
@@ -22,6 +23,11 @@ class _LockInfo(TypedDict):
22
23
  expiration_timer: Optional[threading.Timer]
23
24
 
24
25
 
26
+ @deprecated.deprecated_class(
27
+ start_date="Sep 2024",
28
+ end_date="Nov 2024",
29
+ help="Use `ResultStore` with a `MemoryLockManager` instead.",
30
+ )
25
31
  class MemoryRecordStore(RecordStore):
26
32
  """
27
33
  A record store that stores data in memory.
@@ -3,6 +3,7 @@ from typing import Any, Optional
3
3
 
4
4
  import pendulum
5
5
 
6
+ from prefect._internal.compatibility import deprecated
6
7
  from prefect.results import BaseResult, PersistedResult, ResultStore
7
8
  from prefect.transactions import IsolationLevel
8
9
  from prefect.utilities.asyncutils import run_coro_as_sync
@@ -10,6 +11,11 @@ from prefect.utilities.asyncutils import run_coro_as_sync
10
11
  from .base import RecordStore, TransactionRecord
11
12
 
12
13
 
14
+ @deprecated.deprecated_class(
15
+ start_date="Sep 2024",
16
+ end_date="Nov 2024",
17
+ help="Use `ResultStore` directly instead.",
18
+ )
13
19
  @dataclass
14
20
  class ResultRecordStore(RecordStore):
15
21
  """