Skip to content

Commit 9e7e5e7

Browse files
committed
feat(beta): Add a timeout for waiting on document translation
1 parent ddaef45 commit 9e7e5e7

File tree

3 files changed

+87
-12
lines changed

3 files changed

+87
-12
lines changed

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
### Added
9+
* (beta) optional parameter to specify timeout for document translation calls
10+
<!-- * add to here -->
11+
### Changed
12+
<!-- * add to here -->
713

814
## [1.21.0] - 2025-01-15
915
### Added
@@ -324,7 +330,8 @@ Version increased to avoid conflicts with old packages on PyPI.
324330
## [0.1.0] - 2021-07-26
325331
Initial version.
326332

327-
333+
<!-- Unreleased shoud never be deleted -->
334+
[Unreleased]: https://github.com/DeepLcom/deepl-python/compare/v1.21.0...HEAD
328335
[1.21.0]: https://github.com/DeepLcom/deepl-python/compare/v1.20.0...v1.21.0
329336
[1.20.0]: https://github.com/DeepLcom/deepl-python/compare/v1.19.1...v1.20.0
330337
[1.19.1]: https://github.com/DeepLcom/deepl-python/compare/v1.19.0...v1.19.1

deepl/translator.py

+37-11
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ def translate_document_from_filepath(
536536
target_lang: str,
537537
formality: Union[str, Formality] = Formality.DEFAULT,
538538
glossary: Union[str, GlossaryInfo, None] = None,
539+
timeout_s: Optional[int] = None,
539540
) -> DocumentStatus:
540541
"""Upload document at given input path, translate it into the target
541542
language, and download result to given output path.
@@ -551,6 +552,9 @@ def translate_document_from_filepath(
551552
Formality enum, "less", "more", "prefer_less", or "prefer_more".
552553
:param glossary: (Optional) glossary or glossary ID to use for
553554
translation. Must match specified source_lang and target_lang.
555+
:param timeout_s: (beta) (Optional) Maximum time to wait before
556+
the call raises an error. Note that this is not accurate to the
557+
second, but only polls every 5 seconds.
554558
:return: DocumentStatus when document translation completed, this
555559
allows the number of billed characters to be queried.
556560
@@ -574,6 +578,7 @@ def translate_document_from_filepath(
574578
formality=formality,
575579
glossary=glossary,
576580
output_format=output_format,
581+
timeout_s=timeout_s,
577582
)
578583
except Exception as e:
579584
out_file.close()
@@ -591,6 +596,7 @@ def translate_document(
591596
glossary: Union[str, GlossaryInfo, None] = None,
592597
filename: Optional[str] = None,
593598
output_format: Optional[str] = None,
599+
timeout_s: Optional[int] = None,
594600
) -> DocumentStatus:
595601
"""Upload document, translate it into the target language, and download
596602
result.
@@ -612,6 +618,9 @@ def translate_document(
612618
if uploading string or bytes containing file content.
613619
:param output_format: (Optional) Desired output file extension, if
614620
it differs from the input file format.
621+
:param timeout_s: (beta) (Optional) Maximum time to wait before
622+
the call raises an error. Note that this is not accurate to the
623+
second, but only polls every 5 seconds.
615624
:return: DocumentStatus when document translation completed, this
616625
allows the number of billed characters to be queried.
617626
@@ -630,7 +639,7 @@ def translate_document(
630639
)
631640

632641
try:
633-
status = self.translate_document_wait_until_done(handle)
642+
status = self.translate_document_wait_until_done(handle, timeout_s)
634643
if status.ok:
635644
self.translate_document_download(handle, output_document)
636645
except Exception as e:
@@ -752,27 +761,44 @@ def translate_document_get_status(
752761
)
753762

754763
def translate_document_wait_until_done(
755-
self, handle: DocumentHandle
764+
self,
765+
handle: DocumentHandle,
766+
timeout_s: Optional[int] = None,
756767
) -> DocumentStatus:
757768
"""
758769
Continually polls the status of the document translation associated
759770
with the given handle, sleeping in between requests, and returns the
760771
final status when the translation completes (whether successful or
761772
not).
762-
763773
:param handle: DocumentHandle to the document translation to wait on.
774+
:param timeout_s: (beta) (Optional) Maximum time to wait before
775+
the call raises an error. Note that this is not accurate to the
776+
second, but only polls every 5 seconds.
764777
:return: DocumentStatus containing the status when completed.
765778
"""
766779
status = self.translate_document_get_status(handle)
780+
start_time_s = time.time()
767781
while status.ok and not status.done:
768-
secs = 5.0 # seconds_remaining is currently unreliable, so just
769-
# poll equidistantly
770-
util.log_info(
771-
f"Rechecking document translation status "
772-
f"after sleeping for {secs:.3f} seconds."
773-
)
774-
time.sleep(secs)
775-
status = self.translate_document_get_status(handle)
782+
if (
783+
timeout_s is not None
784+
and time.time() - start_time_s > timeout_s
785+
):
786+
raise DeepLException(
787+
f"Manual timeout of {timeout_s}s exceeded for"
788+
+ " document translation",
789+
should_retry=False,
790+
)
791+
else:
792+
secs = (
793+
5.0 # seconds_remaining is currently unreliable, so just
794+
)
795+
# poll equidistantly
796+
util.log_info(
797+
f"Rechecking document translation status "
798+
f"after sleeping for {secs:.3f} seconds."
799+
)
800+
time.sleep(secs)
801+
status = self.translate_document_get_status(handle)
776802
return status
777803

778804
def translate_document_download(

tests/test_translate_document.py

+42
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,48 @@ def test_translate_document_with_waiting(
7373
assert example_document_translation == output_document_path.read_text()
7474

7575

76+
@needs_mock_server
77+
def test_translate_document_with_timeout_succeeds(
78+
translator,
79+
server,
80+
example_document_path,
81+
example_document_translation,
82+
output_document_path,
83+
):
84+
server.set_doc_queue_time(2 * 1000)
85+
server.set_doc_translate_time(2 * 1000)
86+
87+
translator.translate_document_from_filepath(
88+
example_document_path,
89+
output_path=output_document_path,
90+
timeout_s=10,
91+
**default_lang_args,
92+
)
93+
assert example_document_translation == output_document_path.read_text()
94+
95+
96+
@needs_mock_server
97+
def test_translate_document_with_manual_timeout_fails(
98+
translator,
99+
server,
100+
example_document_path,
101+
output_document_path,
102+
):
103+
server.set_doc_queue_time(11 * 1000)
104+
server.set_doc_translate_time(11 * 1000)
105+
106+
with pytest.raises(
107+
deepl.DeepLException,
108+
match="Manual timeout of 10s exceeded for document translation",
109+
):
110+
translator.translate_document_from_filepath(
111+
example_document_path,
112+
output_path=output_document_path,
113+
timeout_s=10,
114+
**default_lang_args,
115+
)
116+
117+
76118
@needs_mock_server
77119
def test_translate_large_document(
78120
translator, example_large_document_path, example_large_document_translation

0 commit comments

Comments
 (0)