From 87a7a1b0eda601ecfaa8bdc33cda016dbf419065 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Fri, 12 Nov 2021 14:16:17 -0700 Subject: [PATCH 1/2] Simplify example to not use `translate` requirement --- constraints.txt | 20 ++------- helloworld/BUILD | 14 +------ helloworld/config.json | 15 ------- helloworld/greet/BUILD | 22 ++++++---- helloworld/greet/greeting.py | 29 ++++++++----- helloworld/greet/greeting_test.py | 4 +- helloworld/greet/translations.json | 18 ++++++++ helloworld/main.py | 7 +--- helloworld/{util => translator}/BUILD | 24 ++--------- helloworld/{util => translator}/__init__.py | 0 helloworld/translator/translator.py | 46 +++++++++++++++++++++ helloworld/translator/translator_test.py | 34 +++++++++++++++ helloworld/util/config.py | 21 ---------- helloworld/util/config_test.py | 10 ----- helloworld/util/config_test_data.json | 10 ----- helloworld/util/lang.py | 27 ------------ helloworld/util/lang_test.py | 17 -------- mypy.ini | 3 -- requirements.txt | 1 - 19 files changed, 143 insertions(+), 179 deletions(-) delete mode 100644 helloworld/config.json create mode 100644 helloworld/greet/translations.json rename helloworld/{util => translator}/BUILD (56%) rename helloworld/{util => translator}/__init__.py (100%) create mode 100644 helloworld/translator/translator.py create mode 100644 helloworld/translator/translator_test.py delete mode 100644 helloworld/util/config.py delete mode 100644 helloworld/util/config_test.py delete mode 100644 helloworld/util/config_test_data.json delete mode 100644 helloworld/util/lang.py delete mode 100644 helloworld/util/lang_test.py diff --git a/constraints.txt b/constraints.txt index 7fdc0f3..b7d46f1 100644 --- a/constraints.txt +++ b/constraints.txt @@ -1,19 +1,5 @@ -# Generated by build-support/generate_constraints.sh on Mon Aug 2 21:58:13 UTC 2021 +# Generated by build-support/generate_constraints.sh on Fri Nov 12 13:02:01 MST 2021 ansicolors==1.1.8 -certifi==2021.5.30 -charset-normalizer==2.0.4 -click==8.0.1 -idna==3.2 -importlib-metadata==4.6.3 -libretranslatepy==2.1.1 -lxml==4.6.3 -pip==21.2.2 -requests==2.26.0 +pip==21.3.1 setuptools==56.2.0 -six==1.16.0 -translate==3.6.1 -types-futures==0.1.6 -types-setuptools==57.0.0 -typing-extensions==3.10.0.0 -urllib3==1.26.6 -zipp==3.5.0 +types-setuptools==57.4.2 diff --git a/helloworld/BUILD b/helloworld/BUILD index 9dfa86c..b05dbcf 100644 --- a/helloworld/BUILD +++ b/helloworld/BUILD @@ -2,19 +2,7 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). # This target sets the metadata for all the Python non-test files in this directory. -# -# * `name` defaults to the name of this directory, i.e., `helloworld`. -# * `sources` defaults to ['*.py', '!*_test.py', '!test_*.py', '!conftest.py']. -# * Pants cannot infer dependencies on resources targets, so we explicitly add it. -python_library( - dependencies=[":config_file"], -) - -# This target teaches Pants about our JSON file, which allows for other targets to depend on it. -resources( - name="config_file", - sources=["config.json"], -) +python_library(name="lib") # This target allows us to bundle our app into a PEX binary file via # `./pants package`. We can also run it with `./pants run`. See diff --git a/helloworld/config.json b/helloworld/config.json deleted file mode 100644 index 6069fa0..0000000 --- a/helloworld/config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "languages": [ - "en", - "de", - "fr", - "es" - ], - "greetings": [ - "hello", - "good morning", - "good afternoon", - "good evening", - "salutations" - ] -} diff --git a/helloworld/greet/BUILD b/helloworld/greet/BUILD index 2194ccf..d71ea37 100644 --- a/helloworld/greet/BUILD +++ b/helloworld/greet/BUILD @@ -3,13 +3,19 @@ # This target sets the metadata for all the Python non-test files in this directory. # -# * `name` defaults to the name of this directory, i.e., `greet`. -# * `sources` defaults to ['*.py', '!*_test.py', '!test_*.py', '!conftest.py']. -# * `dependencies` are inferred. -python_library() +# * Pants cannot infer dependencies on `resources` targets, so we explicitly add the dep. +python_library( + name="lib", + dependencies=[":translations"], +) # This target sets the metadata for all the Python test files in this directory. -# -# * `sources` defaults to ['*_test.py', 'test_*.py', 'conftest.py']. -# * `dependencies` are inferred. -python_tests(name='tests') +python_tests( + name="tests", +) + +# This target teaches Pants about our JSON file, which allows for other targets to depend on it. +resources( + name="translations", + sources=["translations.json"], +) diff --git a/helloworld/greet/greeting.py b/helloworld/greet/greeting.py index 3338652..6a5eb1a 100644 --- a/helloworld/greet/greeting.py +++ b/helloworld/greet/greeting.py @@ -1,21 +1,30 @@ # Copyright 2020 Pants project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). +from __future__ import annotations + +import json import random -from typing import List -from helloworld.util.lang import LanguageTranslator +import pkg_resources +from helloworld.translator.translator import LanguageTranslator -class Greeter: - def __init__(self, greetings: List[str], languages: List[str]) -> None: - self._greetings = greetings - self._language_translator = LanguageTranslator(languages=languages) - def translated_greeting(self) -> str: - random_greeting = random.choice(self._greetings) - return self._language_translator.translate_to_random_language(random_greeting) +class Greeter: + def __init__( + self, *, translations: dict[str, dict[str, str]] | None = None + ) -> None: + self._translations = ( + translations + if translations is not None + else json.loads( + pkg_resources.resource_string(__name__, "translations.json") + ) + ) + self._translator = LanguageTranslator(self._translations) def greet(self, name: str) -> str: - greeting = self.translated_greeting() + random_greeting = random.choice(list(self._translations.keys())) + greeting = self._translator.translate_to_random_language(random_greeting) return f"{greeting}, {name}!".capitalize() diff --git a/helloworld/greet/greeting_test.py b/helloworld/greet/greeting_test.py index 4faa9e8..8d43373 100644 --- a/helloworld/greet/greeting_test.py +++ b/helloworld/greet/greeting_test.py @@ -5,5 +5,5 @@ def test_greeter() -> None: - greeter = Greeter(languages=["es"], greetings=["good morning"]) - assert greeter.greet("world") == "Buenos días, world!" + greeter = Greeter(translations={"hello": {"es": "hola"}}) + assert greeter.greet("test") == "Hola, test!" diff --git a/helloworld/greet/translations.json b/helloworld/greet/translations.json new file mode 100644 index 0000000..a9eddda --- /dev/null +++ b/helloworld/greet/translations.json @@ -0,0 +1,18 @@ +{ + "hello": { + "es": "hola", + "fr": "bonjour", + "af": "hallo", + "ch": "你好", + "ru": "Привет", + "cs": "ahoj" + }, + "good night": { + "es": "buenas noches", + "fr": "bonne nuit", + "af": "Goeie nag", + "ch": "晚安", + "ru": "спокойной ночи", + "cs": "dobrou noc" + } +} diff --git a/helloworld/main.py b/helloworld/main.py index 52ca6f1..125d189 100644 --- a/helloworld/main.py +++ b/helloworld/main.py @@ -4,14 +4,11 @@ from colors import green from helloworld.greet.greeting import Greeter -from helloworld.util.config import Config def say_hello() -> None: - config = Config.load_from_json_resource(__name__, "config.json") - greeter = Greeter(languages=config.languages, greetings=config.greetings) - sentence = greeter.greet("world") - print(green(sentence)) + greeting = Greeter().greet("Pantsbuild") + print(green(greeting)) if __name__ == "__main__": diff --git a/helloworld/util/BUILD b/helloworld/translator/BUILD similarity index 56% rename from helloworld/util/BUILD rename to helloworld/translator/BUILD index bcba562..410ff5d 100644 --- a/helloworld/util/BUILD +++ b/helloworld/translator/BUILD @@ -2,36 +2,20 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). # This target sets the metadata for all the Python non-test files in this directory. -# -# * `name` defaults to the name of this directory, i.e., `util`. -# * `sources` defaults to ['*.py', '!*_test.py', '!test_*.py', '!conftest.py']. -# * `dependencies` are inferred. -python_library() +python_library(name="lib") # This target sets the metadata for all the Python test files in this directory. -# -# * `sources` defaults to ['*_test.py', 'test_*.py', 'conftest.py']. -# * Pants cannot infer dependencies on `resources` targets, so we explicitly add the dep. -python_tests( - name='tests', - dependencies=[":test_data"], -) - -# This target teaches Pants about our JSON file, which allows for other targets to depend on it. -resources( - name='test_data', - sources=['*_test_data.json'], -) +python_tests(name="tests") # This target allows us to build a `.whl` bdist and a `.tar.gz` sdist by auto-generating # `setup.py`. See https://www.pantsbuild.org/docs/python-distributions. # -# Because this target has no source code, Pants cannot infer dependencies. We depend on `:util`, +# Because this target has no source code, Pants cannot infer dependencies. We depend on `:lib`, # which means we'll include all the non-test Python files in this directory, and any of # their dependencies. python_distribution( name="dist", - dependencies=[":util"], + dependencies=[":lib"], setup_py_commands=["bdist_wheel", "sdist"], provides=setup_py( name='helloworld.util', diff --git a/helloworld/util/__init__.py b/helloworld/translator/__init__.py similarity index 100% rename from helloworld/util/__init__.py rename to helloworld/translator/__init__.py diff --git a/helloworld/translator/translator.py b/helloworld/translator/translator.py new file mode 100644 index 0000000..d9fb1b3 --- /dev/null +++ b/helloworld/translator/translator.py @@ -0,0 +1,46 @@ +# Copyright 2020 Pants project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +import random +from dataclasses import dataclass + + +class UnknownLanguage(Exception): + pass + + +class UnknownPhrase(Exception): + pass + + +@dataclass +class LanguageTranslator: + """A mapping of phrases (in English) to ISO 639 language codes (like `es`, + `fr`) and their translation. + + Assumes that every phrase is translated into the same languages. + """ + + phrases_to_translations: dict[str, dict[str, str]] + + @property + def all_languages(self) -> set[str]: + return { + lang + for translations in self.phrases_to_translations.values() + for lang in translations.keys() + } + + def translate(self, lang: str, phrase: str) -> str: + if phrase not in self.phrases_to_translations: + raise UnknownPhrase(phrase) + translations = self.phrases_to_translations[phrase] + if lang not in translations: + raise UnknownLanguage(lang) + return translations[lang] + + def translate_to_random_language(self, phrase: str) -> str: + lang = random.choice(sorted(self.all_languages)) + return self.translate(lang, phrase) diff --git a/helloworld/translator/translator_test.py b/helloworld/translator/translator_test.py new file mode 100644 index 0000000..1f838ff --- /dev/null +++ b/helloworld/translator/translator_test.py @@ -0,0 +1,34 @@ +# Copyright 2020 Pants project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +import pytest + +from helloworld.translator.translator import ( + LanguageTranslator, + UnknownLanguage, + UnknownPhrase, +) + + +def test_language_translator() -> None: + translator = LanguageTranslator( + { + "hello": {"es": "hola", "ar": "مرحبًا"}, + "computer": {"es": "computadora", "ar": "حاسوب"}, + } + ) + assert translator.translate("es", "hello") == "hola" + assert translator.translate("ar", "hello") == "مرحبًا" + assert translator.translate("es", "computer") == "computadora" + + +def test_unknown_phrase() -> None: + translator = LanguageTranslator({"hello": {"es": "hola"}}) + with pytest.raises(UnknownPhrase): + translator.translate("es", "good morning") + + +def test_unknown_language() -> None: + translator = LanguageTranslator({"hello": {"es": "hola"}}) + with pytest.raises(UnknownLanguage): + translator.translate("ch", "hello") diff --git a/helloworld/util/config.py b/helloworld/util/config.py deleted file mode 100644 index 965e15e..0000000 --- a/helloworld/util/config.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2020 Pants project contributors. -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -from __future__ import annotations - -import json -from dataclasses import dataclass - -import pkg_resources - - -@dataclass -class Config: - languages: list[str] - greetings: list[str] - - @classmethod - def load_from_json_resource(cls, pkg_name: str, resource_name: str) -> Config: - resource_content = pkg_resources.resource_string(pkg_name, resource_name) - parsed = json.loads(resource_content) - return cls(languages=parsed["languages"], greetings=parsed["greetings"]) diff --git a/helloworld/util/config_test.py b/helloworld/util/config_test.py deleted file mode 100644 index 95237d0..0000000 --- a/helloworld/util/config_test.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright 2020 Pants project contributors. -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -from helloworld.util.config import Config - - -def test_load_config_from_json() -> None: - config = Config.load_from_json_resource(__name__, "config_test_data.json") - assert config.languages == ["af", "zh"] - assert config.greetings == ["hi", "hey"] diff --git a/helloworld/util/config_test_data.json b/helloworld/util/config_test_data.json deleted file mode 100644 index deeb560..0000000 --- a/helloworld/util/config_test_data.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "languages": [ - "af", - "zh" - ], - "greetings": [ - "hi", - "hey" - ] -} diff --git a/helloworld/util/lang.py b/helloworld/util/lang.py deleted file mode 100644 index 9fc49fe..0000000 --- a/helloworld/util/lang.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2020 Pants project contributors. -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -import random -from typing import List, cast - -from translate import Translator - - -class LanguageTranslator: - class UnknownLanguage(Exception): - pass - - def __init__(self, languages: List[str]) -> None: - self._langs = languages - - def translate(self, lang: str, phrase: str) -> str: - if lang not in self._langs: - raise self.UnknownLanguage(lang) - translator = Translator(lang) - return cast(str, translator.translate(phrase)) - - def translate_to_random_language(self, phrase: str) -> str: - return self.translate(self._pick_random_language(), phrase) - - def _pick_random_language(self) -> str: - return random.choice(self._langs) diff --git a/helloworld/util/lang_test.py b/helloworld/util/lang_test.py deleted file mode 100644 index 190a96a..0000000 --- a/helloworld/util/lang_test.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 Pants project contributors. -# Licensed under the Apache License, Version 2.0 (see LICENSE). - -import pytest - -from helloworld.util.lang import LanguageTranslator - - -def test_language_translator() -> None: - language_translator = LanguageTranslator(languages=["es"]) - assert "hola" == language_translator.translate("es", "hello") - - -def test_unknown_language() -> None: - language_translator = LanguageTranslator(languages=[]) - with pytest.raises(LanguageTranslator.UnknownLanguage): - language_translator.translate("xx", "hello") diff --git a/mypy.ini b/mypy.ini index 1ffad0c..77ca553 100644 --- a/mypy.ini +++ b/mypy.ini @@ -27,8 +27,5 @@ error_summary = True [mypy-colors] ignore_missing_imports = True -[mypy-translate] -ignore_missing_imports = True - [mypy-pytest] ignore_missing_imports = True diff --git a/requirements.txt b/requirements.txt index 5316866..ec244b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,3 @@ ansicolors==1.1.8 setuptools>=56.2.0,<57 types-setuptools>=56.2.0,<58 -translate>=3.6.1,<3.7 From 2cf51d69236a089284789e126249768a60ca4ab0 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Fri, 12 Nov 2021 14:23:42 -0700 Subject: [PATCH 2/2] Review feedback --- helloworld/BUILD | 4 +++- helloworld/greet/BUILD | 2 +- helloworld/translator/BUILD | 8 ++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/helloworld/BUILD b/helloworld/BUILD index b05dbcf..9d83d5f 100644 --- a/helloworld/BUILD +++ b/helloworld/BUILD @@ -2,7 +2,9 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). # This target sets the metadata for all the Python non-test files in this directory. -python_library(name="lib") +python_library( + name="lib", +) # This target allows us to bundle our app into a PEX binary file via # `./pants package`. We can also run it with `./pants run`. See diff --git a/helloworld/greet/BUILD b/helloworld/greet/BUILD index d71ea37..807efb0 100644 --- a/helloworld/greet/BUILD +++ b/helloworld/greet/BUILD @@ -14,7 +14,7 @@ python_tests( name="tests", ) -# This target teaches Pants about our JSON file, which allows for other targets to depend on it. +# This target teaches Pants about our JSON file, which allows other targets to depend on it. resources( name="translations", sources=["translations.json"], diff --git a/helloworld/translator/BUILD b/helloworld/translator/BUILD index 410ff5d..90fa7b6 100644 --- a/helloworld/translator/BUILD +++ b/helloworld/translator/BUILD @@ -2,10 +2,14 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). # This target sets the metadata for all the Python non-test files in this directory. -python_library(name="lib") +python_library( + name="lib", +) # This target sets the metadata for all the Python test files in this directory. -python_tests(name="tests") +python_tests( + name="tests", +) # This target allows us to build a `.whl` bdist and a `.tar.gz` sdist by auto-generating # `setup.py`. See https://www.pantsbuild.org/docs/python-distributions.