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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # Generated by versioningit
2
- __version__ = "3.4.7.dev8"
3
- __build_date__ = "2025-06-25 08:09:28.547496+00:00"
4
- __git_commit__ = "f2c59127b60d3a91a1da167603522185fac2be50"
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
@@ -74,9 +74,10 @@ class WebsocketProxyConnect(connect):
74
74
  "Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
75
75
  )
76
76
 
77
- self._proxy = (
78
- Proxy.from_url(proxy_url) if proxy_url and not proxy_bypass(host) else None
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._proxy:
90
- sock = await self._proxy.connect(
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 type_ in (SecretStr, SecretBytes) or (
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
- jsonable_self.update(
374
- {
375
- field_name: visit_collection(
376
- expr=getattr(self, field_name),
377
- visit_fn=partial(handle_secret_render, context=ctx),
378
- return_data=True,
379
- )
380
- for field_name in type(self).model_fields
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:
@@ -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
- return value.model_dump(context=context)
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prefect-client
3
- Version: 3.4.7.dev8
3
+ Version: 3.4.7.dev9
4
4
  Summary: Workflow orchestration and management.
5
5
  Project-URL: Changelog, https://github.com/PrefectHQ/prefect/releases
6
6
  Project-URL: Documentation, https://docs.prefect.io
@@ -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=vtIbRKEkWF6yy5x3d3LhHvxh02unYDsxXxlBnW3k6Dc,185
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=CloIdusf2Bbefdit46pT91cVDudeYtztPI-MmqSnuLI,3466
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=iP-g6guW9HFkt-sFpgH8WCyWhwnH5zIoUJuI2ykImG0,62894
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=3PADBIqHKzrc6r3mNupgJ-sFyDH4INaVB1lJpvLGj5Q,12295
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.dev8.dist-info/METADATA,sha256=cmb5kLIW8kQyoy34VabdVPMInMhwbOKr08OAj6rwq_M,7517
333
- prefect_client-3.4.7.dev8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
334
- prefect_client-3.4.7.dev8.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
335
- prefect_client-3.4.7.dev8.dist-info/RECORD,,
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,,