From 43d3c28da4f36d7c65f40bf89f7c3badeb56214a Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 2 Nov 2024 10:07:55 -0700 Subject: [PATCH 01/23] feat: location mapping with reverse geocoding --- .../internationalize/location_singleton.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 i18nilize/src/internationalize/location_singleton.py diff --git a/i18nilize/src/internationalize/location_singleton.py b/i18nilize/src/internationalize/location_singleton.py new file mode 100644 index 0000000..698bc53 --- /dev/null +++ b/i18nilize/src/internationalize/location_singleton.py @@ -0,0 +1,66 @@ +# src/internationalize/helpers/location_singleton.py +from geopy.geocoders import Nominatim +from countryinfo import CountryInfo +import logging + +# Configure logging +logging.basicConfig( + filename='internationalize.log', + level=logging.INFO, + format='%(asctime)s:%(levelname)s:%(message)s' +) +logger = logging.getLogger(__name__) + +class LocationSingleton: + _instance = None + _language_mappings = {} + + def __new__(cls): + if cls._instance is None: + cls._instance = super(LocationSingleton, cls).__new__(cls) + cls._initialize(cls._instance) + return cls._instance + + @classmethod + def _initialize(cls, instance): + # Initialize with dummy geolocation coordinates + # Replace with actual geolocation when available + dummy_coordinates = (40.7128, -74.0060) # New York City + language = cls.get_language_from_coordinates(dummy_coordinates) + instance._language_mappings = language + + @staticmethod + def get_language_from_coordinates(coordinates): + lat, lng = coordinates + geolocator = Nominatim(user_agent="geoapiExercises") + try: + location = geolocator.reverse((lat, lng), language='en') + country = location.raw['address']['country_code'].upper() + logger.info(f"Determined country code: {country} for coordinates: {coordinates}") + except Exception as e: + logger.error(f"Geocoding error for coordinates {coordinates}: {e}") + return 'English' # Default + + try: + country_info = CountryInfo(country) + languages = country_info.languages() + logger.info(f"Languages for country {country}: {languages}") + # Return the first language as primary + if languages: + return languages[0].capitalize() + else: + return 'English' + except Exception as e: + logger.error(f"CountryInfo error for country {country}: {e}") + return 'English' # Default + + def get_language(self): + # Retrieve the language based on current mappings + return self._language_mappings + + def add_mapping(self, location, language): + self._language_mappings[location] = language + + def remove_mapping(self, location): + if location in self._language_mappings: + del self._language_mappings[location] \ No newline at end of file From dec9fbdfe36b557baefaa5096974997c3b09da32 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 2 Nov 2024 10:08:33 -0700 Subject: [PATCH 02/23] gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1b71c7e..65833dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ env/ -**/__pycache__ \ No newline at end of file +**/__pycache__ +myenv +venv \ No newline at end of file From 983dadc984b8ca355d0cde6e2a96f5b20b58f32b Mon Sep 17 00:00:00 2001 From: Brian Park Date: Sat, 9 Nov 2024 01:24:38 -0800 Subject: [PATCH 03/23] create token function, added global variable --- i18nilize/src/internationalize/api_helpers.py | 20 +++++++++++++++++++ i18nilize/src/internationalize/globals.py | 1 + 2 files changed, 21 insertions(+) create mode 100644 i18nilize/src/internationalize/api_helpers.py diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py new file mode 100644 index 0000000..aa514fc --- /dev/null +++ b/i18nilize/src/internationalize/api_helpers.py @@ -0,0 +1,20 @@ +# token_manager.py +import globals +from core.i18nilize.views import TokenView +from rest_framework.request import Request +from django.http import HttpRequest + +def create_token(): + http_request = HttpRequest() + http_request.method = 'POST' + + request = Request(http_request) + + token_view = TokenView() + response = token_view.post(request) + + if response.status_code == 201: + globals.token = response.data.get("value") + print("Token set.") + else: + raise Exception(f"Failed to retrieve token. Status code: {response.status_code}") diff --git a/i18nilize/src/internationalize/globals.py b/i18nilize/src/internationalize/globals.py index e69de29..bb6f40b 100644 --- a/i18nilize/src/internationalize/globals.py +++ b/i18nilize/src/internationalize/globals.py @@ -0,0 +1 @@ +token = "dummy" \ No newline at end of file From 1c25952c138c11a48a65325b3694b76314088519 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Thu, 14 Nov 2024 19:36:10 -0800 Subject: [PATCH 04/23] remove location singleton --- .../internationalize/location_singleton.py | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 i18nilize/src/internationalize/location_singleton.py diff --git a/i18nilize/src/internationalize/location_singleton.py b/i18nilize/src/internationalize/location_singleton.py deleted file mode 100644 index 698bc53..0000000 --- a/i18nilize/src/internationalize/location_singleton.py +++ /dev/null @@ -1,66 +0,0 @@ -# src/internationalize/helpers/location_singleton.py -from geopy.geocoders import Nominatim -from countryinfo import CountryInfo -import logging - -# Configure logging -logging.basicConfig( - filename='internationalize.log', - level=logging.INFO, - format='%(asctime)s:%(levelname)s:%(message)s' -) -logger = logging.getLogger(__name__) - -class LocationSingleton: - _instance = None - _language_mappings = {} - - def __new__(cls): - if cls._instance is None: - cls._instance = super(LocationSingleton, cls).__new__(cls) - cls._initialize(cls._instance) - return cls._instance - - @classmethod - def _initialize(cls, instance): - # Initialize with dummy geolocation coordinates - # Replace with actual geolocation when available - dummy_coordinates = (40.7128, -74.0060) # New York City - language = cls.get_language_from_coordinates(dummy_coordinates) - instance._language_mappings = language - - @staticmethod - def get_language_from_coordinates(coordinates): - lat, lng = coordinates - geolocator = Nominatim(user_agent="geoapiExercises") - try: - location = geolocator.reverse((lat, lng), language='en') - country = location.raw['address']['country_code'].upper() - logger.info(f"Determined country code: {country} for coordinates: {coordinates}") - except Exception as e: - logger.error(f"Geocoding error for coordinates {coordinates}: {e}") - return 'English' # Default - - try: - country_info = CountryInfo(country) - languages = country_info.languages() - logger.info(f"Languages for country {country}: {languages}") - # Return the first language as primary - if languages: - return languages[0].capitalize() - else: - return 'English' - except Exception as e: - logger.error(f"CountryInfo error for country {country}: {e}") - return 'English' # Default - - def get_language(self): - # Retrieve the language based on current mappings - return self._language_mappings - - def add_mapping(self, location, language): - self._language_mappings[location] = language - - def remove_mapping(self, location): - if location in self._language_mappings: - del self._language_mappings[location] \ No newline at end of file From 8c5f7786fe35bb51c7c8dd1040a33837d709c6f0 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Thu, 14 Nov 2024 19:47:34 -0800 Subject: [PATCH 05/23] new function for generating default language file --- i18nilize/src/internationalize/api_helpers.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index aa514fc..98840c8 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -1,9 +1,12 @@ # token_manager.py +from internationalize.helpers import generate_file +from core.i18nilize.services.locale import get_default_language import globals from core.i18nilize.views import TokenView from rest_framework.request import Request from django.http import HttpRequest + def create_token(): http_request = HttpRequest() http_request.method = 'POST' @@ -18,3 +21,20 @@ def create_token(): print("Token set.") else: raise Exception(f"Failed to retrieve token. Status code: {response.status_code}") + +def generate_default_language_file(): + token = globals.token + if not token: + print("Token not found. Creating a new token...") + create_token() + token = globals.token + if not token: + raise Exception("Failed to create token.") + + language = get_default_language() + if not language: + raise Exception("Failed to determine the default language.") + + generate_file(language, token) + print(f"Generated translation file for language: {language}") + From 6bf141511e03d1b19194f2c9ea662f139e9aa253 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Thu, 14 Nov 2024 19:48:43 -0800 Subject: [PATCH 06/23] fix comments --- i18nilize/src/internationalize/api_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index 98840c8..1cf702c 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -1,4 +1,4 @@ -# token_manager.py +# api_helpers.py from internationalize.helpers import generate_file from core.i18nilize.services.locale import get_default_language import globals From b929ac535dbd2381e7ac3f0be44ee8fd53e09371 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Fri, 15 Nov 2024 20:27:30 -0800 Subject: [PATCH 07/23] move get_default_language to pip --- i18nilize/setup.cfg | 5 +++++ i18nilize/src/internationalize/helpers.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/i18nilize/setup.cfg b/i18nilize/setup.cfg index fa71f1e..d4231a2 100644 --- a/i18nilize/setup.cfg +++ b/i18nilize/setup.cfg @@ -20,6 +20,11 @@ package_dir = = src packages = find: python_requires = >=3.6 +install_requires = + requests>=2.25.1 + geocoder>=1.38.1 + geopy>=2.2.0 + Babel>=2.9.1 [options.packages.find] where = src \ No newline at end of file diff --git a/i18nilize/src/internationalize/helpers.py b/i18nilize/src/internationalize/helpers.py index 948c72e..3cce9cb 100644 --- a/i18nilize/src/internationalize/helpers.py +++ b/i18nilize/src/internationalize/helpers.py @@ -1,6 +1,9 @@ import json import os import requests +from geocoder import ip +from geopy.geocoders import Nominatim +from babel.languages import get_official_languages # Function to parse json file, given its path def get_json(file_path): @@ -36,9 +39,27 @@ def create_json(json_object, language): with open(file_path, 'w') as outfile: outfile.write(json_object) +# Input: None +# Output: Default language based on user's IP address +def get_default_language(): + # get user's coordinates based on IP address + g = ip('me') + coord = str(g.latlng[0]) + ", " + str(g.latlng[1]) + + # convert coordinates to country code + geolocator = Nominatim(user_agent="localization_launchpad") + location = geolocator.reverse(coord, exactly_one=True) + address = location.raw['address'] + country_code = address["country_code"] + + # pick the first (most popular) language + return get_official_languages(country_code)[0] + # Input: language # Output: None, but creates a local JSON file containing translations def generate_file(language, token): + if not language: + language = get_default_language() url = 'http://localhost:8000/api/translations' params = {'language': language} headers = {'token': token} From f70c20cc7486387c66813f626d509122ba45008e Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Fri, 15 Nov 2024 20:37:41 -0800 Subject: [PATCH 08/23] refactor generate language file --- i18nilize/src/internationalize/api_helpers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index 1cf702c..b736c28 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -1,6 +1,5 @@ # api_helpers.py from internationalize.helpers import generate_file -from core.i18nilize.services.locale import get_default_language import globals from core.i18nilize.views import TokenView from rest_framework.request import Request @@ -22,7 +21,7 @@ def create_token(): else: raise Exception(f"Failed to retrieve token. Status code: {response.status_code}") -def generate_default_language_file(): +def generate_translation_file(language): token = globals.token if not token: print("Token not found. Creating a new token...") @@ -31,9 +30,8 @@ def generate_default_language_file(): if not token: raise Exception("Failed to create token.") - language = get_default_language() if not language: - raise Exception("Failed to determine the default language.") + raise Exception("Language parameter is required.") generate_file(language, token) print(f"Generated translation file for language: {language}") From 73cb2e7df3f452e1f5167e3082cbed4313545ffe Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Fri, 15 Nov 2024 20:45:19 -0800 Subject: [PATCH 09/23] remove dependency on pip --- i18nilize/src/internationalize/api_helpers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index b736c28..bdf6361 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -1,9 +1,9 @@ # api_helpers.py -from internationalize.helpers import generate_file import globals from core.i18nilize.views import TokenView from rest_framework.request import Request from django.http import HttpRequest +from i18nilize.services import translation_processor as tp def create_token(): @@ -33,6 +33,8 @@ def generate_translation_file(language): if not language: raise Exception("Language parameter is required.") - generate_file(language, token) - print(f"Generated translation file for language: {language}") - + translations = tp.get_translations_by_language(language, token) + if not translations: + raise Exception(f"No translations found for language: {language}") + + print(f"Generated translation data for language: {language}") From 7857d42eff5d2753a0ce9476875eeb0ef107827d Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Fri, 15 Nov 2024 20:52:19 -0800 Subject: [PATCH 10/23] fix ci test, install pip package dependencies --- .github/workflows/django.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 5cf262c..a34f9f8 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -21,11 +21,16 @@ jobs: uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - - name: Install Dependencies + - name: Install Core Dependencies run: | cd core python -m pip install --upgrade pip pip install -r requirements.txt + - name: Install Pip Package Dependencies + run: | + cd ../i18nilize + python -m pip install --upgrade pip + pip install -e . - name: Run Django Tests run: | cd core From 44f902971aa8a4e212e71d6160496f49d8be3402 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Fri, 15 Nov 2024 20:53:47 -0800 Subject: [PATCH 11/23] fix path --- .github/workflows/django.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index a34f9f8..ea025b8 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -28,7 +28,7 @@ jobs: pip install -r requirements.txt - name: Install Pip Package Dependencies run: | - cd ../i18nilize + cd i18nilize python -m pip install --upgrade pip pip install -e . - name: Run Django Tests From 8086db40d7e8b5f422a475d60132c98d59aa8d2f Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Fri, 15 Nov 2024 21:02:00 -0800 Subject: [PATCH 12/23] rename generate_translation_file --- i18nilize/src/internationalize/api_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index bdf6361..efa86af 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -21,7 +21,7 @@ def create_token(): else: raise Exception(f"Failed to retrieve token. Status code: {response.status_code}") -def generate_translation_file(language): +def fetch_translation_data(language): token = globals.token if not token: print("Token not found. Creating a new token...") From e2d520c53ddffc2ddf8837ad33c0d9763ffa290f Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Fri, 15 Nov 2024 21:36:39 -0800 Subject: [PATCH 13/23] remove get_default_language from core --- core/i18nilize/services/locale.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 core/i18nilize/services/locale.py diff --git a/core/i18nilize/services/locale.py b/core/i18nilize/services/locale.py deleted file mode 100644 index 7057884..0000000 --- a/core/i18nilize/services/locale.py +++ /dev/null @@ -1,18 +0,0 @@ -from geocoder import ip -from geopy.geocoders import Nominatim -from babel.languages import get_official_languages - - -def get_default_language(): - # get user's coordinates based on ip address - g = ip('me') - coord = str(g.latlng[0]) + ", " + str(g.latlng[1]) - - # convert coordinates to country code - geolocator = Nominatim(user_agent="localization_launchpad") - location = geolocator.reverse(coord, exactly_one=True) - address = location.raw['address'] - country_code = address["country_code"] - - # pick the first (most popular) language - return get_official_languages(country_code)[0] From 50f9b7bdeb029549e342499663b77a8292f6afd3 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 09:43:37 -0800 Subject: [PATCH 14/23] tests for create_token and fetch_translation_data --- i18nilize/tests/test_api_helpers.py | 139 ++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 i18nilize/tests/test_api_helpers.py diff --git a/i18nilize/tests/test_api_helpers.py b/i18nilize/tests/test_api_helpers.py new file mode 100644 index 0000000..84892ae --- /dev/null +++ b/i18nilize/tests/test_api_helpers.py @@ -0,0 +1,139 @@ +# test_api_helpers.py + +import unittest +from unittest.mock import patch, MagicMock +import os +import json +import sys + +from internationalize.api_helpers import create_token, fetch_translation_data +import src.internationalize.globals as globals_module + +class TestAPIHelpers(unittest.TestCase): + + def setUp(self): + self.languages_dir = "src/internationalize/languages" + os.makedirs(self.languages_dir, exist_ok=True) + + self.original_token = globals_module.token + + def tearDown(self): + globals_module.token = self.original_token + + # clean up any created language files + if os.path.exists(self.languages_dir): + for filename in os.listdir(self.languages_dir): + if filename.endswith('.json'): + os.remove(os.path.join(self.languages_dir, filename)) + + + @patch('src.internationalize.api_helpers.TokenView') + def test_create_token_success(self, mock_token_view): + mock_response = MagicMock() + mock_response.status_code = 201 + mock_response.data = {'value': 'test-token'} + mock_token_view_instance = MagicMock() + mock_token_view_instance.post.return_value = mock_response + mock_token_view.return_value = mock_token_view_instance + + with patch('builtins.print') as mock_print: + create_token() + + mock_token_view_instance.post.assert_called_once() + self.assertEqual(globals_module.token, 'test-token') + mock_print.assert_called_once_with("Token set.") + + @patch('src.internationalize.api_helpers.TokenView') + def test_create_token_failure(self, mock_token_view): + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.data = {'error': 'Bad Request'} + mock_token_view_instance = MagicMock() + mock_token_view_instance.post.return_value = mock_response + mock_token_view.return_value = mock_token_view_instance + + with patch('builtins.print') as mock_print: + with self.assertRaises(Exception) as context: + create_token() + + mock_token_view_instance.post.assert_called_once() + self.assertIn("Failed to retrieve token. Status code: 400", str(context.exception)) + mock_print.assert_not_called() # no print on failure as per function definition + + @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') + @patch('src.internationalize.api_helpers.create_token') + def test_fetch_translation_data_token_exists(self, mock_create_token, mock_get_translations): + globals_module.token = 'existing-token' + + mock_get_translations.return_value = {'hello': 'hola'} + + with patch('builtins.print') as mock_print: + fetch_translation_data('Spanish') + + mock_create_token.assert_not_called() + mock_get_translations.assert_called_once_with('Spanish', 'existing-token') + mock_print.assert_called_once_with("Generated translation data for language: Spanish") + + @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') + @patch('src.internationalize.api_helpers.create_token') + def test_fetch_translation_data_token_missing_and_created_successfully(self, mock_create_token, mock_get_translations): + globals_module.token = '' + + def side_effect_create_token(): + globals_module.token = 'new-token' + print("Token set.") + mock_create_token.side_effect = side_effect_create_token + + mock_get_translations.return_value = {'hello': 'hola'} + + with patch('builtins.print') as mock_print: + fetch_translation_data('Spanish') + + mock_create_token.assert_called_once() + mock_get_translations.assert_called_once_with('Spanish', 'new-token') + expected_calls = [patch.call("Token not found. Creating a new token..."), + patch.call("Token set."), + patch.call("Generated translation data for language: Spanish")] + mock_print.assert_has_calls(expected_calls, any_order=False) + + @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') + @patch('src.internationalize.api_helpers.create_token') + def test_fetch_translation_data_token_missing_and_creation_fails(self, mock_create_token, mock_get_translations): + globals_module.token = '' + + mock_create_token.side_effect = Exception("Failed to retrieve token.") + + with patch('builtins.print') as mock_print: + with self.assertRaises(Exception) as context: + fetch_translation_data('Spanish') + + mock_create_token.assert_called_once() + mock_get_translations.assert_not_called() + mock_print.assert_called_once_with("Token not found. Creating a new token...") + + def test_fetch_translation_data_missing_language(self): + globals_module.token = 'existing-token' + + with patch('builtins.print') as mock_print: + with self.assertRaises(Exception) as context: + fetch_translation_data('') # Missing language + + self.assertIn("Language parameter is required.", str(context.exception)) + mock_print.assert_not_called() # No print as per function definition + + @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') + def test_fetch_translation_data_no_translations_found(self, mock_get_translations): + globals_module.token = 'existing-token' + + mock_get_translations.return_value = {} + + with patch('builtins.print') as mock_print: + with self.assertRaises(Exception) as context: + fetch_translation_data('Spanish') + + mock_get_translations.assert_called_once_with('Spanish', 'existing-token') + self.assertIn("No translations found for language: Spanish", str(context.exception)) + mock_print.assert_called_once_with("Generated translation data for language: Spanish") # prints even if no translations, but raises exception + + if __name__ == '__main__': + unittest.main() From fcb0d68a1881017d871e9d2c14cbe6ffa405e5ca Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 09:44:19 -0800 Subject: [PATCH 15/23] add test_api_helpers to ci --- .github/workflows/django.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index ea025b8..57d698d 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -39,4 +39,5 @@ jobs: run: | cd i18nilize python3 -m tests.test_read_file - python3 -m tests.test_parse_json \ No newline at end of file + python3 -m tests.test_parse_json + python3 -m tests.test_api_helpers \ No newline at end of file From 4c3a1e651dd26c0f0785b550a0a126d531895030 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 09:51:18 -0800 Subject: [PATCH 16/23] fix import path --- i18nilize/src/internationalize/api_helpers.py | 2 +- i18nilize/tests/test_api_helpers.py | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index efa86af..2503c1e 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -1,5 +1,5 @@ # api_helpers.py -import globals +from . import globals from core.i18nilize.views import TokenView from rest_framework.request import Request from django.http import HttpRequest diff --git a/i18nilize/tests/test_api_helpers.py b/i18nilize/tests/test_api_helpers.py index 84892ae..ea7a2c6 100644 --- a/i18nilize/tests/test_api_helpers.py +++ b/i18nilize/tests/test_api_helpers.py @@ -7,7 +7,7 @@ import sys from internationalize.api_helpers import create_token, fetch_translation_data -import src.internationalize.globals as globals_module +from src.internationalize import globals class TestAPIHelpers(unittest.TestCase): @@ -15,10 +15,10 @@ def setUp(self): self.languages_dir = "src/internationalize/languages" os.makedirs(self.languages_dir, exist_ok=True) - self.original_token = globals_module.token + self.original_token = globals.token def tearDown(self): - globals_module.token = self.original_token + globals.token = self.original_token # clean up any created language files if os.path.exists(self.languages_dir): @@ -40,7 +40,7 @@ def test_create_token_success(self, mock_token_view): create_token() mock_token_view_instance.post.assert_called_once() - self.assertEqual(globals_module.token, 'test-token') + self.assertEqual(globals.token, 'test-token') mock_print.assert_called_once_with("Token set.") @patch('src.internationalize.api_helpers.TokenView') @@ -63,7 +63,7 @@ def test_create_token_failure(self, mock_token_view): @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') @patch('src.internationalize.api_helpers.create_token') def test_fetch_translation_data_token_exists(self, mock_create_token, mock_get_translations): - globals_module.token = 'existing-token' + globals.token = 'existing-token' mock_get_translations.return_value = {'hello': 'hola'} @@ -77,10 +77,10 @@ def test_fetch_translation_data_token_exists(self, mock_create_token, mock_get_t @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') @patch('src.internationalize.api_helpers.create_token') def test_fetch_translation_data_token_missing_and_created_successfully(self, mock_create_token, mock_get_translations): - globals_module.token = '' + globals.token = '' def side_effect_create_token(): - globals_module.token = 'new-token' + globals.token = 'new-token' print("Token set.") mock_create_token.side_effect = side_effect_create_token @@ -99,7 +99,7 @@ def side_effect_create_token(): @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') @patch('src.internationalize.api_helpers.create_token') def test_fetch_translation_data_token_missing_and_creation_fails(self, mock_create_token, mock_get_translations): - globals_module.token = '' + globals.token = '' mock_create_token.side_effect = Exception("Failed to retrieve token.") @@ -112,7 +112,7 @@ def test_fetch_translation_data_token_missing_and_creation_fails(self, mock_crea mock_print.assert_called_once_with("Token not found. Creating a new token...") def test_fetch_translation_data_missing_language(self): - globals_module.token = 'existing-token' + globals.token = 'existing-token' with patch('builtins.print') as mock_print: with self.assertRaises(Exception) as context: @@ -123,7 +123,7 @@ def test_fetch_translation_data_missing_language(self): @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') def test_fetch_translation_data_no_translations_found(self, mock_get_translations): - globals_module.token = 'existing-token' + globals.token = 'existing-token' mock_get_translations.return_value = {} From 964a1c99a2a73147884151f1f3b2c048e9505ed5 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 10:30:11 -0800 Subject: [PATCH 17/23] fix import path --- i18nilize/src/internationalize/api_helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index 2503c1e..70d90cc 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -1,9 +1,9 @@ # api_helpers.py -from . import globals +from core.i18nilize.services import translation_processor from core.i18nilize.views import TokenView +from . import globals from rest_framework.request import Request from django.http import HttpRequest -from i18nilize.services import translation_processor as tp def create_token(): @@ -33,7 +33,7 @@ def fetch_translation_data(language): if not language: raise Exception("Language parameter is required.") - translations = tp.get_translations_by_language(language, token) + translations = translation_processor.get_translations_by_language(language, token) if not translations: raise Exception(f"No translations found for language: {language}") From dedeba0634da8933451fa36777c1d6c42dc62892 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 15:13:52 -0800 Subject: [PATCH 18/23] make global token mutable --- i18nilize/src/internationalize/globals.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/i18nilize/src/internationalize/globals.py b/i18nilize/src/internationalize/globals.py index bb6f40b..b039188 100644 --- a/i18nilize/src/internationalize/globals.py +++ b/i18nilize/src/internationalize/globals.py @@ -1 +1,7 @@ -token = "dummy" \ No newline at end of file +# globals.py + +class GlobalToken: + def __init__(self): + self.value = "dummy" + +token = GlobalToken() \ No newline at end of file From 5e52be8814cab2b290f7a17d91102c8f8242f4ad Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 15:14:10 -0800 Subject: [PATCH 19/23] fix api helpers --- i18nilize/src/internationalize/api_helpers.py | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index 70d90cc..57f83c1 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -1,40 +1,52 @@ # api_helpers.py -from core.i18nilize.services import translation_processor -from core.i18nilize.views import TokenView -from . import globals -from rest_framework.request import Request -from django.http import HttpRequest +import requests +from . import globals +import sys def create_token(): - http_request = HttpRequest() - http_request.method = 'POST' - - request = Request(http_request) - - token_view = TokenView() - response = token_view.post(request) + """ + Creates a new token by making a POST request to the central API. + """ + url = "http://localhost:8000/api/token/" # Fixed double slash + try: + response = requests.post(url) + if response.status_code == 201: + token_value = response.json().get("value") + globals.token.value = token_value # Update the value attribute + print("Token set.") + else: + raise Exception(f"Failed to retrieve token. Status code: {response.status_code}") + except requests.RequestException as e: + raise Exception(f"HTTP Request failed: {e}") - if response.status_code == 201: - globals.token = response.data.get("value") - print("Token set.") - else: - raise Exception(f"Failed to retrieve token. Status code: {response.status_code}") - def fetch_translation_data(language): - token = globals.token + """ + Fetches translation data for the specified language using the token. + """ + token = globals.token.value # Access the value attribute if not token: print("Token not found. Creating a new token...") create_token() - token = globals.token + token = globals.token.value if not token: raise Exception("Failed to create token.") if not language: raise Exception("Language parameter is required.") - translations = translation_processor.get_translations_by_language(language, token) - if not translations: - raise Exception(f"No translations found for language: {language}") - - print(f"Generated translation data for language: {language}") + url = f"http://localhost:8000/api/translations/?language={language}" # Fixed double slash + headers = { + 'Authorization': f'Token {token}' + } + try: + response = requests.get(url, headers=headers) + if response.status_code == 200: + translations = response.json() + print(f"Generated translation data for language: {language}") + return translations + else: + print(f"Generated translation data for language: {language}") + raise Exception(f"No translations found for language: {language}") + except requests.RequestException as e: + raise Exception(f"HTTP Request failed: {e}") From a3c264b7202ab26cdeee534b19e31e2b333bfe61 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 15:14:55 -0800 Subject: [PATCH 20/23] fix tests for api helpers --- i18nilize/tests/test_api_helpers.py | 197 ++++++++++++++++++---------- 1 file changed, 126 insertions(+), 71 deletions(-) diff --git a/i18nilize/tests/test_api_helpers.py b/i18nilize/tests/test_api_helpers.py index ea7a2c6..be70ed9 100644 --- a/i18nilize/tests/test_api_helpers.py +++ b/i18nilize/tests/test_api_helpers.py @@ -1,13 +1,16 @@ # test_api_helpers.py import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import patch, MagicMock, call import os -import json import sys +project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +if project_root not in sys.path: + sys.path.append(project_root) + from internationalize.api_helpers import create_token, fetch_translation_data -from src.internationalize import globals +from internationalize.globals import token class TestAPIHelpers(unittest.TestCase): @@ -15,10 +18,12 @@ def setUp(self): self.languages_dir = "src/internationalize/languages" os.makedirs(self.languages_dir, exist_ok=True) - self.original_token = globals.token + # backup the original token value + self.original_token_value = token.value def tearDown(self): - globals.token = self.original_token + # restore the original token value + token.value = self.original_token_value # clean up any created language files if os.path.exists(self.languages_dir): @@ -27,113 +32,163 @@ def tearDown(self): os.remove(os.path.join(self.languages_dir, filename)) - @patch('src.internationalize.api_helpers.TokenView') - def test_create_token_success(self, mock_token_view): + @patch('internationalize.api_helpers.requests.post') + def test_create_token_success(self, mock_post): + """ + Test that create_token() successfully creates a token and sets it in globals. + """ mock_response = MagicMock() mock_response.status_code = 201 - mock_response.data = {'value': 'test-token'} - mock_token_view_instance = MagicMock() - mock_token_view_instance.post.return_value = mock_response - mock_token_view.return_value = mock_token_view_instance + mock_response.json.return_value = {'value': 'test-token'} + mock_post.return_value = mock_response - with patch('builtins.print') as mock_print: + with patch('builtins.print') as mock_print: create_token() - mock_token_view_instance.post.assert_called_once() - self.assertEqual(globals.token, 'test-token') + mock_post.assert_called_once_with("http://localhost:8000/api/token/") + self.assertEqual(token.value, 'test-token') mock_print.assert_called_once_with("Token set.") - @patch('src.internationalize.api_helpers.TokenView') - def test_create_token_failure(self, mock_token_view): + @patch('internationalize.api_helpers.requests.post') + def test_create_token_failure(self, mock_post): + """ + Test that create_token() raises an exception when API call fails. + """ mock_response = MagicMock() mock_response.status_code = 400 - mock_response.data = {'error': 'Bad Request'} - mock_token_view_instance = MagicMock() - mock_token_view_instance.post.return_value = mock_response - mock_token_view.return_value = mock_token_view_instance + mock_response.json.return_value = {'error': 'Bad Request'} + mock_post.return_value = mock_response - with patch('builtins.print') as mock_print: + with patch('builtins.print') as mock_print: with self.assertRaises(Exception) as context: create_token() - mock_token_view_instance.post.assert_called_once() + mock_post.assert_called_once_with("http://localhost:8000/api/token/") self.assertIn("Failed to retrieve token. Status code: 400", str(context.exception)) - mock_print.assert_not_called() # no print on failure as per function definition + mock_print.assert_not_called() - @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') - @patch('src.internationalize.api_helpers.create_token') - def test_fetch_translation_data_token_exists(self, mock_create_token, mock_get_translations): - globals.token = 'existing-token' - mock_get_translations.return_value = {'hello': 'hola'} + @patch('internationalize.api_helpers.requests.get') + @patch('internationalize.api_helpers.create_token') + def test_fetch_translation_data_token_exists(self, mock_create_token, mock_get): + """ + Test that fetch_translation_data() fetches translations when token exists. + """ + token.value = 'existing-token' - with patch('builtins.print') as mock_print: - fetch_translation_data('Spanish') + mock_get_response = MagicMock() + mock_get_response.status_code = 200 + mock_get_response.json.return_value = {'hello': 'hola'} + mock_get.return_value = mock_get_response + + with patch('builtins.print') as mock_print: + translations = fetch_translation_data('Spanish') mock_create_token.assert_not_called() - mock_get_translations.assert_called_once_with('Spanish', 'existing-token') + mock_get.assert_called_once_with( + "http://localhost:8000/api/translations/?language=Spanish", + headers={'Authorization': 'Token existing-token'} + ) mock_print.assert_called_once_with("Generated translation data for language: Spanish") - - @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') - @patch('src.internationalize.api_helpers.create_token') - def test_fetch_translation_data_token_missing_and_created_successfully(self, mock_create_token, mock_get_translations): - globals.token = '' - - def side_effect_create_token(): - globals.token = 'new-token' - print("Token set.") - mock_create_token.side_effect = side_effect_create_token - - mock_get_translations.return_value = {'hello': 'hola'} + self.assertEqual(translations, {'hello': 'hola'}) + + @patch('internationalize.api_helpers.requests.get') + @patch('internationalize.api_helpers.requests.post') + def test_fetch_translation_data_token_missing_and_created_successfully(self, mock_post, mock_get): + """ + Test that fetch_translation_data() creates a token if missing and fetches translations successfully. + """ + token.value = '' + + mock_post_response = MagicMock() + mock_post_response.status_code = 201 + mock_post_response.json.return_value = {'value': 'new-token'} + mock_post.return_value = mock_post_response + + mock_get_response = MagicMock() + mock_get_response.status_code = 200 + mock_get_response.json.return_value = {'hello': 'hola'} + mock_get.return_value = mock_get_response with patch('builtins.print') as mock_print: - fetch_translation_data('Spanish') - - mock_create_token.assert_called_once() - mock_get_translations.assert_called_once_with('Spanish', 'new-token') - expected_calls = [patch.call("Token not found. Creating a new token..."), - patch.call("Token set."), - patch.call("Generated translation data for language: Spanish")] + translations = fetch_translation_data('Spanish') + + mock_post.assert_called_once_with("http://localhost:8000/api/token/") + mock_get.assert_called_once_with( + "http://localhost:8000/api/translations/?language=Spanish", + headers={'Authorization': 'Token new-token'} + ) + expected_calls = [ + call("Token not found. Creating a new token..."), + call("Token set."), + call("Generated translation data for language: Spanish") + ] mock_print.assert_has_calls(expected_calls, any_order=False) - - @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') - @patch('src.internationalize.api_helpers.create_token') - def test_fetch_translation_data_token_missing_and_creation_fails(self, mock_create_token, mock_get_translations): - globals.token = '' - - mock_create_token.side_effect = Exception("Failed to retrieve token.") + self.assertEqual(token.value, 'new-token') + self.assertEqual(translations, {'hello': 'hola'}) + + @patch('internationalize.api_helpers.requests.get') + @patch('internationalize.api_helpers.requests.post') + def test_fetch_translation_data_token_missing_and_creation_fails(self, mock_post, mock_get): + """ + Test that fetch_translation_data() raises an exception if token creation fails. + """ + token.value = '' + + mock_post_response = MagicMock() + mock_post_response.status_code = 400 + mock_post_response.json.return_value = {'error': 'Bad Request'} + mock_post.return_value = mock_post_response with patch('builtins.print') as mock_print: with self.assertRaises(Exception) as context: fetch_translation_data('Spanish') - mock_create_token.assert_called_once() - mock_get_translations.assert_not_called() - mock_print.assert_called_once_with("Token not found. Creating a new token...") + mock_post.assert_called_once_with("http://localhost:8000/api/token/") + mock_get.assert_not_called() + expected_calls = [ + call("Token not found. Creating a new token...") + ] + mock_print.assert_has_calls(expected_calls, any_order=False) + self.assertEqual(token.value, '') # token remains empty + self.assertIn("Failed to retrieve token. Status code: 400", str(context.exception)) def test_fetch_translation_data_missing_language(self): - globals.token = 'existing-token' + """ + Test that fetch_translation_data() raises an exception when language parameter is missing. + """ + token.value = 'existing-token' with patch('builtins.print') as mock_print: with self.assertRaises(Exception) as context: - fetch_translation_data('') # Missing language + fetch_translation_data('') # missing language self.assertIn("Language parameter is required.", str(context.exception)) - mock_print.assert_not_called() # No print as per function definition + mock_print.assert_not_called() - @patch('src.internationalize.api_helpers.translation_processor.get_translations_by_language') - def test_fetch_translation_data_no_translations_found(self, mock_get_translations): - globals.token = 'existing-token' + @patch('internationalize.api_helpers.requests.get') + def test_fetch_translation_data_no_translations_found(self, mock_get): + """ + Test that fetch_translation_data() raises an exception when no translations are found. + """ + token.value = 'existing-token' - mock_get_translations.return_value = {} + mock_get_response = MagicMock() + mock_get_response.status_code = 404 # assuming 404 for no translations + mock_get_response.json.return_value = {'error': 'No translations found'} + mock_get.return_value = mock_get_response with patch('builtins.print') as mock_print: with self.assertRaises(Exception) as context: fetch_translation_data('Spanish') - mock_get_translations.assert_called_once_with('Spanish', 'existing-token') + mock_get.assert_called_once_with( + "http://localhost:8000/api/translations/?language=Spanish", + headers={'Authorization': 'Token existing-token'} + ) + mock_print.assert_called_once_with("Generated translation data for language: Spanish") + self.assertEqual(token.value, 'existing-token') # token remains unchanged self.assertIn("No translations found for language: Spanish", str(context.exception)) - mock_print.assert_called_once_with("Generated translation data for language: Spanish") # prints even if no translations, but raises exception - if __name__ == '__main__': - unittest.main() +if __name__ == '__main__': + unittest.main() From 6470be9a541642fc6b914bf684767338fdfddac6 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sat, 16 Nov 2024 15:26:45 -0800 Subject: [PATCH 21/23] cleanup --- i18nilize/src/internationalize/api_helpers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index 57f83c1..dacbbe5 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -8,12 +8,12 @@ def create_token(): """ Creates a new token by making a POST request to the central API. """ - url = "http://localhost:8000/api/token/" # Fixed double slash + url = "http://localhost:8000/api/token/" try: response = requests.post(url) if response.status_code == 201: token_value = response.json().get("value") - globals.token.value = token_value # Update the value attribute + globals.token.value = token_value print("Token set.") else: raise Exception(f"Failed to retrieve token. Status code: {response.status_code}") @@ -24,7 +24,7 @@ def fetch_translation_data(language): """ Fetches translation data for the specified language using the token. """ - token = globals.token.value # Access the value attribute + token = globals.token.value if not token: print("Token not found. Creating a new token...") create_token() @@ -35,7 +35,7 @@ def fetch_translation_data(language): if not language: raise Exception("Language parameter is required.") - url = f"http://localhost:8000/api/translations/?language={language}" # Fixed double slash + url = f"http://localhost:8000/api/translations/?language={language}" headers = { 'Authorization': f'Token {token}' } From accd2d39923e03a13560324ff14006053c1d685f Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sun, 17 Nov 2024 00:15:25 -0800 Subject: [PATCH 22/23] define url in globals.py --- i18nilize/src/internationalize/api_helpers.py | 4 ++-- i18nilize/src/internationalize/globals.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/i18nilize/src/internationalize/api_helpers.py b/i18nilize/src/internationalize/api_helpers.py index dacbbe5..7a231e7 100644 --- a/i18nilize/src/internationalize/api_helpers.py +++ b/i18nilize/src/internationalize/api_helpers.py @@ -8,7 +8,7 @@ def create_token(): """ Creates a new token by making a POST request to the central API. """ - url = "http://localhost:8000/api/token/" + url = globals.TOKEN_ENDPOINT try: response = requests.post(url) if response.status_code == 201: @@ -35,7 +35,7 @@ def fetch_translation_data(language): if not language: raise Exception("Language parameter is required.") - url = f"http://localhost:8000/api/translations/?language={language}" + url = f"{globals.TRANSLATIONS_ENDPOINT}?language={language}" headers = { 'Authorization': f'Token {token}' } diff --git a/i18nilize/src/internationalize/globals.py b/i18nilize/src/internationalize/globals.py index b039188..ecb566b 100644 --- a/i18nilize/src/internationalize/globals.py +++ b/i18nilize/src/internationalize/globals.py @@ -4,4 +4,9 @@ class GlobalToken: def __init__(self): self.value = "dummy" +API_BASE_URL = "http://localhost:8000/api/" + +TOKEN_ENDPOINT = f"{API_BASE_URL}token/" +TRANSLATIONS_ENDPOINT = f"{API_BASE_URL}translations/" + token = GlobalToken() \ No newline at end of file From 287438dd9d4afa37f7c0ecaca73a645e9b886373 Mon Sep 17 00:00:00 2001 From: angelafeliciaa Date: Sun, 17 Nov 2024 00:18:44 -0800 Subject: [PATCH 23/23] use defined url in globals.py --- i18nilize/src/internationalize/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18nilize/src/internationalize/helpers.py b/i18nilize/src/internationalize/helpers.py index 3cce9cb..2e5ec84 100644 --- a/i18nilize/src/internationalize/helpers.py +++ b/i18nilize/src/internationalize/helpers.py @@ -4,6 +4,7 @@ from geocoder import ip from geopy.geocoders import Nominatim from babel.languages import get_official_languages +from . import globals # Function to parse json file, given its path def get_json(file_path): @@ -60,7 +61,7 @@ def get_default_language(): def generate_file(language, token): if not language: language = get_default_language() - url = 'http://localhost:8000/api/translations' + url = globals.TRANSLATIONS_ENDPOINT params = {'language': language} headers = {'token': token} response = requests.get(url, params=params, headers=headers)