Skip to content

Commit aa17fef

Browse files
Simplify example to not use translate requirement (#85)
As explained in #84, we think it's valuable for this example repository to be simple. In particular, we are demoing Pants itself, not the underlying code. This simplifies the project to use our own hardcoded `translate.json`, rather than the much more robust `translate` requirement. We still demonstrate the same things `translate` was showing for us: * third-party management * loading resource files There's an added benefit that the repository is now much faster to build, especially on Apple Silicon where `lxml` must be compiled from source. Likewise, I no longer have to force Pants to use Py39+ for this to build on my M1! The default of 3.7+ now works.
1 parent 2b4c66b commit aa17fef

19 files changed

+145
-175
lines changed

constraints.txt

+3-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
1-
# Generated by build-support/generate_constraints.sh on Mon Aug 2 21:58:13 UTC 2021
1+
# Generated by build-support/generate_constraints.sh on Fri Nov 12 13:02:01 MST 2021
22
ansicolors==1.1.8
3-
certifi==2021.5.30
4-
charset-normalizer==2.0.4
5-
click==8.0.1
6-
idna==3.2
7-
importlib-metadata==4.6.3
8-
libretranslatepy==2.1.1
9-
lxml==4.6.3
10-
pip==21.2.2
11-
requests==2.26.0
3+
pip==21.3.1
124
setuptools==56.2.0
13-
six==1.16.0
14-
translate==3.6.1
15-
types-futures==0.1.6
16-
types-setuptools==57.0.0
17-
typing-extensions==3.10.0.0
18-
urllib3==1.26.6
19-
zipp==3.5.0
5+
types-setuptools==57.4.2

helloworld/BUILD

+1-11
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,8 @@
22
# Licensed under the Apache License, Version 2.0 (see LICENSE).
33

44
# This target sets the metadata for all the Python non-test files in this directory.
5-
#
6-
# * `name` defaults to the name of this directory, i.e., `helloworld`.
7-
# * `sources` defaults to ['*.py', '!*_test.py', '!test_*.py', '!conftest.py'].
8-
# * Pants cannot infer dependencies on resources targets, so we explicitly add it.
95
python_library(
10-
dependencies=[":config_file"],
11-
)
12-
13-
# This target teaches Pants about our JSON file, which allows for other targets to depend on it.
14-
resources(
15-
name="config_file",
16-
sources=["config.json"],
6+
name="lib",
177
)
188

199
# This target allows us to bundle our app into a PEX binary file via

helloworld/config.json

-15
This file was deleted.

helloworld/greet/BUILD

+14-8
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33

44
# This target sets the metadata for all the Python non-test files in this directory.
55
#
6-
# * `name` defaults to the name of this directory, i.e., `greet`.
7-
# * `sources` defaults to ['*.py', '!*_test.py', '!test_*.py', '!conftest.py'].
8-
# * `dependencies` are inferred.
9-
python_library()
6+
# * Pants cannot infer dependencies on `resources` targets, so we explicitly add the dep.
7+
python_library(
8+
name="lib",
9+
dependencies=[":translations"],
10+
)
1011

1112
# This target sets the metadata for all the Python test files in this directory.
12-
#
13-
# * `sources` defaults to ['*_test.py', 'test_*.py', 'conftest.py'].
14-
# * `dependencies` are inferred.
15-
python_tests(name='tests')
13+
python_tests(
14+
name="tests",
15+
)
16+
17+
# This target teaches Pants about our JSON file, which allows other targets to depend on it.
18+
resources(
19+
name="translations",
20+
sources=["translations.json"],
21+
)

helloworld/greet/greeting.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
# Copyright 2020 Pants project contributors.
22
# Licensed under the Apache License, Version 2.0 (see LICENSE).
33

4+
from __future__ import annotations
5+
6+
import json
47
import random
5-
from typing import List
68

7-
from helloworld.util.lang import LanguageTranslator
9+
import pkg_resources
810

11+
from helloworld.translator.translator import LanguageTranslator
912

10-
class Greeter:
11-
def __init__(self, greetings: List[str], languages: List[str]) -> None:
12-
self._greetings = greetings
13-
self._language_translator = LanguageTranslator(languages=languages)
1413

15-
def translated_greeting(self) -> str:
16-
random_greeting = random.choice(self._greetings)
17-
return self._language_translator.translate_to_random_language(random_greeting)
14+
class Greeter:
15+
def __init__(
16+
self, *, translations: dict[str, dict[str, str]] | None = None
17+
) -> None:
18+
self._translations = (
19+
translations
20+
if translations is not None
21+
else json.loads(
22+
pkg_resources.resource_string(__name__, "translations.json")
23+
)
24+
)
25+
self._translator = LanguageTranslator(self._translations)
1826

1927
def greet(self, name: str) -> str:
20-
greeting = self.translated_greeting()
28+
random_greeting = random.choice(list(self._translations.keys()))
29+
greeting = self._translator.translate_to_random_language(random_greeting)
2130
return f"{greeting}, {name}!".capitalize()

helloworld/greet/greeting_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66

77
def test_greeter() -> None:
8-
greeter = Greeter(languages=["es"], greetings=["good morning"])
9-
assert greeter.greet("world") == "Buenos días, world!"
8+
greeter = Greeter(translations={"hello": {"es": "hola"}})
9+
assert greeter.greet("test") == "Hola, test!"

helloworld/greet/translations.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"hello": {
3+
"es": "hola",
4+
"fr": "bonjour",
5+
"af": "hallo",
6+
"ch": "你好",
7+
"ru": "Привет",
8+
"cs": "ahoj"
9+
},
10+
"good night": {
11+
"es": "buenas noches",
12+
"fr": "bonne nuit",
13+
"af": "Goeie nag",
14+
"ch": "晚安",
15+
"ru": "спокойной ночи",
16+
"cs": "dobrou noc"
17+
}
18+
}

helloworld/main.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44
from colors import green
55

66
from helloworld.greet.greeting import Greeter
7-
from helloworld.util.config import Config
87

98

109
def say_hello() -> None:
11-
config = Config.load_from_json_resource(__name__, "config.json")
12-
greeter = Greeter(languages=config.languages, greetings=config.greetings)
13-
sentence = greeter.greet("world")
14-
print(green(sentence))
10+
greeting = Greeter().greet("Pantsbuild")
11+
print(green(greeting))
1512

1613

1714
if __name__ == "__main__":

helloworld/util/BUILD renamed to helloworld/translator/BUILD

+6-18
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,24 @@
22
# Licensed under the Apache License, Version 2.0 (see LICENSE).
33

44
# This target sets the metadata for all the Python non-test files in this directory.
5-
#
6-
# * `name` defaults to the name of this directory, i.e., `util`.
7-
# * `sources` defaults to ['*.py', '!*_test.py', '!test_*.py', '!conftest.py'].
8-
# * `dependencies` are inferred.
9-
python_library()
5+
python_library(
6+
name="lib",
7+
)
108

119
# This target sets the metadata for all the Python test files in this directory.
12-
#
13-
# * `sources` defaults to ['*_test.py', 'test_*.py', 'conftest.py'].
14-
# * Pants cannot infer dependencies on `resources` targets, so we explicitly add the dep.
1510
python_tests(
16-
name='tests',
17-
dependencies=[":test_data"],
18-
)
19-
20-
# This target teaches Pants about our JSON file, which allows for other targets to depend on it.
21-
resources(
22-
name='test_data',
23-
sources=['*_test_data.json'],
11+
name="tests",
2412
)
2513

2614
# This target allows us to build a `.whl` bdist and a `.tar.gz` sdist by auto-generating
2715
# `setup.py`. See https://www.pantsbuild.org/docs/python-distributions.
2816
#
29-
# Because this target has no source code, Pants cannot infer dependencies. We depend on `:util`,
17+
# Because this target has no source code, Pants cannot infer dependencies. We depend on `:lib`,
3018
# which means we'll include all the non-test Python files in this directory, and any of
3119
# their dependencies.
3220
python_distribution(
3321
name="dist",
34-
dependencies=[":util"],
22+
dependencies=[":lib"],
3523
setup_py_commands=["bdist_wheel", "sdist"],
3624
provides=setup_py(
3725
name='helloworld.util',
File renamed without changes.

helloworld/translator/translator.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2020 Pants project contributors.
2+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3+
4+
from __future__ import annotations
5+
6+
import random
7+
from dataclasses import dataclass
8+
9+
10+
class UnknownLanguage(Exception):
11+
pass
12+
13+
14+
class UnknownPhrase(Exception):
15+
pass
16+
17+
18+
@dataclass
19+
class LanguageTranslator:
20+
"""A mapping of phrases (in English) to ISO 639 language codes (like `es`,
21+
`fr`) and their translation.
22+
23+
Assumes that every phrase is translated into the same languages.
24+
"""
25+
26+
phrases_to_translations: dict[str, dict[str, str]]
27+
28+
@property
29+
def all_languages(self) -> set[str]:
30+
return {
31+
lang
32+
for translations in self.phrases_to_translations.values()
33+
for lang in translations.keys()
34+
}
35+
36+
def translate(self, lang: str, phrase: str) -> str:
37+
if phrase not in self.phrases_to_translations:
38+
raise UnknownPhrase(phrase)
39+
translations = self.phrases_to_translations[phrase]
40+
if lang not in translations:
41+
raise UnknownLanguage(lang)
42+
return translations[lang]
43+
44+
def translate_to_random_language(self, phrase: str) -> str:
45+
lang = random.choice(sorted(self.all_languages))
46+
return self.translate(lang, phrase)
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2020 Pants project contributors.
2+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3+
4+
import pytest
5+
6+
from helloworld.translator.translator import (
7+
LanguageTranslator,
8+
UnknownLanguage,
9+
UnknownPhrase,
10+
)
11+
12+
13+
def test_language_translator() -> None:
14+
translator = LanguageTranslator(
15+
{
16+
"hello": {"es": "hola", "ar": "مرحبًا"},
17+
"computer": {"es": "computadora", "ar": "حاسوب"},
18+
}
19+
)
20+
assert translator.translate("es", "hello") == "hola"
21+
assert translator.translate("ar", "hello") == "مرحبًا"
22+
assert translator.translate("es", "computer") == "computadora"
23+
24+
25+
def test_unknown_phrase() -> None:
26+
translator = LanguageTranslator({"hello": {"es": "hola"}})
27+
with pytest.raises(UnknownPhrase):
28+
translator.translate("es", "good morning")
29+
30+
31+
def test_unknown_language() -> None:
32+
translator = LanguageTranslator({"hello": {"es": "hola"}})
33+
with pytest.raises(UnknownLanguage):
34+
translator.translate("ch", "hello")

helloworld/util/config.py

-21
This file was deleted.

helloworld/util/config_test.py

-10
This file was deleted.

helloworld/util/config_test_data.json

-10
This file was deleted.

helloworld/util/lang.py

-27
This file was deleted.

helloworld/util/lang_test.py

-17
This file was deleted.

mypy.ini

-3
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,5 @@ error_summary = True
2727
[mypy-colors]
2828
ignore_missing_imports = True
2929

30-
[mypy-translate]
31-
ignore_missing_imports = True
32-
3330
[mypy-pytest]
3431
ignore_missing_imports = True

0 commit comments

Comments
 (0)