sapling-py 4.4.0__tar.gz → 4.11.0__tar.gz
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.
- {sapling_py-4.4.0/sapling_py.egg-info → sapling_py-4.11.0}/PKG-INFO +18 -3
- {sapling_py-4.4.0 → sapling_py-4.11.0}/README.md +17 -2
- {sapling_py-4.4.0 → sapling_py-4.11.0}/sapling/client.py +202 -25
- sapling_py-4.11.0/sapling/version.py +1 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0/sapling_py.egg-info}/PKG-INFO +18 -3
- {sapling_py-4.4.0 → sapling_py-4.11.0}/sapling_py.egg-info/SOURCES.txt +1 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/test/test_classify.py +23 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/test/test_client.py +122 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/test/test_extract.py +24 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/test/test_safety.py +23 -0
- sapling_py-4.11.0/test/test_simplify.py +95 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/test/test_translate.py +28 -0
- sapling_py-4.4.0/sapling/version.py +0 -1
- {sapling_py-4.4.0 → sapling_py-4.11.0}/LICENSE +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/pyproject.toml +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/sapling/__init__.py +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/sapling_py.egg-info/dependency_links.txt +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/sapling_py.egg-info/requires.txt +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/sapling_py.egg-info/top_level.txt +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/setup.cfg +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/setup.py +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/test/test_pii.py +0 -0
- {sapling_py-4.4.0 → sapling_py-4.11.0}/test/test_seo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sapling-py
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.11.0
|
|
4
4
|
Summary: Sapling Python Client
|
|
5
5
|
Author-email: Sapling Intelligence <info@sapling.ai>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -31,8 +31,9 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
|
|
|
31
31
|
|
|
32
32
|
Sapling is a toolkit for helping developers build language model-powered applications.
|
|
33
33
|
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
34
|
-
zero-shot text classification, structured data extraction, translation, PII detection
|
|
35
|
-
content safety scoring,
|
|
34
|
+
zero-shot text classification, structured data extraction, translation, PII detection
|
|
35
|
+
and redaction, content safety scoring, plain-language
|
|
36
|
+
simplification, SEO analysis, and more.
|
|
36
37
|
|
|
37
38
|
Key Features:
|
|
38
39
|
- **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
|
|
@@ -94,6 +95,20 @@ Get a production key by following [this documentation](https://sapling.ai/docs/a
|
|
|
94
95
|
|
|
95
96
|
Here's some more information on [request options and response structure](https://sapling.ai/docs/api/edits-overview).
|
|
96
97
|
|
|
98
|
+
## Batch requests
|
|
99
|
+
|
|
100
|
+
The analysis endpoints — `classify`, `extract`, `safety`, `langdetect`,
|
|
101
|
+
`translate`, `summarize`, `quality` and `aidetect` — also accept a list of
|
|
102
|
+
1-10 texts in
|
|
103
|
+
place of a single string. The batch is sent as one request (the same options
|
|
104
|
+
apply to every item) and returns `{'results': [...]}` with one
|
|
105
|
+
single-response-shaped dict per input, in order:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
scores = client.quality(['First draft.', 'Second draft.'], sentence_scores=True)
|
|
109
|
+
langs = client.langdetect(['Bonjour le monde', 'Hello world'])
|
|
110
|
+
```
|
|
111
|
+
|
|
97
112
|
## License
|
|
98
113
|
|
|
99
114
|
Copyright 2024--present Sapling Intelligence, Inc.
|
|
@@ -9,8 +9,9 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
|
|
|
9
9
|
|
|
10
10
|
Sapling is a toolkit for helping developers build language model-powered applications.
|
|
11
11
|
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
12
|
-
zero-shot text classification, structured data extraction, translation, PII detection
|
|
13
|
-
content safety scoring,
|
|
12
|
+
zero-shot text classification, structured data extraction, translation, PII detection
|
|
13
|
+
and redaction, content safety scoring, plain-language
|
|
14
|
+
simplification, SEO analysis, and more.
|
|
14
15
|
|
|
15
16
|
Key Features:
|
|
16
17
|
- **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
|
|
@@ -72,6 +73,20 @@ Get a production key by following [this documentation](https://sapling.ai/docs/a
|
|
|
72
73
|
|
|
73
74
|
Here's some more information on [request options and response structure](https://sapling.ai/docs/api/edits-overview).
|
|
74
75
|
|
|
76
|
+
## Batch requests
|
|
77
|
+
|
|
78
|
+
The analysis endpoints — `classify`, `extract`, `safety`, `langdetect`,
|
|
79
|
+
`translate`, `summarize`, `quality` and `aidetect` — also accept a list of
|
|
80
|
+
1-10 texts in
|
|
81
|
+
place of a single string. The batch is sent as one request (the same options
|
|
82
|
+
apply to every item) and returns `{'results': [...]}` with one
|
|
83
|
+
single-response-shaped dict per input, in order:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
scores = client.quality(['First draft.', 'Second draft.'], sentence_scores=True)
|
|
87
|
+
langs = client.langdetect(['Bonjour le monde', 'Hello world'])
|
|
88
|
+
```
|
|
89
|
+
|
|
75
90
|
## License
|
|
76
91
|
|
|
77
92
|
Copyright 2024--present Sapling Intelligence, Inc.
|
|
@@ -430,8 +430,13 @@ class SaplingClient:
|
|
|
430
430
|
'''
|
|
431
431
|
Score a piece of text on how likely it was generated by AI.
|
|
432
432
|
|
|
433
|
-
:param text: Text to score for AI-generated content
|
|
434
|
-
|
|
433
|
+
:param text: Text to score for AI-generated content (up to 200,000
|
|
434
|
+
characters). Pass a list or tuple of 1-10 strings to score a batch
|
|
435
|
+
in one request (sent as the API's ``texts`` parameter; the same
|
|
436
|
+
options apply to every item, and the combined length is capped at
|
|
437
|
+
200,000 characters). A batch response is ``{'results': [...]}``
|
|
438
|
+
with one single-response-shaped dict per input, in order.
|
|
439
|
+
:type text: str | list[str]
|
|
435
440
|
:param sent_scores: If true, each sentence will also be scored individually.
|
|
436
441
|
:type sent_scores: bool
|
|
437
442
|
|
|
@@ -445,12 +450,94 @@ class SaplingClient:
|
|
|
445
450
|
url = f'{self.url_endpoint}aidetect'
|
|
446
451
|
data = {
|
|
447
452
|
'key': self.api_key,
|
|
448
|
-
'text': text,
|
|
449
453
|
}
|
|
454
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
455
|
+
# item ({'results': [...]}), same options applied to every item.
|
|
456
|
+
if isinstance(text, (list, tuple)):
|
|
457
|
+
data['texts'] = list(text)
|
|
458
|
+
else:
|
|
459
|
+
data['text'] = text
|
|
450
460
|
if sent_scores is not None:
|
|
451
461
|
data['sent_scores'] = sent_scores
|
|
452
462
|
return self._request(url, data)
|
|
453
463
|
|
|
464
|
+
def simplify(
|
|
465
|
+
self,
|
|
466
|
+
text,
|
|
467
|
+
reading_level=None,
|
|
468
|
+
preserve_terms=None,
|
|
469
|
+
lang=None,
|
|
470
|
+
):
|
|
471
|
+
'''
|
|
472
|
+
Rewrites a text in plain language at a target reading level, keeping
|
|
473
|
+
all of its content, structure and language — nothing is summarized or
|
|
474
|
+
dropped. The response includes a deterministic before/after
|
|
475
|
+
readability block (Flesch-Kincaid grade and reading ease) when the
|
|
476
|
+
text's language supports it, so the improvement is verifiable.
|
|
477
|
+
|
|
478
|
+
Example::
|
|
479
|
+
|
|
480
|
+
client.simplify(
|
|
481
|
+
'The party of the first part shall remit payment within '
|
|
482
|
+
'thirty (30) days of receipt of the invoice.',
|
|
483
|
+
reading_level='plain',
|
|
484
|
+
preserve_terms=['invoice'],
|
|
485
|
+
)
|
|
486
|
+
# {'simplified': 'You must pay within 30 days of getting '
|
|
487
|
+
# 'the invoice.',
|
|
488
|
+
# 'reading_level': 'plain', 'lang': 'en',
|
|
489
|
+
# 'readability': {'before': {'grade': 12.3, 'ease': 42.1},
|
|
490
|
+
# 'after': {'grade': 5.8, 'ease': 78.4}}}
|
|
491
|
+
|
|
492
|
+
:param text: Text to simplify, up to 5,000 characters. Sent to the
|
|
493
|
+
model as submitted — markup and line structure are preserved, not
|
|
494
|
+
stripped.
|
|
495
|
+
:type text: str
|
|
496
|
+
:param reading_level: Target audience: ``'plain'`` (plain-language
|
|
497
|
+
style for a general audience, the API default), ``'elementary'``
|
|
498
|
+
(~US grade 3-5), ``'middle_school'`` (~grade 6-8) or
|
|
499
|
+
``'high_school'`` (~grade 9-10).
|
|
500
|
+
:type reading_level: str
|
|
501
|
+
:param preserve_terms: Up to 20 terms (each up to 100 characters) the
|
|
502
|
+
rewrite must keep exactly as written — product names, defined
|
|
503
|
+
legal terms. A rewrite that drops one is refused server-side
|
|
504
|
+
rather than returned.
|
|
505
|
+
:type preserve_terms: list[str]
|
|
506
|
+
:param lang: ISO 639-1 code used to pick the readability formula for
|
|
507
|
+
the before/after scores. Omit for auto-detection (falls back to
|
|
508
|
+
``'en'``). The rewrite itself always stays in the text's own
|
|
509
|
+
language.
|
|
510
|
+
:type lang: str
|
|
511
|
+
:raises TypeError: If ``preserve_terms`` is a single string rather
|
|
512
|
+
than a list/tuple of terms.
|
|
513
|
+
:rtype: dict
|
|
514
|
+
:return:
|
|
515
|
+
- simplified: The rewritten text, structure preserved.
|
|
516
|
+
- reading_level: The reading level that was applied.
|
|
517
|
+
- lang: The language the readability scores were computed for
|
|
518
|
+
(detected when not supplied).
|
|
519
|
+
- readability: ``{'before': {'grade', 'ease'}, 'after': {'grade',
|
|
520
|
+
'ease'}}`` Flesch-Kincaid scores, present when the language
|
|
521
|
+
supports readability scoring.
|
|
522
|
+
'''
|
|
523
|
+
# A bare string would silently be split into one-character terms;
|
|
524
|
+
# fail loudly instead (same guard as extract/classify).
|
|
525
|
+
if preserve_terms is not None and (
|
|
526
|
+
not isinstance(preserve_terms, Iterable)
|
|
527
|
+
or isinstance(preserve_terms, (str, bytes, dict))):
|
|
528
|
+
raise TypeError('preserve_terms must be a list of strings')
|
|
529
|
+
url = f'{self.url_endpoint}simplify'
|
|
530
|
+
data = {
|
|
531
|
+
'key': self.api_key,
|
|
532
|
+
'text': text,
|
|
533
|
+
}
|
|
534
|
+
if reading_level is not None:
|
|
535
|
+
data['reading_level'] = reading_level
|
|
536
|
+
if preserve_terms is not None:
|
|
537
|
+
data['preserve_terms'] = list(preserve_terms)
|
|
538
|
+
if lang is not None:
|
|
539
|
+
data['lang'] = lang
|
|
540
|
+
return self._request(url, data)
|
|
454
541
|
def chunk_text(
|
|
455
542
|
self,
|
|
456
543
|
text,
|
|
@@ -615,21 +702,55 @@ class SaplingClient:
|
|
|
615
702
|
def summarize(
|
|
616
703
|
self,
|
|
617
704
|
text,
|
|
705
|
+
length=None,
|
|
618
706
|
):
|
|
619
707
|
'''
|
|
620
|
-
Summarizes a longer document
|
|
708
|
+
Summarizes a longer document, webpage or email thread into a prose
|
|
709
|
+
summary plus key points.
|
|
621
710
|
|
|
622
|
-
|
|
623
|
-
|
|
711
|
+
Example::
|
|
712
|
+
|
|
713
|
+
client.summarize(
|
|
714
|
+
'Hi, I was charged twice for my order this month. ...',
|
|
715
|
+
length='short',
|
|
716
|
+
)
|
|
717
|
+
# {'result': 'The customer was double-charged and wants a refund.',
|
|
718
|
+
# 'summary': 'The customer was double-charged and wants a refund.',
|
|
719
|
+
# 'key_points': ['Charged twice for one order.',
|
|
720
|
+
# 'Requests a refund of the duplicate.'],
|
|
721
|
+
# 'length': 'short'}
|
|
722
|
+
|
|
723
|
+
:param text: Input document to summarize, plain text or HTML (tags
|
|
724
|
+
are stripped), up to 20,000 characters. Pass a list or tuple of
|
|
725
|
+
1-10 strings to summarize a batch in one request (sent as the
|
|
726
|
+
API's ``texts`` parameter; combined length up to 20,000
|
|
727
|
+
characters, same ``length`` option applied to every item). A
|
|
728
|
+
batch response is ``{'results': [...]}`` with one
|
|
729
|
+
single-response-shaped dict per input, in order.
|
|
730
|
+
:type text: str | list[str]
|
|
731
|
+
:param length: Optional summary length: ``'short'``, ``'medium'``
|
|
732
|
+
(the default) or ``'long'``.
|
|
733
|
+
:type length: str
|
|
624
734
|
:rtype: dict
|
|
625
735
|
:return:
|
|
626
|
-
-
|
|
736
|
+
- summary: The prose summary.
|
|
737
|
+
- result: Mirrors ``summary`` (the endpoint's original response
|
|
738
|
+
key, kept for backward compatibility).
|
|
739
|
+
- key_points: List of key-point strings.
|
|
740
|
+
- length: The length option the summary was generated with.
|
|
627
741
|
'''
|
|
628
742
|
url = self.url_endpoint + 'summarize'
|
|
629
743
|
data = {
|
|
630
744
|
'key': self.api_key,
|
|
631
|
-
'text': text,
|
|
632
745
|
}
|
|
746
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
747
|
+
# item ({'results': [...]}), same options applied to every item.
|
|
748
|
+
if isinstance(text, (list, tuple)):
|
|
749
|
+
data['texts'] = list(text)
|
|
750
|
+
else:
|
|
751
|
+
data['text'] = text
|
|
752
|
+
if length is not None:
|
|
753
|
+
data['length'] = length
|
|
633
754
|
return self._request(url, data)
|
|
634
755
|
|
|
635
756
|
def extract(
|
|
@@ -672,8 +793,12 @@ class SaplingClient:
|
|
|
672
793
|
# 'missing': ['purchase_order']}
|
|
673
794
|
|
|
674
795
|
:param text: Document to extract from, plain text or HTML (tags are
|
|
675
|
-
stripped), up to 10,000 characters.
|
|
676
|
-
|
|
796
|
+
stripped), up to 10,000 characters. Pass a list or tuple of 1-10
|
|
797
|
+
strings to extract from a batch in one request (sent as the API's
|
|
798
|
+
``texts`` parameter; combined length up to 10,000 characters). A batch
|
|
799
|
+
response is ``{'results': [...]}`` with one single-response-shaped
|
|
800
|
+
dict per input, in order.
|
|
801
|
+
:type text: str | list[str]
|
|
677
802
|
:param fields: 1-20 fields to extract. Each entry is either a field name
|
|
678
803
|
(str) or a dict ``{'name': str, 'type': str, 'description': str,
|
|
679
804
|
'required': bool}`` where everything but ``name`` is optional. Names are
|
|
@@ -711,9 +836,14 @@ class SaplingClient:
|
|
|
711
836
|
url = self.url_endpoint + 'extract'
|
|
712
837
|
data = {
|
|
713
838
|
'key': self.api_key,
|
|
714
|
-
'text': text,
|
|
715
839
|
'fields': list(fields),
|
|
716
840
|
}
|
|
841
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
842
|
+
# item ({'results': [...]}), same fields applied to every item.
|
|
843
|
+
if isinstance(text, (list, tuple)):
|
|
844
|
+
data['texts'] = list(text)
|
|
845
|
+
else:
|
|
846
|
+
data['text'] = text
|
|
717
847
|
if context is not None:
|
|
718
848
|
data['context'] = context
|
|
719
849
|
return self._request(url, data)
|
|
@@ -742,8 +872,13 @@ class SaplingClient:
|
|
|
742
872
|
# 'target_lang': 'fr', 'target_lang_name': 'French'}
|
|
743
873
|
|
|
744
874
|
:param text: Text to translate, up to 5,000 characters. Markup and
|
|
745
|
-
whitespace are preserved, not stripped.
|
|
746
|
-
|
|
875
|
+
whitespace are preserved, not stripped. Pass a list or tuple of
|
|
876
|
+
1-10 strings to translate a batch in one request (sent as the
|
|
877
|
+
API's ``texts`` parameter; combined length up to 5,000
|
|
878
|
+
characters, same target/source/formality applied to every item).
|
|
879
|
+
A batch response is ``{'results': [...]}`` with one
|
|
880
|
+
single-response-shaped dict per input, in order.
|
|
881
|
+
:type text: str | list[str]
|
|
747
882
|
:param target_lang: Language to translate into: an ISO 639 code
|
|
748
883
|
(``'fr'``, ``'zh-TW'``) or an English language name
|
|
749
884
|
(``'French'``). Region variants ``zh-CN``/``zh-TW``/``zh-HK``,
|
|
@@ -766,9 +901,14 @@ class SaplingClient:
|
|
|
766
901
|
url = self.url_endpoint + 'translate'
|
|
767
902
|
data = {
|
|
768
903
|
'key': self.api_key,
|
|
769
|
-
'text': text,
|
|
770
904
|
'target_lang': target_lang,
|
|
771
905
|
}
|
|
906
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
907
|
+
# item ({'results': [...]}), same options applied to every item.
|
|
908
|
+
if isinstance(text, (list, tuple)):
|
|
909
|
+
data['texts'] = list(text)
|
|
910
|
+
else:
|
|
911
|
+
data['text'] = text
|
|
772
912
|
if source_lang is not None:
|
|
773
913
|
data['source_lang'] = source_lang
|
|
774
914
|
if formality is not None:
|
|
@@ -836,8 +976,13 @@ class SaplingClient:
|
|
|
836
976
|
|
|
837
977
|
:param text: Text to compute a quality score for. The base score is computed
|
|
838
978
|
on the leading ~4,000 characters; the rubric evaluates the whole text
|
|
839
|
-
(up to 20,000 characters).
|
|
840
|
-
|
|
979
|
+
(up to 20,000 characters). Pass a list or tuple of 1-10 strings to
|
|
980
|
+
score a batch in one request (sent as the API's ``texts`` parameter;
|
|
981
|
+
the same options apply to every item, and with ``rubric`` the
|
|
982
|
+
combined length is capped at 20,000 characters). A batch response is
|
|
983
|
+
``{'results': [...]}`` with one single-response-shaped dict per
|
|
984
|
+
input, in order.
|
|
985
|
+
:type text: str | list[str]
|
|
841
986
|
:param sentence_scores: If true, also return a 1-5 score for each sentence
|
|
842
987
|
under `sentences`.
|
|
843
988
|
:type sentence_scores: bool
|
|
@@ -856,8 +1001,13 @@ class SaplingClient:
|
|
|
856
1001
|
url = self.url_endpoint + 'quality'
|
|
857
1002
|
data = {
|
|
858
1003
|
'key': self.api_key,
|
|
859
|
-
'text': text,
|
|
860
1004
|
}
|
|
1005
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
1006
|
+
# item ({'results': [...]}), same options applied to every item.
|
|
1007
|
+
if isinstance(text, (list, tuple)):
|
|
1008
|
+
data['texts'] = list(text)
|
|
1009
|
+
else:
|
|
1010
|
+
data['text'] = text
|
|
861
1011
|
if sentence_scores is not None:
|
|
862
1012
|
data['sentence_scores'] = sentence_scores
|
|
863
1013
|
if rubric is not None:
|
|
@@ -875,7 +1025,11 @@ class SaplingClient:
|
|
|
875
1025
|
candidates and a per-segment breakdown for mixed-language text.
|
|
876
1026
|
|
|
877
1027
|
:param text: Text to identify the language for (up to 20,000 characters).
|
|
878
|
-
|
|
1028
|
+
Pass a list or tuple of 1-10 strings to detect a batch in one
|
|
1029
|
+
request (sent as the API's ``texts`` parameter; combined length up
|
|
1030
|
+
to 20,000 characters). A batch response is ``{'results': [...]}``
|
|
1031
|
+
with one single-response-shaped dict per input, in order.
|
|
1032
|
+
:type text: str | list[str]
|
|
879
1033
|
:param top_k: Number of language candidates to return, 1-10. Defaults to 3 server-side.
|
|
880
1034
|
:type top_k: int
|
|
881
1035
|
:param segments: If true, also detect the language of each line/sentence
|
|
@@ -893,8 +1047,13 @@ class SaplingClient:
|
|
|
893
1047
|
url = self.url_endpoint + 'langdetect'
|
|
894
1048
|
data = {
|
|
895
1049
|
'key': self.api_key,
|
|
896
|
-
'text': text,
|
|
897
1050
|
}
|
|
1051
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
1052
|
+
# item ({'results': [...]}), same options applied to every item.
|
|
1053
|
+
if isinstance(text, (list, tuple)):
|
|
1054
|
+
data['texts'] = list(text)
|
|
1055
|
+
else:
|
|
1056
|
+
data['text'] = text
|
|
898
1057
|
if top_k is not None:
|
|
899
1058
|
data['top_k'] = top_k
|
|
900
1059
|
if segments is not None:
|
|
@@ -932,8 +1091,12 @@ class SaplingClient:
|
|
|
932
1091
|
Scores the provided text on seven content-safety categories: toxicity,
|
|
933
1092
|
profanity, harassment, hate_speech, self_harm, sexual, and violence.
|
|
934
1093
|
|
|
935
|
-
:param text: Text to score, up to 20,000 characters.
|
|
936
|
-
|
|
1094
|
+
:param text: Text to score, up to 20,000 characters. Pass a list or
|
|
1095
|
+
tuple of 1-10 strings to score a batch in one request (sent as the
|
|
1096
|
+
API's ``texts`` parameter; combined length up to 20,000
|
|
1097
|
+
characters). A batch response is ``{'results': [...]}`` with one
|
|
1098
|
+
single-response-shaped dict per input, in order.
|
|
1099
|
+
:type text: str | list[str]
|
|
937
1100
|
:param threshold: Score at or above which a category is flagged.
|
|
938
1101
|
Between 0 and 1 inclusive; the API defaults to 0.5.
|
|
939
1102
|
:type threshold: float
|
|
@@ -954,8 +1117,13 @@ class SaplingClient:
|
|
|
954
1117
|
url = self.url_endpoint + 'safety'
|
|
955
1118
|
data = {
|
|
956
1119
|
'key': self.api_key,
|
|
957
|
-
'text': text,
|
|
958
1120
|
}
|
|
1121
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
1122
|
+
# item ({'results': [...]}), same options applied to every item.
|
|
1123
|
+
if isinstance(text, (list, tuple)):
|
|
1124
|
+
data['texts'] = list(text)
|
|
1125
|
+
else:
|
|
1126
|
+
data['text'] = text
|
|
959
1127
|
if threshold is not None:
|
|
960
1128
|
data['threshold'] = threshold
|
|
961
1129
|
if spans is not None:
|
|
@@ -1104,8 +1272,12 @@ class SaplingClient:
|
|
|
1104
1272
|
# 'multi_label': False}
|
|
1105
1273
|
|
|
1106
1274
|
:param text: Text to classify, plain text or HTML (tags are stripped), up to
|
|
1107
|
-
10,000 characters.
|
|
1108
|
-
|
|
1275
|
+
10,000 characters. Pass a list or tuple of 1-10 strings to classify a
|
|
1276
|
+
batch in one request (sent as the API's ``texts`` parameter; combined
|
|
1277
|
+
length up to 10,000 characters). A batch response is
|
|
1278
|
+
``{'results': [...]}`` with one single-response-shaped dict per input,
|
|
1279
|
+
in order.
|
|
1280
|
+
:type text: str | list[str]
|
|
1109
1281
|
:param labels: 2-20 candidate labels. Each entry is either a label name (str) or
|
|
1110
1282
|
a dict ``{'name': str, 'description': str}`` where ``description`` is
|
|
1111
1283
|
optional. Names are up to 50 characters and must be unique
|
|
@@ -1147,9 +1319,14 @@ class SaplingClient:
|
|
|
1147
1319
|
url = self.url_endpoint + 'classify'
|
|
1148
1320
|
data = {
|
|
1149
1321
|
'key': self.api_key,
|
|
1150
|
-
'text': text,
|
|
1151
1322
|
'labels': list(labels),
|
|
1152
1323
|
}
|
|
1324
|
+
# A list/tuple of texts is the batch form: one request, one result per
|
|
1325
|
+
# item ({'results': [...]}), same labels applied to every item.
|
|
1326
|
+
if isinstance(text, (list, tuple)):
|
|
1327
|
+
data['texts'] = list(text)
|
|
1328
|
+
else:
|
|
1329
|
+
data['text'] = text
|
|
1153
1330
|
if multi_label is not None:
|
|
1154
1331
|
data['multi_label'] = multi_label
|
|
1155
1332
|
if threshold is not None:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '4.11.0'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sapling-py
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.11.0
|
|
4
4
|
Summary: Sapling Python Client
|
|
5
5
|
Author-email: Sapling Intelligence <info@sapling.ai>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -31,8 +31,9 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
|
|
|
31
31
|
|
|
32
32
|
Sapling is a toolkit for helping developers build language model-powered applications.
|
|
33
33
|
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
34
|
-
zero-shot text classification, structured data extraction, translation, PII detection
|
|
35
|
-
content safety scoring,
|
|
34
|
+
zero-shot text classification, structured data extraction, translation, PII detection
|
|
35
|
+
and redaction, content safety scoring, plain-language
|
|
36
|
+
simplification, SEO analysis, and more.
|
|
36
37
|
|
|
37
38
|
Key Features:
|
|
38
39
|
- **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
|
|
@@ -94,6 +95,20 @@ Get a production key by following [this documentation](https://sapling.ai/docs/a
|
|
|
94
95
|
|
|
95
96
|
Here's some more information on [request options and response structure](https://sapling.ai/docs/api/edits-overview).
|
|
96
97
|
|
|
98
|
+
## Batch requests
|
|
99
|
+
|
|
100
|
+
The analysis endpoints — `classify`, `extract`, `safety`, `langdetect`,
|
|
101
|
+
`translate`, `summarize`, `quality` and `aidetect` — also accept a list of
|
|
102
|
+
1-10 texts in
|
|
103
|
+
place of a single string. The batch is sent as one request (the same options
|
|
104
|
+
apply to every item) and returns `{'results': [...]}` with one
|
|
105
|
+
single-response-shaped dict per input, in order:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
scores = client.quality(['First draft.', 'Second draft.'], sentence_scores=True)
|
|
109
|
+
langs = client.langdetect(['Bonjour le monde', 'Hello world'])
|
|
110
|
+
```
|
|
111
|
+
|
|
97
112
|
## License
|
|
98
113
|
|
|
99
114
|
Copyright 2024--present Sapling Intelligence, Inc.
|
|
@@ -198,3 +198,26 @@ def test_classify_custom_hostname_and_pathname():
|
|
|
198
198
|
def test_classify_rejects_non_list_labels(client, bad_labels):
|
|
199
199
|
with pytest.raises(TypeError):
|
|
200
200
|
client.classify('I was charged twice.', bad_labels)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@responses.activate
|
|
204
|
+
def test_classify_batch_list_sent_as_texts(client):
|
|
205
|
+
# A list of texts is the batch form (SAP-395): sent as `texts`, no `text`
|
|
206
|
+
# key, and the {'results': [...]} body comes back untouched.
|
|
207
|
+
batch_response = {'results': [CLASSIFY_RESPONSE, MULTI_LABEL_RESPONSE]}
|
|
208
|
+
responses.add(responses.POST, BASE + 'classify', json=batch_response, status=200)
|
|
209
|
+
result = client.classify(['first ticket', 'second ticket'], LABELS)
|
|
210
|
+
assert result == batch_response
|
|
211
|
+
body = _last_request_body()
|
|
212
|
+
assert body == {'key': API_KEY, 'texts': ['first ticket', 'second ticket'],
|
|
213
|
+
'labels': LABELS}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@responses.activate
|
|
217
|
+
def test_classify_batch_tuple_sent_as_list(client):
|
|
218
|
+
responses.add(responses.POST, BASE + 'classify',
|
|
219
|
+
json={'results': [CLASSIFY_RESPONSE]}, status=200)
|
|
220
|
+
client.classify(('only ticket',), LABELS)
|
|
221
|
+
body = _last_request_body()
|
|
222
|
+
assert body['texts'] == ['only ticket']
|
|
223
|
+
assert 'text' not in body
|
|
@@ -168,6 +168,31 @@ def test_langdetect_options(client):
|
|
|
168
168
|
assert _last_request_body() == {'key': API_KEY, 'text': 'Hello', 'top_k': 1, 'segments': True}
|
|
169
169
|
|
|
170
170
|
|
|
171
|
+
@responses.activate
|
|
172
|
+
def test_langdetect_batch_list_sent_as_texts(client):
|
|
173
|
+
# A list of texts is the batch form (SAP-397): sent as `texts`, no `text`
|
|
174
|
+
# key, and the {'results': [...]} body comes back untouched.
|
|
175
|
+
batch_response = {'results': [
|
|
176
|
+
{'lang': 'en', 'name': 'English', 'conf': 0.99, 'candidates': []},
|
|
177
|
+
{'lang': 'es', 'name': 'Spanish', 'conf': 0.97, 'candidates': []},
|
|
178
|
+
]}
|
|
179
|
+
responses.add(responses.POST, BASE + 'langdetect', json=batch_response, status=200)
|
|
180
|
+
result = client.langdetect(['Hello there.', 'Hola.'], top_k=1)
|
|
181
|
+
assert result == batch_response
|
|
182
|
+
assert _last_request_body() == {'key': API_KEY, 'texts': ['Hello there.', 'Hola.'],
|
|
183
|
+
'top_k': 1}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@responses.activate
|
|
187
|
+
def test_langdetect_batch_tuple_sent_as_list(client):
|
|
188
|
+
responses.add(responses.POST, BASE + 'langdetect',
|
|
189
|
+
json={'results': [{'lang': 'en'}]}, status=200)
|
|
190
|
+
client.langdetect(('Hello there.',))
|
|
191
|
+
body = _last_request_body()
|
|
192
|
+
assert body['texts'] == ['Hello there.']
|
|
193
|
+
assert 'text' not in body
|
|
194
|
+
|
|
195
|
+
|
|
171
196
|
@responses.activate
|
|
172
197
|
def test_hostname_and_pathname_override():
|
|
173
198
|
client = SaplingClient(api_key=API_KEY, hostname='http://localhost:5000', pathname='/api/v1/')
|
|
@@ -193,3 +218,100 @@ def test_quality_options(client):
|
|
|
193
218
|
assert result == payload
|
|
194
219
|
assert _last_request_body() == {'key': API_KEY, 'text': 'Hello',
|
|
195
220
|
'sentence_scores': True, 'rubric': True}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@responses.activate
|
|
224
|
+
def test_quality_batch_list_sent_as_texts(client):
|
|
225
|
+
# A list of texts is the batch form (SAP-400): sent as `texts`, no `text`
|
|
226
|
+
# key, the same options applied to every item, and the {'results': [...]}
|
|
227
|
+
# body comes back untouched.
|
|
228
|
+
batch_response = {'results': [
|
|
229
|
+
{'score': 4.1},
|
|
230
|
+
{'score': 2.3},
|
|
231
|
+
]}
|
|
232
|
+
responses.add(responses.POST, BASE + 'quality', json=batch_response, status=200)
|
|
233
|
+
result = client.quality(['First document.', 'Second document.'],
|
|
234
|
+
sentence_scores=True)
|
|
235
|
+
assert result == batch_response
|
|
236
|
+
assert _last_request_body() == {'key': API_KEY,
|
|
237
|
+
'texts': ['First document.', 'Second document.'],
|
|
238
|
+
'sentence_scores': True}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@responses.activate
|
|
242
|
+
def test_quality_batch_tuple_sent_as_list(client):
|
|
243
|
+
responses.add(responses.POST, BASE + 'quality',
|
|
244
|
+
json={'results': [{'score': 3.0}]}, status=200)
|
|
245
|
+
client.quality(('First document.',))
|
|
246
|
+
body = _last_request_body()
|
|
247
|
+
assert body['texts'] == ['First document.']
|
|
248
|
+
assert 'text' not in body
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@responses.activate
|
|
252
|
+
def test_summarize_length_sent_only_when_given(client):
|
|
253
|
+
payload = {'result': 'Short.', 'summary': 'Short.', 'key_points': ['One.'],
|
|
254
|
+
'length': 'short'}
|
|
255
|
+
responses.add(responses.POST, BASE + 'summarize', json=payload, status=200)
|
|
256
|
+
result = client.summarize('A long document.', length='short')
|
|
257
|
+
assert result == payload
|
|
258
|
+
assert _last_request_body() == {'key': API_KEY, 'text': 'A long document.',
|
|
259
|
+
'length': 'short'}
|
|
260
|
+
|
|
261
|
+
responses.add(responses.POST, BASE + 'summarize', json=payload, status=200)
|
|
262
|
+
client.summarize('A long document.')
|
|
263
|
+
assert 'length' not in _last_request_body()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@responses.activate
|
|
267
|
+
def test_summarize_batch_list_sent_as_texts(client):
|
|
268
|
+
# A list of texts is the batch form (SAP-399): sent as `texts`, no `text`
|
|
269
|
+
# key, and the {'results': [...]} body comes back untouched.
|
|
270
|
+
batch_response = {'results': [
|
|
271
|
+
{'result': 'One.', 'summary': 'One.', 'key_points': ['A.'], 'length': 'medium'},
|
|
272
|
+
{'result': 'Two.', 'summary': 'Two.', 'key_points': ['B.'], 'length': 'medium'},
|
|
273
|
+
]}
|
|
274
|
+
responses.add(responses.POST, BASE + 'summarize', json=batch_response, status=200)
|
|
275
|
+
result = client.summarize(['First document.', 'Second document.'])
|
|
276
|
+
assert result == batch_response
|
|
277
|
+
assert _last_request_body() == {'key': API_KEY,
|
|
278
|
+
'texts': ['First document.', 'Second document.']}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@responses.activate
|
|
282
|
+
def test_summarize_batch_tuple_sent_as_list(client):
|
|
283
|
+
responses.add(responses.POST, BASE + 'summarize',
|
|
284
|
+
json={'results': [{'result': 'One.'}]}, status=200)
|
|
285
|
+
client.summarize(('First document.',), length='long')
|
|
286
|
+
body = _last_request_body()
|
|
287
|
+
assert body['texts'] == ['First document.']
|
|
288
|
+
assert body['length'] == 'long'
|
|
289
|
+
assert 'text' not in body
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@responses.activate
|
|
293
|
+
def test_aidetect_batch_list_sent_as_texts(client):
|
|
294
|
+
# A list of texts is the batch form (SAP-403): sent as `texts`, no `text`
|
|
295
|
+
# key, and the {'results': [...]} body comes back untouched.
|
|
296
|
+
batch_response = {'results': [
|
|
297
|
+
{'score': 0.91, 'text': 'Almost certainly generated.'},
|
|
298
|
+
{'score': 0.08, 'text': 'Plainly human prose.'},
|
|
299
|
+
]}
|
|
300
|
+
responses.add(responses.POST, BASE + 'aidetect', json=batch_response, status=200)
|
|
301
|
+
result = client.aidetect(['Almost certainly generated.', 'Plainly human prose.'],
|
|
302
|
+
sent_scores=False)
|
|
303
|
+
assert result == batch_response
|
|
304
|
+
assert _last_request_body() == {'key': API_KEY,
|
|
305
|
+
'texts': ['Almost certainly generated.',
|
|
306
|
+
'Plainly human prose.'],
|
|
307
|
+
'sent_scores': False}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@responses.activate
|
|
311
|
+
def test_aidetect_batch_tuple_sent_as_list(client):
|
|
312
|
+
responses.add(responses.POST, BASE + 'aidetect',
|
|
313
|
+
json={'results': [{'score': 0.5}]}, status=200)
|
|
314
|
+
client.aidetect(('One draft.',))
|
|
315
|
+
body = _last_request_body()
|
|
316
|
+
assert body['texts'] == ['One draft.']
|
|
317
|
+
assert 'text' not in body
|
|
@@ -211,3 +211,27 @@ def test_extract_accepts_any_iterable_of_fields(client):
|
|
|
211
211
|
responses.add(responses.POST, BASE + 'extract', json=EXTRACT_RESPONSE, status=200)
|
|
212
212
|
client.extract(TEXT, (name for name in ['invoice_number', 'total']))
|
|
213
213
|
assert _last_request_body()['fields'] == ['invoice_number', 'total']
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@responses.activate
|
|
217
|
+
def test_extract_batch_list_sent_as_texts(client):
|
|
218
|
+
# A list of texts is the batch form (SAP-395): sent as `texts`, no `text`
|
|
219
|
+
# key, and the {'results': [...]} body comes back untouched.
|
|
220
|
+
batch_response = {'results': [EXTRACT_RESPONSE, EXTRACT_RESPONSE]}
|
|
221
|
+
responses.add(responses.POST, BASE + 'extract', json=batch_response, status=200)
|
|
222
|
+
result = client.extract([TEXT, 'Invoice INV-2077 for Globex.'], FIELDS)
|
|
223
|
+
assert result == batch_response
|
|
224
|
+
body = _last_request_body()
|
|
225
|
+
assert body['texts'] == [TEXT, 'Invoice INV-2077 for Globex.']
|
|
226
|
+
assert 'text' not in body
|
|
227
|
+
assert body['fields'] == FIELDS
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@responses.activate
|
|
231
|
+
def test_extract_batch_tuple_sent_as_list(client):
|
|
232
|
+
responses.add(responses.POST, BASE + 'extract',
|
|
233
|
+
json={'results': [EXTRACT_RESPONSE]}, status=200)
|
|
234
|
+
client.extract((TEXT,), FIELDS)
|
|
235
|
+
body = _last_request_body()
|
|
236
|
+
assert body['texts'] == [TEXT]
|
|
237
|
+
assert 'text' not in body
|
|
@@ -54,3 +54,26 @@ def test_safety_spans_omitted_by_default(client):
|
|
|
54
54
|
client.safety('some text')
|
|
55
55
|
body = json.loads(responses.calls[-1].request.body)
|
|
56
56
|
assert 'spans' not in body
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@responses.activate
|
|
60
|
+
def test_safety_batch_list_sent_as_texts(client):
|
|
61
|
+
# A list of texts is the batch form (SAP-397): sent as `texts`, no `text`
|
|
62
|
+
# key, and the {'results': [...]} body comes back untouched.
|
|
63
|
+
batch_response = {'results': [{'flagged': True}, {'flagged': False}]}
|
|
64
|
+
responses.add(responses.POST, BASE + 'safety', json=batch_response, status=200)
|
|
65
|
+
result = client.safety(['first message', 'second message'], threshold=0.7)
|
|
66
|
+
assert result == batch_response
|
|
67
|
+
body = json.loads(responses.calls[-1].request.body)
|
|
68
|
+
assert body == {'key': API_KEY, 'texts': ['first message', 'second message'],
|
|
69
|
+
'threshold': 0.7}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@responses.activate
|
|
73
|
+
def test_safety_batch_tuple_sent_as_list(client):
|
|
74
|
+
responses.add(responses.POST, BASE + 'safety',
|
|
75
|
+
json={'results': [{'flagged': False}]}, status=200)
|
|
76
|
+
client.safety(('only message',))
|
|
77
|
+
body = json.loads(responses.calls[-1].request.body)
|
|
78
|
+
assert body['texts'] == ['only message']
|
|
79
|
+
assert 'text' not in body
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import responses
|
|
5
|
+
|
|
6
|
+
from sapling import SaplingClient
|
|
7
|
+
|
|
8
|
+
API_KEY = 'a' * 32
|
|
9
|
+
BASE = 'https://api.sapling.ai/api/v1/'
|
|
10
|
+
|
|
11
|
+
TEXT = ('The party of the first part shall remit payment within thirty (30) '
|
|
12
|
+
'days of receipt of the invoice.')
|
|
13
|
+
|
|
14
|
+
SIMPLIFY_RESPONSE = {
|
|
15
|
+
'simplified': 'You must pay within 30 days of getting the invoice.',
|
|
16
|
+
'reading_level': 'plain',
|
|
17
|
+
'lang': 'en',
|
|
18
|
+
'readability': {
|
|
19
|
+
'before': {'grade': 12.3, 'ease': 42.1},
|
|
20
|
+
'after': {'grade': 5.8, 'ease': 78.4},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def client():
|
|
27
|
+
return SaplingClient(api_key=API_KEY)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _last_request_body():
|
|
31
|
+
return json.loads(responses.calls[-1].request.body)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@responses.activate
|
|
35
|
+
def test_simplify_returns_json_and_sends_key_and_text(client):
|
|
36
|
+
responses.add(responses.POST, BASE + 'simplify',
|
|
37
|
+
json=SIMPLIFY_RESPONSE, status=200)
|
|
38
|
+
result = client.simplify(TEXT)
|
|
39
|
+
assert result == SIMPLIFY_RESPONSE
|
|
40
|
+
body = _last_request_body()
|
|
41
|
+
assert body == {'key': API_KEY, 'text': TEXT}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@responses.activate
|
|
45
|
+
def test_simplify_posts_to_simplify_endpoint(client):
|
|
46
|
+
responses.add(responses.POST, BASE + 'simplify',
|
|
47
|
+
json=SIMPLIFY_RESPONSE, status=200)
|
|
48
|
+
client.simplify(TEXT)
|
|
49
|
+
assert responses.calls[-1].request.url == BASE + 'simplify'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@responses.activate
|
|
53
|
+
def test_simplify_omits_optional_arguments_when_not_given(client):
|
|
54
|
+
responses.add(responses.POST, BASE + 'simplify',
|
|
55
|
+
json=SIMPLIFY_RESPONSE, status=200)
|
|
56
|
+
client.simplify(TEXT)
|
|
57
|
+
body = _last_request_body()
|
|
58
|
+
assert 'reading_level' not in body
|
|
59
|
+
assert 'preserve_terms' not in body
|
|
60
|
+
assert 'lang' not in body
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@responses.activate
|
|
64
|
+
def test_simplify_sends_optional_arguments(client):
|
|
65
|
+
responses.add(responses.POST, BASE + 'simplify',
|
|
66
|
+
json=SIMPLIFY_RESPONSE, status=200)
|
|
67
|
+
client.simplify(TEXT, reading_level='middle_school',
|
|
68
|
+
preserve_terms=['invoice', 'Sapling'], lang='en')
|
|
69
|
+
body = _last_request_body()
|
|
70
|
+
assert body['reading_level'] == 'middle_school'
|
|
71
|
+
assert body['preserve_terms'] == ['invoice', 'Sapling']
|
|
72
|
+
assert body['lang'] == 'en'
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@responses.activate
|
|
76
|
+
def test_simplify_accepts_a_tuple_of_preserve_terms(client):
|
|
77
|
+
responses.add(responses.POST, BASE + 'simplify',
|
|
78
|
+
json=SIMPLIFY_RESPONSE, status=200)
|
|
79
|
+
client.simplify(TEXT, preserve_terms=('invoice',))
|
|
80
|
+
assert _last_request_body()['preserve_terms'] == ['invoice']
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_simplify_rejects_non_list_preserve_terms(client):
|
|
84
|
+
for bad in ('invoice', b'invoice', {'term': 'invoice'}, 5):
|
|
85
|
+
with pytest.raises(TypeError):
|
|
86
|
+
client.simplify(TEXT, preserve_terms=bad)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@responses.activate
|
|
90
|
+
def test_simplify_http_error_raises(client):
|
|
91
|
+
responses.add(responses.POST, BASE + 'simplify',
|
|
92
|
+
json={'msg': 'Unexpected error simplifying text.'},
|
|
93
|
+
status=502)
|
|
94
|
+
with pytest.raises(Exception):
|
|
95
|
+
client.simplify(TEXT)
|
|
@@ -79,3 +79,31 @@ def test_translate_http_error_raises_sapling_error(client):
|
|
|
79
79
|
json={'msg': 'Unknown language: "Klingon".'}, status=400)
|
|
80
80
|
with pytest.raises(SaplingError):
|
|
81
81
|
client.translate(TEXT, target_lang='Klingon')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@responses.activate
|
|
85
|
+
def test_translate_batch_list_sent_as_texts(client):
|
|
86
|
+
# A list of texts is the batch form (SAP-398): sent as `texts`, no `text`
|
|
87
|
+
# key, and the {'results': [...]} body comes back untouched.
|
|
88
|
+
batch_response = {'results': [TRANSLATE_RESPONSE,
|
|
89
|
+
dict(TRANSLATE_RESPONSE,
|
|
90
|
+
translation='À demain.')]}
|
|
91
|
+
responses.add(responses.POST, BASE + 'translate',
|
|
92
|
+
json=batch_response, status=200)
|
|
93
|
+
result = client.translate(['Hello world.', 'See you tomorrow.'],
|
|
94
|
+
target_lang='fr', formality='formal')
|
|
95
|
+
assert result == batch_response
|
|
96
|
+
body = _last_request_body()
|
|
97
|
+
assert body == {'key': API_KEY,
|
|
98
|
+
'texts': ['Hello world.', 'See you tomorrow.'],
|
|
99
|
+
'target_lang': 'fr', 'formality': 'formal'}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@responses.activate
|
|
103
|
+
def test_translate_batch_tuple_sent_as_list(client):
|
|
104
|
+
responses.add(responses.POST, BASE + 'translate',
|
|
105
|
+
json={'results': [TRANSLATE_RESPONSE]}, status=200)
|
|
106
|
+
client.translate(('Hello world.',), target_lang='fr')
|
|
107
|
+
body = _last_request_body()
|
|
108
|
+
assert body['texts'] == ['Hello world.']
|
|
109
|
+
assert 'text' not in body
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '4.4.0'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|