prefect-client 2.19.2__py3-none-any.whl → 3.0.0rc1__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/__init__.py +8 -56
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/concurrency/api.py +0 -34
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/schemas/bases.py +44 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +60 -158
- prefect/artifacts.py +161 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +268 -148
- prefect/blocks/fields.py +2 -57
- prefect/blocks/kubernetes.py +8 -12
- prefect/blocks/notifications.py +40 -20
- prefect/blocks/system.py +22 -11
- prefect/blocks/webhook.py +2 -9
- prefect/client/base.py +4 -4
- prefect/client/cloud.py +8 -13
- prefect/client/orchestration.py +347 -341
- prefect/client/schemas/actions.py +92 -86
- prefect/client/schemas/filters.py +20 -40
- prefect/client/schemas/objects.py +151 -145
- prefect/client/schemas/responses.py +16 -24
- prefect/client/schemas/schedules.py +47 -35
- prefect/client/subscriptions.py +2 -2
- prefect/client/utilities.py +5 -2
- prefect/concurrency/asyncio.py +3 -1
- prefect/concurrency/events.py +1 -1
- prefect/concurrency/services.py +6 -3
- prefect/context.py +195 -27
- prefect/deployments/__init__.py +5 -6
- prefect/deployments/base.py +7 -5
- prefect/deployments/flow_runs.py +185 -0
- prefect/deployments/runner.py +50 -45
- prefect/deployments/schedules.py +28 -23
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +1 -0
- prefect/deployments/steps/pull.py +7 -21
- prefect/engine.py +12 -2422
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +19 -6
- prefect/events/clients.py +14 -37
- prefect/events/filters.py +14 -18
- prefect/events/related.py +2 -2
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +34 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +2 -3
- prefect/events/worker.py +2 -3
- prefect/filesystems.py +6 -517
- prefect/{new_flow_engine.py → flow_engine.py} +313 -72
- prefect/flow_runs.py +377 -5
- prefect/flows.py +307 -166
- prefect/futures.py +186 -345
- prefect/infrastructure/__init__.py +0 -27
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +11 -6
- prefect/infrastructure/provisioners/container_instance.py +11 -7
- prefect/infrastructure/provisioners/ecs.py +6 -4
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +5 -7
- prefect/logging/formatters.py +0 -2
- prefect/logging/handlers.py +3 -11
- prefect/logging/loggers.py +2 -2
- prefect/manifests.py +2 -1
- prefect/records/__init__.py +1 -0
- prefect/records/result_store.py +42 -0
- prefect/records/store.py +9 -0
- prefect/results.py +43 -39
- prefect/runner/runner.py +19 -15
- prefect/runner/server.py +6 -10
- prefect/runner/storage.py +3 -8
- prefect/runner/submit.py +2 -2
- prefect/runner/utils.py +2 -2
- prefect/serializers.py +24 -35
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +70 -133
- prefect/states.py +17 -47
- prefect/task_engine.py +697 -58
- prefect/task_runners.py +269 -301
- prefect/task_server.py +53 -34
- prefect/tasks.py +327 -337
- prefect/transactions.py +220 -0
- prefect/types/__init__.py +61 -82
- prefect/utilities/asyncutils.py +195 -136
- prefect/utilities/callables.py +311 -43
- prefect/utilities/collections.py +23 -38
- prefect/utilities/dispatch.py +11 -3
- prefect/utilities/dockerutils.py +4 -0
- prefect/utilities/engine.py +140 -20
- prefect/utilities/importtools.py +97 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +5 -1
- prefect/utilities/templating.py +12 -2
- prefect/variables.py +78 -61
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +15 -17
- prefect/workers/process.py +3 -8
- prefect/workers/server.py +2 -2
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
- prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/__init__.py +0 -0
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/__init__.py +0 -0
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/agent.py +0 -698
- prefect/deployments/deployments.py +0 -1042
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/base.py +0 -323
- prefect/infrastructure/container.py +0 -818
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/workers/block.py +0 -218
- prefect_client-2.19.2.dist-info/RECORD +0 -292
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
prefect/utilities/callables.py
CHANGED
@@ -2,36 +2,39 @@
|
|
2
2
|
Utilities for working with Python callables.
|
3
3
|
"""
|
4
4
|
|
5
|
+
import ast
|
6
|
+
import importlib.util
|
5
7
|
import inspect
|
8
|
+
import warnings
|
6
9
|
from functools import partial
|
10
|
+
from pathlib import Path
|
7
11
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
8
12
|
|
9
13
|
import cloudpickle
|
10
|
-
|
11
|
-
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
12
|
-
from prefect._internal.pydantic.v1_schema import has_v1_type_as_param
|
13
|
-
|
14
|
-
if HAS_PYDANTIC_V2:
|
15
|
-
import pydantic.v1 as pydantic
|
16
|
-
|
17
|
-
from prefect._internal.pydantic.v2_schema import (
|
18
|
-
create_v2_schema,
|
19
|
-
process_v2_params,
|
20
|
-
)
|
21
|
-
else:
|
22
|
-
import pydantic
|
23
|
-
|
14
|
+
import pydantic
|
24
15
|
from griffe.dataclasses import Docstring
|
25
16
|
from griffe.docstrings.dataclasses import DocstringSectionKind
|
26
17
|
from griffe.docstrings.parsers import Parser, parse
|
27
18
|
from typing_extensions import Literal
|
28
19
|
|
20
|
+
from prefect._internal.pydantic.v1_schema import has_v1_type_as_param
|
21
|
+
from prefect._internal.pydantic.v2_schema import (
|
22
|
+
create_v2_schema,
|
23
|
+
process_v2_params,
|
24
|
+
)
|
29
25
|
from prefect.exceptions import (
|
26
|
+
MappingLengthMismatch,
|
27
|
+
MappingMissingIterable,
|
30
28
|
ParameterBindError,
|
31
29
|
ReservedArgumentError,
|
32
30
|
SignatureMismatchError,
|
33
31
|
)
|
34
|
-
from prefect.logging.loggers import disable_logger
|
32
|
+
from prefect.logging.loggers import disable_logger, get_logger
|
33
|
+
from prefect.utilities.annotations import allow_failure, quote, unmapped
|
34
|
+
from prefect.utilities.collections import isiterable
|
35
|
+
from prefect.utilities.importtools import safe_load_namespace
|
36
|
+
|
37
|
+
logger = get_logger(__name__)
|
35
38
|
|
36
39
|
|
37
40
|
def get_call_parameters(
|
@@ -224,15 +227,14 @@ class ParameterSchema(pydantic.BaseModel):
|
|
224
227
|
title: Literal["Parameters"] = "Parameters"
|
225
228
|
type: Literal["object"] = "object"
|
226
229
|
properties: Dict[str, Any] = pydantic.Field(default_factory=dict)
|
227
|
-
required: List[str] =
|
228
|
-
definitions:
|
230
|
+
required: List[str] = pydantic.Field(default_factory=list)
|
231
|
+
definitions: Dict[str, Any] = pydantic.Field(default_factory=dict)
|
229
232
|
|
230
|
-
def
|
231
|
-
""
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
return super().dict(*args, **kwargs)
|
233
|
+
def model_dump_for_openapi(self) -> Dict[str, Any]:
|
234
|
+
result = self.model_dump(mode="python", exclude_none=True)
|
235
|
+
if "required" in result and not result["required"]:
|
236
|
+
del result["required"]
|
237
|
+
return result
|
236
238
|
|
237
239
|
|
238
240
|
def parameter_docstrings(docstring: Optional[str]) -> Dict[str, str]:
|
@@ -280,21 +282,31 @@ def process_v1_params(
|
|
280
282
|
name = param.name
|
281
283
|
|
282
284
|
type_ = Any if param.annotation is inspect._empty else param.annotation
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
285
|
+
|
286
|
+
with warnings.catch_warnings():
|
287
|
+
warnings.filterwarnings(
|
288
|
+
"ignore", category=pydantic.warnings.PydanticDeprecatedSince20
|
289
|
+
)
|
290
|
+
field = pydantic.Field(
|
291
|
+
default=... if param.default is param.empty else param.default,
|
292
|
+
title=param.name,
|
293
|
+
description=docstrings.get(param.name, None),
|
294
|
+
alias=aliases.get(name),
|
295
|
+
position=position,
|
296
|
+
)
|
290
297
|
return name, type_, field
|
291
298
|
|
292
299
|
|
293
300
|
def create_v1_schema(name_: str, model_cfg, **model_fields):
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
301
|
+
with warnings.catch_warnings():
|
302
|
+
warnings.filterwarnings(
|
303
|
+
"ignore", category=pydantic.warnings.PydanticDeprecatedSince20
|
304
|
+
)
|
305
|
+
|
306
|
+
model: "pydantic.BaseModel" = pydantic.create_model(
|
307
|
+
name_, __config__=model_cfg, **model_fields
|
308
|
+
)
|
309
|
+
return model.schema(by_alias=True)
|
298
310
|
|
299
311
|
|
300
312
|
def parameter_schema(fn: Callable) -> ParameterSchema:
|
@@ -318,20 +330,77 @@ def parameter_schema(fn: Callable) -> ParameterSchema:
|
|
318
330
|
# `eval_str` is not available in Python < 3.10
|
319
331
|
signature = inspect.signature(fn)
|
320
332
|
|
321
|
-
model_fields = {}
|
322
|
-
aliases = {}
|
323
333
|
docstrings = parameter_docstrings(inspect.getdoc(fn))
|
324
334
|
|
325
|
-
|
326
|
-
|
335
|
+
return generate_parameter_schema(signature, docstrings)
|
336
|
+
|
337
|
+
|
338
|
+
def parameter_schema_from_entrypoint(entrypoint: str) -> ParameterSchema:
|
339
|
+
"""
|
340
|
+
Generate a parameter schema from an entrypoint string.
|
341
|
+
|
342
|
+
Will load the source code of the function and extract the signature and docstring
|
343
|
+
to generate the schema.
|
327
344
|
|
328
|
-
|
345
|
+
Useful for generating a schema for a function when instantiating the function may
|
346
|
+
not be possible due to missing imports or other issues.
|
347
|
+
|
348
|
+
Args:
|
349
|
+
entrypoint: A string representing the entrypoint to a function. The string
|
350
|
+
should be in the format of `module.path.to.function:do_stuff`.
|
351
|
+
|
352
|
+
Returns:
|
353
|
+
ParameterSchema: The parameter schema for the function.
|
354
|
+
"""
|
355
|
+
if ":" in entrypoint:
|
356
|
+
# split by the last colon once to handle Windows paths with drive letters i.e C:\path\to\file.py:do_stuff
|
357
|
+
path, func_name = entrypoint.rsplit(":", maxsplit=1)
|
358
|
+
source_code = Path(path).read_text()
|
359
|
+
else:
|
360
|
+
path, func_name = entrypoint.rsplit(".", maxsplit=1)
|
361
|
+
spec = importlib.util.find_spec(path)
|
362
|
+
if not spec or not spec.origin:
|
363
|
+
raise ValueError(f"Could not find module {path!r}")
|
364
|
+
source_code = Path(spec.origin).read_text()
|
365
|
+
signature = _generate_signature_from_source(source_code, func_name)
|
366
|
+
docstring = _get_docstring_from_source(source_code, func_name)
|
367
|
+
return generate_parameter_schema(signature, parameter_docstrings(docstring))
|
368
|
+
|
369
|
+
|
370
|
+
def generate_parameter_schema(
|
371
|
+
signature: inspect.Signature, docstrings: Dict[str, str]
|
372
|
+
) -> ParameterSchema:
|
373
|
+
"""
|
374
|
+
Generate a parameter schema from a function signature and docstrings.
|
375
|
+
|
376
|
+
To get a signature from a function, use `inspect.signature(fn)` or
|
377
|
+
`_generate_signature_from_source(source_code, func_name)`.
|
378
|
+
|
379
|
+
Args:
|
380
|
+
signature: The function signature.
|
381
|
+
docstrings: A dictionary mapping parameter names to docstrings.
|
382
|
+
|
383
|
+
Returns:
|
384
|
+
ParameterSchema: The parameter schema.
|
385
|
+
"""
|
386
|
+
|
387
|
+
model_fields = {}
|
388
|
+
aliases = {}
|
389
|
+
|
390
|
+
if not has_v1_type_as_param(signature):
|
329
391
|
create_schema = create_v2_schema
|
330
392
|
process_params = process_v2_params
|
393
|
+
|
394
|
+
config = pydantic.ConfigDict(arbitrary_types_allowed=True)
|
331
395
|
else:
|
332
396
|
create_schema = create_v1_schema
|
333
397
|
process_params = process_v1_params
|
334
398
|
|
399
|
+
class ModelConfig:
|
400
|
+
arbitrary_types_allowed = True
|
401
|
+
|
402
|
+
config = ModelConfig
|
403
|
+
|
335
404
|
for position, param in enumerate(signature.parameters.values()):
|
336
405
|
name, type_, field = process_params(
|
337
406
|
param, position=position, docstrings=docstrings, aliases=aliases
|
@@ -339,16 +408,14 @@ def parameter_schema(fn: Callable) -> ParameterSchema:
|
|
339
408
|
# Generate a Pydantic model at each step so we can check if this parameter
|
340
409
|
# type supports schema generation
|
341
410
|
try:
|
342
|
-
create_schema(
|
343
|
-
"CheckParameter", model_cfg=ModelConfig, **{name: (type_, field)}
|
344
|
-
)
|
411
|
+
create_schema("CheckParameter", model_cfg=config, **{name: (type_, field)})
|
345
412
|
except (ValueError, TypeError):
|
346
413
|
# This field's type is not valid for schema creation, update it to `Any`
|
347
414
|
type_ = Any
|
348
415
|
model_fields[name] = (type_, field)
|
349
416
|
|
350
417
|
# Generate the final model and schema
|
351
|
-
schema = create_schema("Parameters", model_cfg=
|
418
|
+
schema = create_schema("Parameters", model_cfg=config, **model_fields)
|
352
419
|
return ParameterSchema(**schema)
|
353
420
|
|
354
421
|
|
@@ -362,3 +429,204 @@ def raise_for_reserved_arguments(fn: Callable, reserved_arguments: Iterable[str]
|
|
362
429
|
raise ReservedArgumentError(
|
363
430
|
f"{argument!r} is a reserved argument name and cannot be used."
|
364
431
|
)
|
432
|
+
|
433
|
+
|
434
|
+
def _generate_signature_from_source(
|
435
|
+
source_code: str, func_name: str
|
436
|
+
) -> inspect.Signature:
|
437
|
+
"""
|
438
|
+
Extract the signature of a function from its source code.
|
439
|
+
|
440
|
+
Will ignore missing imports and exceptions while loading local class definitions.
|
441
|
+
|
442
|
+
Args:
|
443
|
+
source_code: The source code where the function named `func_name` is declared.
|
444
|
+
func_name: The name of the function.
|
445
|
+
|
446
|
+
Returns:
|
447
|
+
The signature of the function.
|
448
|
+
"""
|
449
|
+
# Load the namespace from the source code. Missing imports and exceptions while
|
450
|
+
# loading local class definitions are ignored.
|
451
|
+
namespace = safe_load_namespace(source_code)
|
452
|
+
# Parse the source code into an AST
|
453
|
+
parsed_code = ast.parse(source_code)
|
454
|
+
|
455
|
+
func_def = next(
|
456
|
+
(
|
457
|
+
node
|
458
|
+
for node in ast.walk(parsed_code)
|
459
|
+
if isinstance(node, ast.FunctionDef) and node.name == func_name
|
460
|
+
),
|
461
|
+
None,
|
462
|
+
)
|
463
|
+
if func_def is None:
|
464
|
+
raise ValueError(f"Function {func_name} not found in source code")
|
465
|
+
parameters = []
|
466
|
+
|
467
|
+
for arg in func_def.args.args:
|
468
|
+
name = arg.arg
|
469
|
+
annotation = arg.annotation
|
470
|
+
if annotation is not None:
|
471
|
+
try:
|
472
|
+
# Compile and evaluate the annotation
|
473
|
+
ann_code = compile(ast.Expression(annotation), "<string>", "eval")
|
474
|
+
annotation = eval(ann_code, namespace)
|
475
|
+
except Exception as e:
|
476
|
+
# Don't raise an error if the annotation evaluation fails. Set the
|
477
|
+
# annotation to `inspect.Parameter.empty` instead which is equivalent to
|
478
|
+
# not having an annotation.
|
479
|
+
logger.debug("Failed to evaluate annotation for %s: %s", name, e)
|
480
|
+
annotation = inspect.Parameter.empty
|
481
|
+
else:
|
482
|
+
annotation = inspect.Parameter.empty
|
483
|
+
|
484
|
+
param = inspect.Parameter(
|
485
|
+
name, inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=annotation
|
486
|
+
)
|
487
|
+
parameters.append(param)
|
488
|
+
|
489
|
+
defaults = [None] * (
|
490
|
+
len(func_def.args.args) - len(func_def.args.defaults)
|
491
|
+
) + func_def.args.defaults
|
492
|
+
for param, default in zip(parameters, defaults):
|
493
|
+
if default is not None:
|
494
|
+
try:
|
495
|
+
def_code = compile(ast.Expression(default), "<string>", "eval")
|
496
|
+
default = eval(def_code, namespace)
|
497
|
+
except Exception as e:
|
498
|
+
logger.debug(
|
499
|
+
"Failed to evaluate default value for %s: %s", param.name, e
|
500
|
+
)
|
501
|
+
default = None # Set to None if evaluation fails
|
502
|
+
parameters[parameters.index(param)] = param.replace(default=default)
|
503
|
+
|
504
|
+
if func_def.args.vararg:
|
505
|
+
parameters.append(
|
506
|
+
inspect.Parameter(
|
507
|
+
func_def.args.vararg.arg, inspect.Parameter.VAR_POSITIONAL
|
508
|
+
)
|
509
|
+
)
|
510
|
+
if func_def.args.kwarg:
|
511
|
+
parameters.append(
|
512
|
+
inspect.Parameter(func_def.args.kwarg.arg, inspect.Parameter.VAR_KEYWORD)
|
513
|
+
)
|
514
|
+
|
515
|
+
# Handle return annotation
|
516
|
+
return_annotation = func_def.returns
|
517
|
+
if return_annotation is not None:
|
518
|
+
try:
|
519
|
+
ret_ann_code = compile(
|
520
|
+
ast.Expression(return_annotation), "<string>", "eval"
|
521
|
+
)
|
522
|
+
return_annotation = eval(ret_ann_code, namespace)
|
523
|
+
except Exception as e:
|
524
|
+
logger.debug("Failed to evaluate return annotation: %s", e)
|
525
|
+
return_annotation = inspect.Signature.empty
|
526
|
+
|
527
|
+
return inspect.Signature(parameters, return_annotation=return_annotation)
|
528
|
+
|
529
|
+
|
530
|
+
def _get_docstring_from_source(source_code: str, func_name: str) -> Optional[str]:
|
531
|
+
"""
|
532
|
+
Extract the docstring of a function from its source code.
|
533
|
+
|
534
|
+
Args:
|
535
|
+
source_code (str): The source code of the function.
|
536
|
+
func_name (str): The name of the function.
|
537
|
+
|
538
|
+
Returns:
|
539
|
+
The docstring of the function. If the function has no docstring, returns None.
|
540
|
+
"""
|
541
|
+
parsed_code = ast.parse(source_code)
|
542
|
+
|
543
|
+
func_def = next(
|
544
|
+
(
|
545
|
+
node
|
546
|
+
for node in ast.walk(parsed_code)
|
547
|
+
if isinstance(node, ast.FunctionDef) and node.name == func_name
|
548
|
+
),
|
549
|
+
None,
|
550
|
+
)
|
551
|
+
if func_def is None:
|
552
|
+
raise ValueError(f"Function {func_name} not found in source code")
|
553
|
+
|
554
|
+
if (
|
555
|
+
func_def.body
|
556
|
+
and isinstance(func_def.body[0], ast.Expr)
|
557
|
+
and isinstance(func_def.body[0].value, ast.Constant)
|
558
|
+
):
|
559
|
+
return func_def.body[0].value.value
|
560
|
+
return None
|
561
|
+
|
562
|
+
|
563
|
+
def expand_mapping_parameters(
|
564
|
+
func: Callable, parameters: Dict[str, Any]
|
565
|
+
) -> List[Dict[str, Any]]:
|
566
|
+
"""
|
567
|
+
Generates a list of call parameters to be used for individual calls in a mapping
|
568
|
+
operation.
|
569
|
+
|
570
|
+
Args:
|
571
|
+
func: The function to be called
|
572
|
+
parameters: A dictionary of parameters with iterables to be mapped over
|
573
|
+
|
574
|
+
Returns:
|
575
|
+
List: A list of dictionaries to be used as parameters for each
|
576
|
+
call in the mapping operation
|
577
|
+
"""
|
578
|
+
# Ensure that any parameters in kwargs are expanded before this check
|
579
|
+
parameters = explode_variadic_parameter(func, parameters)
|
580
|
+
|
581
|
+
iterable_parameters = {}
|
582
|
+
static_parameters = {}
|
583
|
+
annotated_parameters = {}
|
584
|
+
for key, val in parameters.items():
|
585
|
+
if isinstance(val, (allow_failure, quote)):
|
586
|
+
# Unwrap annotated parameters to determine if they are iterable
|
587
|
+
annotated_parameters[key] = val
|
588
|
+
val = val.unwrap()
|
589
|
+
|
590
|
+
if isinstance(val, unmapped):
|
591
|
+
static_parameters[key] = val.value
|
592
|
+
elif isiterable(val):
|
593
|
+
iterable_parameters[key] = list(val)
|
594
|
+
else:
|
595
|
+
static_parameters[key] = val
|
596
|
+
|
597
|
+
if not len(iterable_parameters):
|
598
|
+
raise MappingMissingIterable(
|
599
|
+
"No iterable parameters were received. Parameters for map must "
|
600
|
+
f"include at least one iterable. Parameters: {parameters}"
|
601
|
+
)
|
602
|
+
|
603
|
+
iterable_parameter_lengths = {
|
604
|
+
key: len(val) for key, val in iterable_parameters.items()
|
605
|
+
}
|
606
|
+
lengths = set(iterable_parameter_lengths.values())
|
607
|
+
if len(lengths) > 1:
|
608
|
+
raise MappingLengthMismatch(
|
609
|
+
"Received iterable parameters with different lengths. Parameters for map"
|
610
|
+
f" must all be the same length. Got lengths: {iterable_parameter_lengths}"
|
611
|
+
)
|
612
|
+
|
613
|
+
map_length = list(lengths)[0]
|
614
|
+
|
615
|
+
call_parameters_list = []
|
616
|
+
for i in range(map_length):
|
617
|
+
call_parameters = {key: value[i] for key, value in iterable_parameters.items()}
|
618
|
+
call_parameters.update({key: value for key, value in static_parameters.items()})
|
619
|
+
|
620
|
+
# Add default values for parameters; these are skipped earlier since they should
|
621
|
+
# not be mapped over
|
622
|
+
for key, value in get_parameter_defaults(func).items():
|
623
|
+
call_parameters.setdefault(key, value)
|
624
|
+
|
625
|
+
# Re-apply annotations to each key again
|
626
|
+
for key, annotation in annotated_parameters.items():
|
627
|
+
call_parameters[key] = annotation.rewrap(call_parameters[key])
|
628
|
+
|
629
|
+
# Collapse any previously exploded kwargs
|
630
|
+
call_parameters_list.append(collapse_variadic_parameters(func, call_parameters))
|
631
|
+
|
632
|
+
return call_parameters_list
|
prefect/utilities/collections.py
CHANGED
@@ -4,6 +4,7 @@ Utilities for extensions of and operations on Python collections.
|
|
4
4
|
|
5
5
|
import io
|
6
6
|
import itertools
|
7
|
+
import warnings
|
7
8
|
from collections import OrderedDict, defaultdict
|
8
9
|
from collections.abc import Iterator as IteratorABC
|
9
10
|
from collections.abc import Sequence
|
@@ -28,12 +29,7 @@ from typing import (
|
|
28
29
|
)
|
29
30
|
from unittest.mock import Mock
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
if HAS_PYDANTIC_V2:
|
34
|
-
import pydantic.v1 as pydantic
|
35
|
-
else:
|
36
|
-
import pydantic
|
32
|
+
import pydantic
|
37
33
|
|
38
34
|
# Quote moved to `prefect.utilities.annotations` but preserved here for compatibility
|
39
35
|
from prefect.utilities.annotations import BaseAnnotation, Quote, quote # noqa
|
@@ -225,7 +221,7 @@ class StopVisiting(BaseException):
|
|
225
221
|
|
226
222
|
def visit_collection(
|
227
223
|
expr,
|
228
|
-
visit_fn: Callable[[Any], Any],
|
224
|
+
visit_fn: Union[Callable[[Any, dict], Any], Callable[[Any], Any]],
|
229
225
|
return_data: bool = False,
|
230
226
|
max_depth: int = -1,
|
231
227
|
context: Optional[dict] = None,
|
@@ -252,8 +248,10 @@ def visit_collection(
|
|
252
248
|
|
253
249
|
Args:
|
254
250
|
expr (Any): a Python object or expression
|
255
|
-
visit_fn (Callable[[Any], Awaitable[Any]]):
|
256
|
-
will be applied to every non-collection element of expr.
|
251
|
+
visit_fn (Callable[[Any, Optional[dict]], Awaitable[Any]]): a function that
|
252
|
+
will be applied to every non-collection element of expr. The function can
|
253
|
+
accept one or two arguments. If two arguments are accepted, the second
|
254
|
+
argument will be the context dictionary.
|
257
255
|
return_data (bool): if `True`, a copy of `expr` containing data modified
|
258
256
|
by `visit_fn` will be returned. This is slower than `return_data=False`
|
259
257
|
(the default).
|
@@ -343,39 +341,26 @@ def visit_collection(
|
|
343
341
|
result = typ(**items) if return_data else None
|
344
342
|
|
345
343
|
elif isinstance(expr, pydantic.BaseModel):
|
346
|
-
|
347
|
-
# Pydantic does not expose extras in `__fields__` so we use `__fields_set__`
|
348
|
-
# as well to get all of the relevant attributes
|
349
|
-
# Check for presence of attrs even if they're in the field set due to pydantic#4916
|
350
|
-
model_fields = {
|
351
|
-
f for f in expr.__fields_set__.union(expr.__fields__) if hasattr(expr, f)
|
352
|
-
}
|
353
|
-
items = [visit_nested(getattr(expr, key)) for key in model_fields]
|
344
|
+
typ = cast(Type[pydantic.BaseModel], typ)
|
354
345
|
|
355
|
-
|
356
|
-
|
357
|
-
aliases = {
|
358
|
-
key: value.alias
|
359
|
-
for key, value in expr.__fields__.items()
|
360
|
-
if value.has_alias
|
361
|
-
}
|
346
|
+
# when extra=allow, fields not in model_fields may be in model_fields_set
|
347
|
+
model_fields = expr.model_fields_set.union(expr.model_fields.keys())
|
362
348
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
for key, value in zip(model_fields, items)
|
367
|
-
}
|
368
|
-
)
|
349
|
+
# We may encounter a deprecated field here, but this isn't the caller's fault
|
350
|
+
with warnings.catch_warnings():
|
351
|
+
warnings.simplefilter("ignore", category=DeprecationWarning)
|
369
352
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
for attr in expr.__private_attributes__:
|
374
|
-
# Use `object.__setattr__` to avoid errors on immutable models
|
375
|
-
object.__setattr__(model_instance, attr, getattr(expr, attr))
|
353
|
+
updated_data = {
|
354
|
+
field: visit_nested(getattr(expr, field)) for field in model_fields
|
355
|
+
}
|
376
356
|
|
377
|
-
|
378
|
-
|
357
|
+
if return_data:
|
358
|
+
# Use construct to avoid validation and handle immutability
|
359
|
+
model_instance = typ.model_construct(
|
360
|
+
_fields_set=expr.model_fields_set, **updated_data
|
361
|
+
)
|
362
|
+
for private_attr in expr.__private_attributes__:
|
363
|
+
setattr(model_instance, private_attr, getattr(expr, private_attr))
|
379
364
|
result = model_instance
|
380
365
|
else:
|
381
366
|
result = None
|
prefect/utilities/dispatch.py
CHANGED
@@ -90,8 +90,10 @@ def get_dispatch_key(
|
|
90
90
|
|
91
91
|
@classmethod
|
92
92
|
def _register_subclass_of_base_type(cls, **kwargs):
|
93
|
-
if cls
|
93
|
+
if hasattr(cls, "__init_subclass_original__"):
|
94
94
|
cls.__init_subclass_original__(**kwargs)
|
95
|
+
elif hasattr(cls, "__pydantic_init_subclass_original__"):
|
96
|
+
cls.__pydantic_init_subclass_original__(**kwargs)
|
95
97
|
|
96
98
|
# Do not register abstract base classes
|
97
99
|
if abc.ABC in getattr(cls, "__bases__", []):
|
@@ -114,8 +116,14 @@ def register_base_type(cls: T) -> T:
|
|
114
116
|
registry[base_key] = cls
|
115
117
|
|
116
118
|
# Add automatic subtype registration
|
117
|
-
|
118
|
-
|
119
|
+
if hasattr(cls, "__pydantic_init_subclass__"):
|
120
|
+
cls.__pydantic_init_subclass_original__ = getattr(
|
121
|
+
cls, "__pydantic_init_subclass__"
|
122
|
+
)
|
123
|
+
cls.__pydantic_init_subclass__ = _register_subclass_of_base_type
|
124
|
+
else:
|
125
|
+
cls.__init_subclass_original__ = getattr(cls, "__init_subclass__")
|
126
|
+
cls.__init_subclass__ = _register_subclass_of_base_type
|
119
127
|
|
120
128
|
return cls
|
121
129
|
|
prefect/utilities/dockerutils.py
CHANGED
@@ -27,6 +27,10 @@ import prefect
|
|
27
27
|
from prefect.utilities.importtools import lazy_import
|
28
28
|
from prefect.utilities.slugify import slugify
|
29
29
|
|
30
|
+
CONTAINER_LABELS = {
|
31
|
+
"io.prefect.version": prefect.__version__,
|
32
|
+
}
|
33
|
+
|
30
34
|
|
31
35
|
def python_version_minor() -> str:
|
32
36
|
return f"{sys.version_info.major}.{sys.version_info.minor}"
|