prefect-client 3.0.0rc13__py3-none-any.whl → 3.0.0rc15__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.
- prefect/_internal/compatibility/deprecated.py +0 -53
- prefect/blocks/core.py +132 -4
- prefect/blocks/notifications.py +26 -3
- prefect/client/base.py +30 -24
- prefect/client/orchestration.py +121 -47
- prefect/client/utilities.py +4 -4
- prefect/concurrency/asyncio.py +48 -7
- prefect/concurrency/context.py +24 -0
- prefect/concurrency/services.py +24 -8
- prefect/concurrency/sync.py +30 -3
- prefect/context.py +85 -24
- prefect/events/clients.py +93 -60
- prefect/events/utilities.py +0 -2
- prefect/events/worker.py +9 -2
- prefect/flow_engine.py +6 -3
- prefect/flows.py +176 -12
- prefect/futures.py +84 -7
- prefect/profiles.toml +16 -2
- prefect/runner/runner.py +6 -1
- prefect/runner/storage.py +4 -0
- prefect/settings.py +108 -14
- prefect/task_engine.py +901 -285
- prefect/task_runs.py +24 -1
- prefect/task_worker.py +7 -1
- prefect/tasks.py +9 -5
- prefect/utilities/asyncutils.py +0 -6
- prefect/utilities/callables.py +5 -3
- prefect/utilities/engine.py +3 -0
- prefect/utilities/importtools.py +138 -58
- prefect/utilities/schema_tools/validation.py +30 -0
- prefect/utilities/services.py +32 -0
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/METADATA +39 -39
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/RECORD +36 -35
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/WHEEL +1 -1
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc13.dist-info → prefect_client-3.0.0rc15.dist-info}/top_level.txt +0 -0
prefect/task_runs.py
CHANGED
@@ -2,7 +2,7 @@ import asyncio
|
|
2
2
|
import atexit
|
3
3
|
import threading
|
4
4
|
import uuid
|
5
|
-
from typing import Dict, Optional
|
5
|
+
from typing import Callable, Dict, Optional
|
6
6
|
|
7
7
|
import anyio
|
8
8
|
from cachetools import TTLCache
|
@@ -74,6 +74,7 @@ class TaskRunWaiter:
|
|
74
74
|
maxsize=10000, ttl=600
|
75
75
|
)
|
76
76
|
self._completion_events: Dict[uuid.UUID, asyncio.Event] = {}
|
77
|
+
self._completion_callbacks: Dict[uuid.UUID, Callable] = {}
|
77
78
|
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
78
79
|
self._observed_completed_task_runs_lock = threading.Lock()
|
79
80
|
self._completion_events_lock = threading.Lock()
|
@@ -135,6 +136,8 @@ class TaskRunWaiter:
|
|
135
136
|
# so the waiter can wake up the waiting coroutine
|
136
137
|
if task_run_id in self._completion_events:
|
137
138
|
self._completion_events[task_run_id].set()
|
139
|
+
if task_run_id in self._completion_callbacks:
|
140
|
+
self._completion_callbacks[task_run_id]()
|
138
141
|
except Exception as exc:
|
139
142
|
self.logger.error(f"Error processing event: {exc}")
|
140
143
|
|
@@ -195,6 +198,26 @@ class TaskRunWaiter:
|
|
195
198
|
# Remove the event from the cache after it has been waited on
|
196
199
|
instance._completion_events.pop(task_run_id, None)
|
197
200
|
|
201
|
+
@classmethod
|
202
|
+
def add_done_callback(cls, task_run_id: uuid.UUID, callback):
|
203
|
+
"""
|
204
|
+
Add a callback to be called when a task run finishes.
|
205
|
+
|
206
|
+
Args:
|
207
|
+
task_run_id: The ID of the task run to wait for.
|
208
|
+
callback: The callback to call when the task run finishes.
|
209
|
+
"""
|
210
|
+
instance = cls.instance()
|
211
|
+
with instance._observed_completed_task_runs_lock:
|
212
|
+
if task_run_id in instance._observed_completed_task_runs:
|
213
|
+
callback()
|
214
|
+
return
|
215
|
+
|
216
|
+
with instance._completion_events_lock:
|
217
|
+
# Cache the event for the task run ID so the consumer can set it
|
218
|
+
# when the event is received
|
219
|
+
instance._completion_callbacks[task_run_id] = callback
|
220
|
+
|
198
221
|
@classmethod
|
199
222
|
def instance(cls):
|
200
223
|
"""
|
prefect/task_worker.py
CHANGED
@@ -38,6 +38,7 @@ from prefect.utilities.annotations import NotSet
|
|
38
38
|
from prefect.utilities.asyncutils import asyncnullcontext, sync_compatible
|
39
39
|
from prefect.utilities.engine import emit_task_run_state_change_event, propose_state
|
40
40
|
from prefect.utilities.processutils import _register_signal
|
41
|
+
from prefect.utilities.services import start_client_metrics_server
|
41
42
|
from prefect.utilities.urls import url_for
|
42
43
|
|
43
44
|
logger = get_logger("task_worker")
|
@@ -158,6 +159,8 @@ class TaskWorker:
|
|
158
159
|
"""
|
159
160
|
_register_signal(signal.SIGTERM, self.handle_sigterm)
|
160
161
|
|
162
|
+
start_client_metrics_server()
|
163
|
+
|
161
164
|
async with asyncnullcontext() if self.started else self:
|
162
165
|
logger.info("Starting task worker...")
|
163
166
|
try:
|
@@ -290,12 +293,15 @@ class TaskWorker:
|
|
290
293
|
await self._client._client.delete(f"/task_runs/{task_run.id}")
|
291
294
|
return
|
292
295
|
|
296
|
+
initial_state = task_run.state
|
297
|
+
|
293
298
|
if PREFECT_EXPERIMENTAL_ENABLE_CLIENT_SIDE_TASK_ORCHESTRATION:
|
294
299
|
new_state = Pending()
|
295
300
|
new_state.state_details.deferred = True
|
296
301
|
new_state.state_details.task_run_id = task_run.id
|
297
302
|
new_state.state_details.flow_run_id = task_run.flow_run_id
|
298
303
|
state = new_state
|
304
|
+
task_run.state = state
|
299
305
|
else:
|
300
306
|
try:
|
301
307
|
new_state = Pending()
|
@@ -327,7 +333,7 @@ class TaskWorker:
|
|
327
333
|
|
328
334
|
emit_task_run_state_change_event(
|
329
335
|
task_run=task_run,
|
330
|
-
initial_state=
|
336
|
+
initial_state=initial_state,
|
331
337
|
validated_state=state,
|
332
338
|
)
|
333
339
|
|
prefect/tasks.py
CHANGED
@@ -33,9 +33,6 @@ from uuid import UUID, uuid4
|
|
33
33
|
from typing_extensions import Literal, ParamSpec
|
34
34
|
|
35
35
|
import prefect.states
|
36
|
-
from prefect._internal.compatibility.deprecated import (
|
37
|
-
deprecated_async_method,
|
38
|
-
)
|
39
36
|
from prefect.cache_policies import DEFAULT, NONE, CachePolicy
|
40
37
|
from prefect.client.orchestration import get_client
|
41
38
|
from prefect.client.schemas import TaskRun
|
@@ -1038,7 +1035,6 @@ class Task(Generic[P, R]):
|
|
1038
1035
|
) -> State[T]:
|
1039
1036
|
...
|
1040
1037
|
|
1041
|
-
@deprecated_async_method
|
1042
1038
|
def submit(
|
1043
1039
|
self,
|
1044
1040
|
*args: Any,
|
@@ -1203,7 +1199,6 @@ class Task(Generic[P, R]):
|
|
1203
1199
|
) -> PrefectFutureList[State[T]]:
|
1204
1200
|
...
|
1205
1201
|
|
1206
|
-
@deprecated_async_method
|
1207
1202
|
def map(
|
1208
1203
|
self,
|
1209
1204
|
*args: Any,
|
@@ -1455,6 +1450,15 @@ class Task(Generic[P, R]):
|
|
1455
1450
|
)
|
1456
1451
|
) # type: ignore
|
1457
1452
|
|
1453
|
+
from prefect.utilities.engine import emit_task_run_state_change_event
|
1454
|
+
|
1455
|
+
# emit a `SCHEDULED` event for the task run
|
1456
|
+
emit_task_run_state_change_event(
|
1457
|
+
task_run=task_run,
|
1458
|
+
initial_state=None,
|
1459
|
+
validated_state=task_run.state,
|
1460
|
+
)
|
1461
|
+
|
1458
1462
|
if task_run_url := url_for(task_run):
|
1459
1463
|
logger.info(
|
1460
1464
|
f"Created task run {task_run.name!r}. View it in the UI at {task_run_url!r}"
|
prefect/utilities/asyncutils.py
CHANGED
@@ -30,7 +30,6 @@ import anyio.abc
|
|
30
30
|
import anyio.from_thread
|
31
31
|
import anyio.to_thread
|
32
32
|
import sniffio
|
33
|
-
import wrapt
|
34
33
|
from typing_extensions import Literal, ParamSpec, TypeGuard
|
35
34
|
|
36
35
|
from prefect._internal.concurrency.api import _cast_to_call, from_sync
|
@@ -210,11 +209,6 @@ def run_coro_as_sync(
|
|
210
209
|
Returns:
|
211
210
|
The result of the coroutine if wait_for_result is True, otherwise None.
|
212
211
|
"""
|
213
|
-
if not asyncio.iscoroutine(coroutine):
|
214
|
-
if isinstance(coroutine, wrapt.ObjectProxy):
|
215
|
-
return coroutine.__wrapped__
|
216
|
-
else:
|
217
|
-
raise TypeError("`coroutine` must be a coroutine object")
|
218
212
|
|
219
213
|
async def coroutine_wrapper() -> Union[R, None]:
|
220
214
|
"""
|
prefect/utilities/callables.py
CHANGED
@@ -364,17 +364,19 @@ def parameter_schema_from_entrypoint(entrypoint: str) -> ParameterSchema:
|
|
364
364
|
Returns:
|
365
365
|
ParameterSchema: The parameter schema for the function.
|
366
366
|
"""
|
367
|
+
filepath = None
|
367
368
|
if ":" in entrypoint:
|
368
369
|
# split by the last colon once to handle Windows paths with drive letters i.e C:\path\to\file.py:do_stuff
|
369
370
|
path, func_name = entrypoint.rsplit(":", maxsplit=1)
|
370
371
|
source_code = Path(path).read_text()
|
372
|
+
filepath = path
|
371
373
|
else:
|
372
374
|
path, func_name = entrypoint.rsplit(".", maxsplit=1)
|
373
375
|
spec = importlib.util.find_spec(path)
|
374
376
|
if not spec or not spec.origin:
|
375
377
|
raise ValueError(f"Could not find module {path!r}")
|
376
378
|
source_code = Path(spec.origin).read_text()
|
377
|
-
signature = _generate_signature_from_source(source_code, func_name)
|
379
|
+
signature = _generate_signature_from_source(source_code, func_name, filepath)
|
378
380
|
docstring = _get_docstring_from_source(source_code, func_name)
|
379
381
|
return generate_parameter_schema(signature, parameter_docstrings(docstring))
|
380
382
|
|
@@ -444,7 +446,7 @@ def raise_for_reserved_arguments(fn: Callable, reserved_arguments: Iterable[str]
|
|
444
446
|
|
445
447
|
|
446
448
|
def _generate_signature_from_source(
|
447
|
-
source_code: str, func_name: str
|
449
|
+
source_code: str, func_name: str, filepath: Optional[str] = None
|
448
450
|
) -> inspect.Signature:
|
449
451
|
"""
|
450
452
|
Extract the signature of a function from its source code.
|
@@ -460,7 +462,7 @@ def _generate_signature_from_source(
|
|
460
462
|
"""
|
461
463
|
# Load the namespace from the source code. Missing imports and exceptions while
|
462
464
|
# loading local class definitions are ignored.
|
463
|
-
namespace = safe_load_namespace(source_code)
|
465
|
+
namespace = safe_load_namespace(source_code, filepath=filepath)
|
464
466
|
# Parse the source code into an AST
|
465
467
|
parsed_code = ast.parse(source_code)
|
466
468
|
|
prefect/utilities/engine.py
CHANGED
prefect/utilities/importtools.py
CHANGED
@@ -361,79 +361,159 @@ class AliasedModuleLoader(Loader):
|
|
361
361
|
sys.modules[self.alias] = root_module
|
362
362
|
|
363
363
|
|
364
|
-
def safe_load_namespace(
|
364
|
+
def safe_load_namespace(
|
365
|
+
source_code: str, filepath: Optional[str] = None
|
366
|
+
) -> Dict[str, Any]:
|
365
367
|
"""
|
366
|
-
Safely load a namespace from source code.
|
368
|
+
Safely load a namespace from source code, optionally handling relative imports.
|
367
369
|
|
368
|
-
|
369
|
-
|
370
|
-
|
370
|
+
If a `filepath` is provided, `sys.path` is modified to support relative imports.
|
371
|
+
Changes to `sys.path` are reverted after completion, but this function is not thread safe
|
372
|
+
and use of it in threaded contexts may result in undesirable behavior.
|
371
373
|
|
372
374
|
Args:
|
373
375
|
source_code: The source code to load
|
376
|
+
filepath: Optional file path of the source code. If provided, enables relative imports.
|
374
377
|
|
375
378
|
Returns:
|
376
|
-
The namespace loaded from the source code.
|
377
|
-
code.
|
379
|
+
The namespace loaded from the source code.
|
378
380
|
"""
|
379
381
|
parsed_code = ast.parse(source_code)
|
380
382
|
|
381
|
-
namespace = {"__name__": "prefect_safe_namespace_loader"}
|
383
|
+
namespace: Dict[str, Any] = {"__name__": "prefect_safe_namespace_loader"}
|
382
384
|
|
383
|
-
# Remove the body of the if __name__ == "__main__": block
|
384
|
-
|
385
|
-
new_body = []
|
386
|
-
for node in parsed_code.body:
|
387
|
-
if _is_main_block(node):
|
388
|
-
continue
|
389
|
-
new_body.append(node)
|
385
|
+
# Remove the body of the if __name__ == "__main__": block
|
386
|
+
new_body = [node for node in parsed_code.body if not _is_main_block(node)]
|
390
387
|
parsed_code.body = new_body
|
391
388
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
389
|
+
temp_module = None
|
390
|
+
original_sys_path = None
|
391
|
+
|
392
|
+
if filepath:
|
393
|
+
# Setup for relative imports
|
394
|
+
file_dir = os.path.dirname(os.path.abspath(filepath))
|
395
|
+
package_name = os.path.basename(file_dir)
|
396
|
+
parent_dir = os.path.dirname(file_dir)
|
397
|
+
|
398
|
+
# Save original sys.path and modify it
|
399
|
+
original_sys_path = sys.path.copy()
|
400
|
+
sys.path.insert(0, parent_dir)
|
401
|
+
|
402
|
+
# Create a temporary module for import context
|
403
|
+
temp_module = ModuleType(package_name)
|
404
|
+
temp_module.__file__ = filepath
|
405
|
+
temp_module.__package__ = package_name
|
406
|
+
|
407
|
+
# Create a spec for the module
|
408
|
+
temp_module.__spec__ = ModuleSpec(package_name, None)
|
409
|
+
temp_module.__spec__.loader = None
|
410
|
+
temp_module.__spec__.submodule_search_locations = [file_dir]
|
411
|
+
|
412
|
+
try:
|
413
|
+
for node in parsed_code.body:
|
414
|
+
if isinstance(node, ast.Import):
|
410
415
|
for alias in node.names:
|
411
|
-
|
412
|
-
|
416
|
+
module_name = alias.name
|
417
|
+
as_name = alias.asname or module_name
|
413
418
|
try:
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
419
|
+
namespace[as_name] = importlib.import_module(module_name)
|
420
|
+
logger.debug("Successfully imported %s", module_name)
|
421
|
+
except ImportError as e:
|
422
|
+
logger.debug(f"Failed to import {module_name}: {e}")
|
423
|
+
elif isinstance(node, ast.ImportFrom):
|
424
|
+
module_name = node.module or ""
|
425
|
+
if filepath:
|
426
|
+
try:
|
427
|
+
if node.level > 0:
|
428
|
+
# For relative imports, use the parent package to inform the import
|
429
|
+
package_parts = temp_module.__package__.split(".")
|
430
|
+
if len(package_parts) < node.level:
|
431
|
+
raise ImportError(
|
432
|
+
"Attempted relative import beyond top-level package"
|
433
|
+
)
|
434
|
+
parent_package = ".".join(
|
435
|
+
package_parts[: (1 - node.level)]
|
436
|
+
if node.level > 1
|
437
|
+
else package_parts
|
438
|
+
)
|
439
|
+
module = importlib.import_module(
|
440
|
+
f".{module_name}" if module_name else "",
|
441
|
+
package=parent_package,
|
442
|
+
)
|
443
|
+
else:
|
444
|
+
# Absolute imports are handled as normal
|
445
|
+
module = importlib.import_module(module_name)
|
446
|
+
|
447
|
+
for alias in node.names:
|
448
|
+
name = alias.name
|
449
|
+
asname = alias.asname or name
|
450
|
+
if name == "*":
|
451
|
+
# Handle 'from module import *'
|
452
|
+
module_dict = {
|
453
|
+
k: v
|
454
|
+
for k, v in module.__dict__.items()
|
455
|
+
if not k.startswith("_")
|
456
|
+
}
|
457
|
+
namespace.update(module_dict)
|
458
|
+
else:
|
459
|
+
try:
|
460
|
+
attribute = getattr(module, name)
|
461
|
+
namespace[asname] = attribute
|
462
|
+
except AttributeError as e:
|
463
|
+
logger.debug(
|
464
|
+
"Failed to retrieve %s from %s: %s",
|
465
|
+
name,
|
466
|
+
module_name,
|
467
|
+
e,
|
468
|
+
)
|
469
|
+
except ImportError as e:
|
470
|
+
logger.debug("Failed to import from %s: %s", module_name, e)
|
471
|
+
else:
|
472
|
+
# Handle as absolute import when no filepath is provided
|
473
|
+
try:
|
474
|
+
module = importlib.import_module(module_name)
|
475
|
+
for alias in node.names:
|
476
|
+
name = alias.name
|
477
|
+
asname = alias.asname or name
|
478
|
+
if name == "*":
|
479
|
+
# Handle 'from module import *'
|
480
|
+
module_dict = {
|
481
|
+
k: v
|
482
|
+
for k, v in module.__dict__.items()
|
483
|
+
if not k.startswith("_")
|
484
|
+
}
|
485
|
+
namespace.update(module_dict)
|
486
|
+
else:
|
487
|
+
try:
|
488
|
+
attribute = getattr(module, name)
|
489
|
+
namespace[asname] = attribute
|
490
|
+
except AttributeError as e:
|
491
|
+
logger.debug(
|
492
|
+
"Failed to retrieve %s from %s: %s",
|
493
|
+
name,
|
494
|
+
module_name,
|
495
|
+
e,
|
496
|
+
)
|
497
|
+
except ImportError as e:
|
498
|
+
logger.debug("Failed to import from %s: %s", module_name, e)
|
499
|
+
# Handle local definitions
|
500
|
+
for node in parsed_code.body:
|
501
|
+
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.Assign)):
|
502
|
+
try:
|
503
|
+
code = compile(
|
504
|
+
ast.Module(body=[node], type_ignores=[]),
|
505
|
+
filename="<ast>",
|
506
|
+
mode="exec",
|
507
|
+
)
|
508
|
+
exec(code, namespace)
|
509
|
+
except Exception as e:
|
510
|
+
logger.debug("Failed to compile: %s", e)
|
511
|
+
|
512
|
+
finally:
|
513
|
+
# Restore original sys.path if it was modified
|
514
|
+
if original_sys_path:
|
515
|
+
sys.path[:] = original_sys_path
|
516
|
+
|
437
517
|
return namespace
|
438
518
|
|
439
519
|
|
@@ -253,5 +253,35 @@ def preprocess_schema(
|
|
253
253
|
process_properties(
|
254
254
|
definition["properties"], required_fields, allow_none_with_default
|
255
255
|
)
|
256
|
+
# Allow block types to be referenced by their id
|
257
|
+
if "block_type_slug" in definition:
|
258
|
+
schema["definitions"][definition["title"]] = {
|
259
|
+
"oneOf": [
|
260
|
+
definition,
|
261
|
+
{
|
262
|
+
"type": "object",
|
263
|
+
"properties": {
|
264
|
+
"$ref": {
|
265
|
+
"oneOf": [
|
266
|
+
{
|
267
|
+
"type": "string",
|
268
|
+
"format": "uuid",
|
269
|
+
},
|
270
|
+
{
|
271
|
+
"type": "object",
|
272
|
+
"additionalProperties": {
|
273
|
+
"type": "string",
|
274
|
+
},
|
275
|
+
"minProperties": 1,
|
276
|
+
},
|
277
|
+
]
|
278
|
+
}
|
279
|
+
},
|
280
|
+
"required": [
|
281
|
+
"$ref",
|
282
|
+
],
|
283
|
+
},
|
284
|
+
]
|
285
|
+
}
|
256
286
|
|
257
287
|
return schema
|
prefect/utilities/services.py
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
import sys
|
2
|
+
import threading
|
2
3
|
from collections import deque
|
3
4
|
from traceback import format_exception
|
4
5
|
from types import TracebackType
|
5
6
|
from typing import Callable, Coroutine, Deque, Optional, Tuple
|
7
|
+
from wsgiref.simple_server import WSGIServer
|
6
8
|
|
7
9
|
import anyio
|
8
10
|
import httpx
|
9
11
|
|
10
12
|
from prefect.logging.loggers import get_logger
|
13
|
+
from prefect.settings import PREFECT_CLIENT_ENABLE_METRICS, PREFECT_CLIENT_METRICS_PORT
|
11
14
|
from prefect.utilities.collections import distinct
|
12
15
|
from prefect.utilities.math import clamped_poisson_interval
|
13
16
|
|
@@ -150,3 +153,32 @@ async def critical_service_loop(
|
|
150
153
|
sleep = interval * 2**backoff_count
|
151
154
|
|
152
155
|
await anyio.sleep(sleep)
|
156
|
+
|
157
|
+
|
158
|
+
_metrics_server: Optional[Tuple[WSGIServer, threading.Thread]] = None
|
159
|
+
|
160
|
+
|
161
|
+
def start_client_metrics_server():
|
162
|
+
"""Start the process-wide Prometheus metrics server for client metrics (if enabled
|
163
|
+
with `PREFECT_CLIENT_ENABLE_METRICS`) on the port `PREFECT_CLIENT_METRICS_PORT`."""
|
164
|
+
if not PREFECT_CLIENT_ENABLE_METRICS:
|
165
|
+
return
|
166
|
+
|
167
|
+
global _metrics_server
|
168
|
+
if _metrics_server:
|
169
|
+
return
|
170
|
+
|
171
|
+
from prometheus_client import start_http_server
|
172
|
+
|
173
|
+
_metrics_server = start_http_server(port=PREFECT_CLIENT_METRICS_PORT.value())
|
174
|
+
|
175
|
+
|
176
|
+
def stop_client_metrics_server():
|
177
|
+
"""Start the process-wide Prometheus metrics server for client metrics, if it has
|
178
|
+
previously been started"""
|
179
|
+
global _metrics_server
|
180
|
+
if _metrics_server:
|
181
|
+
server, thread = _metrics_server
|
182
|
+
server.shutdown()
|
183
|
+
thread.join()
|
184
|
+
_metrics_server = None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: prefect-client
|
3
|
-
Version: 3.0.
|
3
|
+
Version: 3.0.0rc15
|
4
4
|
Summary: Workflow orchestration and management.
|
5
5
|
Home-page: https://www.prefect.io
|
6
6
|
Author: Prefect Technologies, Inc.
|
@@ -24,46 +24,46 @@ Classifier: Topic :: Software Development :: Libraries
|
|
24
24
|
Requires-Python: >=3.9
|
25
25
|
Description-Content-Type: text/markdown
|
26
26
|
License-File: LICENSE
|
27
|
-
Requires-Dist: anyio
|
28
|
-
Requires-Dist: asgi-lifespan
|
29
|
-
Requires-Dist: cachetools
|
30
|
-
Requires-Dist: cloudpickle
|
31
|
-
Requires-Dist: coolname
|
32
|
-
Requires-Dist: croniter
|
33
|
-
Requires-Dist: exceptiongroup
|
34
|
-
Requires-Dist: fastapi
|
35
|
-
Requires-Dist: fsspec
|
36
|
-
Requires-Dist: graphviz
|
37
|
-
Requires-Dist: griffe
|
38
|
-
Requires-Dist: httpcore
|
39
|
-
Requires-Dist: httpx[http2]
|
40
|
-
Requires-Dist: importlib-resources
|
41
|
-
Requires-Dist: jsonpatch
|
42
|
-
Requires-Dist: jsonschema
|
43
|
-
Requires-Dist: orjson
|
44
|
-
Requires-Dist: packaging
|
45
|
-
Requires-Dist: pathspec
|
46
|
-
Requires-Dist: pendulum
|
47
|
-
Requires-Dist:
|
48
|
-
Requires-Dist: pydantic
|
49
|
-
Requires-Dist: pydantic-
|
27
|
+
Requires-Dist: anyio<5.0.0,>=4.4.0
|
28
|
+
Requires-Dist: asgi-lifespan<3.0,>=1.0
|
29
|
+
Requires-Dist: cachetools<6.0,>=5.3
|
30
|
+
Requires-Dist: cloudpickle<4.0,>=2.0
|
31
|
+
Requires-Dist: coolname<3.0.0,>=1.0.4
|
32
|
+
Requires-Dist: croniter<4.0.0,>=1.0.12
|
33
|
+
Requires-Dist: exceptiongroup>=1.0.0
|
34
|
+
Requires-Dist: fastapi<1.0.0,>=0.111.0
|
35
|
+
Requires-Dist: fsspec>=2022.5.0
|
36
|
+
Requires-Dist: graphviz>=0.20.1
|
37
|
+
Requires-Dist: griffe<0.48.0,>=0.20.0
|
38
|
+
Requires-Dist: httpcore<2.0.0,>=1.0.5
|
39
|
+
Requires-Dist: httpx[http2]!=0.23.2,>=0.23
|
40
|
+
Requires-Dist: importlib-resources<6.2.0,>=6.1.3
|
41
|
+
Requires-Dist: jsonpatch<2.0,>=1.32
|
42
|
+
Requires-Dist: jsonschema<5.0.0,>=4.0.0
|
43
|
+
Requires-Dist: orjson<4.0,>=3.7
|
44
|
+
Requires-Dist: packaging<24.3,>=21.3
|
45
|
+
Requires-Dist: pathspec>=0.8.0
|
46
|
+
Requires-Dist: pendulum<4,>=3.0.0
|
47
|
+
Requires-Dist: prometheus-client>=0.20.0
|
48
|
+
Requires-Dist: pydantic<3.0.0,>=2.7
|
49
|
+
Requires-Dist: pydantic-core<3.0.0,>=2.12.0
|
50
|
+
Requires-Dist: pydantic-extra-types<3.0.0,>=2.8.2
|
50
51
|
Requires-Dist: pydantic-settings
|
51
|
-
Requires-Dist: python-dateutil
|
52
|
-
Requires-Dist: python-slugify
|
53
|
-
Requires-Dist: pyyaml
|
54
|
-
Requires-Dist: rfc3339-validator
|
55
|
-
Requires-Dist: rich
|
56
|
-
Requires-Dist: ruamel.yaml
|
57
|
-
Requires-Dist: sniffio
|
58
|
-
Requires-Dist: toml
|
59
|
-
Requires-Dist: typing-extensions
|
60
|
-
Requires-Dist: ujson
|
61
|
-
Requires-Dist: uvicorn
|
62
|
-
Requires-Dist: websockets
|
63
|
-
Requires-Dist:
|
64
|
-
Requires-Dist: importlib-metadata >=4.4 ; python_version < "3.10"
|
52
|
+
Requires-Dist: python-dateutil<3.0.0,>=2.8.2
|
53
|
+
Requires-Dist: python-slugify<9.0,>=5.0
|
54
|
+
Requires-Dist: pyyaml<7.0.0,>=5.4.1
|
55
|
+
Requires-Dist: rfc3339-validator<0.2.0,>=0.1.4
|
56
|
+
Requires-Dist: rich<14.0,>=11.0
|
57
|
+
Requires-Dist: ruamel.yaml>=0.17.0
|
58
|
+
Requires-Dist: sniffio<2.0.0,>=1.3.0
|
59
|
+
Requires-Dist: toml>=0.10.0
|
60
|
+
Requires-Dist: typing-extensions<5.0.0,>=4.5.0
|
61
|
+
Requires-Dist: ujson<6.0.0,>=5.8.0
|
62
|
+
Requires-Dist: uvicorn!=0.29.0,>=0.14.0
|
63
|
+
Requires-Dist: websockets<13.0,>=10.4
|
64
|
+
Requires-Dist: importlib-metadata>=4.4; python_version < "3.10"
|
65
65
|
Provides-Extra: notifications
|
66
|
-
Requires-Dist: apprise
|
66
|
+
Requires-Dist: apprise<2.0.0,>=1.1.0; extra == "notifications"
|
67
67
|
|
68
68
|
<p align="center"><img src="https://github.com/PrefectHQ/prefect/assets/3407835/c654cbc6-63e8-4ada-a92a-efd2f8f24b85" width=1000></p>
|
69
69
|
|