From c057875319541985264c23b7aa2f211c05742496 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 12 Feb 2023 17:32:58 -0500 Subject: [PATCH 01/12] Prevent gettext() from binding to _ namespace --- apprise/AppriseLocale.py | 155 ++++++++++++++++++++++----------------- test/test_api.py | 7 +- test/test_cli.py | 2 + test/test_locale.py | 114 +++++++++++++++++++--------- 4 files changed, 174 insertions(+), 104 deletions(-) diff --git a/apprise/AppriseLocale.py b/apprise/AppriseLocale.py index b3b0fab2fa..b703c41a6d 100644 --- a/apprise/AppriseLocale.py +++ b/apprise/AppriseLocale.py @@ -51,43 +51,13 @@ # Initialize gettext import gettext - # install() creates a _() in our builtins - gettext.install(DOMAIN, localedir=LOCALE_DIR) - # Toggle our flag GETTEXT_LOADED = True except ImportError: - # gettext isn't available; no problem, just fall back to using - # the library features without multi-language support. - import builtins - builtins.__dict__['_'] = lambda x: x # pragma: no branch - - -class LazyTranslation: - """ - Doesn't translate anything until str() or unicode() references - are made. - - """ - def __init__(self, text, *args, **kwargs): - """ - Store our text - """ - self.text = text - - super().__init__(*args, **kwargs) - - def __str__(self): - return gettext.gettext(self.text) - - -# Lazy translation handling -def gettext_lazy(text): - """ - A dummy function that can be referenced - """ - return LazyTranslation(text=text) + # gettext isn't available; no problem; Use the library features without + # multi-language support. + pass class AppriseLocale: @@ -105,7 +75,10 @@ class AppriseLocale: # Define our default encoding _default_encoding = 'utf-8' - # Define our default language + # The function to assign `_` by default + _fn = 'gettext' + + # The language we should fall back to if all else fails _default_language = 'en' def __init__(self, language=None): @@ -123,25 +96,48 @@ def __init__(self, language=None): # Get our language self.lang = AppriseLocale.detect_language(language) + # Our mapping to our _fn + self.__fn_map = None + if GETTEXT_LOADED is False: # We're done return + # Add default language if self.lang: + self.add(self.lang) + + else: + # Fall back to our default + self.add(self._default_language) + self.lang = self._default_language + + logger.debug('Language set to %s', self.lang) + + def add(self, lang): + """ + Add a language to our list + """ + if lang not in self._gtobjs: # Load our gettext object and install our language try: - self._gtobjs[self.lang] = gettext.translation( - DOMAIN, localedir=LOCALE_DIR, languages=[self.lang]) + self._gtobjs[lang] = gettext.translation( + DOMAIN, localedir=LOCALE_DIR, languages=[lang]) - # Install our language - self._gtobjs[self.lang].install() + # The non-intrusive method of applying the gettext change to + # the global namespace only + self.__fn_map = getattr(self._gtobjs[lang], self._fn) except IOError: # This occurs if we can't access/load our translations - pass + return False + + logger.trace('Loaded language %s', lang) + + return True @contextlib.contextmanager - def lang_at(self, lang): + def lang_at(self, lang, mapto=_fn): """ The syntax works as: with at.lang_at('fr'): @@ -151,45 +147,36 @@ def lang_at(self, lang): """ if GETTEXT_LOADED is False: - # yield - yield + # Do nothing + yield None # we're done return # Tidy the language lang = AppriseLocale.detect_language(lang, detect_fallback=False) - - # Now attempt to load it - try: - if lang in self._gtobjs: - if lang != self.lang: - # Install our language only if we aren't using it - # already - self._gtobjs[lang].install() + if lang not in self._gtobjs and not self.add(lang): + if self._default_language not in self._gtobjs \ + and not self.add(self._default_language): + # Do Nothing + yield None else: - self._gtobjs[lang] = gettext.translation( - DOMAIN, localedir=LOCALE_DIR, languages=[self.lang]) - - # Install our language - self._gtobjs[lang].install() - + yield getattr(self._gtobjs[self._default_language], mapto) + else: # Yield - yield + yield getattr(self._gtobjs[lang], mapto) - except (IOError, KeyError): - # This occurs if we can't access/load our translations - # Yield reguardless - yield + return - finally: - # Fall back to our previous language - if lang != self.lang and lang in self._gtobjs: - # Install our language - self._gtobjs[self.lang].install() + @property + def gettext(self): + """ + Return the current language gettext() function - return + Useful for assigning to `_` + """ + return self._gtobjs[self.lang].gettext @staticmethod def detect_language(lang=None, detect_fallback=True): @@ -227,7 +214,7 @@ def detect_language(lang=None, detect_fallback=True): # Fallback to posix detection pass - # Linux Handling + # Built in locale library check try: # Acquire our locale lang = locale.getlocale()[0] @@ -259,3 +246,35 @@ def __setstate__(self, state): """ self.__dict__.update(state) self._gtobjs = {} + + +# +# Prepare our default LOCALE Singleton +# +LOCALE = AppriseLocale() + + +class LazyTranslation: + """ + Doesn't translate anything until str() or unicode() references + are made. + + """ + def __init__(self, text, *args, **kwargs): + """ + Store our text + """ + self.text = text + + super().__init__(*args, **kwargs) + + def __str__(self): + return LOCALE.gettext(self.text) if GETTEXT_LOADED else self.text + + +# Lazy translation handling +def gettext_lazy(text): + """ + A dummy function that can be referenced + """ + return LazyTranslation(text=text) diff --git a/test/test_api.py b/test/test_api.py index 0b53656dd3..c37d214b87 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -53,6 +53,7 @@ from apprise import URLBase from apprise import PrivacyMode from apprise.AppriseLocale import LazyTranslation +from apprise.AppriseLocale import gettext_lazy as _ from apprise import common from apprise.plugins import __load_matrix @@ -1379,7 +1380,8 @@ def send(self, **kwargs): assert 'details' in entry['requirements'] assert 'packages_required' in entry['requirements'] assert 'packages_recommended' in entry['requirements'] - assert isinstance(entry['requirements']['details'], str) + assert isinstance(entry['requirements']['details'], ( + str, LazyTranslation)) assert isinstance(entry['requirements']['packages_required'], list) assert isinstance(entry['requirements']['packages_recommended'], list) @@ -1406,7 +1408,8 @@ def send(self, **kwargs): assert 'details' in entry['requirements'] assert 'packages_required' in entry['requirements'] assert 'packages_recommended' in entry['requirements'] - assert isinstance(entry['requirements']['details'], str) + assert isinstance(entry['requirements']['details'], ( + str, LazyTranslation)) assert isinstance(entry['requirements']['packages_required'], list) assert isinstance(entry['requirements']['packages_recommended'], list) diff --git a/test/test_cli.py b/test/test_cli.py index d29a7b2cb4..9612fdffb8 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -49,6 +49,8 @@ from apprise.plugins import __load_matrix from apprise.plugins import __reset_matrix +from apprise.AppriseLocale import gettext_lazy as _ + from importlib import reload diff --git a/test/test_locale.py b/test/test_locale.py index a16f178505..2a29e82b68 100644 --- a/test/test_locale.py +++ b/test/test_locale.py @@ -41,14 +41,12 @@ from apprise.utils import environ from importlib import reload - # Disable logging for a cleaner testing output import logging logging.disable(logging.CRITICAL) -@mock.patch('gettext.install') -def test_apprise_locale(mock_gettext_install): +def test_apprise_locale(): """ API: Test apprise locale object """ @@ -56,29 +54,30 @@ def test_apprise_locale(mock_gettext_install): assert str(lazytrans) == 'Token' -@mock.patch('gettext.install') -def test_gettext_init(mock_gettext_install): +@pytest.mark.skipif( + 'gettext' not in sys.modules, reason="Requires gettext") +def test_apprise_locale_gettext_init(): """ - API: Mock Gettext init + API: Handle gettext """ - mock_gettext_install.side_effect = ImportError() - # Test our fall back to not supporting translations - reload(AppriseLocale) + # Toggle + AppriseLocale.GETTEXT_LOADED = False # Objects can still be created al = AppriseLocale.AppriseLocale() - with al.lang_at('en'): + with al.lang_at('en') as _: # functions still behave as normal - pass + assert _ is None - # restore the object - mock_gettext_install.side_effect = None - reload(AppriseLocale) + # Restore the object + AppriseLocale.GETTEXT_LOADED = True +@pytest.mark.skipif( + 'gettext' not in sys.modules, reason="Requires gettext") @mock.patch('gettext.translation') -def test_gettext_translations(mock_gettext_trans): +def test_apprise_locale_gettext_translations(mock_gettext_trans): """ API: Apprise() Gettext translations @@ -97,17 +96,14 @@ def test_gettext_translations(mock_gettext_trans): AppriseLocale.AppriseLocale(language="fr") -@mock.patch('gettext.translation') -def test_gettext_installs(mock_gettext_trans): +@pytest.mark.skipif( + 'gettext' not in sys.modules, reason="Requires gettext") +def test_apprise_locale_gettext_lang_at(): """ - API: Apprise() Gettext install + API: Apprise() Gettext lang_at """ - mock_lang = mock.Mock() - mock_lang.install.return_value = True - mock_gettext_trans.return_value = mock_lang - # This throws internally but we handle it gracefully al = AppriseLocale.AppriseLocale() @@ -118,25 +114,42 @@ def test_gettext_installs(mock_gettext_trans): # This throws internally but we handle it gracefully AppriseLocale.AppriseLocale(language="fr") - # Force a few different languages - al._gtobjs['en'] = mock_lang - al._gtobjs['es'] = mock_lang - al.lang = 'en' - - with al.lang_at('en'): + with al.lang_at('en') as _: # functions still behave as normal - pass + assert callable(_) - with al.lang_at('es'): + with al.lang_at('es') as _: # functions still behave as normal - pass + assert callable(_) - with al.lang_at('fr'): + with al.lang_at('fr') as _: # functions still behave as normal - pass + assert callable(_) -def test_detect_language_windows_users(): +@pytest.mark.skipif( + 'gettext' not in sys.modules, reason="Requires gettext") +def test_apprise_locale_add(): + """ + API: Apprise() Gettext add + + """ + + # This throws internally but we handle it gracefully + al = AppriseLocale.AppriseLocale() + + assert al.add('en') is True + + # Double add (copy of above) to access logic that prevents adding it again + assert al.add('en') is True + + # Invalid Language + assert al.add('bad') is False + + +@pytest.mark.skipif( + 'gettext' not in sys.modules, reason="Requires gettext") +def test_apprise_locale_detect_language_windows_users(): """ API: Apprise() Detect language @@ -213,3 +226,36 @@ def test_detect_language_locale(mock_getlocale): # if detect_language and windows env fail us, then we don't # set up a default language on first load AppriseLocale.AppriseLocale() + + +@pytest.mark.skipif( + 'gettext' not in sys.modules, reason="Requires gettext") +def test_apprise_locale_gettext_missing(tmpdir): + """ + Verify we can still operate without the gettext library + """ + + # remove gettext from our system enviroment + del sys.modules["gettext"] + + # Make our new path to a fake gettext (used to over-ride real one) + # have it fail right out of the gate + gettext_dir = tmpdir.mkdir("gettext") + gettext_dir.join("__init__.py").write("") + gettext_dir.join("gettext.py").write("""raise ImportError()""") + + # Update our path to point path to head + sys.path.insert(0, str(gettext_dir)) + + # reload our module (forcing the import error when it tries to load gettext + reload(sys.modules['apprise.AppriseLocale']) + from apprise import AppriseLocale + assert AppriseLocale.GETTEXT_LOADED is False + + # Now roll our changes back + sys.path.pop(0) + + # Reload again (reverting back) + reload(sys.modules['apprise.AppriseLocale']) + from apprise import AppriseLocale + assert AppriseLocale.GETTEXT_LOADED is True From cb86d3f20c1cb68f2b20ec31c17a5cc79031e2d8 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Fri, 17 Feb 2023 17:54:34 -0500 Subject: [PATCH 02/12] refactored --- apprise/AppriseLocale.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apprise/AppriseLocale.py b/apprise/AppriseLocale.py index b703c41a6d..ed7b2adfd2 100644 --- a/apprise/AppriseLocale.py +++ b/apprise/AppriseLocale.py @@ -40,9 +40,6 @@ from os.path import abspath from .logger import logger -# Define our translation domain -DOMAIN = 'apprise' -LOCALE_DIR = abspath(join(dirname(__file__), 'i18n')) # This gets toggled to True if we succeed GETTEXT_LOADED = False @@ -67,6 +64,12 @@ class AppriseLocale: """ + # Define our translation domain + _domain = 'apprise' + + # The path to our translations + _locale_dir = abspath(join(dirname(__file__), 'i18n')) + # Locale regular expression _local_re = re.compile( r'^\s*(?P[a-z]{2})([_:]((?P[a-z]{2}))?' @@ -122,14 +125,16 @@ def add(self, lang): # Load our gettext object and install our language try: self._gtobjs[lang] = gettext.translation( - DOMAIN, localedir=LOCALE_DIR, languages=[lang]) + self._domain, localedir=self._locale_dir, languages=[lang], + fallback=False) # The non-intrusive method of applying the gettext change to # the global namespace only self.__fn_map = getattr(self._gtobjs[lang], self._fn) - except IOError: + except IOError as e: # This occurs if we can't access/load our translations + logger.debug('IOError: %s' % str(e)) return False logger.trace('Loaded language %s', lang) @@ -219,7 +224,7 @@ def detect_language(lang=None, detect_fallback=True): # Acquire our locale lang = locale.getlocale()[0] - except TypeError as e: + except (ValueError, TypeError) as e: # This occurs when an invalid locale was parsed from the # environment variable. While we still return None in this # case, we want to better notify the end user of this. Users From d7ccec6d2b25c41edf139a099d75e7afb457fe8d Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 26 Feb 2023 14:38:47 -0500 Subject: [PATCH 03/12] more bulletproofing --- apprise/AppriseLocale.py | 36 ++++++++++++++++++++++-------------- test/test_locale.py | 26 +++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/apprise/AppriseLocale.py b/apprise/AppriseLocale.py index ed7b2adfd2..2295de3a98 100644 --- a/apprise/AppriseLocale.py +++ b/apprise/AppriseLocale.py @@ -106,18 +106,12 @@ def __init__(self, language=None): # We're done return - # Add default language - if self.lang: - self.add(self.lang) - - else: + # Add language + if not (self.lang and self.add(self.lang)): # Fall back to our default self.add(self._default_language) - self.lang = self._default_language - logger.debug('Language set to %s', self.lang) - - def add(self, lang): + def add(self, lang, set_default=True): """ Add a language to our list """ @@ -132,13 +126,26 @@ def add(self, lang): # the global namespace only self.__fn_map = getattr(self._gtobjs[lang], self._fn) - except IOError as e: - # This occurs if we can't access/load our translations - logger.debug('IOError: %s' % str(e)) + except FileNotFoundError: + # The translation directory does not exist + logger.debug( + 'Could not load translation path: %s', + join(self._locale_dir, lang)) + + # Fallback + if None not in self._gtobjs: + self._gtobjs[None] = gettext + self.__fn_map = getattr(self._gtobjs[None], self._fn) + if set_default: + self.lang = None return False logger.trace('Loaded language %s', lang) + if set_default: + logger.debug('Language set to %s', self.lang) + self.lang = self._default_language + return True @contextlib.contextmanager @@ -160,9 +167,10 @@ def lang_at(self, lang, mapto=_fn): # Tidy the language lang = AppriseLocale.detect_language(lang, detect_fallback=False) - if lang not in self._gtobjs and not self.add(lang): + if lang not in self._gtobjs and not self.add(lang, set_default=False): if self._default_language not in self._gtobjs \ - and not self.add(self._default_language): + and not self.add(self._default_language, + set_default=False): # Do Nothing yield None diff --git a/test/test_locale.py b/test/test_locale.py index 2a29e82b68..192fef5f1b 100644 --- a/test/test_locale.py +++ b/test/test_locale.py @@ -83,7 +83,7 @@ def test_apprise_locale_gettext_translations(mock_gettext_trans): """ - mock_gettext_trans.side_effect = IOError() + mock_gettext_trans.side_effect = FileNotFoundError() # This throws internally but we handle it gracefully al = AppriseLocale.AppriseLocale() @@ -107,6 +107,10 @@ def test_apprise_locale_gettext_lang_at(): # This throws internally but we handle it gracefully al = AppriseLocale.AppriseLocale() + # Edge Cases + assert al.add('en', set_default=False) is True + assert al.add('en', set_default=True) is True + with al.lang_at('en'): # functions still behave as normal pass @@ -126,6 +130,26 @@ def test_apprise_locale_gettext_lang_at(): # functions still behave as normal assert callable(_) + # Test our initialization when our fallback is a language we do + # not have. This is only done to test edge cases when for whatever + # reason the person who set up apprise does not have the languages + # installed. + fallback = AppriseLocale.AppriseLocale._default_language + with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="en_CA"): + AppriseLocale.AppriseLocale._default_language = 'zz' + al = AppriseLocale.AppriseLocale() + assert al.gettext('test') == 'test' + + # Test case with set_default set to False (so we're still set to 'zz') + assert al.add('zy', set_default=False) is False + assert al.gettext('test') == 'test' + + al.add('ab', set_default=True) + assert al.gettext('test') == 'test' + + assert al.add('zy', set_default=False) is False + AppriseLocale.AppriseLocale._default_language = fallback + @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") From c76992632bad5bfefb59150197ac374fa4bda8a1 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 20 Aug 2023 13:02:13 -0400 Subject: [PATCH 04/12] improvements added --- .gitignore | 1 + apprise/AppriseLocale.py | 27 +- apprise/i18n/en/LC_MESSAGES/apprise.po | 369 +++++++++--------- setup.cfg | 4 +- setup.py | 4 +- ...{test_locale.py => test_apprise_locale.py} | 45 ++- 6 files changed, 243 insertions(+), 207 deletions(-) rename test/{test_locale.py => test_apprise_locale.py} (85%) diff --git a/.gitignore b/.gitignore index 174d28b3bb..4ae5f5880c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ sdist/ *.egg-info/ .installed.cfg *.egg +.local # Generated from Docker Instance .bash_history diff --git a/apprise/AppriseLocale.py b/apprise/AppriseLocale.py index 2295de3a98..dd88958d77 100644 --- a/apprise/AppriseLocale.py +++ b/apprise/AppriseLocale.py @@ -107,14 +107,13 @@ def __init__(self, language=None): return # Add language - if not (self.lang and self.add(self.lang)): - # Fall back to our default - self.add(self._default_language) + self.add(self.lang) - def add(self, lang, set_default=True): + def add(self, lang=None, set_default=True): """ Add a language to our list """ + lang = lang if lang else self._default_language if lang not in self._gtobjs: # Load our gettext object and install our language try: @@ -132,19 +131,18 @@ def add(self, lang, set_default=True): 'Could not load translation path: %s', join(self._locale_dir, lang)) - # Fallback - if None not in self._gtobjs: - self._gtobjs[None] = gettext - self.__fn_map = getattr(self._gtobjs[None], self._fn) - if set_default: - self.lang = None + # Fallback (handle case where self.lang does not exist) + if self.lang not in self._gtobjs: + self._gtobjs[self.lang] = gettext + self.__fn_map = getattr(self._gtobjs[self.lang], self._fn) + return False logger.trace('Loaded language %s', lang) if set_default: - logger.debug('Language set to %s', self.lang) - self.lang = self._default_language + logger.debug('Language set to %s', lang) + self.lang = lang return True @@ -249,8 +247,10 @@ def __getstate__(self): Pickle Support dumps() """ state = self.__dict__.copy() + # Remove the unpicklable entries. del state['_gtobjs'] + del state['_AppriseLocale__fn_map'] return state def __setstate__(self, state): @@ -258,7 +258,10 @@ def __setstate__(self, state): Pickle Support loads() """ self.__dict__.update(state) + # Our mapping to our _fn + self.__fn_map = None self._gtobjs = {} + self.add(state['lang'], set_default=True) # diff --git a/apprise/i18n/en/LC_MESSAGES/apprise.po b/apprise/i18n/en/LC_MESSAGES/apprise.po index 44451262cf..65deb7775d 100644 --- a/apprise/i18n/en/LC_MESSAGES/apprise.po +++ b/apprise/i18n/en/LC_MESSAGES/apprise.po @@ -3,9 +3,10 @@ # This file is distributed under the same license as the apprise project. # Chris Caron , 2019. # -msgid "" +msgid "" msgstr "" -"Project-Id-Version: apprise 0.7.6\n" + +"Project-Id-Version: apprise 1.4.5\n" "Report-Msgid-Bugs-To: lead2gold@gmail.com\n" "POT-Creation-Date: 2019-05-28 16:56-0400\n" "PO-Revision-Date: 2019-05-24 20:00-0400\n" @@ -18,276 +19,272 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.6.0\n" -msgid "API Key" -msgstr "" +msgid "API Key" +msgstr "API Key" -msgid "Access Key" -msgstr "" +msgid "Access Key" +msgstr "Access Key" -msgid "Access Key ID" -msgstr "" +msgid "Access Key ID" +msgstr "Access Key ID" -msgid "Access Secret" -msgstr "" +msgid "Access Secret" +msgstr "Access Secret" -msgid "Access Token" -msgstr "" +msgid "Access Token" +msgstr "Access Token" -msgid "Account SID" -msgstr "" +msgid "Account SID" +msgstr "Account SID" -msgid "Add Tokens" -msgstr "" +msgid "Add Tokens" +msgstr "Add Tokens" -msgid "Application Key" -msgstr "" +msgid "Application Key" +msgstr "Application Key" -msgid "Application Secret" -msgstr "" +msgid "Application Secret" +msgstr "Application Secret" -msgid "Auth Token" -msgstr "" +msgid "Auth Token" +msgstr "Auth Token" -msgid "Authorization Token" -msgstr "" +msgid "Authorization Token" +msgstr "Authorization Token" -msgid "Avatar Image" -msgstr "" +msgid "Avatar Image" +msgstr "Avatar Image" -msgid "Bot Name" -msgstr "" +msgid "Bot Name" +msgstr "Bot Name" -msgid "Bot Token" -msgstr "" +msgid "Bot Token" +msgstr "Bot Token" -msgid "Channels" -msgstr "" +msgid "Channels" +msgstr "Channels" -msgid "Consumer Key" -msgstr "" +msgid "Consumer Key" +msgstr "Consumer Key" -msgid "Consumer Secret" -msgstr "" +msgid "Consumer Secret" +msgstr "Consumer Secret" -msgid "Detect Bot Owner" -msgstr "" +msgid "Detect Bot Owner" +msgstr "Detect Bot Owner" -msgid "Device ID" -msgstr "" +msgid "Device ID" +msgstr "Device ID" -msgid "Display Footer" -msgstr "" +msgid "Display Footer" +msgstr "Display Footer" -msgid "Domain" -msgstr "" +msgid "Domain" +msgstr "Domain" -msgid "Duration" -msgstr "" +msgid "Duration" +msgstr "Duration" -msgid "Events" -msgstr "" +msgid "Events" +msgstr "Events" -msgid "Footer Logo" -msgstr "" +msgid "Footer Logo" +msgstr "Footer Logo" -msgid "From Email" -msgstr "" +msgid "From Email" +msgstr "From Email" -msgid "From Name" -msgstr "" +msgid "From Name" +msgstr "From Name" -msgid "From Phone No" -msgstr "" +msgid "From Phone No" +msgstr "From Phone No" -msgid "Group" -msgstr "" +msgid "Group" +msgstr "Group" -msgid "HTTP Header" -msgstr "" +msgid "HTTP Header" +msgstr "HTTP Header" -msgid "Hostname" -msgstr "" +msgid "Hostname" +msgstr "Hostname" -msgid "Include Image" -msgstr "" +msgid "Include Image" +msgstr "Include Image" -msgid "Modal" -msgstr "" +msgid "Modal" +msgstr "Modal" -msgid "Notify Format" -msgstr "" +msgid "Notify Format" +msgstr "Notify Format" -msgid "Organization" -msgstr "" +msgid "Organization" +msgstr "Organization" -msgid "Overflow Mode" -msgstr "" +msgid "Overflow Mode" +msgstr "Overflow Mode" -msgid "Password" -msgstr "" +msgid "Password" +msgstr "Password" -msgid "Port" -msgstr "" +msgid "Port" +msgstr "Port" -msgid "Priority" -msgstr "" +msgid "Priority" +msgstr "Priority" -msgid "Provider Key" -msgstr "" +msgid "Provider Key" +msgstr "Provider Key" -msgid "Region" -msgstr "" +msgid "Region" +msgstr "Region" -msgid "Region Name" -msgstr "" +msgid "Region Name" +msgstr "Region Name" -msgid "Remove Tokens" -msgstr "" +msgid "Remove Tokens" +msgstr "Remove Tokens" -msgid "Rooms" -msgstr "" +msgid "Rooms" +msgstr "Rooms" -msgid "SMTP Server" -msgstr "" +msgid "SMTP Server" +msgstr "SMTP Server" -msgid "Schema" -msgstr "" +msgid "Schema" +msgstr "Schema" -msgid "Secret Access Key" -msgstr "" +msgid "Secret Access Key" +msgstr "Secret Access Key" -msgid "Secret Key" -msgstr "" +msgid "Secret Key" +msgstr "Secret Key" -msgid "Secure Mode" -msgstr "" +msgid "Secure Mode" +msgstr "Secure Mode" -msgid "Server Timeout" -msgstr "" +msgid "Server Timeout" +msgstr "Server Timeout" -msgid "Sound" -msgstr "" +msgid "Sound" +msgstr "Sound" -msgid "Source JID" -msgstr "" +msgid "Source JID" +msgstr "Source JID" -msgid "Target Channel" -msgstr "" +msgid "Target Channel" +msgstr "Target Channel" -msgid "Target Chat ID" -msgstr "" +msgid "Target Chat ID" +msgstr "Target Chat ID" -msgid "Target Device" -msgstr "" +msgid "Target Device" +msgstr "Target Device" -msgid "Target Device ID" -msgstr "" +msgid "Target Device ID" +msgstr "Target Device ID" -msgid "Target Email" -msgstr "" +msgid "Target Email" +msgstr "Target Email" -msgid "Target Emails" -msgstr "" +msgid "Target Emails" +msgstr "Target Emails" -msgid "Target Encoded ID" -msgstr "" +msgid "Target Encoded ID" +msgstr "Target Encoded ID" -msgid "Target JID" -msgstr "" +msgid "Target JID" +msgstr "Target JID" -msgid "Target Phone No" -msgstr "" +msgid "Target Phone No" +msgstr "Target Phone No" -msgid "Target Room Alias" -msgstr "" +msgid "Target Room Alias" +msgstr "Target Room Alias" -msgid "Target Room ID" -msgstr "" +msgid "Target Room ID" +msgstr "Target Room ID" -msgid "Target Short Code" -msgstr "" +msgid "Target Short Code" +msgstr "Target Short Code" -msgid "Target Tag ID" -msgstr "" +msgid "Target Tag ID" +msgstr "Target Tag ID" -msgid "Target Topic" -msgstr "" - -msgid "Target User" -msgstr "" - -msgid "Targets" -msgstr "" +msgid "Target Topic" +msgstr "Target Topic" -msgid "Text To Speech" -msgstr "" +msgid "Target User" +msgstr "Target User" -msgid "To Channel ID" -msgstr "" +msgid "Targets" +msgstr "Targets" -msgid "To Email" -msgstr "" +msgid "Text To Speech" +msgstr "Text To Speech" -msgid "To User ID" -msgstr "" +msgid "To Channel ID" +msgstr "To Channel ID" -msgid "Token" -msgstr "" +msgid "To Email" +msgstr "To Email" -msgid "Token A" -msgstr "" +msgid "To User ID" +msgstr "To User ID" -msgid "Token B" -msgstr "" +msgid "Token" +msgstr "Token" -msgid "Token C" -msgstr "" +msgid "Token A" +msgstr "Token A" -msgid "Urgency" -msgstr "" +msgid "Token B" +msgstr "Token B" -msgid "Use Avatar" -msgstr "" +msgid "Token C" +msgstr "Token C" -msgid "User" -msgstr "" +msgid "Urgency" +msgstr "Urgency" -msgid "User Key" -msgstr "" +msgid "Use Avatar" +msgstr "Use Avatar" -msgid "User Name" -msgstr "" +msgid "User" +msgstr "User" -msgid "Username" -msgstr "" +msgid "User Key" +msgstr "User Key" -msgid "Verify SSL" -msgstr "" +msgid "User Name" +msgstr "User Name" -msgid "Version" -msgstr "" +msgid "Username" +msgstr "Username" -msgid "Webhook" -msgstr "" +msgid "Verify SSL" +msgstr "Verify SSL" -msgid "Webhook ID" -msgstr "" +msgid "Version" +msgstr "Version" -msgid "Webhook Mode" -msgstr "" +msgid "Webhook" +msgstr "Webhook" -msgid "Webhook Token" -msgstr "" +msgid "Webhook ID" +msgstr "Webhook ID" -msgid "X-Axis" -msgstr "" +msgid "Webhook Mode" +msgstr "Webhook Mode" -msgid "XEP" -msgstr "" +msgid "Webhook Token" +msgstr "Webhook Token" -msgid "Y-Axis" -msgstr "" +msgid "X-Axis" +msgstr "X-Axis" -#~ msgid "Access Key Secret" -#~ msgstr "" +msgid "XEP" +msgstr "XEP" +msgid "Y-Axis" +msgstr "Y-Axis" diff --git a/setup.cfg b/setup.cfg index 830db59158..c4861b4e35 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,11 +3,11 @@ universal = 0 [metadata] # ensure LICENSE is included in wheel metadata -license_file = LICENSE +license_files = LICENSE [flake8] # We exclude packages we don't maintain -exclude = .eggs,.tox +exclude = .eggs,.tox,.local ignore = E741,E722,W503,W504,W605 statistics = true builtins = _ diff --git a/setup.py b/setup.py index ed3d1e8a62..128f9ff7e8 100755 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ ], }, install_requires=install_requires, - classifiers=( + classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', @@ -109,7 +109,7 @@ 'License :: OSI Approved :: BSD License', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Application Frameworks', - ), + ], entry_points={'console_scripts': console_scripts}, python_requires='>=3.6', setup_requires=['babel', ], diff --git a/test/test_locale.py b/test/test_apprise_locale.py similarity index 85% rename from test/test_locale.py rename to test/test_apprise_locale.py index 192fef5f1b..d1039e0a9f 100644 --- a/test/test_locale.py +++ b/test/test_apprise_locale.py @@ -77,12 +77,17 @@ def test_apprise_locale_gettext_init(): @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") @mock.patch('gettext.translation') -def test_apprise_locale_gettext_translations(mock_gettext_trans): +@mock.patch('locale.getlocale') +def test_apprise_locale_gettext_translations( + mock_getlocale, mock_gettext_trans): """ API: Apprise() Gettext translations """ + # Set- our gettext.locale() return value + mock_getlocale.return_value = ('en_US', 'UTF-8') + mock_gettext_trans.side_effect = FileNotFoundError() # This throws internally but we handle it gracefully @@ -98,12 +103,16 @@ def test_apprise_locale_gettext_translations(mock_gettext_trans): @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") -def test_apprise_locale_gettext_lang_at(): +@mock.patch('locale.getlocale') +def test_apprise_locale_gettext_lang_at(mock_getlocale): """ API: Apprise() Gettext lang_at """ + # Set- our gettext.locale() return value + mock_getlocale.return_value = ('en_CA', 'UTF-8') + # This throws internally but we handle it gracefully al = AppriseLocale.AppriseLocale() @@ -135,12 +144,34 @@ def test_apprise_locale_gettext_lang_at(): # reason the person who set up apprise does not have the languages # installed. fallback = AppriseLocale.AppriseLocale._default_language - with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="en_CA"): + mock_getlocale.return_value = None + + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'): + # Our default language AppriseLocale.AppriseLocale._default_language = 'zz' + + # We will detect the zz since there were no environment variables to + # help us otherwise + assert AppriseLocale.AppriseLocale.detect_language() is None al = AppriseLocale.AppriseLocale() + + # No Language could be set becuause no locale directory exists for this + assert al.lang is None + + # We can still perform simple lookups; they access a dummy wrapper: + assert al.gettext('test') == 'test' + + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="en_CA"): + AppriseLocale.AppriseLocale._default_language = 'fr' + + # We will detect the english language (found in the LANG= environment + # variable which over-rides the _default + assert AppriseLocale.AppriseLocale.detect_language() == "en" + al = AppriseLocale.AppriseLocale() + assert al.lang == "en" assert al.gettext('test') == 'test' - # Test case with set_default set to False (so we're still set to 'zz') + # Test case with set_default set to False (so we're still set to 'fr') assert al.add('zy', set_default=False) is False assert al.gettext('test') == 'test' @@ -202,11 +233,15 @@ def test_apprise_locale_detect_language_windows_users(): assert AppriseLocale.AppriseLocale.detect_language() == 'en' -def test_detect_language_using_env(): +@mock.patch('locale.getlocale') +def test_detect_language_using_env(mock_getlocale): """ Test the reading of information from an environment variable """ + # Set- our gettext.locale() return value + mock_getlocale.return_value = ('en_CA', 'UTF-8') + # The below accesses the windows fallback code and fail # then it will resort to the environment variables. with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE'): From 7c46cc5b7a22d48408f33109fa13c22123b9c59b Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 20 Aug 2023 13:06:02 -0400 Subject: [PATCH 05/12] testing reordered so they would pass --- ...se_locale.py => test_apprise_translations.py} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename test/{test_apprise_locale.py => test_apprise_translations.py} (96%) diff --git a/test/test_apprise_locale.py b/test/test_apprise_translations.py similarity index 96% rename from test/test_apprise_locale.py rename to test/test_apprise_translations.py index d1039e0a9f..362d0de8c5 100644 --- a/test/test_apprise_locale.py +++ b/test/test_apprise_translations.py @@ -46,7 +46,7 @@ logging.disable(logging.CRITICAL) -def test_apprise_locale(): +def test_apprise_trans(): """ API: Test apprise locale object """ @@ -56,7 +56,7 @@ def test_apprise_locale(): @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") -def test_apprise_locale_gettext_init(): +def test_apprise_trans_gettext_init(): """ API: Handle gettext """ @@ -78,7 +78,7 @@ def test_apprise_locale_gettext_init(): 'gettext' not in sys.modules, reason="Requires gettext") @mock.patch('gettext.translation') @mock.patch('locale.getlocale') -def test_apprise_locale_gettext_translations( +def test_apprise_trans_gettext_translations( mock_getlocale, mock_gettext_trans): """ API: Apprise() Gettext translations @@ -104,7 +104,7 @@ def test_apprise_locale_gettext_translations( @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") @mock.patch('locale.getlocale') -def test_apprise_locale_gettext_lang_at(mock_getlocale): +def test_apprise_trans_gettext_lang_at(mock_getlocale): """ API: Apprise() Gettext lang_at @@ -184,7 +184,7 @@ def test_apprise_locale_gettext_lang_at(mock_getlocale): @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") -def test_apprise_locale_add(): +def test_apprise_trans_add(): """ API: Apprise() Gettext add @@ -204,7 +204,7 @@ def test_apprise_locale_add(): @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") -def test_apprise_locale_detect_language_windows_users(): +def test_apprise_trans_detect_language_windows_users(): """ API: Apprise() Detect language @@ -268,7 +268,7 @@ def test_detect_language_using_env(mock_getlocale): @pytest.mark.skipif(sys.platform == "win32", reason="Does not work on Windows") @mock.patch('locale.getlocale') -def test_detect_language_locale(mock_getlocale): +def test_detect_language_trans(mock_getlocale): """ API: Apprise() Default locale detection @@ -289,7 +289,7 @@ def test_detect_language_locale(mock_getlocale): @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") -def test_apprise_locale_gettext_missing(tmpdir): +def test_apprise_trans_gettext_missing(tmpdir): """ Verify we can still operate without the gettext library """ From 6207cdb3627cba8f0a71f0cae7cdf5579063887e Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 20 Aug 2023 15:12:22 -0400 Subject: [PATCH 06/12] improved test coverage --- apprise/AppriseLocale.py | 10 ++-------- test/test_apprise_translations.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apprise/AppriseLocale.py b/apprise/AppriseLocale.py index dd88958d77..e1e867af4d 100644 --- a/apprise/AppriseLocale.py +++ b/apprise/AppriseLocale.py @@ -166,14 +166,8 @@ def lang_at(self, lang, mapto=_fn): # Tidy the language lang = AppriseLocale.detect_language(lang, detect_fallback=False) if lang not in self._gtobjs and not self.add(lang, set_default=False): - if self._default_language not in self._gtobjs \ - and not self.add(self._default_language, - set_default=False): - # Do Nothing - yield None - - else: - yield getattr(self._gtobjs[self._default_language], mapto) + # Do Nothing + yield getattr(self._gtobjs[self.lang], mapto) else: # Yield yield getattr(self._gtobjs[lang], mapto) diff --git a/test/test_apprise_translations.py b/test/test_apprise_translations.py index 362d0de8c5..db7eabd067 100644 --- a/test/test_apprise_translations.py +++ b/test/test_apprise_translations.py @@ -158,6 +158,22 @@ def test_apprise_trans_gettext_lang_at(mock_getlocale): # No Language could be set becuause no locale directory exists for this assert al.lang is None + with al.lang_at(None) as _: + # functions still behave as normal + assert callable(_) + + with al.lang_at('en') as _: + # functions still behave as normal + assert callable(_) + + with al.lang_at('es') as _: + # functions still behave as normal + assert callable(_) + + with al.lang_at('fr') as _: + # functions still behave as normal + assert callable(_) + # We can still perform simple lookups; they access a dummy wrapper: assert al.gettext('test') == 'test' From 56cb513e7a03e7e2de4a421c7067a545f2a3f03b Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 20 Aug 2023 16:31:38 -0400 Subject: [PATCH 07/12] more test coverage --- apprise/AppriseLocale.py | 4 ++-- test/{test_cli.py => test_apprise_cli.py} | 0 test/test_apprise_translations.py | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) rename test/{test_cli.py => test_apprise_cli.py} (100%) diff --git a/apprise/AppriseLocale.py b/apprise/AppriseLocale.py index e1e867af4d..49372ca70f 100644 --- a/apprise/AppriseLocale.py +++ b/apprise/AppriseLocale.py @@ -72,8 +72,8 @@ class AppriseLocale: # Locale regular expression _local_re = re.compile( - r'^\s*(?P[a-z]{2})([_:]((?P[a-z]{2}))?' - r'(\.(?P[a-z0-9]+))?|.+)?', re.IGNORECASE) + r'^((?PC)|(?P([a-z]{2}))([_:](?P[a-z]{2}))?)' + r'(\.(?P[a-z0-9-]+))?$', re.IGNORECASE) # Define our default encoding _default_encoding = 'utf-8' diff --git a/test/test_cli.py b/test/test_apprise_cli.py similarity index 100% rename from test/test_cli.py rename to test/test_apprise_cli.py diff --git a/test/test_apprise_translations.py b/test/test_apprise_translations.py index db7eabd067..1f98f2ec22 100644 --- a/test/test_apprise_translations.py +++ b/test/test_apprise_translations.py @@ -177,7 +177,8 @@ def test_apprise_trans_gettext_lang_at(mock_getlocale): # We can still perform simple lookups; they access a dummy wrapper: assert al.gettext('test') == 'test' - with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="en_CA"): + with environ('LANGUAGE', 'LC_CTYPE', LC_ALL='C.UTF-8', LANG="en_CA"): + # the UTF-8 entry is skipped over AppriseLocale.AppriseLocale._default_language = 'fr' # We will detect the english language (found in the LANG= environment @@ -266,7 +267,7 @@ def test_detect_language_using_env(mock_getlocale): AppriseLocale.AppriseLocale.detect_language(), str) # Detect French language. - with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="fr_CA"): + with environ('LANGUAGE', 'LC_ALL', LC_CTYPE="garbage", LANG="fr_CA"): assert AppriseLocale.AppriseLocale.detect_language() == 'fr' # The following unsets all environment variables and sets LC_CTYPE From 17c082392705f4a36fa70e28c35fccaa28b759c0 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 20 Aug 2023 16:38:33 -0400 Subject: [PATCH 08/12] fixed rpm packaging --- packaging/redhat/apprise-click67-support.patch | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packaging/redhat/apprise-click67-support.patch b/packaging/redhat/apprise-click67-support.patch index fa9079f7aa..3a52487bd2 100644 --- a/packaging/redhat/apprise-click67-support.patch +++ b/packaging/redhat/apprise-click67-support.patch @@ -1,7 +1,7 @@ -diff -Naur apprise-1.0.0/test/test_cli.py apprise-1.0.0.patched/test/test_cli.py ---- apprise-1.0.0/test/test_cli.py 2022-07-15 14:52:13.000000000 -0400 -+++ apprise-1.0.0.patched/test/test_cli.py 2022-08-06 13:32:50.796935607 -0400 -@@ -1022,9 +1022,6 @@ +diff -Naur apprise-1.4.5/test/test_apprise_cli.py apprise-1.4.5-patched/test/test_apprise_cli.py +--- apprise-1.4.5/test/test_apprise_cli.py 2023-08-20 11:26:43.000000000 -0400 ++++ apprise-1.4.5-patched/test/test_apprise_cli.py 2023-08-20 16:37:42.922342103 -0400 +@@ -1027,9 +1027,6 @@ # Absolute path to __init__.py is okay assert result.exit_code == 0 @@ -11,7 +11,7 @@ diff -Naur apprise-1.0.0/test/test_cli.py apprise-1.0.0.patched/test/test_cli.py # Clear our working variables so they don't obstruct the next test # This simulates an actual call from the CLI. Unfortunately through # testing were occupying the same memory space so our singleton's -@@ -1044,9 +1041,6 @@ +@@ -1049,9 +1046,6 @@ # an __init__.py is found on the inside of it assert result.exit_code == 0 @@ -21,7 +21,7 @@ diff -Naur apprise-1.0.0/test/test_cli.py apprise-1.0.0.patched/test/test_cli.py # Test double paths that are the same; this ensures we only # load the plugin once result = runner.invoke(cli.main, [ -@@ -1179,15 +1173,6 @@ +@@ -1183,15 +1177,6 @@ # Print our custom details to the screen '--details', ]) From f159dc6ee003ea682c924d276ebc38bfe74250cd Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 20 Aug 2023 20:10:27 -0400 Subject: [PATCH 09/12] windows fixes --- test/test_apprise_translations.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/test/test_apprise_translations.py b/test/test_apprise_translations.py index 1f98f2ec22..5b0c92d88c 100644 --- a/test/test_apprise_translations.py +++ b/test/test_apprise_translations.py @@ -219,6 +219,8 @@ def test_apprise_trans_add(): assert al.add('bad') is False +@pytest.mark.skipif( + sys.platform != "win32", reason="Unique Windows test cases") @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") def test_apprise_trans_detect_language_windows_users(): @@ -236,10 +238,17 @@ def test_apprise_trans_detect_language_windows_users(): windll.kernel32.GetUserDefaultUILanguage.return_value = 4105 setattr(ctypes, 'windll', windll) - # The below accesses the windows fallback code - with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="en_CA"): + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'): + # Our default language + AppriseLocale.AppriseLocale._default_language = 'zz' + + # We will pick up the windll module assert AppriseLocale.AppriseLocale.detect_language() == 'en' + # The below accesses the windows fallback code + with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="fr_CA"): + assert AppriseLocale.AppriseLocale.detect_language() == 'fr' + assert AppriseLocale.AppriseLocale\ .detect_language(detect_fallback=False) is None @@ -250,6 +259,7 @@ def test_apprise_trans_detect_language_windows_users(): assert AppriseLocale.AppriseLocale.detect_language() == 'en' +@pytest.mark.skipif(sys.platform == "win32", reason="Unique Nux test cases") @mock.patch('locale.getlocale') def test_detect_language_using_env(mock_getlocale): """ @@ -282,14 +292,6 @@ def test_detect_language_using_env(mock_getlocale): with environ(*list(os.environ.keys())): assert isinstance(AppriseLocale.AppriseLocale.detect_language(), str) - -@pytest.mark.skipif(sys.platform == "win32", reason="Does not work on Windows") -@mock.patch('locale.getlocale') -def test_detect_language_trans(mock_getlocale): - """ - API: Apprise() Default locale detection - - """ # Handle case where getlocale() can't be detected mock_getlocale.return_value = None with environ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'): From 91246bc494d8008e4e747fa2b8b70c660e4baf76 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 21 Aug 2023 19:37:26 -0400 Subject: [PATCH 10/12] windows testing updated --- test/test_apprise_translations.py | 40 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/test/test_apprise_translations.py b/test/test_apprise_translations.py index 5b0c92d88c..85c30db934 100644 --- a/test/test_apprise_translations.py +++ b/test/test_apprise_translations.py @@ -219,44 +219,56 @@ def test_apprise_trans_add(): assert al.add('bad') is False -@pytest.mark.skipif( - sys.platform != "win32", reason="Unique Windows test cases") @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") -def test_apprise_trans_detect_language_windows_users(): +@mock.patch('locale.getlocale') +def test_apprise_trans_windows_users(mock_getlocale): """ API: Apprise() Detect language """ + # Set- our gettext.locale() return value + mock_getlocale.return_value = ('fr_CA', 'UTF-8') + + # Emulate a windows environment if hasattr(ctypes, 'windll'): + # Windows Distribution from ctypes import windll else: + # Linux Distribution windll = mock.Mock() - # 4105 = en_CA - windll.kernel32.GetUserDefaultUILanguage.return_value = 4105 setattr(ctypes, 'windll', windll) + # 4105 = en_CA + windll.kernel32.GetUserDefaultUILanguage.return_value = 4105 + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'): # Our default language AppriseLocale.AppriseLocale._default_language = 'zz' - # We will pick up the windll module + # We will pick up the windll module and detect english assert AppriseLocale.AppriseLocale.detect_language() == 'en' # The below accesses the windows fallback code - with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="fr_CA"): - assert AppriseLocale.AppriseLocale.detect_language() == 'fr' + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="es_AR"): + # Environment Variable Trumps + assert AppriseLocale.AppriseLocale.detect_language() == 'es' + + # No environment variable, then the Windows environment is used + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'): + # Windows Environment + assert AppriseLocale.AppriseLocale.detect_language() == 'en' assert AppriseLocale.AppriseLocale\ .detect_language(detect_fallback=False) is None # 0 = IndexError windll.kernel32.GetUserDefaultUILanguage.return_value = 0 - setattr(ctypes, 'windll', windll) - with environ('LANG', 'LC_ALL', 'LC_CTYPE', LANGUAGE="en_CA"): - assert AppriseLocale.AppriseLocale.detect_language() == 'en' + with environ('LANGUAGE', 'LANG', 'LC_ALL', 'LC_CTYPE'): + # We fall back to posix locale + assert AppriseLocale.AppriseLocale.detect_language() == 'fr' @pytest.mark.skipif(sys.platform == "win32", reason="Unique Nux test cases") @@ -305,6 +317,12 @@ def test_detect_language_using_env(mock_getlocale): # set up a default language on first load AppriseLocale.AppriseLocale() + with mock.patch('ctypes') as mock_ctypes: + + # 4105 = en_CA + mock_ctypes.windll.kernel32.GetUserDefaultUILanguage\ + .return_value = 4105 + @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") From 0852b504346032e7994f0d2c69754f9530b4c61f Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 21 Aug 2023 19:49:16 -0400 Subject: [PATCH 11/12] preserved windows unique test set --- test/test_apprise_translations.py | 71 ++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/test/test_apprise_translations.py b/test/test_apprise_translations.py index 85c30db934..44d966bec0 100644 --- a/test/test_apprise_translations.py +++ b/test/test_apprise_translations.py @@ -219,27 +219,70 @@ def test_apprise_trans_add(): assert al.add('bad') is False +@pytest.mark.skipif( + not hasattr(ctypes, 'windll'), reason="Unique Windows test cases") @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") @mock.patch('locale.getlocale') -def test_apprise_trans_windows_users(mock_getlocale): +def test_apprise_trans_windows_users_win(mock_getlocale): """ - API: Apprise() Detect language + API: Apprise() Windows Locale Testing (Win version) """ # Set- our gettext.locale() return value mock_getlocale.return_value = ('fr_CA', 'UTF-8') - # Emulate a windows environment - if hasattr(ctypes, 'windll'): - # Windows Distribution - from ctypes import windll + with mock.patch( + 'ctypes.windll.kernel32.GetUserDefaultUILanguage') as ui_lang: + + # 4105 = en_CA + ui_lang.return_value = 4105 + + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'): + # Our default language + AppriseLocale.AppriseLocale._default_language = 'zz' + + # We will pick up the windll module and detect english + assert AppriseLocale.AppriseLocale.detect_language() == 'en' + + # The below accesses the windows fallback code + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="es_AR"): + # Environment Variable Trumps + assert AppriseLocale.AppriseLocale.detect_language() == 'es' - else: - # Linux Distribution - windll = mock.Mock() - setattr(ctypes, 'windll', windll) + # No environment variable, then the Windows environment is used + with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'): + # Windows Environment + assert AppriseLocale.AppriseLocale.detect_language() == 'en' + + assert AppriseLocale.AppriseLocale\ + .detect_language(detect_fallback=False) is None + + # 0 = IndexError + ui_lang.return_value = 0 + with environ('LANGUAGE', 'LANG', 'LC_ALL', 'LC_CTYPE'): + # We fall back to posix locale + assert AppriseLocale.AppriseLocale.detect_language() == 'fr' + + +@pytest.mark.skipif( + hasattr(ctypes, 'windll'), reason="Unique Nux test cases") +@pytest.mark.skipif( + 'gettext' not in sys.modules, reason="Requires gettext") +@mock.patch('locale.getlocale') +def test_apprise_trans_windows_users_nux(mock_getlocale): + """ + API: Apprise() Windows Locale Testing (Nux version) + + """ + + # Set- our gettext.locale() return value + mock_getlocale.return_value = ('fr_CA', 'UTF-8') + + # Emulate a windows environment + windll = mock.Mock() + setattr(ctypes, 'windll', windll) # 4105 = en_CA windll.kernel32.GetUserDefaultUILanguage.return_value = 4105 @@ -270,6 +313,8 @@ def test_apprise_trans_windows_users(mock_getlocale): # We fall back to posix locale assert AppriseLocale.AppriseLocale.detect_language() == 'fr' + delattr(ctypes, 'windll') + @pytest.mark.skipif(sys.platform == "win32", reason="Unique Nux test cases") @mock.patch('locale.getlocale') @@ -317,12 +362,6 @@ def test_detect_language_using_env(mock_getlocale): # set up a default language on first load AppriseLocale.AppriseLocale() - with mock.patch('ctypes') as mock_ctypes: - - # 4105 = en_CA - mock_ctypes.windll.kernel32.GetUserDefaultUILanguage\ - .return_value = 4105 - @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") From deec3f98759f28c9325a1efd631ca86d7c8024d5 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 21 Aug 2023 20:03:07 -0400 Subject: [PATCH 12/12] prevent windows from running a test set for posix testing --- test/test_apprise_translations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_apprise_translations.py b/test/test_apprise_translations.py index 44d966bec0..db2ad6bedc 100644 --- a/test/test_apprise_translations.py +++ b/test/test_apprise_translations.py @@ -101,6 +101,8 @@ def test_apprise_trans_gettext_translations( AppriseLocale.AppriseLocale(language="fr") +@pytest.mark.skipif( + hasattr(ctypes, 'windll'), reason="Unique Nux test cases") @pytest.mark.skipif( 'gettext' not in sys.modules, reason="Requires gettext") @mock.patch('locale.getlocale')