prefect-client 2.19.5__py3-none-any.whl → 2.19.7__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.
@@ -13,7 +13,6 @@ except ImportError:
13
13
 
14
14
  from prefect import get_client
15
15
  from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
16
- from prefect.utilities.timeout import timeout_async
17
16
 
18
17
  from .events import (
19
18
  _emit_concurrency_acquisition_events,
@@ -26,6 +25,10 @@ class ConcurrencySlotAcquisitionError(Exception):
26
25
  """Raised when an unhandlable occurs while acquiring concurrency slots."""
27
26
 
28
27
 
28
+ class AcquireConcurrencySlotTimeoutError(TimeoutError):
29
+ """Raised when acquiring a concurrency slot times out."""
30
+
31
+
29
32
  @asynccontextmanager
30
33
  async def concurrency(
31
34
  names: Union[str, List[str]],
@@ -58,8 +61,9 @@ async def concurrency(
58
61
  ```
59
62
  """
60
63
  names = names if isinstance(names, list) else [names]
61
- with timeout_async(seconds=timeout_seconds):
62
- limits = await _acquire_concurrency_slots(names, occupy)
64
+ limits = await _acquire_concurrency_slots(
65
+ names, occupy, timeout_seconds=timeout_seconds
66
+ )
63
67
  acquisition_time = pendulum.now("UTC")
64
68
  emitted_events = _emit_concurrency_acquisition_events(limits, occupy)
65
69
 
@@ -91,12 +95,18 @@ async def _acquire_concurrency_slots(
91
95
  names: List[str],
92
96
  slots: int,
93
97
  mode: Union[Literal["concurrency"], Literal["rate_limit"]] = "concurrency",
98
+ timeout_seconds: Optional[float] = None,
94
99
  ) -> List[MinimalConcurrencyLimitResponse]:
95
100
  service = ConcurrencySlotAcquisitionService.instance(frozenset(names))
96
- future = service.send((slots, mode))
101
+ future = service.send((slots, mode, timeout_seconds))
97
102
  response_or_exception = await asyncio.wrap_future(future)
98
103
 
99
104
  if isinstance(response_or_exception, Exception):
105
+ if isinstance(response_or_exception, TimeoutError):
106
+ raise AcquireConcurrencySlotTimeoutError(
107
+ f"Attempt to acquire concurrency slots timed out after {timeout_seconds} second(s)"
108
+ ) from response_or_exception
109
+
100
110
  raise ConcurrencySlotAcquisitionError(
101
111
  f"Unable to acquire concurrency slots on {names!r}"
102
112
  ) from response_or_exception
@@ -3,6 +3,7 @@ import concurrent.futures
3
3
  from contextlib import asynccontextmanager
4
4
  from typing import (
5
5
  FrozenSet,
6
+ Optional,
6
7
  Tuple,
7
8
  )
8
9
 
@@ -13,6 +14,7 @@ from prefect import get_client
13
14
  from prefect._internal.concurrency import logger
14
15
  from prefect._internal.concurrency.services import QueueService
15
16
  from prefect.client.orchestration import PrefectClient
17
+ from prefect.utilities.timeout import timeout_async
16
18
 
17
19
 
18
20
  class ConcurrencySlotAcquisitionService(QueueService):
@@ -27,10 +29,12 @@ class ConcurrencySlotAcquisitionService(QueueService):
27
29
  self._client = client
28
30
  yield
29
31
 
30
- async def _handle(self, item: Tuple[int, str, concurrent.futures.Future]):
31
- occupy, mode, future = item
32
+ async def _handle(
33
+ self, item: Tuple[int, str, Optional[float], concurrent.futures.Future]
34
+ ):
35
+ occupy, mode, timeout_seconds, future = item
32
36
  try:
33
- response = await self.acquire_slots(occupy, mode)
37
+ response = await self.acquire_slots(occupy, mode, timeout_seconds)
34
38
  except Exception as exc:
35
39
  # If the request to the increment endpoint fails in a non-standard
36
40
  # way, we need to set the future's result so that the caller can
@@ -40,25 +44,28 @@ class ConcurrencySlotAcquisitionService(QueueService):
40
44
  else:
41
45
  future.set_result(response)
42
46
 
43
- async def acquire_slots(self, slots: int, mode: str) -> httpx.Response:
44
- while True:
45
- try:
46
- response = await self._client.increment_concurrency_slots(
47
- names=self.concurrency_limit_names, slots=slots, mode=mode
48
- )
49
- except Exception as exc:
50
- if (
51
- isinstance(exc, httpx.HTTPStatusError)
52
- and exc.response.status_code == status.HTTP_423_LOCKED
53
- ):
54
- retry_after = float(exc.response.headers["Retry-After"])
55
- await asyncio.sleep(retry_after)
47
+ async def acquire_slots(
48
+ self, slots: int, mode: str, timeout_seconds: Optional[float] = None
49
+ ):
50
+ with timeout_async(timeout_seconds):
51
+ while True:
52
+ try:
53
+ response = await self._client.increment_concurrency_slots(
54
+ names=self.concurrency_limit_names, slots=slots, mode=mode
55
+ )
56
+ except Exception as exc:
57
+ if (
58
+ isinstance(exc, httpx.HTTPStatusError)
59
+ and exc.response.status_code == status.HTTP_423_LOCKED
60
+ ):
61
+ retry_after = float(exc.response.headers["Retry-After"])
62
+ await asyncio.sleep(retry_after)
63
+ else:
64
+ raise exc
56
65
  else:
57
- raise exc
58
- else:
59
- return response
66
+ return response
60
67
 
61
- def send(self, item: Tuple[int, str]):
68
+ def send(self, item: Tuple[int, str, Optional[float]]) -> concurrent.futures.Future:
62
69
  with self._lock:
63
70
  if self._stopped:
64
71
  raise RuntimeError("Cannot put items in a stopped service instance.")
@@ -66,7 +73,7 @@ class ConcurrencySlotAcquisitionService(QueueService):
66
73
  logger.debug("Service %r enqueuing item %r", self, item)
67
74
  future: concurrent.futures.Future = concurrent.futures.Future()
68
75
 
69
- occupy, mode = item
70
- self._queue.put_nowait((occupy, mode, future))
76
+ occupy, mode, timeout_seconds = item
77
+ self._queue.put_nowait((occupy, mode, timeout_seconds, future))
71
78
 
72
79
  return future
@@ -12,7 +12,6 @@ except ImportError:
12
12
  from prefect._internal.concurrency.api import create_call, from_sync
13
13
  from prefect._internal.concurrency.event_loop import get_running_loop
14
14
  from prefect.client.schemas.responses import MinimalConcurrencyLimitResponse
15
- from prefect.utilities.timeout import timeout
16
15
 
17
16
  from .asyncio import (
18
17
  _acquire_concurrency_slots,
@@ -57,10 +56,9 @@ def concurrency(
57
56
  """
58
57
  names = names if isinstance(names, list) else [names]
59
58
 
60
- with timeout(seconds=timeout_seconds):
61
- limits: List[MinimalConcurrencyLimitResponse] = _call_async_function_from_sync(
62
- _acquire_concurrency_slots, names, occupy
63
- )
59
+ limits: List[MinimalConcurrencyLimitResponse] = _call_async_function_from_sync(
60
+ _acquire_concurrency_slots, names, occupy, timeout_seconds=timeout_seconds
61
+ )
64
62
  acquisition_time = pendulum.now("UTC")
65
63
  emitted_events = _emit_concurrency_acquisition_events(limits, occupy)
66
64
 
prefect/flows.py CHANGED
@@ -83,8 +83,9 @@ from prefect.logging import get_logger
83
83
  from prefect.results import ResultSerializer, ResultStorage
84
84
  from prefect.runner.storage import (
85
85
  BlockStorageAdapter,
86
+ LocalStorage,
86
87
  RunnerStorage,
87
- create_storage_from_url,
88
+ create_storage_from_source,
88
89
  )
89
90
  from prefect.settings import (
90
91
  PREFECT_DEFAULT_WORK_POOL_NAME,
@@ -910,7 +911,7 @@ class Flow(Generic[P, R]):
910
911
  ```
911
912
  """
912
913
  if isinstance(source, str):
913
- storage = create_storage_from_url(source)
914
+ storage = create_storage_from_source(source)
914
915
  elif isinstance(source, RunnerStorage):
915
916
  storage = source
916
917
  elif hasattr(source, "get_directory"):
@@ -920,9 +921,11 @@ class Flow(Generic[P, R]):
920
921
  f"Unsupported source type {type(source).__name__!r}. Please provide a"
921
922
  " URL to remote storage or a storage object."
922
923
  )
924
+
923
925
  with tempfile.TemporaryDirectory() as tmpdir:
924
- storage.set_base_path(Path(tmpdir))
925
- await storage.pull_code()
926
+ if not isinstance(storage, LocalStorage):
927
+ storage.set_base_path(Path(tmpdir))
928
+ await storage.pull_code()
926
929
 
927
930
  full_entrypoint = str(storage.destination / entrypoint)
928
931
  flow: "Flow" = await from_async.wait_for_call_in_new_thread(
prefect/runner/storage.py CHANGED
@@ -564,8 +564,74 @@ class BlockStorageAdapter:
564
564
  return False
565
565
 
566
566
 
567
- def create_storage_from_url(
568
- url: str, pull_interval: Optional[int] = 60
567
+ class LocalStorage:
568
+ """
569
+ Sets the working directory in the local filesystem.
570
+
571
+ Parameters:
572
+ Path: Local file path to set the working directory for the flow
573
+
574
+ Examples:
575
+ Sets the working directory for the local path to the flow:
576
+
577
+ ```python
578
+ from prefect.runner.storage import Localstorage
579
+
580
+ storage = LocalStorage(
581
+ path="/path/to/local/flow_directory",
582
+ )
583
+ ```
584
+ """
585
+
586
+ def __init__(
587
+ self,
588
+ path: str,
589
+ pull_interval: Optional[int] = None,
590
+ ):
591
+ self._path = Path(path).resolve()
592
+ self._logger = get_logger("runner.storage.local-storage")
593
+ self._storage_base_path = Path.cwd()
594
+ self._pull_interval = pull_interval
595
+
596
+ @property
597
+ def destination(self) -> Path:
598
+ return self._path
599
+
600
+ def set_base_path(self, path: Path):
601
+ self._storage_base_path = path
602
+
603
+ @property
604
+ def pull_interval(self) -> Optional[int]:
605
+ return self._pull_interval
606
+
607
+ async def pull_code(self):
608
+ # Local storage assumes the code already exists on the local filesystem
609
+ # and does not need to be pulled from a remote location
610
+ pass
611
+
612
+ def to_pull_step(self) -> dict:
613
+ """
614
+ Returns a dictionary representation of the storage object that can be
615
+ used as a deployment pull step.
616
+ """
617
+ step = {
618
+ "prefect.deployments.steps.set_working_directory": {
619
+ "directory": str(self.destination)
620
+ }
621
+ }
622
+ return step
623
+
624
+ def __eq__(self, __value) -> bool:
625
+ if isinstance(__value, LocalStorage):
626
+ return self._path == __value._path
627
+ return False
628
+
629
+ def __repr__(self) -> str:
630
+ return f"LocalStorage(path={self._path!r})"
631
+
632
+
633
+ def create_storage_from_source(
634
+ source: str, pull_interval: Optional[int] = 60
569
635
  ) -> RunnerStorage:
570
636
  """
571
637
  Creates a storage object from a URL.
@@ -579,11 +645,18 @@ def create_storage_from_url(
579
645
  Returns:
580
646
  RunnerStorage: A runner storage compatible object
581
647
  """
582
- parsed_url = urlparse(url)
583
- if parsed_url.scheme == "git" or parsed_url.path.endswith(".git"):
584
- return GitRepository(url=url, pull_interval=pull_interval)
648
+ logger = get_logger("runner.storage")
649
+ parsed_source = urlparse(source)
650
+ if parsed_source.scheme == "git" or parsed_source.path.endswith(".git"):
651
+ return GitRepository(url=source, pull_interval=pull_interval)
652
+ elif parsed_source.scheme in ("file", "local"):
653
+ source_path = source.split("://", 1)[-1]
654
+ return LocalStorage(path=source_path, pull_interval=pull_interval)
655
+ elif parsed_source.scheme in fsspec.available_protocols():
656
+ return RemoteStorage(url=source, pull_interval=pull_interval)
585
657
  else:
586
- return RemoteStorage(url=url, pull_interval=pull_interval)
658
+ logger.debug("No valid fsspec protocol found for URL, assuming local storage.")
659
+ return LocalStorage(path=source, pull_interval=pull_interval)
587
660
 
588
661
 
589
662
  def _format_token_from_credentials(netloc: str, credentials: dict) -> str:
@@ -423,7 +423,7 @@ def safe_load_namespace(source_code: str):
423
423
  logger.debug("Failed to import from %s: %s", node.module, e)
424
424
 
425
425
  # Handle local definitions
426
- for node in ast.walk(parsed_code):
426
+ for node in parsed_code.body:
427
427
  if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.Assign)):
428
428
  try:
429
429
  # Compile and execute each class and function definition and assignment
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 2.19.5
3
+ Version: 2.19.7
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
@@ -52,7 +52,7 @@ Requires-Dist: sniffio <2.0.0,>=1.3.0
52
52
  Requires-Dist: toml >=0.10.0
53
53
  Requires-Dist: typing-extensions <5.0.0,>=4.5.0
54
54
  Requires-Dist: ujson <6.0.0,>=5.8.0
55
- Requires-Dist: uvicorn <0.29.0,>=0.14.0
55
+ Requires-Dist: uvicorn !=0.29.0,>=0.14.0
56
56
  Requires-Dist: websockets <13.0,>=10.4
57
57
  Requires-Dist: itsdangerous
58
58
  Requires-Dist: python-multipart >=0.0.7
@@ -9,7 +9,7 @@ prefect/engine.py,sha256=hGaxEyJB0OSb_fc2sRsoL550DGOeuwttWOY3dQR6wZw,90418
9
9
  prefect/exceptions.py,sha256=Fyl-GXvF9OuKHtsyn5EhWg81pkU1UG3DFHsI1JzhOQE,10851
10
10
  prefect/filesystems.py,sha256=XniPSdBAqywj43X7GyfuWJQIbz07QJ5Y3cVNLhIF3lQ,35260
11
11
  prefect/flow_runs.py,sha256=mFHLavZk1yZ62H3UazuNDBZWAF7AqKttA4rMcHgsVSw,3119
12
- prefect/flows.py,sha256=uB5LVVi_Gk4aBk69kyXX1VuU9AiQwtpGifgnpZtJc-Y,76400
12
+ prefect/flows.py,sha256=od5Yt1315Z0vd2kROdcuPTK9HYbJUTT8wd2B3obXK7w,76487
13
13
  prefect/futures.py,sha256=RaWfYIXtH7RsWxQ5QWTTlAzwtVV8XWpXaZT_hLq35vQ,12590
14
14
  prefect/manifests.py,sha256=sTM7j8Us5d49zaydYKWsKb7zJ96v1ChkLkLeR0GFYD8,683
15
15
  prefect/new_flow_engine.py,sha256=A1adTWTBAwPCn6ay003Jsoc2SdYgHV4AcJo1bmpa_7Y,16039
@@ -175,10 +175,10 @@ prefect/client/schemas/responses.py,sha256=XAc95g3PRL9UIkH9_VMuv0ECHKdc19guBLmdt
175
175
  prefect/client/schemas/schedules.py,sha256=ZF7fFbkcc8rVdx2MxE8DR0av3FUE9qDPjLreEuV8HfM,12193
176
176
  prefect/client/schemas/sorting.py,sha256=EIQ6FUjUWMwk6fn6ckVLQLXOP-GI5kce7ftjUkDFWV0,2490
177
177
  prefect/concurrency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
178
- prefect/concurrency/asyncio.py,sha256=FSlx_4VbXD96qIBpspc26vIMg4ei0zViWKrqqNK99Co,4264
178
+ prefect/concurrency/asyncio.py,sha256=39PIbNmbf1VK768X3X1dL-2RDeNiNPGeFqjpVctB-sM,4644
179
179
  prefect/concurrency/events.py,sha256=agci0Y5S0SwZhENgXIG_lbsqh4om9oeU6E_GbtZ55wM,1797
180
- prefect/concurrency/services.py,sha256=qk4y4H5UsUKz67BufZBJ3WXt2y8UEndXn6TxDkqPzeA,2549
181
- prefect/concurrency/sync.py,sha256=gXyiiA0bul7jjpHWPm6su8pFdBiMOekhu9FHnjiPWBQ,3339
180
+ prefect/concurrency/services.py,sha256=PQb4cs72lTRz_XEgDEfv4OkpHWC7KAmW_Xd9vpOOuM4,2906
181
+ prefect/concurrency/sync.py,sha256=QtnPRfVX9GqVyuZOt6W9yJuT9G-PlCSVnxlZKFTjuKY,3271
182
182
  prefect/deployments/__init__.py,sha256=dM866rOEz3BbAN_xaFMHj3Hw1oOFemBTZ2yxVE6IGoY,394
183
183
  prefect/deployments/base.py,sha256=0l2D_laMc3q2Q5nvh-WANv3iDy4Ih5BqcPMNJJbHuP0,16391
184
184
  prefect/deployments/deployments.py,sha256=bYNmxU0yn2jzluGIr2tUkgRi73WGQ6gGbjb0GlD4EIk,41656
@@ -237,7 +237,7 @@ prefect/pydantic/main.py,sha256=ups_UULBhCPhB-E7X7-Qgbpor1oJdqChRzpD0ZYQH8A,839
237
237
  prefect/runner/__init__.py,sha256=7U-vAOXFkzMfRz1q8Uv6Otsvc0OrPYLLP44srwkJ_8s,89
238
238
  prefect/runner/runner.py,sha256=45sZR2kDvhTODyGmeiRe-bgVWq5JHsmZvFPBsiiyKxA,45147
239
239
  prefect/runner/server.py,sha256=mgjH5SPlj3xu0_pZHg15zw59OSJ5lIzTIZ101s281Oo,10655
240
- prefect/runner/storage.py,sha256=iZey8Am51c1fZFpS9iVXWYpKiM_lSocvaJEOZVExhvA,22428
240
+ prefect/runner/storage.py,sha256=rjvbni_vvYu4msApYFyu-bM1iOawiOvz9w575_H1QZM,24673
241
241
  prefect/runner/submit.py,sha256=w53VdsqfwjW-M3e8hUAAoVlNrXsvGuuyGpEN0wi3vX0,8537
242
242
  prefect/runner/utils.py,sha256=G8qv6AwAa43HcgLOo5vDhoXna1xP0HlaMVYEbAv0Pck,3318
243
243
  prefect/runtime/__init__.py,sha256=iYmfK1HmXiXXCQK77wDloOqZmY7SFF5iyr37jRzuf-c,406
@@ -264,7 +264,7 @@ prefect/utilities/dockerutils.py,sha256=O5lIgCej5KGRYU2TC1NzNuIK595uOIWJilhZXYEV
264
264
  prefect/utilities/engine.py,sha256=TKiYqpfgt4zopuI8yvh2e-V9GgLcRrh3TpKRhvLuHdw,25669
265
265
  prefect/utilities/filesystem.py,sha256=M_TeZ1MftjBf7hDLWk-Iphir369TpJ1binMsBKiO9YE,4449
266
266
  prefect/utilities/hashing.py,sha256=EOwZLmoIZImuSTxAvVqInabxJ-4RpEfYeg9e2EDQF8o,1752
267
- prefect/utilities/importtools.py,sha256=zgBL4ba-H-g8bYZWm8s-yHKNlqIDqakNMs6Q7o7GdtA,15540
267
+ prefect/utilities/importtools.py,sha256=MxGyPxfKjn6WtXVY9t8xRfDqK5MPUa4NBZqvyqIvp-4,15535
268
268
  prefect/utilities/math.py,sha256=wLwcKVidpNeWQi1TUIWWLHGjlz9UgboX9FUGhx_CQzo,2821
269
269
  prefect/utilities/names.py,sha256=x-stHcF7_tebJPvB1dz-5FvdXJXNBTg2kFZXSnIBBmk,1657
270
270
  prefect/utilities/processutils.py,sha256=yo_GO48pZzgn4A0IK5irTAoqyUCYvWKDSqHXCrtP8c4,14547
@@ -285,8 +285,8 @@ prefect/workers/block.py,sha256=5bdCuqT-4I-et_8ZLG2y1AODzYiCQwFiivhdt5NMEog,7635
285
285
  prefect/workers/process.py,sha256=pPtCdA7fKQ4OsvoitT-cayZeh5HgLX4xBUYlb2Zad-Q,9475
286
286
  prefect/workers/server.py,sha256=WVZJxR8nTMzK0ov0BD0xw5OyQpT26AxlXbsGQ1OrxeQ,1551
287
287
  prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
288
- prefect_client-2.19.5.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
289
- prefect_client-2.19.5.dist-info/METADATA,sha256=DVM0ZmLi7wy762ToB9BMNl_D3bbM8WfF_FW8Xmi_DlI,7401
290
- prefect_client-2.19.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
291
- prefect_client-2.19.5.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
292
- prefect_client-2.19.5.dist-info/RECORD,,
288
+ prefect_client-2.19.7.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
289
+ prefect_client-2.19.7.dist-info/METADATA,sha256=_HPhCI5qmdT7oYPeZcMecJkpOEZlxtxKJehPR4oc11U,7402
290
+ prefect_client-2.19.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
291
+ prefect_client-2.19.7.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
292
+ prefect_client-2.19.7.dist-info/RECORD,,