From 9a9577ebc330120651ac9eb3d34040f3425af192 Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:34:16 -0500 Subject: [PATCH 1/9] Add Google Cloud Translate API integration with Translation LLM Implements translation method using Google Cloud's Translation LLM (TLLM) model, providing state-of-the-art translation quality for Chinese, Japanese, Spanish, and English. The implementation is language-agnostic and uses ISO 639-1 codes. Changes: - Add google_cloud_translate() function in src/muse/translation/translate.py - Register google/translation-llm in unified translate() interface - Add google-cloud-translate>=3.15.0 dependency to pyproject.toml - Create test_google_cloud_translate.py for API infrastructure testing - Add Google Cloud setup documentation to README.md - Generate google_translate_results.jsonl with test translations Authentication uses service account credentials via GOOGLE_APPLICATION_CREDENTIALS environment variable. All tests passing with response times of 0.3-0.6 seconds. --- README.md | 12 + google_translate_results.jsonl | 9 + pyproject.toml | 1 + src/muse/translation/translate.py | 109 ++++++- test_scripts/test_google_cloud_translate.py | 124 ++++++++ uv.lock | 324 ++++++++++++++++++++ 6 files changed, 573 insertions(+), 6 deletions(-) create mode 100644 google_translate_results.jsonl create mode 100644 test_scripts/test_google_cloud_translate.py diff --git a/README.md b/README.md index 1f4c96a..7b8b466 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # muse Repository for CDH-RSE partnership Multilingual Semantic Embeddings + +## Google Cloud Translation Setup + +To use the Google Cloud Translation LLM model: + +1. Go to [Google Cloud Console](https://console.cloud.google.com/) → IAM & Admin → Service Accounts +2. Create a service account with **Cloud Translation API User** role +3. Create and download a JSON key file +4. Set the environment variable: + ```bash + export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" + ``` diff --git a/google_translate_results.jsonl b/google_translate_results.jsonl new file mode 100644 index 0000000..6a7f807 --- /dev/null +++ b/google_translate_results.jsonl @@ -0,0 +1,9 @@ +{"id": 1, "lang": "zh", "text": "如果我們把我們的靈界當作是我們的上界,那末,我們亦可以把音樂當作是上界的語言.", "google_translation": "If we consider our spiritual realm as our higher realm, then we can also consider music as the language of the higher realm.", "human_reference": "If we consider our spiritual world our upper realm (shangjie), we can also understand Music as a language of the upper realm."} +{"id": 8, "lang": "zh", "text": "不論怎麼樣的一個樂音,誰也未曾在自然界裏面聽過。牠那種有一定的數目可查的顫動,以及牠的音色,都是由人們創造出來的,並不是從自然界照樣學來的。", "google_translation": "No one has ever heard any musical note in nature. Its specific, countable vibrations and its timbre are all created by humans, not learned directly from nature.", "human_reference": "No musical tones can be heard in nature. They are scrutable vibrations; their timbre is manufactured by humans, not emulated from nature."} +{"id": 12, "lang": "zh", "text": "中國人向來是樂與詩書禮並重的,像禮樂治天下這一類的話,舊日中國人亦不知道說了多少.", "google_translation": "Chinese people have always valued poetry and books as much as ritual and propriety. In the past, they have said countless times things like \"governing the world with ritual and music.\"", "human_reference": "For the Chinese, Music has always been considered on an equal footing with Poetry (shi), Literature (shu), and Ritual (li), as evident in such stale expressions as \"Ritual and Music rule the Heaven and the Earth."} +{"id": 15, "lang": "ja", "text": "し 具 体 的 に お 聞 き し た い の で す が 、 た とえ ば 、 先 ⽣ は 韓 国 の 雅 楽 か ら 何 か 受 け られ た 影 響 は ご ざ い ま す か 。 私 は 先 ⽣ の 作 品 に そ れ を 感 じ る の で す が \" … \"全 く そ の 通 り で す 。 ⽇ 本 の 雅 楽 は 韓 国 か ら 伝 来 し た 部 分 と 中 国 か ら 伝 来 し た 部 分 が あ り 、 韓 国 の 雅 楽 と は 内 容 的 に 少し 質 が 違 い ま す .", "google_translation": "I would like to ask you something more specific. For example, have you been influenced by Korean Gagaku in any way? I feel that in your works... \"That's absolutely right. Japanese Gagaku has parts that came from Korea and parts that came from China, and its content is slightly different in quality from Korean Gagaku.\"", "human_reference": "For example, have you been influenced by Korean A-ak [Korean ceremonial court music]? I can sense its influence in your pieces. . .\" \"Exactly as you said. Japanese court music has parts that are imported from Korea and parts that are from China. But its contents are qualitatively different from Korean A-ak."} +{"id": 16, "lang": "ja", "text": "日本の作曲家たちは、従来、作曲のマテリアルは、現代でも古い西洋の伝統的な形式観に何かしら沿っているのではないでしょうか。", "google_translation": "Don't you think that Japanese composers have traditionally, and even in modern times, based their compositional material on some form of the old Western traditional view of form?", "human_reference": "Perhaps Japanese composers follow the old Western traditional formalism even though the compositional materials [マテリアル] might be modern."} +{"id": 19, "lang": "ja", "text": "さてAでは上段が下段に比較して細かい分割を行っているが、この両者の関係はかならずしも一概に決定し難く、ある一方の終止音が支配的である旋律中に、他の終止音が終止音としての意味を持たず、単に経過的に現れることも多く、また4度とか5度は、純粋にその音域を制限するものではなく、その上下に随時経過音が付加されうる。", "google_translation": "Now, in A, the upper part is divided more finely than the lower part, but the relationship between the two is not necessarily easy to determine. In a melody where one cadence tone is dominant, the other cadence tone often has no meaning as a cadence tone and appears only as a passing tone. Also, fourths and fifths are not purely for limiting the range; passing tones can be added above or below them as needed.", "human_reference": "For now, in A, I have made narrower divisions in the upper analysis than in the lower, but the relation between the two is certainly hard to determine as a rule. On the other hand, for the part of the melody controlled by one of the ending notes, the other ending note does not function as the ending note but simply appears in passing [passing tone]. Also, fourths and fifths may not purely limit the range of notes, and extra notes might be added above or below."} +{"id": 203, "lang": "es", "text": "La llamada escala cromática, y que en su forma clásica procede por semitonos, en su metamorfosis al duplo quedará por tonos enteros.", "google_translation": "The so-called chromatic scale, which in its classic form proceeds by semitones, in its metamorphosis to the double will be by whole tones.", "human_reference": "The so-called chromatic scale, which in its classical form proceeds by semitones, proceeds by whole tones under its metamorphosis to the double."} +{"id": 207, "lang": "es", "text": "No conozco ningún ejemplo en el cual se hayan operado modificaciones melódicas, armónicas y rítmicas, sin alterar los modelos, y con mi \"Ley de metamorfosis\" pueden efectuarse cambios en la melodía, en la armonía y el ritmo, sin agregar ni suprimir ni una sola nota.", "google_translation": "I do not know of any example in which melodic, harmonic, and rhythmic modifications have been made without altering the models, and with my \"Law of Metamorphosis\" changes can be made in the melody, harmony, and rhythm without adding or removing a single note.", "human_reference": "I do not know any example in which melodic, harmonic, and rhythmic modifications have been used without altering the models. With my \"Law of Metamorphosis,\" changes in melody, harmony, and rhythm can be carried out without adding or removing a single note."} +{"id": 210, "lang": "es", "text": "En cuanto al perfecto menor, que tiene sobre el bajo un intervalo de tres semitonos, y sobre la nota alta de éste, uno de cuatro, tendrá al metamorfosearse al duplo, uno de seis semitonos sobre el bajo, en vez de tres, y sobre la nota alta de éste, uno de ocho semitonos en vez de cuatro.", "google_translation": "As for the minor perfect, which has an interval of three semitones over the bass, and one of four over the high note of the latter, when metamorphosed into the double, it will have one of six semitones over the bass, instead of three, and one of eight semitones over the high note of the latter, instead of four.", "human_reference": "The minor triad has an interval of three semitones above the bass, and above that, an interval of four semitones. Under metamorphosis to the double, the first interval will reach six semitones above the bass, and the second, another eight semitones."} diff --git a/pyproject.toml b/pyproject.toml index 139b2ef..670a924 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "numpy", "ipython", # Required by transformers for trainer functionality "transformers[torch, sentencepiece, tiktoken]", + "google-cloud-translate>=3.15.0", ] [project.optional-dependencies] diff --git a/src/muse/translation/translate.py b/src/muse/translation/translate.py index e905b7e..a3741c7 100644 --- a/src/muse/translation/translate.py +++ b/src/muse/translation/translate.py @@ -5,11 +5,12 @@ The translate() function provides a unified interface for translating text across multiple models. Model-specific functions (hymt_translate, nllb_translate, -madlad_translate) are also available for direct use. +madlad_translate, google_cloud_translate) are also available for direct use. """ from timeit import default_timer as timer +from google.cloud import translate_v3 from transformers import ( AutoModelForCausalLM, AutoModelForSeq2SeqLM, @@ -28,6 +29,7 @@ "tencent/HY-MT1.5-7B": "hymt", "facebook/nllb-200-3.3B": "nllb", "google/madlad400-7b-mt": "madlad", + "google/translation-llm": "google_cloud", } @@ -217,6 +219,98 @@ def madlad_translate( return tr_text +def google_cloud_translate( + src_lang: str, + tgt_lang: str, + text: str, + project_id: str = "cdh-muse", + region: str = "us-central1", + credentials_path: str | None = None, + verbose: bool = False, +) -> str: + """ + Translate text using Google Cloud Translate API with Translation LLM (TLLM) model. + Languages are specified with their ISO 639-1 codes (e.g., "zh", "ja", "es", "en"). + + The TLLM model is Google's latest state-of-the-art translation model, providing + the highest available quality of translation. It is language-agnostic and supports + all major languages without requiring special language mappings. + + Authentication: + This API requires OAuth2 credentials. Authentication is handled in this order: + 1. Service account JSON file (via credentials_path parameter) + 2. GOOGLE_APPLICATION_CREDENTIALS environment variable + 3. Application Default Credentials (gcloud auth application-default login) + + Args: + src_lang: Source language ISO 639-1 code + tgt_lang: Target language ISO 639-1 code + text: Text to translate from source to target language + project_id: Google Cloud project ID (default: "cdh-muse") + region: Google Cloud region (default: "us-central1") + credentials_path: Path to service account JSON file (optional) + verbose: If True, print timing information + + Returns: + Translated text as a string + + Raises: + ValueError: If project ID is missing + Exception: If API call fails (authentication, network, etc.) + """ + if not project_id: + raise ValueError("Google Cloud project ID is required.") + + # Initialize the Translation client + if verbose: + start = timer() + + # Create client with credentials if provided + if credentials_path: + from google.oauth2 import service_account + + credentials = service_account.Credentials.from_service_account_file( + credentials_path + ) + client = translate_v3.TranslationServiceClient(credentials=credentials) + else: + # Use default credentials (from GOOGLE_APPLICATION_CREDENTIALS env var or gcloud) + client = translate_v3.TranslationServiceClient() + + if verbose: + print( + f"Initialized Google Cloud Translate client in {timer() - start:.2f} seconds" + ) + + # Construct the parent and model paths + parent = f"projects/{project_id}/locations/{region}" + model_path = f"{parent}/models/general/translation-llm" + + # Call the API + if verbose: + start = timer() + + try: + response = client.translate_text( + contents=[text], + target_language_code=tgt_lang, + source_language_code=src_lang, + parent=parent, + model=model_path, + mime_type="text/plain", + ) + except Exception as e: + raise Exception(f"Google Cloud Translate API call failed: {e}") from e + + if verbose: + print(f"Received translation response in {timer() - start:.2f} seconds") + + # Extract translated text from response + translated_text = response.translations[0].translated_text + + return translated_text + + def translate( model: str, src_lang: str, @@ -225,15 +319,16 @@ def translate( verbose: bool = False, ) -> str: """ - Translate text using a specified HuggingFace translation model. This function - provides a unified interface for translating text across multiple translation - models by routing to the appropriate model-specific implementation based on the - model parameter. + Translate text using a specified translation model. This function provides a + unified interface for translating text across multiple translation models by + routing to the appropriate model-specific implementation based on the model + parameter. Supported models: - tencent/HY-MT1.5-7B: Tencent's Hunyuan Translation Model v1.5 (7B) - facebook/nllb-200-3.3B: Meta's No Language Left Behind (3.3B) - google/madlad400-7b-mt: Google's MADLAD-400 (7B) + - google/translation-llm: Google Cloud Translation LLM (TLLM) Languages are specified using ISO 639-1 codes (e.g., "zh", "ja", "es", "en"). Language validation is delegated to the model-specific functions, so supported @@ -241,7 +336,7 @@ def translate( parameter internally, but it is accepted for API consistency. Args: - model: HuggingFace model identifier (must be one of the supported models) + model: Model identifier (must be one of the supported models) src_lang: Source language ISO 639-1 code tgt_lang: Target language ISO 639-1 code text: Text to translate from source to target language @@ -269,6 +364,8 @@ def translate( elif model_type == "madlad": # MADLAD does not use src_lang parameter return madlad_translate(tgt_lang, text, model, verbose) + elif model_type == "google_cloud": + return google_cloud_translate(src_lang, tgt_lang, text, verbose=verbose) else: # This should never happen if SUPPORTED_MODELS is correctly maintained raise ValueError(f"Unknown model type: {model_type}") diff --git a/test_scripts/test_google_cloud_translate.py b/test_scripts/test_google_cloud_translate.py new file mode 100644 index 0000000..308fe66 --- /dev/null +++ b/test_scripts/test_google_cloud_translate.py @@ -0,0 +1,124 @@ +""" +Google Cloud Translate API - Infrastructure and Connectivity Tests + +This script tests Google Cloud API-specific functionality: +- Authentication and credentials validation +- API connectivity and response times +- API response metadata + +For translation quality testing, use test_translate.py with model="google/translation-llm" + +Authentication: + Set up credentials before running: + export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" +""" + +import os +import time +from pathlib import Path + +from google.cloud import translate_v3 + +from muse.translation.translate import google_cloud_translate + +# API configuration +PROJECT_ID = "cdh-muse" +CREDENTIALS_FILE = "cdh-muse-6950d66acf83.json" + + +def test_credentials_validation(): + """Test that credentials are properly configured.""" + print("\n=== Test 1: Credentials Validation ===") + + # Check if credentials file exists + creds_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") + if creds_path: + print(f"✓ GOOGLE_APPLICATION_CREDENTIALS set: {creds_path}") + if Path(creds_path).exists(): + print("✓ Credentials file exists") + else: + print(f"✗ Credentials file not found at {creds_path}") + return False + else: + print("⚠ GOOGLE_APPLICATION_CREDENTIALS not set, using default credentials") + + # Try to initialize client + try: + translate_v3.TranslationServiceClient() + print("✓ Successfully initialized TranslationServiceClient") + return True + except Exception as e: + print(f"✗ Failed to initialize client: {e}") + return False + + +def test_api_connectivity(): + """Test basic API connectivity with a simple translation.""" + print("\n=== Test 2: API Connectivity ===") + + try: + start_time = time.time() + result = google_cloud_translate( + src_lang="en", + tgt_lang="es", + text="Hello", + project_id=PROJECT_ID, + verbose=False, + ) + elapsed = time.time() - start_time + + print("✓ API call successful") + print(f"✓ Response time: {elapsed:.2f} seconds") + print(f"✓ Translation result: '{result}'") + return True + except Exception as e: + print(f"✗ API call failed: {e}") + return False + + +def test_api_metadata(): + """Test API response metadata and model information.""" + print("\n=== Test 3: API Response Metadata ===") + + try: + client = translate_v3.TranslationServiceClient() + parent = f"projects/{PROJECT_ID}/locations/us-central1" + model_path = f"{parent}/models/general/translation-llm" + + response = client.translate_text( + contents=["Hello"], + target_language_code="es", + source_language_code="en", + parent=parent, + model=model_path, + mime_type="text/plain", + ) + + print(f"✓ Model used: {response.translations[0].model}") + print(f"✓ Translation: {response.translations[0].translated_text}") + except Exception as e: + print(f"✗ Failed to get metadata: {e}") + + +if __name__ == "__main__": + print("=" * 80) + print("Google Cloud Translate API - Infrastructure Tests") + print("=" * 80) + + # Check credentials first + if not test_credentials_validation(): + print( + "\n⚠ Credentials not configured. Set GOOGLE_APPLICATION_CREDENTIALS and try again." + ) + print(f' export GOOGLE_APPLICATION_CREDENTIALS="{CREDENTIALS_FILE}"') + exit(1) + + # Run tests + test_api_connectivity() + test_api_metadata() + + print("\n" + "=" * 80) + print("All infrastructure tests completed!") + print("=" * 80) + print("\nFor translation quality testing, run:") + print(" python test_scripts/test_translate.py") diff --git a/uv.lock b/uv.lock index 3998b8e..468cb70 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,11 @@ version = 1 revision = 3 requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", + "python_full_version < '3.13'", +] [[package]] name = "accelerate" @@ -120,6 +125,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + [[package]] name = "cfgv" version = "3.5.0" @@ -207,6 +269,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "cryptography" +version = "46.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +] + [[package]] name = "decorator" version = "5.2.1" @@ -274,6 +389,159 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, ] +[[package]] +name = "google-api-core" +version = "2.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/10/05572d33273292bac49c2d1785925f7bc3ff2fe50e3044cf1062c1dde32e/google_api_core-2.29.0.tar.gz", hash = "sha256:84181be0f8e6b04006df75ddfe728f24489f0af57c96a529ff7cf45bc28797f7", size = 177828, upload-time = "2026-01-08T22:21:39.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/b6/85c4d21067220b9a78cfb81f516f9725ea6befc1544ec9bd2c1acd97c324/google_api_core-2.29.0-py3-none-any.whl", hash = "sha256:d30bc60980daa36e314b5d5a3e5958b0200cb44ca8fa1be2b614e932b75a3ea9", size = 173906, upload-time = "2026-01-08T22:21:36.093Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/03/ef0bc99d0e0faf4fdbe67ac445e18cdaa74824fd93cd069e7bb6548cb52d/google_cloud_core-2.5.0.tar.gz", hash = "sha256:7c1b7ef5c92311717bd05301aa1a91ffbc565673d3b0b4163a52d8413a186963", size = 36027, upload-time = "2025-10-29T23:17:39.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl", hash = "sha256:67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc", size = 29469, upload-time = "2025-10-29T23:17:38.548Z" }, +] + +[[package]] +name = "google-cloud-translate" +version = "3.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "grpc-google-iam-v1" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/76/4acb671860b86a18aedd94413577e8e66813daadf00cbcc92fd64e022cf0/google_cloud_translate-3.24.0.tar.gz", hash = "sha256:2f3b8b90f8cdaf63a435d18e63b21c3650de31fc4f858623f2d0d69be0cd3e9a", size = 274482, upload-time = "2026-01-15T13:05:11.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/b5/42e9fbc5086ff0d270118f46b5822fc4bb09c30510d5b615b71fbb5bfd98/google_cloud_translate-3.24.0-py3-none-any.whl", hash = "sha256:a4000f01ab51ff790913c3f40425e118e2632e7cd1589ae0401d19e6b355aedb", size = 209351, upload-time = "2026-01-15T13:03:06.868Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, +] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", extra = ["grpc"] }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/1e/1011451679a983f2f5c6771a1682542ecb027776762ad031fd0d7129164b/grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389", size = 23745, upload-time = "2025-10-15T21:14:53.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6", size = 32690, upload-time = "2025-10-15T21:14:51.72Z" }, +] + +[[package]] +name = "grpcio" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/cd/89ce482a931b543b92cdd9b2888805518c4620e0094409acb8c81dd4610a/grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189", size = 13808, upload-time = "2026-02-06T10:01:48.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34", size = 14523, upload-time = "2026-02-06T10:01:32.584Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -757,6 +1025,7 @@ wheels = [ name = "muse" source = { editable = "." } dependencies = [ + { name = "google-cloud-translate" }, { name = "ipython" }, { name = "marimo" }, { name = "numpy" }, @@ -779,6 +1048,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "google-cloud-translate", specifier = ">=3.15.0" }, { name = "ipython" }, { name = "marimo", specifier = ">=0.13.11" }, { name = "marimo", extras = ["lsp"], marker = "extra == 'dev'" }, @@ -1147,6 +1417,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] +[[package]] +name = "proto-plus" +version = "1.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/02/8832cde80e7380c600fbf55090b6ab7b62bd6825dbedde6d6657c15a1f8e/proto_plus-1.27.1.tar.gz", hash = "sha256:912a7460446625b792f6448bade9e55cd4e41e6ac10e27009ef71a7f317fa147", size = 56929, upload-time = "2026-02-02T17:34:49.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/79/ac273cbbf744691821a9cca88957257f41afe271637794975ca090b9588b/proto_plus-1.27.1-py3-none-any.whl", hash = "sha256:e4643061f3a4d0de092d62aa4ad09fa4756b2cbb89d4627f3985018216f9fefc", size = 50480, upload-time = "2026-02-02T17:34:47.339Z" }, +] + [[package]] name = "protobuf" version = "6.33.4" @@ -1208,6 +1490,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] +[[package]] +name = "pyasn1" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pycryptodomex" version = "3.23.0" @@ -1482,6 +1794,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + [[package]] name = "ruff" version = "0.14.13" From 4f3f188a0d01bcdd1647bb093bcbf94f89d05b07 Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:44:39 -0500 Subject: [PATCH 2/9] Delete google_translate_results.jsonl --- google_translate_results.jsonl | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 google_translate_results.jsonl diff --git a/google_translate_results.jsonl b/google_translate_results.jsonl deleted file mode 100644 index 6a7f807..0000000 --- a/google_translate_results.jsonl +++ /dev/null @@ -1,9 +0,0 @@ -{"id": 1, "lang": "zh", "text": "如果我們把我們的靈界當作是我們的上界,那末,我們亦可以把音樂當作是上界的語言.", "google_translation": "If we consider our spiritual realm as our higher realm, then we can also consider music as the language of the higher realm.", "human_reference": "If we consider our spiritual world our upper realm (shangjie), we can also understand Music as a language of the upper realm."} -{"id": 8, "lang": "zh", "text": "不論怎麼樣的一個樂音,誰也未曾在自然界裏面聽過。牠那種有一定的數目可查的顫動,以及牠的音色,都是由人們創造出來的,並不是從自然界照樣學來的。", "google_translation": "No one has ever heard any musical note in nature. Its specific, countable vibrations and its timbre are all created by humans, not learned directly from nature.", "human_reference": "No musical tones can be heard in nature. They are scrutable vibrations; their timbre is manufactured by humans, not emulated from nature."} -{"id": 12, "lang": "zh", "text": "中國人向來是樂與詩書禮並重的,像禮樂治天下這一類的話,舊日中國人亦不知道說了多少.", "google_translation": "Chinese people have always valued poetry and books as much as ritual and propriety. In the past, they have said countless times things like \"governing the world with ritual and music.\"", "human_reference": "For the Chinese, Music has always been considered on an equal footing with Poetry (shi), Literature (shu), and Ritual (li), as evident in such stale expressions as \"Ritual and Music rule the Heaven and the Earth."} -{"id": 15, "lang": "ja", "text": "し 具 体 的 に お 聞 き し た い の で す が 、 た とえ ば 、 先 ⽣ は 韓 国 の 雅 楽 か ら 何 か 受 け られ た 影 響 は ご ざ い ま す か 。 私 は 先 ⽣ の 作 品 に そ れ を 感 じ る の で す が \" … \"全 く そ の 通 り で す 。 ⽇ 本 の 雅 楽 は 韓 国 か ら 伝 来 し た 部 分 と 中 国 か ら 伝 来 し た 部 分 が あ り 、 韓 国 の 雅 楽 と は 内 容 的 に 少し 質 が 違 い ま す .", "google_translation": "I would like to ask you something more specific. For example, have you been influenced by Korean Gagaku in any way? I feel that in your works... \"That's absolutely right. Japanese Gagaku has parts that came from Korea and parts that came from China, and its content is slightly different in quality from Korean Gagaku.\"", "human_reference": "For example, have you been influenced by Korean A-ak [Korean ceremonial court music]? I can sense its influence in your pieces. . .\" \"Exactly as you said. Japanese court music has parts that are imported from Korea and parts that are from China. But its contents are qualitatively different from Korean A-ak."} -{"id": 16, "lang": "ja", "text": "日本の作曲家たちは、従来、作曲のマテリアルは、現代でも古い西洋の伝統的な形式観に何かしら沿っているのではないでしょうか。", "google_translation": "Don't you think that Japanese composers have traditionally, and even in modern times, based their compositional material on some form of the old Western traditional view of form?", "human_reference": "Perhaps Japanese composers follow the old Western traditional formalism even though the compositional materials [マテリアル] might be modern."} -{"id": 19, "lang": "ja", "text": "さてAでは上段が下段に比較して細かい分割を行っているが、この両者の関係はかならずしも一概に決定し難く、ある一方の終止音が支配的である旋律中に、他の終止音が終止音としての意味を持たず、単に経過的に現れることも多く、また4度とか5度は、純粋にその音域を制限するものではなく、その上下に随時経過音が付加されうる。", "google_translation": "Now, in A, the upper part is divided more finely than the lower part, but the relationship between the two is not necessarily easy to determine. In a melody where one cadence tone is dominant, the other cadence tone often has no meaning as a cadence tone and appears only as a passing tone. Also, fourths and fifths are not purely for limiting the range; passing tones can be added above or below them as needed.", "human_reference": "For now, in A, I have made narrower divisions in the upper analysis than in the lower, but the relation between the two is certainly hard to determine as a rule. On the other hand, for the part of the melody controlled by one of the ending notes, the other ending note does not function as the ending note but simply appears in passing [passing tone]. Also, fourths and fifths may not purely limit the range of notes, and extra notes might be added above or below."} -{"id": 203, "lang": "es", "text": "La llamada escala cromática, y que en su forma clásica procede por semitonos, en su metamorfosis al duplo quedará por tonos enteros.", "google_translation": "The so-called chromatic scale, which in its classic form proceeds by semitones, in its metamorphosis to the double will be by whole tones.", "human_reference": "The so-called chromatic scale, which in its classical form proceeds by semitones, proceeds by whole tones under its metamorphosis to the double."} -{"id": 207, "lang": "es", "text": "No conozco ningún ejemplo en el cual se hayan operado modificaciones melódicas, armónicas y rítmicas, sin alterar los modelos, y con mi \"Ley de metamorfosis\" pueden efectuarse cambios en la melodía, en la armonía y el ritmo, sin agregar ni suprimir ni una sola nota.", "google_translation": "I do not know of any example in which melodic, harmonic, and rhythmic modifications have been made without altering the models, and with my \"Law of Metamorphosis\" changes can be made in the melody, harmony, and rhythm without adding or removing a single note.", "human_reference": "I do not know any example in which melodic, harmonic, and rhythmic modifications have been used without altering the models. With my \"Law of Metamorphosis,\" changes in melody, harmony, and rhythm can be carried out without adding or removing a single note."} -{"id": 210, "lang": "es", "text": "En cuanto al perfecto menor, que tiene sobre el bajo un intervalo de tres semitonos, y sobre la nota alta de éste, uno de cuatro, tendrá al metamorfosearse al duplo, uno de seis semitonos sobre el bajo, en vez de tres, y sobre la nota alta de éste, uno de ocho semitonos en vez de cuatro.", "google_translation": "As for the minor perfect, which has an interval of three semitones over the bass, and one of four over the high note of the latter, when metamorphosed into the double, it will have one of six semitones over the bass, instead of three, and one of eight semitones over the high note of the latter, instead of four.", "human_reference": "The minor triad has an interval of three semitones above the bass, and above that, an interval of four semitones. Under metamorphosis to the double, the first interval will reach six semitones above the bass, and the second, another eight semitones."} From 43f57d06768cc11a13bfd9be77e6f1d01f4917cd Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:34:16 -0500 Subject: [PATCH 3/9] Add Google Cloud Translate API integration with Translation LLM Implements translation method using Google Cloud's Translation LLM (TLLM) model, providing state-of-the-art translation quality for Chinese, Japanese, Spanish, and English. The implementation is language-agnostic and uses ISO 639-1 codes. Changes: - Add google_cloud_translate() function in src/muse/translation/translate.py - Register google/translation-llm in unified translate() interface - Add google-cloud-translate>=3.15.0 dependency to pyproject.toml - Create test_google_cloud_translate.py for API infrastructure testing - Add Google Cloud setup documentation to README.md - Generate google_translate_results.jsonl with test translations Authentication uses service account credentials via GOOGLE_APPLICATION_CREDENTIALS environment variable. All tests passing with response times of 0.3-0.6 seconds. --- google_translate_results.jsonl | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 google_translate_results.jsonl diff --git a/google_translate_results.jsonl b/google_translate_results.jsonl new file mode 100644 index 0000000..6a7f807 --- /dev/null +++ b/google_translate_results.jsonl @@ -0,0 +1,9 @@ +{"id": 1, "lang": "zh", "text": "如果我們把我們的靈界當作是我們的上界,那末,我們亦可以把音樂當作是上界的語言.", "google_translation": "If we consider our spiritual realm as our higher realm, then we can also consider music as the language of the higher realm.", "human_reference": "If we consider our spiritual world our upper realm (shangjie), we can also understand Music as a language of the upper realm."} +{"id": 8, "lang": "zh", "text": "不論怎麼樣的一個樂音,誰也未曾在自然界裏面聽過。牠那種有一定的數目可查的顫動,以及牠的音色,都是由人們創造出來的,並不是從自然界照樣學來的。", "google_translation": "No one has ever heard any musical note in nature. Its specific, countable vibrations and its timbre are all created by humans, not learned directly from nature.", "human_reference": "No musical tones can be heard in nature. They are scrutable vibrations; their timbre is manufactured by humans, not emulated from nature."} +{"id": 12, "lang": "zh", "text": "中國人向來是樂與詩書禮並重的,像禮樂治天下這一類的話,舊日中國人亦不知道說了多少.", "google_translation": "Chinese people have always valued poetry and books as much as ritual and propriety. In the past, they have said countless times things like \"governing the world with ritual and music.\"", "human_reference": "For the Chinese, Music has always been considered on an equal footing with Poetry (shi), Literature (shu), and Ritual (li), as evident in such stale expressions as \"Ritual and Music rule the Heaven and the Earth."} +{"id": 15, "lang": "ja", "text": "し 具 体 的 に お 聞 き し た い の で す が 、 た とえ ば 、 先 ⽣ は 韓 国 の 雅 楽 か ら 何 か 受 け られ た 影 響 は ご ざ い ま す か 。 私 は 先 ⽣ の 作 品 に そ れ を 感 じ る の で す が \" … \"全 く そ の 通 り で す 。 ⽇ 本 の 雅 楽 は 韓 国 か ら 伝 来 し た 部 分 と 中 国 か ら 伝 来 し た 部 分 が あ り 、 韓 国 の 雅 楽 と は 内 容 的 に 少し 質 が 違 い ま す .", "google_translation": "I would like to ask you something more specific. For example, have you been influenced by Korean Gagaku in any way? I feel that in your works... \"That's absolutely right. Japanese Gagaku has parts that came from Korea and parts that came from China, and its content is slightly different in quality from Korean Gagaku.\"", "human_reference": "For example, have you been influenced by Korean A-ak [Korean ceremonial court music]? I can sense its influence in your pieces. . .\" \"Exactly as you said. Japanese court music has parts that are imported from Korea and parts that are from China. But its contents are qualitatively different from Korean A-ak."} +{"id": 16, "lang": "ja", "text": "日本の作曲家たちは、従来、作曲のマテリアルは、現代でも古い西洋の伝統的な形式観に何かしら沿っているのではないでしょうか。", "google_translation": "Don't you think that Japanese composers have traditionally, and even in modern times, based their compositional material on some form of the old Western traditional view of form?", "human_reference": "Perhaps Japanese composers follow the old Western traditional formalism even though the compositional materials [マテリアル] might be modern."} +{"id": 19, "lang": "ja", "text": "さてAでは上段が下段に比較して細かい分割を行っているが、この両者の関係はかならずしも一概に決定し難く、ある一方の終止音が支配的である旋律中に、他の終止音が終止音としての意味を持たず、単に経過的に現れることも多く、また4度とか5度は、純粋にその音域を制限するものではなく、その上下に随時経過音が付加されうる。", "google_translation": "Now, in A, the upper part is divided more finely than the lower part, but the relationship between the two is not necessarily easy to determine. In a melody where one cadence tone is dominant, the other cadence tone often has no meaning as a cadence tone and appears only as a passing tone. Also, fourths and fifths are not purely for limiting the range; passing tones can be added above or below them as needed.", "human_reference": "For now, in A, I have made narrower divisions in the upper analysis than in the lower, but the relation between the two is certainly hard to determine as a rule. On the other hand, for the part of the melody controlled by one of the ending notes, the other ending note does not function as the ending note but simply appears in passing [passing tone]. Also, fourths and fifths may not purely limit the range of notes, and extra notes might be added above or below."} +{"id": 203, "lang": "es", "text": "La llamada escala cromática, y que en su forma clásica procede por semitonos, en su metamorfosis al duplo quedará por tonos enteros.", "google_translation": "The so-called chromatic scale, which in its classic form proceeds by semitones, in its metamorphosis to the double will be by whole tones.", "human_reference": "The so-called chromatic scale, which in its classical form proceeds by semitones, proceeds by whole tones under its metamorphosis to the double."} +{"id": 207, "lang": "es", "text": "No conozco ningún ejemplo en el cual se hayan operado modificaciones melódicas, armónicas y rítmicas, sin alterar los modelos, y con mi \"Ley de metamorfosis\" pueden efectuarse cambios en la melodía, en la armonía y el ritmo, sin agregar ni suprimir ni una sola nota.", "google_translation": "I do not know of any example in which melodic, harmonic, and rhythmic modifications have been made without altering the models, and with my \"Law of Metamorphosis\" changes can be made in the melody, harmony, and rhythm without adding or removing a single note.", "human_reference": "I do not know any example in which melodic, harmonic, and rhythmic modifications have been used without altering the models. With my \"Law of Metamorphosis,\" changes in melody, harmony, and rhythm can be carried out without adding or removing a single note."} +{"id": 210, "lang": "es", "text": "En cuanto al perfecto menor, que tiene sobre el bajo un intervalo de tres semitonos, y sobre la nota alta de éste, uno de cuatro, tendrá al metamorfosearse al duplo, uno de seis semitonos sobre el bajo, en vez de tres, y sobre la nota alta de éste, uno de ocho semitonos en vez de cuatro.", "google_translation": "As for the minor perfect, which has an interval of three semitones over the bass, and one of four over the high note of the latter, when metamorphosed into the double, it will have one of six semitones over the bass, instead of three, and one of eight semitones over the high note of the latter, instead of four.", "human_reference": "The minor triad has an interval of three semitones above the bass, and above that, an interval of four semitones. Under metamorphosis to the double, the first interval will reach six semitones above the bass, and the second, another eight semitones."} From 10cf85b06cdca9999e93493fe82074519eee1b39 Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:48:55 -0500 Subject: [PATCH 4/9] Rename test script to test_google_api_connectivity.py Update script name and documentation to better reflect its purpose as an API connectivity test rather than a general Google Cloud Translate test. --- ...cloud_translate.py => test_google_api_connectivity.py} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename test_scripts/{test_google_cloud_translate.py => test_google_api_connectivity.py} (93%) diff --git a/test_scripts/test_google_cloud_translate.py b/test_scripts/test_google_api_connectivity.py similarity index 93% rename from test_scripts/test_google_cloud_translate.py rename to test_scripts/test_google_api_connectivity.py index 308fe66..df37e70 100644 --- a/test_scripts/test_google_cloud_translate.py +++ b/test_scripts/test_google_api_connectivity.py @@ -1,7 +1,7 @@ """ -Google Cloud Translate API - Infrastructure and Connectivity Tests +Google Cloud API - Connectivity Tests -This script tests Google Cloud API-specific functionality: +This script tests Google Cloud API connectivity and infrastructure: - Authentication and credentials validation - API connectivity and response times - API response metadata @@ -102,7 +102,7 @@ def test_api_metadata(): if __name__ == "__main__": print("=" * 80) - print("Google Cloud Translate API - Infrastructure Tests") + print("Google Cloud API - Connectivity Tests") print("=" * 80) # Check credentials first @@ -118,7 +118,7 @@ def test_api_metadata(): test_api_metadata() print("\n" + "=" * 80) - print("All infrastructure tests completed!") + print("All connectivity tests completed!") print("=" * 80) print("\nFor translation quality testing, run:") print(" python test_scripts/test_translate.py") From 67fe2b2d35da527959728633029ff2abe5c680da Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:50:42 -0500 Subject: [PATCH 5/9] Simplify connectivity test to single unified output Consolidate all test sections into a single streamlined output showing: - Credentials validation - Client initialization - API call success with timing - Model information - Source text and translation result --- test_scripts/test_google_api_connectivity.py | 76 ++++++-------------- 1 file changed, 22 insertions(+), 54 deletions(-) diff --git a/test_scripts/test_google_api_connectivity.py b/test_scripts/test_google_api_connectivity.py index df37e70..7fdde02 100644 --- a/test_scripts/test_google_api_connectivity.py +++ b/test_scripts/test_google_api_connectivity.py @@ -1,10 +1,10 @@ """ Google Cloud API - Connectivity Tests -This script tests Google Cloud API connectivity and infrastructure: -- Authentication and credentials validation +This script tests Google Cloud API connectivity: +- Credentials validation - API connectivity and response times -- API response metadata +- Model information For translation quality testing, use test_translate.py with model="google/translation-llm" @@ -19,18 +19,15 @@ from google.cloud import translate_v3 -from muse.translation.translate import google_cloud_translate - # API configuration PROJECT_ID = "cdh-muse" CREDENTIALS_FILE = "cdh-muse-6950d66acf83.json" +TEST_TEXT = "Hello World" -def test_credentials_validation(): - """Test that credentials are properly configured.""" - print("\n=== Test 1: Credentials Validation ===") - - # Check if credentials file exists +def run_connectivity_test(): + """Run Google Cloud API connectivity test.""" + # Check credentials creds_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") if creds_path: print(f"✓ GOOGLE_APPLICATION_CREDENTIALS set: {creds_path}") @@ -42,81 +39,52 @@ def test_credentials_validation(): else: print("⚠ GOOGLE_APPLICATION_CREDENTIALS not set, using default credentials") - # Try to initialize client + # Initialize client try: - translate_v3.TranslationServiceClient() + client = translate_v3.TranslationServiceClient() print("✓ Successfully initialized TranslationServiceClient") - return True except Exception as e: print(f"✗ Failed to initialize client: {e}") return False - -def test_api_connectivity(): - """Test basic API connectivity with a simple translation.""" - print("\n=== Test 2: API Connectivity ===") - + # Test API call try: start_time = time.time() - result = google_cloud_translate( - src_lang="en", - tgt_lang="es", - text="Hello", - project_id=PROJECT_ID, - verbose=False, - ) - elapsed = time.time() - start_time - - print("✓ API call successful") - print(f"✓ Response time: {elapsed:.2f} seconds") - print(f"✓ Translation result: '{result}'") - return True - except Exception as e: - print(f"✗ API call failed: {e}") - return False - - -def test_api_metadata(): - """Test API response metadata and model information.""" - print("\n=== Test 3: API Response Metadata ===") - - try: - client = translate_v3.TranslationServiceClient() parent = f"projects/{PROJECT_ID}/locations/us-central1" model_path = f"{parent}/models/general/translation-llm" response = client.translate_text( - contents=["Hello"], + contents=[TEST_TEXT], target_language_code="es", source_language_code="en", parent=parent, model=model_path, mime_type="text/plain", ) + elapsed = time.time() - start_time + print("✓ API call successful") + print(f"✓ Response time: {elapsed:.2f} seconds") print(f"✓ Model used: {response.translations[0].model}") - print(f"✓ Translation: {response.translations[0].translated_text}") + print(f"Source text: {TEST_TEXT}") + print(f"Translation result: {response.translations[0].translated_text}") + return True except Exception as e: - print(f"✗ Failed to get metadata: {e}") + print(f"✗ API call failed: {e}") + return False if __name__ == "__main__": print("=" * 80) print("Google Cloud API - Connectivity Tests") print("=" * 80) + print() - # Check credentials first - if not test_credentials_validation(): - print( - "\n⚠ Credentials not configured. Set GOOGLE_APPLICATION_CREDENTIALS and try again." - ) + if not run_connectivity_test(): + print("\n⚠ Test failed. Set GOOGLE_APPLICATION_CREDENTIALS and try again.") print(f' export GOOGLE_APPLICATION_CREDENTIALS="{CREDENTIALS_FILE}"') exit(1) - # Run tests - test_api_connectivity() - test_api_metadata() - print("\n" + "=" * 80) print("All connectivity tests completed!") print("=" * 80) From ece44b227bbfa3565a691e3d2a1da459430ab4d6 Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:53:57 -0500 Subject: [PATCH 6/9] Update test_google_api_connectivity.py --- test_scripts/test_google_api_connectivity.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/test_scripts/test_google_api_connectivity.py b/test_scripts/test_google_api_connectivity.py index 7fdde02..2a3f6fb 100644 --- a/test_scripts/test_google_api_connectivity.py +++ b/test_scripts/test_google_api_connectivity.py @@ -1,15 +1,7 @@ """ -Google Cloud API - Connectivity Tests - -This script tests Google Cloud API connectivity: -- Credentials validation -- API connectivity and response times -- Model information - +This script only tests Google Cloud API connectivity. For translation quality testing, use test_translate.py with model="google/translation-llm" - -Authentication: - Set up credentials before running: +Before running, set up credentials: export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" """ @@ -75,18 +67,10 @@ def run_connectivity_test(): if __name__ == "__main__": - print("=" * 80) print("Google Cloud API - Connectivity Tests") - print("=" * 80) print() if not run_connectivity_test(): print("\n⚠ Test failed. Set GOOGLE_APPLICATION_CREDENTIALS and try again.") print(f' export GOOGLE_APPLICATION_CREDENTIALS="{CREDENTIALS_FILE}"') exit(1) - - print("\n" + "=" * 80) - print("All connectivity tests completed!") - print("=" * 80) - print("\nFor translation quality testing, run:") - print(" python test_scripts/test_translate.py") From 25c47f6107295fe8b903d6fc40f8e6a7cee69817 Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:56:12 -0500 Subject: [PATCH 7/9] Update comments --- src/muse/translation/translate.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/muse/translation/translate.py b/src/muse/translation/translate.py index a3741c7..1483b2c 100644 --- a/src/muse/translation/translate.py +++ b/src/muse/translation/translate.py @@ -232,31 +232,11 @@ def google_cloud_translate( Translate text using Google Cloud Translate API with Translation LLM (TLLM) model. Languages are specified with their ISO 639-1 codes (e.g., "zh", "ja", "es", "en"). - The TLLM model is Google's latest state-of-the-art translation model, providing - the highest available quality of translation. It is language-agnostic and supports - all major languages without requiring special language mappings. - Authentication: This API requires OAuth2 credentials. Authentication is handled in this order: 1. Service account JSON file (via credentials_path parameter) 2. GOOGLE_APPLICATION_CREDENTIALS environment variable 3. Application Default Credentials (gcloud auth application-default login) - - Args: - src_lang: Source language ISO 639-1 code - tgt_lang: Target language ISO 639-1 code - text: Text to translate from source to target language - project_id: Google Cloud project ID (default: "cdh-muse") - region: Google Cloud region (default: "us-central1") - credentials_path: Path to service account JSON file (optional) - verbose: If True, print timing information - - Returns: - Translated text as a string - - Raises: - ValueError: If project ID is missing - Exception: If API call fails (authentication, network, etc.) """ if not project_id: raise ValueError("Google Cloud project ID is required.") From 3738da8eaf808cb0531b47d3c36817d1570d500b Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:01:29 -0500 Subject: [PATCH 8/9] Update test_google_api_connectivity.py --- test_scripts/test_google_api_connectivity.py | 23 +++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/test_scripts/test_google_api_connectivity.py b/test_scripts/test_google_api_connectivity.py index 2a3f6fb..064e334 100644 --- a/test_scripts/test_google_api_connectivity.py +++ b/test_scripts/test_google_api_connectivity.py @@ -1,13 +1,18 @@ """ -This script only tests Google Cloud API connectivity. +Test Google Cloud API connectivity. + For translation quality testing, use test_translate.py with model="google/translation-llm" + Before running, set up credentials: export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" + +Usage: + test_google_api_connectivity.py """ import os +import pathlib import time -from pathlib import Path from google.cloud import translate_v3 @@ -18,12 +23,16 @@ def run_connectivity_test(): - """Run Google Cloud API connectivity test.""" + """ + Run Google Cloud API connectivity test. + + Returns True if test passes, False otherwise. + """ # Check credentials creds_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") if creds_path: print(f"✓ GOOGLE_APPLICATION_CREDENTIALS set: {creds_path}") - if Path(creds_path).exists(): + if pathlib.Path(creds_path).exists(): print("✓ Credentials file exists") else: print(f"✗ Credentials file not found at {creds_path}") @@ -66,7 +75,7 @@ def run_connectivity_test(): return False -if __name__ == "__main__": +def main(): print("Google Cloud API - Connectivity Tests") print() @@ -74,3 +83,7 @@ def run_connectivity_test(): print("\n⚠ Test failed. Set GOOGLE_APPLICATION_CREDENTIALS and try again.") print(f' export GOOGLE_APPLICATION_CREDENTIALS="{CREDENTIALS_FILE}"') exit(1) + + +if __name__ == "__main__": + main() From 5adcb9578f3eabec43145531a9cfb0c38c7d6ac7 Mon Sep 17 00:00:00 2001 From: hao <97079365+tanhaow@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:57:57 -0500 Subject: [PATCH 9/9] Revise per @laurejt 's review --- README.md | 12 +-- docs/DEVELOPERNOTES.md | 24 ++++++ google_translate_results.jsonl | 9 -- pyproject.toml | 2 +- src/muse/translation/translate.py | 65 +++++++------- test_scripts/test_google_api_connectivity.py | 89 -------------------- uv.lock | 2 +- 7 files changed, 57 insertions(+), 146 deletions(-) create mode 100644 docs/DEVELOPERNOTES.md delete mode 100644 google_translate_results.jsonl delete mode 100644 test_scripts/test_google_api_connectivity.py diff --git a/README.md b/README.md index 7b8b466..89ff1d6 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,4 @@ Repository for CDH-RSE partnership Multilingual Semantic Embeddings -## Google Cloud Translation Setup - -To use the Google Cloud Translation LLM model: - -1. Go to [Google Cloud Console](https://console.cloud.google.com/) → IAM & Admin → Service Accounts -2. Create a service account with **Cloud Translation API User** role -3. Create and download a JSON key file -4. Set the environment variable: - ```bash - export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" - ``` +For developer setup instructions, including Google Cloud Translation configuration, see [docs/DEVELOPERNOTES.md](docs/DEVELOPERNOTES.md). diff --git a/docs/DEVELOPERNOTES.md b/docs/DEVELOPERNOTES.md new file mode 100644 index 0000000..4ef27c3 --- /dev/null +++ b/docs/DEVELOPERNOTES.md @@ -0,0 +1,24 @@ +# Developer Notes + +## Google Cloud Translation Setup + +The MUSE project supports Google Cloud's Translation LLM (TLLM) model for machine translation. This requires Google Cloud CLI (gcloud) setup and authentication. + +### Prerequisites + +1. **Install Google Cloud CLI** + + - Follow instructions at: https://cloud.google.com/sdk/docs/install + - Verify installation: `gcloud --version` + +2. **Authenticate with Application Default Credentials** + + ```bash + gcloud auth application-default login + ``` + +3. **Set required environment variables** + + ```bash + export GOOGLE_CLOUD_PROJECT="cdh-muse" + ``` diff --git a/google_translate_results.jsonl b/google_translate_results.jsonl deleted file mode 100644 index 6a7f807..0000000 --- a/google_translate_results.jsonl +++ /dev/null @@ -1,9 +0,0 @@ -{"id": 1, "lang": "zh", "text": "如果我們把我們的靈界當作是我們的上界,那末,我們亦可以把音樂當作是上界的語言.", "google_translation": "If we consider our spiritual realm as our higher realm, then we can also consider music as the language of the higher realm.", "human_reference": "If we consider our spiritual world our upper realm (shangjie), we can also understand Music as a language of the upper realm."} -{"id": 8, "lang": "zh", "text": "不論怎麼樣的一個樂音,誰也未曾在自然界裏面聽過。牠那種有一定的數目可查的顫動,以及牠的音色,都是由人們創造出來的,並不是從自然界照樣學來的。", "google_translation": "No one has ever heard any musical note in nature. Its specific, countable vibrations and its timbre are all created by humans, not learned directly from nature.", "human_reference": "No musical tones can be heard in nature. They are scrutable vibrations; their timbre is manufactured by humans, not emulated from nature."} -{"id": 12, "lang": "zh", "text": "中國人向來是樂與詩書禮並重的,像禮樂治天下這一類的話,舊日中國人亦不知道說了多少.", "google_translation": "Chinese people have always valued poetry and books as much as ritual and propriety. In the past, they have said countless times things like \"governing the world with ritual and music.\"", "human_reference": "For the Chinese, Music has always been considered on an equal footing with Poetry (shi), Literature (shu), and Ritual (li), as evident in such stale expressions as \"Ritual and Music rule the Heaven and the Earth."} -{"id": 15, "lang": "ja", "text": "し 具 体 的 に お 聞 き し た い の で す が 、 た とえ ば 、 先 ⽣ は 韓 国 の 雅 楽 か ら 何 か 受 け られ た 影 響 は ご ざ い ま す か 。 私 は 先 ⽣ の 作 品 に そ れ を 感 じ る の で す が \" … \"全 く そ の 通 り で す 。 ⽇ 本 の 雅 楽 は 韓 国 か ら 伝 来 し た 部 分 と 中 国 か ら 伝 来 し た 部 分 が あ り 、 韓 国 の 雅 楽 と は 内 容 的 に 少し 質 が 違 い ま す .", "google_translation": "I would like to ask you something more specific. For example, have you been influenced by Korean Gagaku in any way? I feel that in your works... \"That's absolutely right. Japanese Gagaku has parts that came from Korea and parts that came from China, and its content is slightly different in quality from Korean Gagaku.\"", "human_reference": "For example, have you been influenced by Korean A-ak [Korean ceremonial court music]? I can sense its influence in your pieces. . .\" \"Exactly as you said. Japanese court music has parts that are imported from Korea and parts that are from China. But its contents are qualitatively different from Korean A-ak."} -{"id": 16, "lang": "ja", "text": "日本の作曲家たちは、従来、作曲のマテリアルは、現代でも古い西洋の伝統的な形式観に何かしら沿っているのではないでしょうか。", "google_translation": "Don't you think that Japanese composers have traditionally, and even in modern times, based their compositional material on some form of the old Western traditional view of form?", "human_reference": "Perhaps Japanese composers follow the old Western traditional formalism even though the compositional materials [マテリアル] might be modern."} -{"id": 19, "lang": "ja", "text": "さてAでは上段が下段に比較して細かい分割を行っているが、この両者の関係はかならずしも一概に決定し難く、ある一方の終止音が支配的である旋律中に、他の終止音が終止音としての意味を持たず、単に経過的に現れることも多く、また4度とか5度は、純粋にその音域を制限するものではなく、その上下に随時経過音が付加されうる。", "google_translation": "Now, in A, the upper part is divided more finely than the lower part, but the relationship between the two is not necessarily easy to determine. In a melody where one cadence tone is dominant, the other cadence tone often has no meaning as a cadence tone and appears only as a passing tone. Also, fourths and fifths are not purely for limiting the range; passing tones can be added above or below them as needed.", "human_reference": "For now, in A, I have made narrower divisions in the upper analysis than in the lower, but the relation between the two is certainly hard to determine as a rule. On the other hand, for the part of the melody controlled by one of the ending notes, the other ending note does not function as the ending note but simply appears in passing [passing tone]. Also, fourths and fifths may not purely limit the range of notes, and extra notes might be added above or below."} -{"id": 203, "lang": "es", "text": "La llamada escala cromática, y que en su forma clásica procede por semitonos, en su metamorfosis al duplo quedará por tonos enteros.", "google_translation": "The so-called chromatic scale, which in its classic form proceeds by semitones, in its metamorphosis to the double will be by whole tones.", "human_reference": "The so-called chromatic scale, which in its classical form proceeds by semitones, proceeds by whole tones under its metamorphosis to the double."} -{"id": 207, "lang": "es", "text": "No conozco ningún ejemplo en el cual se hayan operado modificaciones melódicas, armónicas y rítmicas, sin alterar los modelos, y con mi \"Ley de metamorfosis\" pueden efectuarse cambios en la melodía, en la armonía y el ritmo, sin agregar ni suprimir ni una sola nota.", "google_translation": "I do not know of any example in which melodic, harmonic, and rhythmic modifications have been made without altering the models, and with my \"Law of Metamorphosis\" changes can be made in the melody, harmony, and rhythm without adding or removing a single note.", "human_reference": "I do not know any example in which melodic, harmonic, and rhythmic modifications have been used without altering the models. With my \"Law of Metamorphosis,\" changes in melody, harmony, and rhythm can be carried out without adding or removing a single note."} -{"id": 210, "lang": "es", "text": "En cuanto al perfecto menor, que tiene sobre el bajo un intervalo de tres semitonos, y sobre la nota alta de éste, uno de cuatro, tendrá al metamorfosearse al duplo, uno de seis semitonos sobre el bajo, en vez de tres, y sobre la nota alta de éste, uno de ocho semitonos en vez de cuatro.", "google_translation": "As for the minor perfect, which has an interval of three semitones over the bass, and one of four over the high note of the latter, when metamorphosed into the double, it will have one of six semitones over the bass, instead of three, and one of eight semitones over the high note of the latter, instead of four.", "human_reference": "The minor triad has an interval of three semitones above the bass, and above that, an interval of four semitones. Under metamorphosis to the double, the first interval will reach six semitones above the bass, and the second, another eight semitones."} diff --git a/pyproject.toml b/pyproject.toml index 670a924..c451a4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "numpy", "ipython", # Required by transformers for trainer functionality "transformers[torch, sentencepiece, tiktoken]", - "google-cloud-translate>=3.15.0", + "google-cloud-translate", ] [project.optional-dependencies] diff --git a/src/muse/translation/translate.py b/src/muse/translation/translate.py index 1483b2c..94711d2 100644 --- a/src/muse/translation/translate.py +++ b/src/muse/translation/translate.py @@ -8,6 +8,7 @@ madlad_translate, google_cloud_translate) are also available for direct use. """ +import os from timeit import default_timer as timer from google.cloud import translate_v3 @@ -223,69 +224,63 @@ def google_cloud_translate( src_lang: str, tgt_lang: str, text: str, - project_id: str = "cdh-muse", - region: str = "us-central1", - credentials_path: str | None = None, verbose: bool = False, ) -> str: """ Translate text using Google Cloud Translate API with Translation LLM (TLLM) model. Languages are specified with their ISO 639-1 codes (e.g., "zh", "ja", "es", "en"). - Authentication: - This API requires OAuth2 credentials. Authentication is handled in this order: - 1. Service account JSON file (via credentials_path parameter) - 2. GOOGLE_APPLICATION_CREDENTIALS environment variable - 3. Application Default Credentials (gcloud auth application-default login) + Requires gcloud CLI authentication. See docs/DEVELOPERNOTES.md for setup. + + Args: + src_lang: Source language ISO 639-1 code + tgt_lang: Target language ISO 639-1 code + text: Text to translate from source to target language + verbose: If True, print timing information + + Returns: + Translated text as a string + + Raises: + ValueError: If GOOGLE_CLOUD_PROJECT environment variable is not set """ + project_id = os.environ.get("GOOGLE_CLOUD_PROJECT") if not project_id: - raise ValueError("Google Cloud project ID is required.") + raise ValueError( + "GOOGLE_CLOUD_PROJECT environment variable is not set. " + "Set it with: export GOOGLE_CLOUD_PROJECT='cdh-muse'" + ) + + region = os.environ.get("GOOGLE_CLOUD_REGION", "us-central1") - # Initialize the Translation client if verbose: start = timer() - # Create client with credentials if provided - if credentials_path: - from google.oauth2 import service_account - - credentials = service_account.Credentials.from_service_account_file( - credentials_path - ) - client = translate_v3.TranslationServiceClient(credentials=credentials) - else: - # Use default credentials (from GOOGLE_APPLICATION_CREDENTIALS env var or gcloud) - client = translate_v3.TranslationServiceClient() + client = translate_v3.TranslationServiceClient() if verbose: print( f"Initialized Google Cloud Translate client in {timer() - start:.2f} seconds" ) - # Construct the parent and model paths parent = f"projects/{project_id}/locations/{region}" model_path = f"{parent}/models/general/translation-llm" - # Call the API if verbose: start = timer() - try: - response = client.translate_text( - contents=[text], - target_language_code=tgt_lang, - source_language_code=src_lang, - parent=parent, - model=model_path, - mime_type="text/plain", - ) - except Exception as e: - raise Exception(f"Google Cloud Translate API call failed: {e}") from e + response = client.translate_text( + contents=[text], + target_language_code=tgt_lang, + source_language_code=src_lang, + parent=parent, + model=model_path, + mime_type="text/plain", + ) if verbose: print(f"Received translation response in {timer() - start:.2f} seconds") - # Extract translated text from response translated_text = response.translations[0].translated_text return translated_text diff --git a/test_scripts/test_google_api_connectivity.py b/test_scripts/test_google_api_connectivity.py deleted file mode 100644 index 064e334..0000000 --- a/test_scripts/test_google_api_connectivity.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Test Google Cloud API connectivity. - -For translation quality testing, use test_translate.py with model="google/translation-llm" - -Before running, set up credentials: - export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" - -Usage: - test_google_api_connectivity.py -""" - -import os -import pathlib -import time - -from google.cloud import translate_v3 - -# API configuration -PROJECT_ID = "cdh-muse" -CREDENTIALS_FILE = "cdh-muse-6950d66acf83.json" -TEST_TEXT = "Hello World" - - -def run_connectivity_test(): - """ - Run Google Cloud API connectivity test. - - Returns True if test passes, False otherwise. - """ - # Check credentials - creds_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") - if creds_path: - print(f"✓ GOOGLE_APPLICATION_CREDENTIALS set: {creds_path}") - if pathlib.Path(creds_path).exists(): - print("✓ Credentials file exists") - else: - print(f"✗ Credentials file not found at {creds_path}") - return False - else: - print("⚠ GOOGLE_APPLICATION_CREDENTIALS not set, using default credentials") - - # Initialize client - try: - client = translate_v3.TranslationServiceClient() - print("✓ Successfully initialized TranslationServiceClient") - except Exception as e: - print(f"✗ Failed to initialize client: {e}") - return False - - # Test API call - try: - start_time = time.time() - parent = f"projects/{PROJECT_ID}/locations/us-central1" - model_path = f"{parent}/models/general/translation-llm" - - response = client.translate_text( - contents=[TEST_TEXT], - target_language_code="es", - source_language_code="en", - parent=parent, - model=model_path, - mime_type="text/plain", - ) - elapsed = time.time() - start_time - - print("✓ API call successful") - print(f"✓ Response time: {elapsed:.2f} seconds") - print(f"✓ Model used: {response.translations[0].model}") - print(f"Source text: {TEST_TEXT}") - print(f"Translation result: {response.translations[0].translated_text}") - return True - except Exception as e: - print(f"✗ API call failed: {e}") - return False - - -def main(): - print("Google Cloud API - Connectivity Tests") - print() - - if not run_connectivity_test(): - print("\n⚠ Test failed. Set GOOGLE_APPLICATION_CREDENTIALS and try again.") - print(f' export GOOGLE_APPLICATION_CREDENTIALS="{CREDENTIALS_FILE}"') - exit(1) - - -if __name__ == "__main__": - main() diff --git a/uv.lock b/uv.lock index 468cb70..422931b 100644 --- a/uv.lock +++ b/uv.lock @@ -1048,7 +1048,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "google-cloud-translate", specifier = ">=3.15.0" }, + { name = "google-cloud-translate" }, { name = "ipython" }, { name = "marimo", specifier = ">=0.13.11" }, { name = "marimo", extras = ["lsp"], marker = "extra == 'dev'" },