elasticsearch 8.11.0__py3-none-any.whl → 8.12.0__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.
Files changed (90) hide show
  1. elasticsearch/_async/client/__init__.py +493 -347
  2. elasticsearch/_async/client/async_search.py +108 -72
  3. elasticsearch/_async/client/autoscaling.py +13 -8
  4. elasticsearch/_async/client/cat.py +26 -26
  5. elasticsearch/_async/client/ccr.py +178 -117
  6. elasticsearch/_async/client/cluster.py +56 -48
  7. elasticsearch/_async/client/dangling_indices.py +3 -3
  8. elasticsearch/_async/client/enrich.py +15 -13
  9. elasticsearch/_async/client/eql.py +53 -36
  10. elasticsearch/_async/client/esql.py +99 -0
  11. elasticsearch/_async/client/features.py +2 -2
  12. elasticsearch/_async/client/fleet.py +111 -71
  13. elasticsearch/_async/client/graph.py +13 -11
  14. elasticsearch/_async/client/ilm.py +33 -27
  15. elasticsearch/_async/client/indices.py +326 -227
  16. elasticsearch/_async/client/inference.py +212 -0
  17. elasticsearch/_async/client/ingest.py +28 -24
  18. elasticsearch/_async/client/license.py +15 -13
  19. elasticsearch/_async/client/logstash.py +13 -10
  20. elasticsearch/_async/client/migration.py +3 -3
  21. elasticsearch/_async/client/ml.py +762 -538
  22. elasticsearch/_async/client/monitoring.py +10 -5
  23. elasticsearch/_async/client/nodes.py +13 -11
  24. elasticsearch/_async/client/query_ruleset.py +12 -10
  25. elasticsearch/_async/client/rollup.py +59 -46
  26. elasticsearch/_async/client/search_application.py +23 -16
  27. elasticsearch/_async/client/searchable_snapshots.py +23 -16
  28. elasticsearch/_async/client/security.py +393 -287
  29. elasticsearch/_async/client/shutdown.py +18 -14
  30. elasticsearch/_async/client/slm.py +23 -21
  31. elasticsearch/_async/client/snapshot.py +91 -65
  32. elasticsearch/_async/client/sql.py +81 -58
  33. elasticsearch/_async/client/ssl.py +1 -1
  34. elasticsearch/_async/client/synonyms.py +23 -19
  35. elasticsearch/_async/client/tasks.py +3 -3
  36. elasticsearch/_async/client/text_structure.py +10 -5
  37. elasticsearch/_async/client/transform.py +111 -75
  38. elasticsearch/_async/client/watcher.py +77 -55
  39. elasticsearch/_async/client/xpack.py +2 -2
  40. elasticsearch/_async/helpers.py +1 -1
  41. elasticsearch/_sync/client/__init__.py +493 -347
  42. elasticsearch/_sync/client/async_search.py +108 -72
  43. elasticsearch/_sync/client/autoscaling.py +13 -8
  44. elasticsearch/_sync/client/cat.py +26 -26
  45. elasticsearch/_sync/client/ccr.py +178 -117
  46. elasticsearch/_sync/client/cluster.py +56 -48
  47. elasticsearch/_sync/client/dangling_indices.py +3 -3
  48. elasticsearch/_sync/client/enrich.py +15 -13
  49. elasticsearch/_sync/client/eql.py +53 -36
  50. elasticsearch/_sync/client/esql.py +99 -0
  51. elasticsearch/_sync/client/features.py +2 -2
  52. elasticsearch/_sync/client/fleet.py +111 -71
  53. elasticsearch/_sync/client/graph.py +13 -11
  54. elasticsearch/_sync/client/ilm.py +33 -27
  55. elasticsearch/_sync/client/indices.py +326 -227
  56. elasticsearch/_sync/client/inference.py +212 -0
  57. elasticsearch/_sync/client/ingest.py +28 -24
  58. elasticsearch/_sync/client/license.py +15 -13
  59. elasticsearch/_sync/client/logstash.py +13 -10
  60. elasticsearch/_sync/client/migration.py +3 -3
  61. elasticsearch/_sync/client/ml.py +762 -538
  62. elasticsearch/_sync/client/monitoring.py +10 -5
  63. elasticsearch/_sync/client/nodes.py +13 -11
  64. elasticsearch/_sync/client/query_ruleset.py +12 -10
  65. elasticsearch/_sync/client/rollup.py +59 -46
  66. elasticsearch/_sync/client/search_application.py +23 -16
  67. elasticsearch/_sync/client/searchable_snapshots.py +23 -16
  68. elasticsearch/_sync/client/security.py +393 -287
  69. elasticsearch/_sync/client/shutdown.py +18 -14
  70. elasticsearch/_sync/client/slm.py +23 -21
  71. elasticsearch/_sync/client/snapshot.py +91 -65
  72. elasticsearch/_sync/client/sql.py +81 -58
  73. elasticsearch/_sync/client/ssl.py +1 -1
  74. elasticsearch/_sync/client/synonyms.py +23 -19
  75. elasticsearch/_sync/client/tasks.py +3 -3
  76. elasticsearch/_sync/client/text_structure.py +10 -5
  77. elasticsearch/_sync/client/transform.py +111 -75
  78. elasticsearch/_sync/client/utils.py +34 -10
  79. elasticsearch/_sync/client/watcher.py +77 -55
  80. elasticsearch/_sync/client/xpack.py +2 -2
  81. elasticsearch/_version.py +1 -1
  82. elasticsearch/client.py +2 -0
  83. elasticsearch/helpers/actions.py +1 -1
  84. {elasticsearch-8.11.0.dist-info → elasticsearch-8.12.0.dist-info}/METADATA +7 -4
  85. elasticsearch-8.12.0.dist-info/RECORD +103 -0
  86. {elasticsearch-8.11.0.dist-info → elasticsearch-8.12.0.dist-info}/WHEEL +1 -1
  87. elasticsearch-8.11.0.dist-info/RECORD +0 -99
  88. {elasticsearch-8.11.0.dist-info → elasticsearch-8.12.0.dist-info}/LICENSE +0 -0
  89. {elasticsearch-8.11.0.dist-info → elasticsearch-8.12.0.dist-info}/NOTICE +0 -0
  90. {elasticsearch-8.11.0.dist-info → elasticsearch-8.12.0.dist-info}/top_level.txt +0 -0
@@ -40,7 +40,7 @@ class TransformClient(NamespacedClient):
40
40
  """
41
41
  Deletes an existing transform.
42
42
 
43
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/delete-transform.html>`_
43
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/delete-transform.html>`_
44
44
 
45
45
  :param transform_id: Identifier for the transform.
46
46
  :param delete_dest_index: If this value is true, the destination index is deleted
@@ -94,7 +94,7 @@ class TransformClient(NamespacedClient):
94
94
  """
95
95
  Retrieves configuration information for transforms.
96
96
 
97
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/get-transform.html>`_
97
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/get-transform.html>`_
98
98
 
99
99
  :param transform_id: Identifier for the transform. It can be a transform identifier
100
100
  or a wildcard expression. You can get information for all transforms by using
@@ -155,7 +155,7 @@ class TransformClient(NamespacedClient):
155
155
  """
156
156
  Retrieves usage information for transforms.
157
157
 
158
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/get-transform-stats.html>`_
158
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/get-transform-stats.html>`_
159
159
 
160
160
  :param transform_id: Identifier for the transform. It can be a transform identifier
161
161
  or a wildcard expression. You can get information for all transforms by using
@@ -195,7 +195,17 @@ class TransformClient(NamespacedClient):
195
195
  )
196
196
 
197
197
  @_rewrite_parameters(
198
- body_fields=True,
198
+ body_fields=(
199
+ "description",
200
+ "dest",
201
+ "frequency",
202
+ "latest",
203
+ "pivot",
204
+ "retention_policy",
205
+ "settings",
206
+ "source",
207
+ "sync",
208
+ ),
199
209
  )
200
210
  def preview_transform(
201
211
  self,
@@ -215,11 +225,12 @@ class TransformClient(NamespacedClient):
215
225
  source: t.Optional[t.Mapping[str, t.Any]] = None,
216
226
  sync: t.Optional[t.Mapping[str, t.Any]] = None,
217
227
  timeout: t.Optional[t.Union["t.Literal[-1]", "t.Literal[0]", str]] = None,
228
+ body: t.Optional[t.Dict[str, t.Any]] = None,
218
229
  ) -> ObjectApiResponse[t.Any]:
219
230
  """
220
231
  Previews a transform.
221
232
 
222
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/preview-transform.html>`_
233
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/preview-transform.html>`_
223
234
 
224
235
  :param transform_id: Identifier for the transform to preview. If you specify
225
236
  this path parameter, you cannot provide transform configuration details in
@@ -247,36 +258,37 @@ class TransformClient(NamespacedClient):
247
258
  __path = f"/_transform/{_quote(transform_id)}/_preview"
248
259
  else:
249
260
  __path = "/_transform/_preview"
250
- __body: t.Dict[str, t.Any] = {}
251
261
  __query: t.Dict[str, t.Any] = {}
252
- if description is not None:
253
- __body["description"] = description
254
- if dest is not None:
255
- __body["dest"] = dest
262
+ __body: t.Dict[str, t.Any] = body if body is not None else {}
256
263
  if error_trace is not None:
257
264
  __query["error_trace"] = error_trace
258
265
  if filter_path is not None:
259
266
  __query["filter_path"] = filter_path
260
- if frequency is not None:
261
- __body["frequency"] = frequency
262
267
  if human is not None:
263
268
  __query["human"] = human
264
- if latest is not None:
265
- __body["latest"] = latest
266
- if pivot is not None:
267
- __body["pivot"] = pivot
268
269
  if pretty is not None:
269
270
  __query["pretty"] = pretty
270
- if retention_policy is not None:
271
- __body["retention_policy"] = retention_policy
272
- if settings is not None:
273
- __body["settings"] = settings
274
- if source is not None:
275
- __body["source"] = source
276
- if sync is not None:
277
- __body["sync"] = sync
278
271
  if timeout is not None:
279
272
  __query["timeout"] = timeout
273
+ if not __body:
274
+ if description is not None:
275
+ __body["description"] = description
276
+ if dest is not None:
277
+ __body["dest"] = dest
278
+ if frequency is not None:
279
+ __body["frequency"] = frequency
280
+ if latest is not None:
281
+ __body["latest"] = latest
282
+ if pivot is not None:
283
+ __body["pivot"] = pivot
284
+ if retention_policy is not None:
285
+ __body["retention_policy"] = retention_policy
286
+ if settings is not None:
287
+ __body["settings"] = settings
288
+ if source is not None:
289
+ __body["source"] = source
290
+ if sync is not None:
291
+ __body["sync"] = sync
280
292
  if not __body:
281
293
  __body = None # type: ignore[assignment]
282
294
  __headers = {"accept": "application/json"}
@@ -287,15 +299,26 @@ class TransformClient(NamespacedClient):
287
299
  )
288
300
 
289
301
  @_rewrite_parameters(
290
- body_fields=True,
302
+ body_fields=(
303
+ "dest",
304
+ "source",
305
+ "description",
306
+ "frequency",
307
+ "latest",
308
+ "meta",
309
+ "pivot",
310
+ "retention_policy",
311
+ "settings",
312
+ "sync",
313
+ ),
291
314
  parameter_aliases={"_meta": "meta"},
292
315
  )
293
316
  def put_transform(
294
317
  self,
295
318
  *,
296
319
  transform_id: str,
297
- dest: t.Mapping[str, t.Any],
298
- source: t.Mapping[str, t.Any],
320
+ dest: t.Optional[t.Mapping[str, t.Any]] = None,
321
+ source: t.Optional[t.Mapping[str, t.Any]] = None,
299
322
  defer_validation: t.Optional[bool] = None,
300
323
  description: t.Optional[str] = None,
301
324
  error_trace: t.Optional[bool] = None,
@@ -310,11 +333,12 @@ class TransformClient(NamespacedClient):
310
333
  settings: t.Optional[t.Mapping[str, t.Any]] = None,
311
334
  sync: t.Optional[t.Mapping[str, t.Any]] = None,
312
335
  timeout: t.Optional[t.Union["t.Literal[-1]", "t.Literal[0]", str]] = None,
336
+ body: t.Optional[t.Dict[str, t.Any]] = None,
313
337
  ) -> ObjectApiResponse[t.Any]:
314
338
  """
315
339
  Instantiates a transform.
316
340
 
317
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/put-transform.html>`_
341
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/put-transform.html>`_
318
342
 
319
343
  :param transform_id: Identifier for the transform. This identifier can contain
320
344
  lowercase alphanumeric characters (a-z and 0-9), hyphens, and underscores.
@@ -348,45 +372,46 @@ class TransformClient(NamespacedClient):
348
372
  """
349
373
  if transform_id in SKIP_IN_PATH:
350
374
  raise ValueError("Empty value passed for parameter 'transform_id'")
351
- if dest is None:
375
+ if dest is None and body is None:
352
376
  raise ValueError("Empty value passed for parameter 'dest'")
353
- if source is None:
377
+ if source is None and body is None:
354
378
  raise ValueError("Empty value passed for parameter 'source'")
355
379
  __path = f"/_transform/{_quote(transform_id)}"
356
- __body: t.Dict[str, t.Any] = {}
357
380
  __query: t.Dict[str, t.Any] = {}
358
- if dest is not None:
359
- __body["dest"] = dest
360
- if source is not None:
361
- __body["source"] = source
381
+ __body: t.Dict[str, t.Any] = body if body is not None else {}
362
382
  if defer_validation is not None:
363
383
  __query["defer_validation"] = defer_validation
364
- if description is not None:
365
- __body["description"] = description
366
384
  if error_trace is not None:
367
385
  __query["error_trace"] = error_trace
368
386
  if filter_path is not None:
369
387
  __query["filter_path"] = filter_path
370
- if frequency is not None:
371
- __body["frequency"] = frequency
372
388
  if human is not None:
373
389
  __query["human"] = human
374
- if latest is not None:
375
- __body["latest"] = latest
376
- if meta is not None:
377
- __body["_meta"] = meta
378
- if pivot is not None:
379
- __body["pivot"] = pivot
380
390
  if pretty is not None:
381
391
  __query["pretty"] = pretty
382
- if retention_policy is not None:
383
- __body["retention_policy"] = retention_policy
384
- if settings is not None:
385
- __body["settings"] = settings
386
- if sync is not None:
387
- __body["sync"] = sync
388
392
  if timeout is not None:
389
393
  __query["timeout"] = timeout
394
+ if not __body:
395
+ if dest is not None:
396
+ __body["dest"] = dest
397
+ if source is not None:
398
+ __body["source"] = source
399
+ if description is not None:
400
+ __body["description"] = description
401
+ if frequency is not None:
402
+ __body["frequency"] = frequency
403
+ if latest is not None:
404
+ __body["latest"] = latest
405
+ if meta is not None:
406
+ __body["_meta"] = meta
407
+ if pivot is not None:
408
+ __body["pivot"] = pivot
409
+ if retention_policy is not None:
410
+ __body["retention_policy"] = retention_policy
411
+ if settings is not None:
412
+ __body["settings"] = settings
413
+ if sync is not None:
414
+ __body["sync"] = sync
390
415
  __headers = {"accept": "application/json", "content-type": "application/json"}
391
416
  return self.perform_request( # type: ignore[return-value]
392
417
  "PUT", __path, params=__query, headers=__headers, body=__body
@@ -406,7 +431,7 @@ class TransformClient(NamespacedClient):
406
431
  """
407
432
  Resets an existing transform.
408
433
 
409
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/reset-transform.html>`_
434
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/reset-transform.html>`_
410
435
 
411
436
  :param transform_id: Identifier for the transform. This identifier can contain
412
437
  lowercase alphanumeric characters (a-z and 0-9), hyphens, and underscores.
@@ -448,7 +473,7 @@ class TransformClient(NamespacedClient):
448
473
  """
449
474
  Schedules now a transform.
450
475
 
451
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/schedule-now-transform.html>`_
476
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/schedule-now-transform.html>`_
452
477
 
453
478
  :param transform_id: Identifier for the transform.
454
479
  :param timeout: Controls the time to wait for the scheduling to take place
@@ -489,7 +514,7 @@ class TransformClient(NamespacedClient):
489
514
  """
490
515
  Starts one or more transforms.
491
516
 
492
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/start-transform.html>`_
517
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/start-transform.html>`_
493
518
 
494
519
  :param transform_id: Identifier for the transform.
495
520
  :param from_: Restricts the set of transformed entities to those changed after
@@ -537,7 +562,7 @@ class TransformClient(NamespacedClient):
537
562
  """
538
563
  Stops one or more transforms.
539
564
 
540
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/stop-transform.html>`_
565
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/stop-transform.html>`_
541
566
 
542
567
  :param transform_id: Identifier for the transform. To stop multiple transforms,
543
568
  use a comma-separated list or a wildcard expression. To stop all transforms,
@@ -589,7 +614,16 @@ class TransformClient(NamespacedClient):
589
614
  )
590
615
 
591
616
  @_rewrite_parameters(
592
- body_fields=True,
617
+ body_fields=(
618
+ "description",
619
+ "dest",
620
+ "frequency",
621
+ "meta",
622
+ "retention_policy",
623
+ "settings",
624
+ "source",
625
+ "sync",
626
+ ),
593
627
  parameter_aliases={"_meta": "meta"},
594
628
  )
595
629
  def update_transform(
@@ -610,11 +644,12 @@ class TransformClient(NamespacedClient):
610
644
  source: t.Optional[t.Mapping[str, t.Any]] = None,
611
645
  sync: t.Optional[t.Mapping[str, t.Any]] = None,
612
646
  timeout: t.Optional[t.Union["t.Literal[-1]", "t.Literal[0]", str]] = None,
647
+ body: t.Optional[t.Dict[str, t.Any]] = None,
613
648
  ) -> ObjectApiResponse[t.Any]:
614
649
  """
615
650
  Updates certain properties of a transform.
616
651
 
617
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/update-transform.html>`_
652
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/update-transform.html>`_
618
653
 
619
654
  :param transform_id: Identifier for the transform.
620
655
  :param defer_validation: When true, deferrable validations are not run. This
@@ -639,35 +674,36 @@ class TransformClient(NamespacedClient):
639
674
  raise ValueError("Empty value passed for parameter 'transform_id'")
640
675
  __path = f"/_transform/{_quote(transform_id)}/_update"
641
676
  __query: t.Dict[str, t.Any] = {}
642
- __body: t.Dict[str, t.Any] = {}
677
+ __body: t.Dict[str, t.Any] = body if body is not None else {}
643
678
  if defer_validation is not None:
644
679
  __query["defer_validation"] = defer_validation
645
- if description is not None:
646
- __body["description"] = description
647
- if dest is not None:
648
- __body["dest"] = dest
649
680
  if error_trace is not None:
650
681
  __query["error_trace"] = error_trace
651
682
  if filter_path is not None:
652
683
  __query["filter_path"] = filter_path
653
- if frequency is not None:
654
- __body["frequency"] = frequency
655
684
  if human is not None:
656
685
  __query["human"] = human
657
- if meta is not None:
658
- __body["_meta"] = meta
659
686
  if pretty is not None:
660
687
  __query["pretty"] = pretty
661
- if retention_policy is not None:
662
- __body["retention_policy"] = retention_policy
663
- if settings is not None:
664
- __body["settings"] = settings
665
- if source is not None:
666
- __body["source"] = source
667
- if sync is not None:
668
- __body["sync"] = sync
669
688
  if timeout is not None:
670
689
  __query["timeout"] = timeout
690
+ if not __body:
691
+ if description is not None:
692
+ __body["description"] = description
693
+ if dest is not None:
694
+ __body["dest"] = dest
695
+ if frequency is not None:
696
+ __body["frequency"] = frequency
697
+ if meta is not None:
698
+ __body["_meta"] = meta
699
+ if retention_policy is not None:
700
+ __body["retention_policy"] = retention_policy
701
+ if settings is not None:
702
+ __body["settings"] = settings
703
+ if source is not None:
704
+ __body["source"] = source
705
+ if sync is not None:
706
+ __body["sync"] = sync
671
707
  __headers = {"accept": "application/json", "content-type": "application/json"}
672
708
  return self.perform_request( # type: ignore[return-value]
673
709
  "POST", __path, params=__query, headers=__headers, body=__body
@@ -687,7 +723,7 @@ class TransformClient(NamespacedClient):
687
723
  """
688
724
  Upgrades all transforms.
689
725
 
690
- `<https://www.elastic.co/guide/en/elasticsearch/reference/8.11/upgrade-transforms.html>`_
726
+ `<https://www.elastic.co/guide/en/elasticsearch/reference/8.12/upgrade-transforms.html>`_
691
727
 
692
728
  :param dry_run: When true, the request checks for updates but does not run them.
693
729
  :param timeout: Period to wait for a response. If no response is received before
@@ -74,6 +74,8 @@ _TYPE_HOSTS = Union[
74
74
  str, Sequence[Union[str, Mapping[str, Union[str, int]], NodeConfig]]
75
75
  ]
76
76
 
77
+ _TYPE_BODY = Union[bytes, str, Dict[str, Any]]
78
+
77
79
  _TYPE_ASYNC_SNIFF_CALLBACK = Callable[
78
80
  [AsyncTransport, SniffOptions], Awaitable[List[NodeConfig]]
79
81
  ]
@@ -289,14 +291,40 @@ def _merge_kwargs_no_duplicates(kwargs: Dict[str, Any], values: Dict[str, Any])
289
291
  if key in kwargs:
290
292
  raise ValueError(
291
293
  f"Received multiple values for '{key}', specify parameters "
292
- "directly instead of using 'body' or 'params'"
294
+ "directly instead of using 'params'"
293
295
  )
294
296
  kwargs[key] = val
295
297
 
296
298
 
299
+ def _merge_body_fields_no_duplicates(
300
+ body: _TYPE_BODY, kwargs: Dict[str, Any], body_fields: Tuple[str, ...]
301
+ ) -> None:
302
+ for key in list(kwargs.keys()):
303
+ if key in body_fields:
304
+ if isinstance(body, (str, bytes)):
305
+ raise ValueError(
306
+ "Couldn't merge 'body' with other parameters as it wasn't a mapping."
307
+ )
308
+
309
+ if key in body:
310
+ raise ValueError(
311
+ f"Received multiple values for '{key}', specify parameters "
312
+ "using either body or parameters, not both."
313
+ )
314
+
315
+ warnings.warn(
316
+ f"Received '{key}' via a specific parameter in the presence of a "
317
+ "'body' parameter, which is deprecated and will be removed in a future "
318
+ "version. Instead, use only 'body' or only specific paremeters.",
319
+ category=DeprecationWarning,
320
+ stacklevel=warn_stacklevel(),
321
+ )
322
+ body[key] = kwargs.pop(key)
323
+
324
+
297
325
  def _rewrite_parameters(
298
326
  body_name: Optional[str] = None,
299
- body_fields: bool = False,
327
+ body_fields: Optional[Tuple[str, ...]] = None,
300
328
  parameter_aliases: Optional[Dict[str, str]] = None,
301
329
  ignore_deprecated_options: Optional[Set[str]] = None,
302
330
  ) -> Callable[[F], F]:
@@ -372,7 +400,7 @@ def _rewrite_parameters(
372
400
  if "body" in kwargs and (
373
401
  not ignore_deprecated_options or "body" not in ignore_deprecated_options
374
402
  ):
375
- body = kwargs.pop("body")
403
+ body: Optional[_TYPE_BODY] = kwargs.pop("body")
376
404
  if body is not None:
377
405
  if body_name:
378
406
  if body_name in kwargs:
@@ -384,13 +412,9 @@ def _rewrite_parameters(
384
412
  )
385
413
  kwargs[body_name] = body
386
414
 
387
- elif body_fields:
388
- if not hasattr(body, "items"):
389
- raise ValueError(
390
- "Couldn't merge 'body' with other parameters as it wasn't a mapping. "
391
- "Instead of using 'body' use individual API parameters"
392
- )
393
- _merge_kwargs_no_duplicates(kwargs, body)
415
+ elif body_fields is not None:
416
+ _merge_body_fields_no_duplicates(body, kwargs, body_fields)
417
+ kwargs["body"] = body
394
418
 
395
419
  if parameter_aliases:
396
420
  for alias, rename_to in parameter_aliases.items():