prefect-client 3.9.2.dev1__py3-none-any.whl → 3.9.2.dev2__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.9.2.dev1"
3
- __build_date__ = "2026-11-01 08:11:41.429923+00:00"
4
- __git_commit__ = "2b76fb3da359c20fc5fa4e4db615a6fc76330b1e"
2
+ __version__ = "3.9.2.dev2"
3
+ __build_date__ = "2026-11-02 08:14:44.192926+00:00"
4
+ __git_commit__ = "da9d9dc15f647f10135a8ff79ce93c4bdb2dbf6a"
5
5
  __dirty__ = False
@@ -11,7 +11,9 @@ import contextlib
11
11
  import gc
12
12
  import hmac
13
13
  import importlib.metadata
14
+ import json
14
15
  import logging
16
+ import math
15
17
  import mimetypes
16
18
  import os
17
19
  import random
@@ -292,16 +294,34 @@ async def validation_exception_handler(
292
294
  """Provide a detailed message for request validation errors."""
293
295
  return JSONResponse(
294
296
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
295
- content=jsonable_encoder(
296
- {
297
- "exception_message": "Invalid request received.",
298
- "exception_detail": exc.errors(),
299
- "request_body": exc.body,
300
- }
297
+ content=_json_safe(
298
+ jsonable_encoder(
299
+ {
300
+ "exception_message": "Invalid request received.",
301
+ "exception_detail": exc.errors(),
302
+ "request_body": exc.body,
303
+ }
304
+ )
301
305
  ),
302
306
  )
303
307
 
304
308
 
309
+ def _json_safe(value: Any) -> Any:
310
+ """
311
+ Replace values that cannot be rendered as strict JSON, such as `NaN` or strings
312
+ containing unpaired surrogates, so that invalid input can be echoed back.
313
+ """
314
+ if isinstance(value, float) and not math.isfinite(value):
315
+ return json.dumps(value)
316
+ if isinstance(value, str):
317
+ return value.encode("utf-8", "backslashreplace").decode("utf-8")
318
+ if isinstance(value, dict):
319
+ return {_json_safe(k): _json_safe(v) for k, v in value.items()}
320
+ if isinstance(value, list):
321
+ return [_json_safe(v) for v in value]
322
+ return value
323
+
324
+
305
325
  async def integrity_exception_handler(request: Request, exc: Exception) -> JSONResponse:
306
326
  """Capture database integrity errors."""
307
327
  logger.error("Encountered exception in request:", exc_info=True)
@@ -317,7 +337,20 @@ async def integrity_exception_handler(request: Request, exc: Exception) -> JSONR
317
337
  )
318
338
 
319
339
 
340
+ def is_invalid_data_exception(exc: Exception) -> bool:
341
+ """
342
+ Whether the database rejected a value as invalid (SQLSTATE class 22, "data
343
+ exception"), e.g. a NUL character or `NaN` in a PostgreSQL JSON value.
344
+ """
345
+ orig = exc.orig if isinstance(exc, sqlalchemy.exc.DBAPIError) else exc
346
+ sqlstate = getattr(orig, "sqlstate", None)
347
+ return isinstance(sqlstate, str) and sqlstate.startswith("22")
348
+
349
+
320
350
  def is_client_retryable_exception(exc: Exception) -> bool:
351
+ if is_invalid_data_exception(exc):
352
+ return False
353
+
321
354
  if isinstance(exc, sqlalchemy.exc.OperationalError) and isinstance(
322
355
  exc.orig, sqlite3.OperationalError
323
356
  ):
@@ -399,8 +432,29 @@ async def custom_internal_exception_handler(
399
432
  """
400
433
  Log a detailed exception for internal server errors before returning.
401
434
 
402
- Send 503 for errors clients can retry on.
435
+ Send 422 for values the database rejected and 503 for errors clients can retry on.
403
436
  """
437
+ if is_invalid_data_exception(exc):
438
+ logger.warning("Database rejected a value in request:", exc_info=True)
439
+ return JSONResponse(
440
+ content={
441
+ "exception_message": "Invalid request received.",
442
+ "exception_detail": [
443
+ {
444
+ "type": "value_error",
445
+ "loc": ["body"],
446
+ "msg": (
447
+ "The database rejected a value in the request. This "
448
+ "usually means a string contains a NUL character or a "
449
+ "JSON value contains NaN or Infinity. See server logs "
450
+ "for details."
451
+ ),
452
+ }
453
+ ],
454
+ },
455
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
456
+ )
457
+
404
458
  if is_client_retryable_exception(exc):
405
459
  if PREFECT_API_LOG_RETRYABLE_ERRORS.value():
406
460
  logger.error("Encountered retryable exception in request:", exc_info=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: prefect-client
3
- Version: 3.9.2.dev1
3
+ Version: 3.9.2.dev2
4
4
  Summary: Workflow orchestration and management.
5
5
  Project-URL: Changelog, https://github.com/PrefectHQ/prefect/releases
6
6
  Project-URL: Documentation, https://www.prefect.io/docs
@@ -2,7 +2,7 @@ prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
2
2
  prefect/AGENTS.md,sha256=SRVqK72XfJPQTRQzfD6-pR04qFZJjZr7nii7W058uJg,13934
3
3
  prefect/__init__.py,sha256=FtzUynR70uuE9WgVABo7cZkGGeNdL8Q1gJVTtmqvpyg,6772
4
4
  prefect/__main__.py,sha256=WFjw3kaYJY6pOTA7WDOgqjsz8zUEUZHCcj3P5wyVa-g,66
5
- prefect/_build_info.py,sha256=MgM0Q1V0yqLvA9GqeWtwGV6DgXa3F_RGLhD36JuEbwY,185
5
+ prefect/_build_info.py,sha256=kTb9Bv9_n73s_06h5F7AKBbO1fjq4W4sU6D8E6pQL3M,185
6
6
  prefect/_flow_run_suspension.py,sha256=5zTTB7ZIBHzoS0pVrhNn23-9hK51qZ3CQA6C-azluC0,4144
7
7
  prefect/agent.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
8
8
  prefect/artifacts.py,sha256=ydlAad-FUepGUvTzmDkSmZLFRdMHohqVhwKnufJkeOw,23955
@@ -331,7 +331,7 @@ prefect/server/api/observed_states.py,sha256=zKe3qfNvlGwP4t6dvQqTEsg9qJHkNU1A0sB
331
331
  prefect/server/api/root.py,sha256=c_av5N4MkjSfFn1fI9F9Rpn6O_haBd6mshoF8OTvNqo,1358
332
332
  prefect/server/api/run_history.py,sha256=OR8YIP7PbviRUyehnN21KywFbuGpAt_VOFgNo2O7kcc,5428
333
333
  prefect/server/api/saved_searches.py,sha256=tGBS8p6DjFOzwEkRhMKdxQuT900lNqA7Rr3walYuv5w,3232
334
- prefect/server/api/server.py,sha256=V1SE92sKlYydaTYsOajzx2h7tUbtcsOYOqOdG3puabM,44956
334
+ prefect/server/api/server.py,sha256=FyM-gBhp6anKsoo7K9DKng9jXB-f-FBxrXHnhP7swIo,47034
335
335
  prefect/server/api/task_run_states.py,sha256=ZZfxj8PyVduF5RsnhVjJluDCHiIqr78WNW34NBscz2c,1651
336
336
  prefect/server/api/task_runs.py,sha256=Y7FOYaiQnrC74YtYSFd33SEpE99Srz_CQ1-IgK_P5BM,14877
337
337
  prefect/server/api/task_workers.py,sha256=IncrNCj2hk_QAcYt6cOWRgwPNnfwhLXEapeptYQ0Yi8,1068
@@ -452,7 +452,7 @@ prefect/workers/_worker_channel/_protocol.py,sha256=GpCNh1o3qmmqHA_UOOTge1QVC6IR
452
452
  prefect/workers/_worker_channel/_state.py,sha256=eQTFZtAVDZH1vVWps3SdeY6aW3qu2wx1UKYQXK3AyuE,5369
453
453
  prefect/workers/_worker_channel/_sync.py,sha256=G5G8_UaQYbeLebi5Mb1Z_KgGfXfyjXo5uT9la5_zwqY,14984
454
454
  prefect/workers/_worker_channel/_transport.py,sha256=6VXxiZKIu0rqImlif14ojoAL3UGOBTmNpnlYxNggaLE,9423
455
- prefect_client-3.9.2.dev1.dist-info/METADATA,sha256=fDr3c49xytuF0CI2nDUzQNhh4AM8rzNJfz79BlcLntk,7506
456
- prefect_client-3.9.2.dev1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
457
- prefect_client-3.9.2.dev1.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
458
- prefect_client-3.9.2.dev1.dist-info/RECORD,,
455
+ prefect_client-3.9.2.dev2.dist-info/METADATA,sha256=gu04e149Ld2w9x-3mZSQ78tFv9P7U-Kti67ARr5kO6o,7506
456
+ prefect_client-3.9.2.dev2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
457
+ prefect_client-3.9.2.dev2.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
458
+ prefect_client-3.9.2.dev2.dist-info/RECORD,,