Skip to content

Commit 8938bbf

Browse files
hive-botclaude
andcommitted
feat: take CustomInstruction and extra_body_parameters in style rules
get_style_rule_custom_instruction(), update_style_rule_custom_instruction() and delete_style_rule_custom_instruction() take a CustomInstruction as instruction_id. The five style rule functions that send a JSON body take extra_body_parameters, merged into the body last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1bd52f2 commit 8938bbf

4 files changed

Lines changed: 181 additions & 25 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Added `NotFoundException`, raised for HTTP 404 on every endpoint.
1717
`GlossaryNotFoundException` is now its subclass.
1818
- Added `is_free_account_auth_key()`, which replaces `auth_key_is_free_account()`.
19+
- Added the `extra_body_parameters` option to `create_style_rule()`,
20+
`update_style_rule_name()`, `update_style_rule_configured_rules()`,
21+
`create_style_rule_custom_instruction()` and
22+
`update_style_rule_custom_instruction()`.
23+
- `get_style_rule_custom_instruction()`,
24+
`update_style_rule_custom_instruction()` and
25+
`delete_style_rule_custom_instruction()` accept a `CustomInstruction` object
26+
as `instruction_id`, as well as an ID string.
1927
- Added the `max_retries` and `timeout_s` options to the `Translator`,
2028
`DeepLClient` and `DeepLAdminClient` constructors, to set the maximum number
2129
of retries and the minimum per-attempt timeout in seconds for each client.

‎README.md‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,10 @@ updated = deepl_client.update_style_rule_configured_rules(
969969
Custom instructions allow you to add free-text prompts to a style rule. Use
970970
`create_style_rule_custom_instruction()`, `get_style_rule_custom_instruction()`,
971971
`update_style_rule_custom_instruction()`, and
972-
`delete_style_rule_custom_instruction()` to manage them.
972+
`delete_style_rule_custom_instruction()` to manage them. The `style_rule`
973+
argument of every style rule function takes a style rule ID or a
974+
`StyleRuleInfo` object, and `instruction_id` takes a custom instruction ID or a
975+
`CustomInstruction` object.
973976

974977
```python
975978
# Create a custom instruction
@@ -993,9 +996,20 @@ updated = deepl_client.update_style_rule_custom_instruction(
993996
prompt="Use very formal language",
994997
)
995998

996-
# Delete a custom instruction
997-
deepl_client.delete_style_rule_custom_instruction(
998-
"YOUR_STYLE_ID", instruction.id
999+
# Delete a custom instruction (by ID or CustomInstruction object)
1000+
deepl_client.delete_style_rule_custom_instruction("YOUR_STYLE_ID", instruction)
1001+
```
1002+
1003+
`create_style_rule()`, `update_style_rule_name()`,
1004+
`update_style_rule_configured_rules()`, `create_style_rule_custom_instruction()`
1005+
and `update_style_rule_custom_instruction()` also accept
1006+
`extra_body_parameters`: a dictionary of extra parameters to pass in the JSON
1007+
request body, which replace request fields of the same name. See the
1008+
`extra_body_parameters` [text translation option](#text-translation-options).
1009+
1010+
```python
1011+
deepl_client.update_style_rule_name(
1012+
"YOUR_STYLE_ID", "New Name", extra_body_parameters={"beta_param": "value"}
9991013
)
10001014
```
10011015

‎deepl/deepl_client.py‎

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,13 +1329,19 @@ def create_style_rule(
13291329
language: str,
13301330
configured_rules: Optional[dict] = None,
13311331
custom_instructions: Optional[List[dict]] = None,
1332+
*,
1333+
extra_body_parameters: Optional[dict] = None,
13321334
) -> StyleRuleInfo:
13331335
"""Creates a new style rule.
13341336
13351337
:param name: Name for the style rule.
13361338
:param language: Language code for the style rule.
13371339
:param configured_rules: Optional dict of configured rules.
13381340
:param custom_instructions: Optional list of custom instruction dicts.
1341+
:param extra_body_parameters: (Optional) Additional key/value pairs to
1342+
include in the JSON request body sent to the API. If provided,
1343+
keys in this dict will be added to the request body, and replace
1344+
keys of the same name that the client sets.
13391345
:return: StyleRuleInfo for the created style rule.
13401346
"""
13411347
if not name:
@@ -1350,7 +1356,8 @@ def create_style_rule(
13501356
request_data["custom_instructions"] = custom_instructions
13511357

13521358
status, content, json = self._api_call(
1353-
"v3/style_rules", json=request_data
1359+
"v3/style_rules",
1360+
json=self._with_extra_body(request_data, extra_body_parameters),
13541361
)
13551362
self._raise_for_status(status, content, json)
13561363
return self._parse_style_rule_response(StyleRuleInfo.from_json, json)
@@ -1375,19 +1382,27 @@ def update_style_rule_name(
13751382
self,
13761383
style_rule: Union[str, StyleRuleInfo],
13771384
name: str,
1385+
*,
1386+
extra_body_parameters: Optional[dict] = None,
13781387
) -> StyleRuleInfo:
13791388
"""Updates the name of a style rule.
13801389
13811390
:param style_rule: Style rule ID string or StyleRuleInfo object.
13821391
:param name: New name for the style rule.
1392+
:param extra_body_parameters: (Optional) Additional key/value pairs to
1393+
include in the JSON request body sent to the API. If provided,
1394+
keys in this dict will be added to the request body, and replace
1395+
keys of the same name that the client sets.
13831396
:return: Updated StyleRuleInfo.
13841397
"""
13851398
style_id = self._style_rule_id(style_rule)
13861399
if not name:
13871400
raise ValueError("name must not be empty")
13881401
request_data = {"name": name}
13891402
status, content, json = self._api_call(
1390-
f"v3/style_rules/{style_id}", method="PATCH", json=request_data
1403+
f"v3/style_rules/{style_id}",
1404+
method="PATCH",
1405+
json=self._with_extra_body(request_data, extra_body_parameters),
13911406
)
13921407
self._raise_for_status(status, content, json)
13931408
return self._parse_style_rule_response(StyleRuleInfo.from_json, json)
@@ -1410,18 +1425,26 @@ def update_style_rule_configured_rules(
14101425
self,
14111426
style_rule: Union[str, StyleRuleInfo],
14121427
configured_rules: dict,
1428+
*,
1429+
extra_body_parameters: Optional[dict] = None,
14131430
) -> StyleRuleInfo:
14141431
"""Updates the configured rules of a style rule.
14151432
14161433
:param style_rule: Style rule ID string or StyleRuleInfo object.
14171434
:param configured_rules: Dict of configured rules to set.
1435+
:param extra_body_parameters: (Optional) Additional key/value pairs to
1436+
include in the JSON request body sent to the API. If provided,
1437+
keys in this dict will be added to the request body, and replace
1438+
keys of the same name that the client sets.
14181439
:return: Updated StyleRuleInfo.
14191440
"""
14201441
style_id = self._style_rule_id(style_rule)
14211442
status, content, json = self._api_call(
14221443
f"v3/style_rules/{style_id}/configured_rules",
14231444
method="PUT",
1424-
json=configured_rules,
1445+
json=self._with_extra_body(
1446+
configured_rules, extra_body_parameters
1447+
),
14251448
)
14261449
self._raise_for_status(status, content, json)
14271450
return self._parse_style_rule_response(StyleRuleInfo.from_json, json)
@@ -1432,13 +1455,19 @@ def create_style_rule_custom_instruction(
14321455
label: str,
14331456
prompt: str,
14341457
source_language: Optional[str] = None,
1458+
*,
1459+
extra_body_parameters: Optional[dict] = None,
14351460
) -> CustomInstruction:
14361461
"""Creates a custom instruction for a style rule.
14371462
14381463
:param style_rule: Style rule ID string or StyleRuleInfo object.
14391464
:param label: Label for the custom instruction.
14401465
:param prompt: Prompt text for the custom instruction.
14411466
:param source_language: Optional source language code.
1467+
:param extra_body_parameters: (Optional) Additional key/value pairs to
1468+
include in the JSON request body sent to the API. If provided,
1469+
keys in this dict will be added to the request body, and replace
1470+
keys of the same name that the client sets.
14421471
:return: Created CustomInstruction.
14431472
"""
14441473
style_id = self._style_rule_id(style_rule)
@@ -1451,7 +1480,7 @@ def create_style_rule_custom_instruction(
14511480
request_data["source_language"] = source_language
14521481
status, content, json = self._api_call(
14531482
f"v3/style_rules/{style_id}/custom_instructions",
1454-
json=request_data,
1483+
json=self._with_extra_body(request_data, extra_body_parameters),
14551484
)
14561485
self._raise_for_status(status, content, json)
14571486
return self._parse_style_rule_response(
@@ -1461,19 +1490,18 @@ def create_style_rule_custom_instruction(
14611490
def get_style_rule_custom_instruction(
14621491
self,
14631492
style_rule: Union[str, StyleRuleInfo],
1464-
instruction_id: str,
1493+
instruction_id: Union[str, CustomInstruction],
14651494
) -> CustomInstruction:
14661495
"""Retrieves a custom instruction by ID.
14671496
14681497
:param style_rule: Style rule ID string or StyleRuleInfo object.
1469-
:param instruction_id: ID of the custom instruction.
1498+
:param instruction_id: ID of the custom instruction, or a
1499+
CustomInstruction object.
14701500
:return: CustomInstruction.
14711501
"""
14721502
style_id = self._style_rule_id(style_rule)
14731503
instruction = self._custom_instruction_id(instruction_id)
1474-
url = (
1475-
f"v3/style_rules/{style_id}" f"/custom_instructions/{instruction}"
1476-
)
1504+
url = f"v3/style_rules/{style_id}/custom_instructions/{instruction}"
14771505
status, content, json = self._api_call(
14781506
url,
14791507
method="GET",
@@ -1486,18 +1514,25 @@ def get_style_rule_custom_instruction(
14861514
def update_style_rule_custom_instruction(
14871515
self,
14881516
style_rule: Union[str, StyleRuleInfo],
1489-
instruction_id: str,
1517+
instruction_id: Union[str, CustomInstruction],
14901518
label: str,
14911519
prompt: str,
14921520
source_language: Optional[str] = None,
1521+
*,
1522+
extra_body_parameters: Optional[dict] = None,
14931523
) -> CustomInstruction:
14941524
"""Updates a custom instruction.
14951525
14961526
:param style_rule: Style rule ID string or StyleRuleInfo object.
1497-
:param instruction_id: ID of the custom instruction.
1527+
:param instruction_id: ID of the custom instruction, or a
1528+
CustomInstruction object.
14981529
:param label: New label for the custom instruction.
14991530
:param prompt: New prompt text for the custom instruction.
15001531
:param source_language: Optional source language code.
1532+
:param extra_body_parameters: (Optional) Additional key/value pairs to
1533+
include in the JSON request body sent to the API. If provided,
1534+
keys in this dict will be added to the request body, and replace
1535+
keys of the same name that the client sets.
15011536
:return: Updated CustomInstruction.
15021537
"""
15031538
style_id = self._style_rule_id(style_rule)
@@ -1509,13 +1544,11 @@ def update_style_rule_custom_instruction(
15091544
request_data = {"label": label, "prompt": prompt}
15101545
if source_language is not None:
15111546
request_data["source_language"] = source_language
1512-
url = (
1513-
f"v3/style_rules/{style_id}" f"/custom_instructions/{instruction}"
1514-
)
1547+
url = f"v3/style_rules/{style_id}/custom_instructions/{instruction}"
15151548
status, content, json = self._api_call(
15161549
url,
15171550
method="PUT",
1518-
json=request_data,
1551+
json=self._with_extra_body(request_data, extra_body_parameters),
15191552
)
15201553
self._raise_for_status(status, content, json)
15211554
return self._parse_style_rule_response(
@@ -1525,18 +1558,17 @@ def update_style_rule_custom_instruction(
15251558
def delete_style_rule_custom_instruction(
15261559
self,
15271560
style_rule: Union[str, StyleRuleInfo],
1528-
instruction_id: str,
1561+
instruction_id: Union[str, CustomInstruction],
15291562
) -> None:
15301563
"""Deletes a custom instruction from a style rule.
15311564
15321565
:param style_rule: Style rule ID string or StyleRuleInfo object.
1533-
:param instruction_id: ID of the custom instruction to delete.
1566+
:param instruction_id: ID of the custom instruction to delete, or a
1567+
CustomInstruction object.
15341568
"""
15351569
style_id = self._style_rule_id(style_rule)
15361570
instruction = self._custom_instruction_id(instruction_id)
1537-
url = (
1538-
f"v3/style_rules/{style_id}" f"/custom_instructions/{instruction}"
1539-
)
1571+
url = f"v3/style_rules/{style_id}/custom_instructions/{instruction}"
15401572
status, content, json = self._api_call(
15411573
url,
15421574
method="DELETE",
@@ -1774,6 +1806,11 @@ def _style_profile_json(
17741806
request_data.update(extra_body_parameters)
17751807
return request_data
17761808

1809+
@staticmethod
1810+
def _with_extra_body(body: dict, extra: Optional[dict]) -> dict:
1811+
# Entries replace keys of the same name set by the client.
1812+
return {**body, **extra} if extra else body
1813+
17771814
@staticmethod
17781815
def _parse_style_rule_response(
17791816
parse: Callable[[Any], _T], json: Any
@@ -1794,7 +1831,11 @@ def _style_rule_id(style_rule: Union[str, StyleRuleInfo]) -> str:
17941831
return urllib.parse.quote(style_rule, safe="")
17951832

17961833
@staticmethod
1797-
def _custom_instruction_id(instruction_id: str) -> str:
1834+
def _custom_instruction_id(
1835+
instruction_id: Union[str, CustomInstruction, None],
1836+
) -> str:
1837+
if isinstance(instruction_id, CustomInstruction):
1838+
instruction_id = instruction_id.id
17981839
if not instruction_id:
17991840
raise ValueError("instruction_id must not be empty")
18001841
return urllib.parse.quote(instruction_id, safe="")

‎tests/offline/test_style_rules.py‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,96 @@ def test_raises_deepl_exception_for_malformed_response(
418418

419419
with pytest.raises(DeepLException, match="Unexpected style rule response"):
420420
call(make_deepl_client())
421+
422+
423+
@pytest.mark.parametrize(
424+
"instruction",
425+
["instruction-1", CustomInstruction.from_json(INSTRUCTION_JSON)],
426+
)
427+
def test_accepts_instruction_id_or_object(mocked, instruction):
428+
path = url("v3/style_rules/style-1/custom_instructions/instruction-1")
429+
mocked.get(path, json=INSTRUCTION_JSON, match=bodyless_request())
430+
mocked.put(path, json=INSTRUCTION_JSON)
431+
mocked.delete(path, status=204, match=bodyless_request())
432+
client = make_deepl_client()
433+
434+
client.get_style_rule_custom_instruction("style-1", instruction)
435+
client.update_style_rule_custom_instruction(
436+
"style-1", instruction, "L", "P"
437+
)
438+
client.delete_style_rule_custom_instruction("style-1", instruction)
439+
440+
441+
def test_rejects_instruction_object_without_id():
442+
with pytest.raises(ValueError, match="instruction_id must not be empty"):
443+
make_deepl_client().get_style_rule_custom_instruction(
444+
"style-1", CustomInstruction("L", "P")
445+
)
446+
447+
448+
EXTRA = {"name": "replaced", "extra": True}
449+
450+
451+
@pytest.mark.parametrize(
452+
"method, path, body, response, call",
453+
[
454+
(
455+
"POST",
456+
"v3/style_rules",
457+
{"name": "replaced", "language": "en", "extra": True},
458+
STYLE_RULE_JSON,
459+
lambda c: c.create_style_rule(
460+
"style", "en", extra_body_parameters=EXTRA
461+
),
462+
),
463+
(
464+
"PATCH",
465+
"v3/style_rules/style-1",
466+
{"name": "replaced", "extra": True},
467+
STYLE_RULE_JSON,
468+
lambda c: c.update_style_rule_name(
469+
"style-1", "new", extra_body_parameters=EXTRA
470+
),
471+
),
472+
(
473+
"PUT",
474+
"v3/style_rules/style-1/configured_rules",
475+
{**CONFIGURED_RULES, "name": "replaced", "extra": True},
476+
STYLE_RULE_JSON,
477+
lambda c: c.update_style_rule_configured_rules(
478+
"style-1", CONFIGURED_RULES, extra_body_parameters=EXTRA
479+
),
480+
),
481+
(
482+
"POST",
483+
"v3/style_rules/style-1/custom_instructions",
484+
{"label": "replaced", "prompt": "P", "extra": True},
485+
INSTRUCTION_JSON,
486+
lambda c: c.create_style_rule_custom_instruction(
487+
"style-1",
488+
"L",
489+
"P",
490+
extra_body_parameters={"label": "replaced", "extra": True},
491+
),
492+
),
493+
(
494+
"PUT",
495+
"v3/style_rules/style-1/custom_instructions/instruction-1",
496+
{"label": "replaced", "prompt": "P", "extra": True},
497+
INSTRUCTION_JSON,
498+
lambda c: c.update_style_rule_custom_instruction(
499+
"style-1",
500+
"instruction-1",
501+
"L",
502+
"P",
503+
extra_body_parameters={"label": "replaced", "extra": True},
504+
),
505+
),
506+
],
507+
)
508+
def test_merges_extra_body_parameters_last(
509+
mocked, method, path, body, response, call
510+
):
511+
mocked.add(method, url(path), json=response, match=json_request(body))
512+
513+
call(make_deepl_client())

0 commit comments

Comments
 (0)