prefect-client 3.4.7.dev8__py3-none-any.whl → 3.4.7.dev9__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/_build_info.py +3 -3
- prefect/_internal/websockets.py +8 -5
- prefect/blocks/core.py +82 -11
- prefect/utilities/pydantic.py +19 -1
- {prefect_client-3.4.7.dev8.dist-info → prefect_client-3.4.7.dev9.dist-info}/METADATA +1 -1
- {prefect_client-3.4.7.dev8.dist-info → prefect_client-3.4.7.dev9.dist-info}/RECORD +8 -8
- {prefect_client-3.4.7.dev8.dist-info → prefect_client-3.4.7.dev9.dist-info}/WHEEL +0 -0
- {prefect_client-3.4.7.dev8.dist-info → prefect_client-3.4.7.dev9.dist-info}/licenses/LICENSE +0 -0
prefect/_build_info.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Generated by versioningit
|
2
|
-
__version__ = "3.4.7.
|
3
|
-
__build_date__ = "2025-06-
|
4
|
-
__git_commit__ = "
|
2
|
+
__version__ = "3.4.7.dev9"
|
3
|
+
__build_date__ = "2025-06-26 08:09:22.695909+00:00"
|
4
|
+
__git_commit__ = "6128bf88e0089a6ef517493eafb157d523ed0afd"
|
5
5
|
__dirty__ = False
|
prefect/_internal/websockets.py
CHANGED
@@ -74,9 +74,10 @@ class WebsocketProxyConnect(connect):
|
|
74
74
|
"Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
|
75
75
|
)
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
)
|
77
|
+
# Store proxy URL for deferred creation. Creating the proxy object here
|
78
|
+
# can bind asyncio futures to the wrong event loop when multiple WebSocket
|
79
|
+
# connections are initialized at different times (e.g., events + logs clients).
|
80
|
+
self._proxy_url = proxy_url if proxy_url and not proxy_bypass(host) else None
|
80
81
|
self._host = host
|
81
82
|
self._port = port
|
82
83
|
|
@@ -86,8 +87,10 @@ class WebsocketProxyConnect(connect):
|
|
86
87
|
self._kwargs.setdefault("ssl", ssl_context)
|
87
88
|
|
88
89
|
async def _proxy_connect(self: Self) -> ClientConnection:
|
89
|
-
if self.
|
90
|
-
|
90
|
+
if self._proxy_url:
|
91
|
+
# Create proxy in the current event loop context
|
92
|
+
proxy = Proxy.from_url(self._proxy_url)
|
93
|
+
sock = await proxy.connect(
|
91
94
|
dest_host=self._host,
|
92
95
|
dest_port=self._port,
|
93
96
|
)
|
prefect/blocks/core.py
CHANGED
@@ -175,11 +175,29 @@ def _collect_secret_fields(
|
|
175
175
|
)
|
176
176
|
return
|
177
177
|
|
178
|
-
if
|
178
|
+
# Check if this is a pydantic Secret type (including generic Secret[T])
|
179
|
+
is_pydantic_secret = False
|
180
|
+
|
181
|
+
# Direct check for SecretStr, SecretBytes
|
182
|
+
if type_ in (SecretStr, SecretBytes):
|
183
|
+
is_pydantic_secret = True
|
184
|
+
# Check for base Secret class
|
185
|
+
elif (
|
179
186
|
isinstance(type_, type) # type: ignore[unnecessaryIsInstance]
|
180
187
|
and getattr(type_, "__module__", None) == "pydantic.types"
|
181
188
|
and getattr(type_, "__name__", None) == "Secret"
|
182
189
|
):
|
190
|
+
is_pydantic_secret = True
|
191
|
+
# Check for generic Secret[T] (e.g., Secret[str], Secret[int])
|
192
|
+
elif get_origin(type_) is not None:
|
193
|
+
origin = get_origin(type_)
|
194
|
+
if (
|
195
|
+
getattr(origin, "__module__", None) == "pydantic.types"
|
196
|
+
and getattr(origin, "__name__", None) == "Secret"
|
197
|
+
):
|
198
|
+
is_pydantic_secret = True
|
199
|
+
|
200
|
+
if is_pydantic_secret:
|
183
201
|
secrets.append(name)
|
184
202
|
elif type_ == SecretDict:
|
185
203
|
# Append .* to field name to signify that all values under a given key are secret and should be obfuscated.
|
@@ -370,16 +388,27 @@ class Block(BaseModel, ABC):
|
|
370
388
|
) -> Any:
|
371
389
|
jsonable_self = handler(self)
|
372
390
|
if (ctx := info.context) and ctx.get("include_secrets") is True:
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
391
|
+
# Add serialization mode to context so handle_secret_render knows how to process nested models
|
392
|
+
ctx["serialization_mode"] = info.mode
|
393
|
+
|
394
|
+
for field_name in type(self).model_fields:
|
395
|
+
field_value = getattr(self, field_name)
|
396
|
+
|
397
|
+
# In JSON mode, skip fields that don't contain secrets
|
398
|
+
# as they're already properly serialized by the handler
|
399
|
+
if (
|
400
|
+
info.mode == "json"
|
401
|
+
and field_name in jsonable_self
|
402
|
+
and not self._field_has_secrets(field_name)
|
403
|
+
):
|
404
|
+
continue
|
405
|
+
|
406
|
+
# For all other fields, use visit_collection with handle_secret_render
|
407
|
+
jsonable_self[field_name] = visit_collection(
|
408
|
+
expr=field_value,
|
409
|
+
visit_fn=partial(handle_secret_render, context=ctx),
|
410
|
+
return_data=True,
|
411
|
+
)
|
383
412
|
extra_fields = {
|
384
413
|
"block_type_slug": self.get_block_type_slug(),
|
385
414
|
"_block_document_id": self._block_document_id,
|
@@ -477,6 +506,25 @@ class Block(BaseModel, ABC):
|
|
477
506
|
else:
|
478
507
|
return f"sha256:{checksum}"
|
479
508
|
|
509
|
+
def _field_has_secrets(self, field_name: str) -> bool:
|
510
|
+
"""Check if a field contains secrets based on the schema's secret_fields."""
|
511
|
+
secret_fields = self.model_json_schema().get("secret_fields", [])
|
512
|
+
|
513
|
+
# Check if field_name matches any secret field pattern
|
514
|
+
for secret_field in secret_fields:
|
515
|
+
if secret_field == field_name:
|
516
|
+
return True
|
517
|
+
elif secret_field.startswith(f"{field_name}."):
|
518
|
+
# This field contains nested secrets
|
519
|
+
return True
|
520
|
+
elif secret_field.endswith(".*"):
|
521
|
+
# Handle wildcard patterns like "field.*"
|
522
|
+
prefix = secret_field[:-2] # Remove .*
|
523
|
+
if field_name == prefix:
|
524
|
+
return True
|
525
|
+
|
526
|
+
return False
|
527
|
+
|
480
528
|
def _to_block_document(
|
481
529
|
self,
|
482
530
|
name: Optional[str] = None,
|
@@ -530,6 +578,29 @@ class Block(BaseModel, ABC):
|
|
530
578
|
context={"include_secrets": include_secrets},
|
531
579
|
)
|
532
580
|
|
581
|
+
# Ensure non-secret fields are JSON-serializable to avoid issues with types
|
582
|
+
# like SemanticVersion when the BlockDocument is later serialized
|
583
|
+
try:
|
584
|
+
json_data = self.model_dump(
|
585
|
+
mode="json",
|
586
|
+
by_alias=True,
|
587
|
+
include=data_keys,
|
588
|
+
context={"include_secrets": include_secrets},
|
589
|
+
)
|
590
|
+
# Replace non-secret, non-Block fields with their JSON representation
|
591
|
+
# We need to check the original field to determine if it's a secret or Block
|
592
|
+
for key in data_keys:
|
593
|
+
if key in block_document_data and key in json_data:
|
594
|
+
field_value = getattr(self, key)
|
595
|
+
# Only replace if the field doesn't contain secrets and is not a Block
|
596
|
+
if not self._field_has_secrets(key) and not isinstance(
|
597
|
+
field_value, Block
|
598
|
+
):
|
599
|
+
block_document_data[key] = json_data[key]
|
600
|
+
except Exception:
|
601
|
+
# If JSON serialization fails, we'll handle it later
|
602
|
+
pass
|
603
|
+
|
533
604
|
# Iterate through and find blocks that already have saved block documents to
|
534
605
|
# create references to those saved block documents.
|
535
606
|
for key in data_keys:
|
prefect/utilities/pydantic.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import warnings
|
2
|
+
from functools import partial
|
2
3
|
from typing import (
|
3
4
|
Any,
|
4
5
|
Callable,
|
@@ -20,6 +21,7 @@ from pydantic import (
|
|
20
21
|
from pydantic_core import to_jsonable_python
|
21
22
|
from typing_extensions import Literal
|
22
23
|
|
24
|
+
from prefect.utilities.collections import visit_collection
|
23
25
|
from prefect.utilities.dispatch import get_dispatch_key, lookup_type, register_base_type
|
24
26
|
from prefect.utilities.importtools import from_qualified_name, to_qualified_name
|
25
27
|
from prefect.utilities.names import obfuscate
|
@@ -344,7 +346,23 @@ def handle_secret_render(value: object, context: dict[str, Any]) -> object:
|
|
344
346
|
else obfuscate(value)
|
345
347
|
)
|
346
348
|
elif isinstance(value, BaseModel):
|
347
|
-
|
349
|
+
# Pass the serialization mode if available in context
|
350
|
+
mode = context.get("serialization_mode", "python")
|
351
|
+
if mode == "json":
|
352
|
+
# For JSON mode with nested models, we need to recursively process fields
|
353
|
+
# because regular Pydantic models don't understand include_secrets
|
354
|
+
|
355
|
+
json_data = value.model_dump(mode="json")
|
356
|
+
for field_name in type(value).model_fields:
|
357
|
+
field_value = getattr(value, field_name)
|
358
|
+
json_data[field_name] = visit_collection(
|
359
|
+
expr=field_value,
|
360
|
+
visit_fn=partial(handle_secret_render, context=context),
|
361
|
+
return_data=True,
|
362
|
+
)
|
363
|
+
return json_data
|
364
|
+
else:
|
365
|
+
return value.model_dump(context=context)
|
348
366
|
return value
|
349
367
|
|
350
368
|
|
@@ -2,7 +2,7 @@ prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
|
|
2
2
|
prefect/AGENTS.md,sha256=qmCZAuKIF9jQyp5TrW_T8bsM_97-QaiCoQp71A_b2Lg,1008
|
3
3
|
prefect/__init__.py,sha256=iCdcC5ZmeewikCdnPEP6YBAjPNV5dvfxpYCTpw30Hkw,3685
|
4
4
|
prefect/__main__.py,sha256=WFjw3kaYJY6pOTA7WDOgqjsz8zUEUZHCcj3P5wyVa-g,66
|
5
|
-
prefect/_build_info.py,sha256=
|
5
|
+
prefect/_build_info.py,sha256=yEuA5A5loMu-7rtiAcPg6Ueq-eaNuXnmZDWR0wamLl8,185
|
6
6
|
prefect/_result_records.py,sha256=S6QmsODkehGVSzbMm6ig022PYbI6gNKz671p_8kBYx4,7789
|
7
7
|
prefect/_versioning.py,sha256=YqR5cxXrY4P6LM1Pmhd8iMo7v_G2KJpGNdsf4EvDFQ0,14132
|
8
8
|
prefect/_waiters.py,sha256=Ia2ITaXdHzevtyWIgJoOg95lrEXQqNEOquHvw3T33UQ,9026
|
@@ -45,7 +45,7 @@ prefect/_internal/integrations.py,sha256=U4cZMDbnilzZSKaMxvzZcSL27a1tzRMjDoTfr2u
|
|
45
45
|
prefect/_internal/pytz.py,sha256=Sy_cD-Hkmo_Yrhx2Jucy7DgTRhvO8ZD0whW1ywbSg_U,13765
|
46
46
|
prefect/_internal/retries.py,sha256=pMHofrTQPDSxbVWclDwXbfhFKaDC6sxe1DkUOWugV6k,3040
|
47
47
|
prefect/_internal/uuid7.py,sha256=yvndhibNDrqnYrG-qUncas4XQp8bKVbmM8XfF7JrjJI,4203
|
48
|
-
prefect/_internal/websockets.py,sha256=
|
48
|
+
prefect/_internal/websockets.py,sha256=G-93Wm8BQeKgXHrCdUtQQNBbVH6puS2G00M-g82Tllc,3792
|
49
49
|
prefect/_internal/compatibility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
50
50
|
prefect/_internal/compatibility/async_dispatch.py,sha256=cUXOqSeseMUaje9oYUzasVPtNttyiHvrqfJl0zK66XI,2949
|
51
51
|
prefect/_internal/compatibility/blocks.py,sha256=SSZXoWVuCMYu1EzjqmTa4lKjDCyxvOFK47XMj6s4hsk,984
|
@@ -78,7 +78,7 @@ prefect/assets/core.py,sha256=9iGsGqZ74UdCwZcLqUogPgVscBROIVeczv-TpB9fnYA,2179
|
|
78
78
|
prefect/assets/materialize.py,sha256=GcHn1HEbCpExka0IOOz2b_2ZsJFROIo5y7DCP5GjpI8,1143
|
79
79
|
prefect/blocks/__init__.py,sha256=D0hB72qMfgqnBB2EMZRxUxlX9yLfkab5zDChOwJZmkY,220
|
80
80
|
prefect/blocks/abstract.py,sha256=mpOAWopSR_RrzdxeurBTXVSKisP8ne-k8LYos-tp7go,17021
|
81
|
-
prefect/blocks/core.py,sha256=
|
81
|
+
prefect/blocks/core.py,sha256=BYcej5ktmiRjoTRLJUKcEOzYrX7DC-ielKCu8azcJJQ,65991
|
82
82
|
prefect/blocks/fields.py,sha256=1m507VVmkpOnMF_7N-qboRjtw4_ceIuDneX3jZ3Jm54,63
|
83
83
|
prefect/blocks/notifications.py,sha256=NEhdnV_Alt_dGSfq8T1q2l0frh8IVvLCfn0YjXBLJdU,34861
|
84
84
|
prefect/blocks/redis.py,sha256=lt_f1SIcS5OVvthCY6KRWiy5DyUZNRlHqkKhKF25P8c,5770
|
@@ -310,7 +310,7 @@ prefect/utilities/importtools.py,sha256=Bgis-5EFaX8XekwiXa2Cr4jE76yiFBmp0mQ9iGZs
|
|
310
310
|
prefect/utilities/math.py,sha256=UPIdJMP13lCU3o0Yz98o4VDw3LTkkrsOAsvAdA3Xifc,2954
|
311
311
|
prefect/utilities/names.py,sha256=PcNp3IbSoJY6P3UiJDYDjpYQw6BYWtn6OarFDCq1dUE,1744
|
312
312
|
prefect/utilities/processutils.py,sha256=k_VD41Q0EBz-DP2lN7AcOkFGpYH3ekKGk4YV_OuvQc8,16255
|
313
|
-
prefect/utilities/pydantic.py,sha256=
|
313
|
+
prefect/utilities/pydantic.py,sha256=qF4brsWU6AYdJZFwglHv9AM9LjJ_rNKspWgbHYSo2Y0,13141
|
314
314
|
prefect/utilities/render_swagger.py,sha256=y0GcR38qW083lUPrfHIbDVKPm_fyyodtBM8MTLNF8oI,4155
|
315
315
|
prefect/utilities/services.py,sha256=WRT77LW2IX3TCYoGIBsG-V8K_2P3oQWgtW7XkBGnhcs,7714
|
316
316
|
prefect/utilities/slugify.py,sha256=57Vb14t13F3zm1P65KAu8nVeAz0iJCd1Qc5eMG-R5y8,169
|
@@ -329,7 +329,7 @@ prefect/workers/cloud.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
|
|
329
329
|
prefect/workers/process.py,sha256=Yi5D0U5AQ51wHT86GdwtImXSefe0gJf3LGq4r4z9zwM,11090
|
330
330
|
prefect/workers/server.py,sha256=2pmVeJZiVbEK02SO6BEZaBIvHMsn6G8LzjW8BXyiTtk,1952
|
331
331
|
prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
|
332
|
-
prefect_client-3.4.7.
|
333
|
-
prefect_client-3.4.7.
|
334
|
-
prefect_client-3.4.7.
|
335
|
-
prefect_client-3.4.7.
|
332
|
+
prefect_client-3.4.7.dev9.dist-info/METADATA,sha256=AzA76GgKo_GT40aBJr76COGca8HnWxy0O7W43ppQbAM,7517
|
333
|
+
prefect_client-3.4.7.dev9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
334
|
+
prefect_client-3.4.7.dev9.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
|
335
|
+
prefect_client-3.4.7.dev9.dist-info/RECORD,,
|
File without changes
|
{prefect_client-3.4.7.dev8.dist-info → prefect_client-3.4.7.dev9.dist-info}/licenses/LICENSE
RENAMED
File without changes
|