sapling-py 4.2.0__tar.gz → 4.4.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.
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sapling-py
3
- Version: 4.2.0
3
+ Version: 4.4.0
4
4
  Summary: Sapling Python Client
5
- Home-page: https://sapling.ai
6
- Author: Sapling Intelligence
7
- Author-email: info@sapling.ai
5
+ Author-email: Sapling Intelligence <info@sapling.ai>
8
6
  License: Apache License 2.0
7
+ Project-URL: Homepage, https://sapling.ai
8
+ Project-URL: Documentation, https://sapling.readthedocs.io/
9
+ Project-URL: Repository, https://github.com/saplingai/sapling-py
9
10
  Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: License :: OSI Approved :: Apache Software License
11
12
  Classifier: Programming Language :: Python :: 3
@@ -17,18 +18,7 @@ Requires-Dist: requests
17
18
  Provides-Extra: test
18
19
  Requires-Dist: pytest; extra == "test"
19
20
  Requires-Dist: responses; extra == "test"
20
- Dynamic: author
21
- Dynamic: author-email
22
- Dynamic: classifier
23
- Dynamic: description
24
- Dynamic: description-content-type
25
- Dynamic: home-page
26
- Dynamic: license
27
21
  Dynamic: license-file
28
- Dynamic: provides-extra
29
- Dynamic: requires-dist
30
- Dynamic: requires-python
31
- Dynamic: summary
32
22
 
33
23
  # Sapling Python Client
34
24
 
@@ -41,8 +31,8 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
41
31
 
42
32
  Sapling is a toolkit for helping developers build language model-powered applications.
43
33
  The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
44
- zero-shot text classification, structured data extraction, PII detection and redaction, content safety
45
- scoring, SEO analysis, and more.
34
+ zero-shot text classification, structured data extraction, translation, PII detection and redaction,
35
+ content safety scoring, SEO analysis, and more.
46
36
 
47
37
  Key Features:
48
38
  - **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
@@ -54,12 +44,19 @@ Key Features:
54
44
 
55
45
  ## Installation
56
46
 
57
- Install the `sapling-py` package with [pip](https://pip.pypa.io/en/stable/installation/)
47
+ Install the `sapling-py` package from PyPI with
48
+ [pip](https://pip.pypa.io/en/stable/installation/):
58
49
 
59
- ```
50
+ ```sh
60
51
  python -m pip install sapling-py
61
52
  ```
62
53
 
54
+ Or add it to a [uv](https://docs.astral.sh/uv/) project:
55
+
56
+ ```sh
57
+ uv add sapling-py
58
+ ```
59
+
63
60
  ## Documentation
64
61
  Documentation for the client is available at [Read the Docs](https://sapling.readthedocs.io/) and
65
62
  documentation for the HTTP API is available at [Sapling.ai](https://sapling.ai/docs).
@@ -9,8 +9,8 @@ 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, PII detection and redaction, content safety
13
- scoring, SEO analysis, and more.
12
+ zero-shot text classification, structured data extraction, translation, PII detection and redaction,
13
+ content safety scoring, SEO analysis, and more.
14
14
 
15
15
  Key Features:
16
16
  - **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
@@ -22,12 +22,19 @@ Key Features:
22
22
 
23
23
  ## Installation
24
24
 
25
- Install the `sapling-py` package with [pip](https://pip.pypa.io/en/stable/installation/)
25
+ Install the `sapling-py` package from PyPI with
26
+ [pip](https://pip.pypa.io/en/stable/installation/):
26
27
 
27
- ```
28
+ ```sh
28
29
  python -m pip install sapling-py
29
30
  ```
30
31
 
32
+ Or add it to a [uv](https://docs.astral.sh/uv/) project:
33
+
34
+ ```sh
35
+ uv add sapling-py
36
+ ```
37
+
31
38
  ## Documentation
32
39
  Documentation for the client is available at [Read the Docs](https://sapling.readthedocs.io/) and
33
40
  documentation for the HTTP API is available at [Sapling.ai](https://sapling.ai/docs).
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sapling-py"
7
+ dynamic = ["version"]
8
+ description = "Sapling Python Client"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = { text = "Apache License 2.0" }
12
+ authors = [
13
+ { name = "Sapling Intelligence", email = "info@sapling.ai" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "License :: OSI Approved :: Apache Software License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ ]
21
+ dependencies = [
22
+ "requests",
23
+ ]
24
+
25
+ [project.optional-dependencies]
26
+ test = [
27
+ "pytest",
28
+ "responses",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://sapling.ai"
33
+ Documentation = "https://sapling.readthedocs.io/"
34
+ Repository = "https://github.com/saplingai/sapling-py"
35
+
36
+ [tool.setuptools.dynamic]
37
+ version = { attr = "sapling.version.__version__" }
38
+
39
+ [tool.setuptools.packages.find]
40
+ include = ["sapling", "sapling.*"]
41
+ exclude = ["test", "test.*"]
42
+ namespaces = false
@@ -718,6 +718,63 @@ class SaplingClient:
718
718
  data['context'] = context
719
719
  return self._request(url, data)
720
720
 
721
+ def translate(
722
+ self,
723
+ text,
724
+ target_lang,
725
+ source_lang=None,
726
+ formality=None,
727
+ ):
728
+ '''
729
+ Translates text into a target language with an LLM, preserving line
730
+ breaks, markup and placeholders in place. The response reports the
731
+ language the text was actually written in (detected, not assumed from
732
+ the hint).
733
+
734
+ Example::
735
+
736
+ client.translate(
737
+ 'Hello world.\\nSee you at 5pm.',
738
+ target_lang='fr',
739
+ )
740
+ # {'translation': 'Bonjour le monde.\\nÀ 17h.',
741
+ # 'source_lang': 'en', 'source_lang_name': 'English',
742
+ # 'target_lang': 'fr', 'target_lang_name': 'French'}
743
+
744
+ :param text: Text to translate, up to 5,000 characters. Markup and
745
+ whitespace are preserved, not stripped.
746
+ :type text: str
747
+ :param target_lang: Language to translate into: an ISO 639 code
748
+ (``'fr'``, ``'zh-TW'``) or an English language name
749
+ (``'French'``). Region variants ``zh-CN``/``zh-TW``/``zh-HK``,
750
+ ``pt-BR``/``pt-PT``, ``en-GB``/``en-US``, ``fr-CA`` and ``es-419``
751
+ are supported as targets.
752
+ :type target_lang: str
753
+ :param source_lang: Optional hint for the source language, same forms
754
+ as ``target_lang``. The text itself wins if they disagree.
755
+ :type source_lang: str
756
+ :param formality: Optional register: ``'formal'`` (vous/Sie/usted) or
757
+ ``'informal'`` (tu/du/tú). Omit for the model default.
758
+ :type formality: str
759
+ :rtype: dict
760
+ :return:
761
+ - translation: The translated text, structure preserved.
762
+ - source_lang / source_lang_name: The detected source language
763
+ (``'und'`` and ``None`` when undetermined).
764
+ - target_lang / target_lang_name: The normalized target.
765
+ '''
766
+ url = self.url_endpoint + 'translate'
767
+ data = {
768
+ 'key': self.api_key,
769
+ 'text': text,
770
+ 'target_lang': target_lang,
771
+ }
772
+ if source_lang is not None:
773
+ data['source_lang'] = source_lang
774
+ if formality is not None:
775
+ data['formality'] = formality
776
+ return self._request(url, data)
777
+
721
778
  def tone(
722
779
  self,
723
780
  text,
@@ -869,6 +926,7 @@ class SaplingClient:
869
926
  self,
870
927
  text,
871
928
  threshold=None,
929
+ spans=None,
872
930
  ):
873
931
  '''
874
932
  Scores the provided text on seven content-safety categories: toxicity,
@@ -879,12 +937,19 @@ class SaplingClient:
879
937
  :param threshold: Score at or above which a category is flagged.
880
938
  Between 0 and 1 inclusive; the API defaults to 0.5.
881
939
  :type threshold: float
940
+ :param spans: When True, the response also includes the offending
941
+ passages as spans, each with its text, start/end character offsets
942
+ into the submitted text, per-passage category scores, and
943
+ flagged_categories. The API defaults to False.
944
+ :type spans: bool
882
945
  :rtype: dict
883
946
  :return:
884
947
  - scores: A probability from 0 to 1 for each category.
885
948
  - flagged: True if any category scored at or above the threshold.
886
949
  - flagged_categories: Categories that scored at or above the threshold.
887
950
  - threshold: The threshold that was applied.
951
+ - spans: Only with spans=True — the offending passages, most
952
+ severe first, as {text, start, end, scores, flagged_categories}.
888
953
  '''
889
954
  url = self.url_endpoint + 'safety'
890
955
  data = {
@@ -893,6 +958,8 @@ class SaplingClient:
893
958
  }
894
959
  if threshold is not None:
895
960
  data['threshold'] = threshold
961
+ if spans is not None:
962
+ data['spans'] = spans
896
963
  return self._request(url, data)
897
964
 
898
965
  def pii(
@@ -0,0 +1 @@
1
+ __version__ = '4.4.0'
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sapling-py
3
- Version: 4.2.0
3
+ Version: 4.4.0
4
4
  Summary: Sapling Python Client
5
- Home-page: https://sapling.ai
6
- Author: Sapling Intelligence
7
- Author-email: info@sapling.ai
5
+ Author-email: Sapling Intelligence <info@sapling.ai>
8
6
  License: Apache License 2.0
7
+ Project-URL: Homepage, https://sapling.ai
8
+ Project-URL: Documentation, https://sapling.readthedocs.io/
9
+ Project-URL: Repository, https://github.com/saplingai/sapling-py
9
10
  Classifier: Development Status :: 5 - Production/Stable
10
11
  Classifier: License :: OSI Approved :: Apache Software License
11
12
  Classifier: Programming Language :: Python :: 3
@@ -17,18 +18,7 @@ Requires-Dist: requests
17
18
  Provides-Extra: test
18
19
  Requires-Dist: pytest; extra == "test"
19
20
  Requires-Dist: responses; extra == "test"
20
- Dynamic: author
21
- Dynamic: author-email
22
- Dynamic: classifier
23
- Dynamic: description
24
- Dynamic: description-content-type
25
- Dynamic: home-page
26
- Dynamic: license
27
21
  Dynamic: license-file
28
- Dynamic: provides-extra
29
- Dynamic: requires-dist
30
- Dynamic: requires-python
31
- Dynamic: summary
32
22
 
33
23
  # Sapling Python Client
34
24
 
@@ -41,8 +31,8 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
41
31
 
42
32
  Sapling is a toolkit for helping developers build language model-powered applications.
43
33
  The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
44
- zero-shot text classification, structured data extraction, PII detection and redaction, content safety
45
- scoring, SEO analysis, and more.
34
+ zero-shot text classification, structured data extraction, translation, PII detection and redaction,
35
+ content safety scoring, SEO analysis, and more.
46
36
 
47
37
  Key Features:
48
38
  - **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
@@ -54,12 +44,19 @@ Key Features:
54
44
 
55
45
  ## Installation
56
46
 
57
- Install the `sapling-py` package with [pip](https://pip.pypa.io/en/stable/installation/)
47
+ Install the `sapling-py` package from PyPI with
48
+ [pip](https://pip.pypa.io/en/stable/installation/):
58
49
 
59
- ```
50
+ ```sh
60
51
  python -m pip install sapling-py
61
52
  ```
62
53
 
54
+ Or add it to a [uv](https://docs.astral.sh/uv/) project:
55
+
56
+ ```sh
57
+ uv add sapling-py
58
+ ```
59
+
63
60
  ## Documentation
64
61
  Documentation for the client is available at [Read the Docs](https://sapling.readthedocs.io/) and
65
62
  documentation for the HTTP API is available at [Sapling.ai](https://sapling.ai/docs).
@@ -1,5 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
+ pyproject.toml
3
4
  setup.py
4
5
  sapling/__init__.py
5
6
  sapling/client.py
@@ -14,4 +15,5 @@ test/test_client.py
14
15
  test/test_extract.py
15
16
  test/test_pii.py
16
17
  test/test_safety.py
17
- test/test_seo.py
18
+ test/test_seo.py
19
+ test/test_translate.py
@@ -0,0 +1,53 @@
1
+ """Compatibility entry point for tools that still invoke ``setup.py`` directly.
2
+
3
+ Modern build frontends use the metadata in ``pyproject.toml``. The explicit
4
+ fallback keeps direct invocations working with setuptools versions that predate
5
+ PEP 621 support.
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ import setuptools
11
+ from setuptools import find_packages, setup
12
+
13
+
14
+ def _legacy_setup_kwargs():
15
+ this_directory = Path(__file__).parent
16
+ version = {}
17
+ exec((this_directory / 'sapling/version.py').read_text(), version)
18
+
19
+ return {
20
+ 'name': 'sapling-py',
21
+ 'version': version['__version__'],
22
+ 'description': 'Sapling Python Client',
23
+ 'long_description': (this_directory / 'README.md').read_text(),
24
+ 'long_description_content_type': 'text/markdown',
25
+ 'url': 'https://sapling.ai',
26
+ 'author': 'Sapling Intelligence',
27
+ 'author_email': 'info@sapling.ai',
28
+ 'license': 'Apache License 2.0',
29
+ 'classifiers': [
30
+ 'Development Status :: 5 - Production/Stable',
31
+ 'License :: OSI Approved :: Apache Software License',
32
+ 'Programming Language :: Python :: 3',
33
+ 'Programming Language :: Python :: 3 :: Only',
34
+ ],
35
+ 'packages': find_packages(exclude=['test', 'test.*']),
36
+ 'include_package_data': True,
37
+ 'python_requires': '>=3.7',
38
+ 'install_requires': ['requests'],
39
+ 'extras_require': {
40
+ 'test': [
41
+ 'pytest',
42
+ 'responses',
43
+ ],
44
+ },
45
+ }
46
+
47
+
48
+ try:
49
+ setuptools_major_version = int(setuptools.__version__.partition('.')[0])
50
+ except (AttributeError, ValueError):
51
+ setuptools_major_version = 0
52
+
53
+ setup(**_legacy_setup_kwargs() if setuptools_major_version < 61 else {})
@@ -0,0 +1,56 @@
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
+
12
+ @pytest.fixture
13
+ def client():
14
+ return SaplingClient(api_key=API_KEY)
15
+
16
+
17
+ @responses.activate
18
+ def test_safety_threshold_sent_when_provided(client):
19
+ responses.add(responses.POST, BASE + 'safety', json={'flagged': False}, status=200)
20
+ client.safety('some text', threshold=0.8)
21
+ body = json.loads(responses.calls[-1].request.body)
22
+ assert body == {'key': API_KEY, 'text': 'some text', 'threshold': 0.8}
23
+
24
+
25
+ @responses.activate
26
+ def test_safety_threshold_omitted_by_default(client):
27
+ responses.add(responses.POST, BASE + 'safety', json={'flagged': False}, status=200)
28
+ client.safety('some text')
29
+ body = json.loads(responses.calls[-1].request.body)
30
+ assert 'threshold' not in body
31
+
32
+
33
+ @responses.activate
34
+ def test_safety_spans_sent_when_provided(client):
35
+ responses.add(responses.POST, BASE + 'safety', json={'flagged': False}, status=200)
36
+ client.safety('some text', spans=True)
37
+ body = json.loads(responses.calls[-1].request.body)
38
+ assert body == {'key': API_KEY, 'text': 'some text', 'spans': True}
39
+
40
+
41
+ @responses.activate
42
+ def test_safety_spans_false_still_sent_explicitly(client):
43
+ # spans=False is a real value (not "unset"): send it rather than silently
44
+ # dropping it, mirroring the threshold-style None sentinel.
45
+ responses.add(responses.POST, BASE + 'safety', json={'flagged': False}, status=200)
46
+ client.safety('some text', spans=False)
47
+ body = json.loads(responses.calls[-1].request.body)
48
+ assert body['spans'] is False
49
+
50
+
51
+ @responses.activate
52
+ def test_safety_spans_omitted_by_default(client):
53
+ responses.add(responses.POST, BASE + 'safety', json={'flagged': False}, status=200)
54
+ client.safety('some text')
55
+ body = json.loads(responses.calls[-1].request.body)
56
+ assert 'spans' not in body
@@ -0,0 +1,81 @@
1
+ import json
2
+
3
+ import pytest
4
+ import responses
5
+
6
+ from sapling import SaplingClient, SaplingError
7
+
8
+ API_KEY = 'a' * 32
9
+ BASE = 'https://api.sapling.ai/api/v1/'
10
+
11
+ TEXT = 'Hello world.\nSee you at 5pm.'
12
+
13
+ TRANSLATE_RESPONSE = {
14
+ 'translation': 'Bonjour le monde.\nÀ 17h.',
15
+ 'source_lang': 'en',
16
+ 'source_lang_name': 'English',
17
+ 'target_lang': 'fr',
18
+ 'target_lang_name': 'French',
19
+ }
20
+
21
+
22
+ @pytest.fixture
23
+ def client():
24
+ return SaplingClient(api_key=API_KEY)
25
+
26
+
27
+ def _last_request_body():
28
+ return json.loads(responses.calls[-1].request.body)
29
+
30
+
31
+ @responses.activate
32
+ def test_translate_returns_json_and_sends_key(client):
33
+ responses.add(responses.POST, BASE + 'translate',
34
+ json=TRANSLATE_RESPONSE, status=200)
35
+ result = client.translate(TEXT, target_lang='fr')
36
+ assert result == TRANSLATE_RESPONSE
37
+ body = _last_request_body()
38
+ assert body == {'key': API_KEY, 'text': TEXT, 'target_lang': 'fr'}
39
+
40
+
41
+ @responses.activate
42
+ def test_translate_posts_to_translate_endpoint(client):
43
+ responses.add(responses.POST, BASE + 'translate',
44
+ json=TRANSLATE_RESPONSE, status=200)
45
+ client.translate(TEXT, target_lang='fr')
46
+ assert responses.calls[-1].request.url == BASE + 'translate'
47
+
48
+
49
+ @responses.activate
50
+ def test_translate_optional_params_sent_only_when_given(client):
51
+ responses.add(responses.POST, BASE + 'translate',
52
+ json=TRANSLATE_RESPONSE, status=200)
53
+ client.translate(TEXT, target_lang='fr')
54
+ body = _last_request_body()
55
+ assert 'source_lang' not in body
56
+ assert 'formality' not in body
57
+
58
+ client.translate(TEXT, target_lang='fr',
59
+ source_lang='en', formality='formal')
60
+ body = _last_request_body()
61
+ assert body['source_lang'] == 'en'
62
+ assert body['formality'] == 'formal'
63
+
64
+
65
+ @responses.activate
66
+ def test_translate_accepts_names_and_region_variants(client):
67
+ responses.add(responses.POST, BASE + 'translate',
68
+ json=dict(TRANSLATE_RESPONSE, target_lang='zh-TW',
69
+ target_lang_name='Traditional Chinese'),
70
+ status=200)
71
+ result = client.translate(TEXT, target_lang='Traditional Chinese')
72
+ assert result['target_lang'] == 'zh-TW'
73
+ assert _last_request_body()['target_lang'] == 'Traditional Chinese'
74
+
75
+
76
+ @responses.activate
77
+ def test_translate_http_error_raises_sapling_error(client):
78
+ responses.add(responses.POST, BASE + 'translate',
79
+ json={'msg': 'Unknown language: "Klingon".'}, status=400)
80
+ with pytest.raises(SaplingError):
81
+ client.translate(TEXT, target_lang='Klingon')
@@ -1 +0,0 @@
1
- __version__ = '4.2.0'
sapling_py-4.2.0/setup.py DELETED
@@ -1,41 +0,0 @@
1
- # Always prefer setuptools over distutils
2
- from pathlib import Path
3
- from setuptools import setup, find_packages
4
-
5
- this_directory = Path(__file__).parent
6
- print(this_directory / 'sapling/version.py')
7
- print(this_directory / "README.md")
8
- # load module version
9
- exec(open(this_directory / 'sapling/version.py').read())
10
- long_description = (this_directory / "README.md").read_text()
11
-
12
- setup(
13
- name='sapling-py',
14
- version=__version__,
15
- description='Sapling Python Client',
16
- long_description=long_description,
17
- long_description_content_type='text/markdown',
18
- url='https://sapling.ai',
19
- author='Sapling Intelligence',
20
- author_email='info@sapling.ai',
21
- license='Apache License 2.0',
22
- classifiers=[
23
- 'Development Status :: 5 - Production/Stable',
24
- 'License :: OSI Approved :: Apache Software License',
25
- 'Programming Language :: Python :: 3',
26
- 'Programming Language :: Python :: 3 :: Only',
27
- ],
28
- packages=find_packages(exclude=['test', 'test.*']),
29
- package_data={'':[]},
30
- include_package_data=True,
31
- python_requires='>=3.7',
32
- install_requires=[
33
- 'requests'
34
- ],
35
- extras_require={
36
- 'test': [
37
- 'pytest',
38
- 'responses',
39
- ],
40
- },
41
- )
@@ -1,30 +0,0 @@
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
-
12
- @pytest.fixture
13
- def client():
14
- return SaplingClient(api_key=API_KEY)
15
-
16
-
17
- @responses.activate
18
- def test_safety_threshold_sent_when_provided(client):
19
- responses.add(responses.POST, BASE + 'safety', json={'flagged': False}, status=200)
20
- client.safety('some text', threshold=0.8)
21
- body = json.loads(responses.calls[-1].request.body)
22
- assert body == {'key': API_KEY, 'text': 'some text', 'threshold': 0.8}
23
-
24
-
25
- @responses.activate
26
- def test_safety_threshold_omitted_by_default(client):
27
- responses.add(responses.POST, BASE + 'safety', json={'flagged': False}, status=200)
28
- client.safety('some text')
29
- body = json.loads(responses.calls[-1].request.body)
30
- assert 'threshold' not in body
File without changes
File without changes
File without changes
File without changes