Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Locale is causing error #45

Closed
stephan1827 opened this issue Jul 15, 2019 · 25 comments · Fixed by #52
Closed

Locale is causing error #45

stephan1827 opened this issue Jul 15, 2019 · 25 comments · Fixed by #52

Comments

@stephan1827
Copy link
Contributor

stephan1827 commented Jul 15, 2019

With the latest version 15 I receive the following error, version 13 works fine. I am located in germany and use MACOS 10.14.5 with Python3

Traceback (most recent call last):
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/startWohnzimmer.py", line 14, in
for robot in account.robots:
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/account.py", line 65, in robots
self.refresh_robots()
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/account.py", line 113, in refresh_robots
endpoint=robot['nucleo_url']))
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/robot.py", line 46, in init
if self.service_version not in SUPPORTED_SERVICES:
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/robot.py", line 231, in service_version
return self.available_services['houseCleaning']
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/robot.py", line 227, in available_services
return self.state['availableServices']
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/robot.py", line 223, in state
return self.get_robot_state().json()
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/robot.py", line 181, in get_robot_state
return self._message({'reqId': "1", 'cmd': "getRobotState"})
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/robot.py", line 63, in _message
headers=self._headers)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests-2.22.0-py3.7.egg/requests/api.py", line 116, in post
return request('post', url, data=data, json=json, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests-2.22.0-py3.7.egg/requests/api.py", line 60, in request
return session.request(method=method, url=url, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests-2.22.0-py3.7.egg/requests/sessions.py", line 519, in request
prep = self.prepare_request(req)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests-2.22.0-py3.7.egg/requests/sessions.py", line 462, in prepare_request
hooks=merge_hooks(request.hooks, self.hooks),
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests-2.22.0-py3.7.egg/requests/models.py", line 317, in prepare
self.prepare_auth(auth, url)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests-2.22.0-py3.7.egg/requests/models.py", line 548, in prepare_auth
r = auth(self)
File "/Users/stephan/Documents/Development/Phyton/Neato/robot/pybotvac/robot.py", line 245, in call
locale.setlocale(locale.LC_TIME, 'en_US.utf8')
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/locale.py", line 604, in setlocale
return _setlocale(category, locale)
locale.Error: unsupported locale setting

@fermulator
Copy link
Contributor

Hm! This is unfortunate. Indeed it seems the change from #30 broke you (and possibly others). fwiw, the context in which we delivered the change was to fix issues for users NOT in en_US, pulling robot data against Neato for newer bots did not work. Our realization is that it seemed that Neato was rejecting requests from the header not formulated from en_US locale. (so we temporarily flipped to en_US during that, then revertted back to default system locale). We had tested with en_CA (non-US), and en_US (US). Obviously missed cosidering the case where the system does not have en_US at all.

As per, https://docs.python.org/3/library/locale.html#locale.setlocale, locale.setlocale() itself should of course exist. (initially I thought the error was about that) - but I think it is actually about the fact your system doesn't have en_US.

Specifically it is probably throwing:
https://docs.python.org/3/library/locale.html#locale.Error

Exception raised when the locale passed to setlocale() is not recognized.

I am curious into your environment.

My environment is like this:

>>> import pprint
>>> pp = pprint.PrettyPrinter(indent=4)
>>> pp.pprint(locale.locale_alias)
{   'a3': 'az_AZ.KOI8-C',
    'a3_az': 'az_AZ.KOI8-C',
    'a3_az.koic': 'az_AZ.KOI8-C',
    'aa_dj': 'aa_DJ.ISO8859-1',
    'aa_er': 'aa_ER.UTF-8',
    'aa_et': 'aa_ET.UTF-8',
    'af': 'af_ZA.ISO8859-1',
    'af_za': 'af_ZA.ISO8859-1',
    'agr_pe': 'agr_PE.UTF-8',
    'ak_gh': 'ak_GH.UTF-8',
    'am': 'am_ET.UTF-8',
    'am_et': 'am_ET.UTF-8',
    'american': 'en_US.ISO8859-1',
    'an_es': 'an_ES.ISO8859-15',
    'anp_in': 'anp_IN.UTF-8',
    'ar': 'ar_AA.ISO8859-6',
    'ar_aa': 'ar_AA.ISO8859-6',
    'ar_ae': 'ar_AE.ISO8859-6',
    'ar_bh': 'ar_BH.ISO8859-6',
    'ar_dz': 'ar_DZ.ISO8859-6',
    'ar_eg': 'ar_EG.ISO8859-6',
    'ar_in': 'ar_IN.UTF-8',
    'ar_iq': 'ar_IQ.ISO8859-6',
    'ar_jo': 'ar_JO.ISO8859-6',
    'ar_kw': 'ar_KW.ISO8859-6',
    'ar_lb': 'ar_LB.ISO8859-6',
    'ar_ly': 'ar_LY.ISO8859-6',
    'ar_ma': 'ar_MA.ISO8859-6',
    'ar_om': 'ar_OM.ISO8859-6',
    'ar_qa': 'ar_QA.ISO8859-6',
    'ar_sa': 'ar_SA.ISO8859-6',
    'ar_sd': 'ar_SD.ISO8859-6',
    'ar_ss': 'ar_SS.UTF-8',
    'ar_sy': 'ar_SY.ISO8859-6',
    'ar_tn': 'ar_TN.ISO8859-6',
    'ar_ye': 'ar_YE.ISO8859-6',
    'arabic': 'ar_AA.ISO8859-6',
    'as': 'as_IN.UTF-8',
    'as_in': 'as_IN.UTF-8',
    'ast_es': 'ast_ES.ISO8859-15',
    'ayc_pe': 'ayc_PE.UTF-8',
    'az': 'az_AZ.ISO8859-9E',
    'az_az': 'az_AZ.ISO8859-9E',
    'az_az.iso88599e': 'az_AZ.ISO8859-9E',
    'az_ir': 'az_IR.UTF-8',
    'be': 'be_BY.CP1251',
    'be@latin': 'be_BY.UTF-8@latin',
    'be_bg.utf8': 'bg_BG.UTF-8',
    'be_by': 'be_BY.CP1251',
    'be_by@latin': 'be_BY.UTF-8@latin',
    'bem_zm': 'bem_ZM.UTF-8',
    'ber_dz': 'ber_DZ.UTF-8',
    'ber_ma': 'ber_MA.UTF-8',
    'bg': 'bg_BG.CP1251',
    'bg_bg': 'bg_BG.CP1251',
    'bhb_in.utf8': 'bhb_IN.UTF-8',
    'bho_in': 'bho_IN.UTF-8',
    'bho_np': 'bho_NP.UTF-8',
    'bi_vu': 'bi_VU.UTF-8',
    'bn_bd': 'bn_BD.UTF-8',
    'bn_in': 'bn_IN.UTF-8',
    'bo_cn': 'bo_CN.UTF-8',
    'bo_in': 'bo_IN.UTF-8',
    'bokmal': 'nb_NO.ISO8859-1',
    'bokmål': 'nb_NO.ISO8859-1',
    'br': 'br_FR.ISO8859-1',
    'br_fr': 'br_FR.ISO8859-1',
    'brx_in': 'brx_IN.UTF-8',
    'bs': 'bs_BA.ISO8859-2',
    'bs_ba': 'bs_BA.ISO8859-2',
    'bulgarian': 'bg_BG.CP1251',
    'byn_er': 'byn_ER.UTF-8',
    'c': 'C',
    'c-french': 'fr_CA.ISO8859-1',
    'c.ascii': 'C',
    'c.en': 'C',
    'c.iso88591': 'en_US.ISO8859-1',
    'c.utf8': 'en_US.UTF-8',
    'c_c': 'C',
    'c_c.c': 'C',
    'ca': 'ca_ES.ISO8859-1',
    'ca_ad': 'ca_AD.ISO8859-1',
    'ca_es': 'ca_ES.ISO8859-1',
    'ca_es@valencia': 'ca_ES.UTF-8@valencia',
    'ca_fr': 'ca_FR.ISO8859-1',
    'ca_it': 'ca_IT.ISO8859-1',
    'catalan': 'ca_ES.ISO8859-1',
    'ce_ru': 'ce_RU.UTF-8',
    'cextend': 'en_US.ISO8859-1',
    'chinese-s': 'zh_CN.eucCN',
    'chinese-t': 'zh_TW.eucTW',
    'chr_us': 'chr_US.UTF-8',
    'ckb_iq': 'ckb_IQ.UTF-8',
    'cmn_tw': 'cmn_TW.UTF-8',
    'crh_ua': 'crh_UA.UTF-8',
    'croatian': 'hr_HR.ISO8859-2',
    'cs': 'cs_CZ.ISO8859-2',
    'cs_cs': 'cs_CZ.ISO8859-2',
    'cs_cz': 'cs_CZ.ISO8859-2',
    'csb_pl': 'csb_PL.UTF-8',
    'cv_ru': 'cv_RU.UTF-8',
    'cy': 'cy_GB.ISO8859-1',
    'cy_gb': 'cy_GB.ISO8859-1',
    'cz': 'cs_CZ.ISO8859-2',
    'cz_cz': 'cs_CZ.ISO8859-2',
    'czech': 'cs_CZ.ISO8859-2',
    'da': 'da_DK.ISO8859-1',
    'da_dk': 'da_DK.ISO8859-1',
    'danish': 'da_DK.ISO8859-1',
    'dansk': 'da_DK.ISO8859-1',
    'de': 'de_DE.ISO8859-1',
    'de_at': 'de_AT.ISO8859-1',
    'de_be': 'de_BE.ISO8859-1',
    'de_ch': 'de_CH.ISO8859-1',
    'de_de': 'de_DE.ISO8859-1',
    'de_it': 'de_IT.ISO8859-1',
    'de_li.utf8': 'de_LI.UTF-8',
    'de_lu': 'de_LU.ISO8859-1',
    'deutsch': 'de_DE.ISO8859-1',
    'doi_in': 'doi_IN.UTF-8',
    'dutch': 'nl_NL.ISO8859-1',
    'dutch.iso88591': 'nl_BE.ISO8859-1',
    'dv_mv': 'dv_MV.UTF-8',
    'dz_bt': 'dz_BT.UTF-8',
    'ee': 'ee_EE.ISO8859-4',
    'ee_ee': 'ee_EE.ISO8859-4',
    'eesti': 'et_EE.ISO8859-1',
    'el': 'el_GR.ISO8859-7',
    'el_cy': 'el_CY.ISO8859-7',
    'el_gr': 'el_GR.ISO8859-7',
    'el_gr@euro': 'el_GR.ISO8859-15',
    'en': 'en_US.ISO8859-1',
    'en_ag': 'en_AG.UTF-8',
    'en_au': 'en_AU.ISO8859-1',
    'en_be': 'en_BE.ISO8859-1',
    'en_bw': 'en_BW.ISO8859-1',
    'en_ca': 'en_CA.ISO8859-1',
    'en_dk': 'en_DK.ISO8859-1',
    'en_dl.utf8': 'en_DL.UTF-8',
    'en_gb': 'en_GB.ISO8859-1',
    'en_hk': 'en_HK.ISO8859-1',
    'en_ie': 'en_IE.ISO8859-1',
    'en_il': 'en_IL.UTF-8',
    'en_in': 'en_IN.ISO8859-1',
    'en_ng': 'en_NG.UTF-8',
    'en_nz': 'en_NZ.ISO8859-1',
    'en_ph': 'en_PH.ISO8859-1',
    'en_sc.utf8': 'en_SC.UTF-8',
    'en_sg': 'en_SG.ISO8859-1',
    'en_uk': 'en_GB.ISO8859-1',
    'en_us': 'en_US.ISO8859-1',
    'en_us@euro@euro': 'en_US.ISO8859-15',
    'en_za': 'en_ZA.ISO8859-1',
    'en_zm': 'en_ZM.UTF-8',
    'en_zw': 'en_ZW.ISO8859-1',
    'en_zw.utf8': 'en_ZS.UTF-8',
    'eng_gb': 'en_GB.ISO8859-1',
    'english': 'en_EN.ISO8859-1',
    'english.iso88591': 'en_US.ISO8859-1',
    'english_uk': 'en_GB.ISO8859-1',
    'english_united-states': 'en_US.ISO8859-1',
    'english_united-states.437': 'C',
    'english_us': 'en_US.ISO8859-1',
    'eo': 'eo_XX.ISO8859-3',
    'eo.utf8': 'eo.UTF-8',
    'eo_eo': 'eo_EO.ISO8859-3',
    'eo_us.utf8': 'eo_US.UTF-8',
    'eo_xx': 'eo_XX.ISO8859-3',
    'es': 'es_ES.ISO8859-1',
    'es_ar': 'es_AR.ISO8859-1',
    'es_bo': 'es_BO.ISO8859-1',
    'es_cl': 'es_CL.ISO8859-1',
    'es_co': 'es_CO.ISO8859-1',
    'es_cr': 'es_CR.ISO8859-1',
    'es_cu': 'es_CU.UTF-8',
    'es_do': 'es_DO.ISO8859-1',
    'es_ec': 'es_EC.ISO8859-1',
    'es_es': 'es_ES.ISO8859-1',
    'es_gt': 'es_GT.ISO8859-1',
    'es_hn': 'es_HN.ISO8859-1',
    'es_mx': 'es_MX.ISO8859-1',
    'es_ni': 'es_NI.ISO8859-1',
    'es_pa': 'es_PA.ISO8859-1',
    'es_pe': 'es_PE.ISO8859-1',
    'es_pr': 'es_PR.ISO8859-1',
    'es_py': 'es_PY.ISO8859-1',
    'es_sv': 'es_SV.ISO8859-1',
    'es_us': 'es_US.ISO8859-1',
    'es_uy': 'es_UY.ISO8859-1',
    'es_ve': 'es_VE.ISO8859-1',
    'estonian': 'et_EE.ISO8859-1',
    'et': 'et_EE.ISO8859-15',
    'et_ee': 'et_EE.ISO8859-15',
    'eu': 'eu_ES.ISO8859-1',
    'eu_es': 'eu_ES.ISO8859-1',
    'eu_fr': 'eu_FR.ISO8859-1',
    'fa': 'fa_IR.UTF-8',
    'fa_ir': 'fa_IR.UTF-8',
    'fa_ir.isiri3342': 'fa_IR.ISIRI-3342',
    'ff_sn': 'ff_SN.UTF-8',
    'fi': 'fi_FI.ISO8859-15',
    'fi_fi': 'fi_FI.ISO8859-15',
    'fil_ph': 'fil_PH.UTF-8',
    'finnish': 'fi_FI.ISO8859-1',
    'fo': 'fo_FO.ISO8859-1',
    'fo_fo': 'fo_FO.ISO8859-1',
    'fr': 'fr_FR.ISO8859-1',
    'fr_be': 'fr_BE.ISO8859-1',
    'fr_ca': 'fr_CA.ISO8859-1',
    'fr_ch': 'fr_CH.ISO8859-1',
    'fr_fr': 'fr_FR.ISO8859-1',
    'fr_lu': 'fr_LU.ISO8859-1',
    'français': 'fr_FR.ISO8859-1',
    'fre_fr': 'fr_FR.ISO8859-1',
    'french': 'fr_FR.ISO8859-1',
    'french.iso88591': 'fr_CH.ISO8859-1',
    'french_france': 'fr_FR.ISO8859-1',
    'fur_it': 'fur_IT.UTF-8',
    'fy_de': 'fy_DE.UTF-8',
    'fy_nl': 'fy_NL.UTF-8',
    'ga': 'ga_IE.ISO8859-1',
    'ga_ie': 'ga_IE.ISO8859-1',
    'galego': 'gl_ES.ISO8859-1',
    'galician': 'gl_ES.ISO8859-1',
    'gd': 'gd_GB.ISO8859-1',
    'gd_gb': 'gd_GB.ISO8859-1',
    'ger_de': 'de_DE.ISO8859-1',
    'german': 'de_DE.ISO8859-1',
    'german.iso88591': 'de_CH.ISO8859-1',
    'german_germany': 'de_DE.ISO8859-1',
    'gez_er': 'gez_ER.UTF-8',
    'gez_et': 'gez_ET.UTF-8',
    'gl': 'gl_ES.ISO8859-1',
    'gl_es': 'gl_ES.ISO8859-1',
    'greek': 'el_GR.ISO8859-7',
    'gu_in': 'gu_IN.UTF-8',
    'gv': 'gv_GB.ISO8859-1',
    'gv_gb': 'gv_GB.ISO8859-1',
    'ha_ng': 'ha_NG.UTF-8',
    'hak_tw': 'hak_TW.UTF-8',
    'he': 'he_IL.ISO8859-8',
    'he_il': 'he_IL.ISO8859-8',
    'hebrew': 'he_IL.ISO8859-8',
    'hi': 'hi_IN.ISCII-DEV',
    'hi_in': 'hi_IN.ISCII-DEV',
    'hi_in.isciidev': 'hi_IN.ISCII-DEV',
    'hif_fj': 'hif_FJ.UTF-8',
    'hne': 'hne_IN.UTF-8',
    'hne_in': 'hne_IN.UTF-8',
    'hr': 'hr_HR.ISO8859-2',
    'hr_hr': 'hr_HR.ISO8859-2',
    'hrvatski': 'hr_HR.ISO8859-2',
    'hsb_de': 'hsb_DE.ISO8859-2',
    'ht_ht': 'ht_HT.UTF-8',
    'hu': 'hu_HU.ISO8859-2',
    'hu_hu': 'hu_HU.ISO8859-2',
    'hungarian': 'hu_HU.ISO8859-2',
    'hy_am': 'hy_AM.UTF-8',
    'hy_am.armscii8': 'hy_AM.ARMSCII_8',
    'ia': 'ia.UTF-8',
    'ia_fr': 'ia_FR.UTF-8',
    'icelandic': 'is_IS.ISO8859-1',
    'id': 'id_ID.ISO8859-1',
    'id_id': 'id_ID.ISO8859-1',
    'ig_ng': 'ig_NG.UTF-8',
    'ik_ca': 'ik_CA.UTF-8',
    'in': 'id_ID.ISO8859-1',
    'in_id': 'id_ID.ISO8859-1',
    'is': 'is_IS.ISO8859-1',
    'is_is': 'is_IS.ISO8859-1',
    'iso-8859-1': 'en_US.ISO8859-1',
    'iso-8859-15': 'en_US.ISO8859-15',
    'iso8859-1': 'en_US.ISO8859-1',
    'iso8859-15': 'en_US.ISO8859-15',
    'iso_8859_1': 'en_US.ISO8859-1',
    'iso_8859_15': 'en_US.ISO8859-15',
    'it': 'it_IT.ISO8859-1',
    'it_ch': 'it_CH.ISO8859-1',
    'it_it': 'it_IT.ISO8859-1',
    'italian': 'it_IT.ISO8859-1',
    'iu': 'iu_CA.NUNACOM-8',
    'iu_ca': 'iu_CA.NUNACOM-8',
    'iu_ca.nunacom8': 'iu_CA.NUNACOM-8',
    'iw': 'he_IL.ISO8859-8',
    'iw_il': 'he_IL.ISO8859-8',
    'iw_il.utf8': 'iw_IL.UTF-8',
    'ja': 'ja_JP.eucJP',
    'ja_jp': 'ja_JP.eucJP',
    'ja_jp.euc': 'ja_JP.eucJP',
    'ja_jp.mscode': 'ja_JP.SJIS',
    'ja_jp.pck': 'ja_JP.SJIS',
    'japan': 'ja_JP.eucJP',
    'japanese': 'ja_JP.eucJP',
    'japanese-euc': 'ja_JP.eucJP',
    'japanese.euc': 'ja_JP.eucJP',
    'jp_jp': 'ja_JP.eucJP',
    'ka': 'ka_GE.GEORGIAN-ACADEMY',
    'ka_ge': 'ka_GE.GEORGIAN-ACADEMY',
    'ka_ge.georgianacademy': 'ka_GE.GEORGIAN-ACADEMY',
    'ka_ge.georgianps': 'ka_GE.GEORGIAN-PS',
    'ka_ge.georgianrs': 'ka_GE.GEORGIAN-ACADEMY',
    'kab_dz': 'kab_DZ.UTF-8',
    'kk_kz': 'kk_KZ.ptcp154',
    'kl': 'kl_GL.ISO8859-1',
    'kl_gl': 'kl_GL.ISO8859-1',
    'km_kh': 'km_KH.UTF-8',
    'kn': 'kn_IN.UTF-8',
    'kn_in': 'kn_IN.UTF-8',
    'ko': 'ko_KR.eucKR',
    'ko_kr': 'ko_KR.eucKR',
    'ko_kr.euc': 'ko_KR.eucKR',
    'kok_in': 'kok_IN.UTF-8',
    'korean': 'ko_KR.eucKR',
    'korean.euc': 'ko_KR.eucKR',
    'ks': 'ks_IN.UTF-8',
    'ks_in': 'ks_IN.UTF-8',
    'ks_in@devanagari.utf8': 'ks_IN.UTF-8@devanagari',
    'ku_tr': 'ku_TR.ISO8859-9',
    'kw': 'kw_GB.ISO8859-1',
    'kw_gb': 'kw_GB.ISO8859-1',
    'ky': 'ky_KG.UTF-8',
    'ky_kg': 'ky_KG.UTF-8',
    'lb_lu': 'lb_LU.UTF-8',
    'lg_ug': 'lg_UG.ISO8859-10',
    'li_be': 'li_BE.UTF-8',
    'li_nl': 'li_NL.UTF-8',
    'lij_it': 'lij_IT.UTF-8',
    'lithuanian': 'lt_LT.ISO8859-13',
    'ln_cd': 'ln_CD.UTF-8',
    'lo': 'lo_LA.MULELAO-1',
    'lo_la': 'lo_LA.MULELAO-1',
    'lo_la.cp1133': 'lo_LA.IBM-CP1133',
    'lo_la.ibmcp1133': 'lo_LA.IBM-CP1133',
    'lo_la.mulelao1': 'lo_LA.MULELAO-1',
    'lt': 'lt_LT.ISO8859-13',
    'lt_lt': 'lt_LT.ISO8859-13',
    'lv': 'lv_LV.ISO8859-13',
    'lv_lv': 'lv_LV.ISO8859-13',
    'lzh_tw': 'lzh_TW.UTF-8',
    'mag_in': 'mag_IN.UTF-8',
    'mai': 'mai_IN.UTF-8',
    'mai_in': 'mai_IN.UTF-8',
    'mai_np': 'mai_NP.UTF-8',
    'mfe_mu': 'mfe_MU.UTF-8',
    'mg_mg': 'mg_MG.ISO8859-15',
    'mhr_ru': 'mhr_RU.UTF-8',
    'mi': 'mi_NZ.ISO8859-1',
    'mi_nz': 'mi_NZ.ISO8859-1',
    'miq_ni': 'miq_NI.UTF-8',
    'mjw_in': 'mjw_IN.UTF-8',
    'mk': 'mk_MK.ISO8859-5',
    'mk_mk': 'mk_MK.ISO8859-5',
    'ml': 'ml_IN.UTF-8',
    'ml_in': 'ml_IN.UTF-8',
    'mn_mn': 'mn_MN.UTF-8',
    'mni_in': 'mni_IN.UTF-8',
    'mr': 'mr_IN.UTF-8',
    'mr_in': 'mr_IN.UTF-8',
    'ms': 'ms_MY.ISO8859-1',
    'ms_my': 'ms_MY.ISO8859-1',
    'mt': 'mt_MT.ISO8859-3',
    'mt_mt': 'mt_MT.ISO8859-3',
    'my_mm': 'my_MM.UTF-8',
    'nan_tw': 'nan_TW.UTF-8',
    'nb': 'nb_NO.ISO8859-1',
    'nb_no': 'nb_NO.ISO8859-1',
    'nds_de': 'nds_DE.UTF-8',
    'nds_nl': 'nds_NL.UTF-8',
    'ne_np': 'ne_NP.UTF-8',
    'nhn_mx': 'nhn_MX.UTF-8',
    'niu_nu': 'niu_NU.UTF-8',
    'niu_nz': 'niu_NZ.UTF-8',
    'nl': 'nl_NL.ISO8859-1',
    'nl_aw': 'nl_AW.UTF-8',
    'nl_be': 'nl_BE.ISO8859-1',
    'nl_nl': 'nl_NL.ISO8859-1',
    'nn': 'nn_NO.ISO8859-1',
    'nn_no': 'nn_NO.ISO8859-1',
    'no': 'no_NO.ISO8859-1',
    'no@nynorsk': 'ny_NO.ISO8859-1',
    'no_no': 'no_NO.ISO8859-1',
    'no_no.iso88591@bokmal': 'no_NO.ISO8859-1',
    'no_no.iso88591@nynorsk': 'no_NO.ISO8859-1',
    'norwegian': 'no_NO.ISO8859-1',
    'nr': 'nr_ZA.ISO8859-1',
    'nr_za': 'nr_ZA.ISO8859-1',
    'nso': 'nso_ZA.ISO8859-15',
    'nso_za': 'nso_ZA.ISO8859-15',
    'ny': 'ny_NO.ISO8859-1',
    'ny_no': 'ny_NO.ISO8859-1',
    'nynorsk': 'nn_NO.ISO8859-1',
    'oc': 'oc_FR.ISO8859-1',
    'oc_fr': 'oc_FR.ISO8859-1',
    'om_et': 'om_ET.UTF-8',
    'om_ke': 'om_KE.ISO8859-1',
    'or': 'or_IN.UTF-8',
    'or_in': 'or_IN.UTF-8',
    'os_ru': 'os_RU.UTF-8',
    'pa': 'pa_IN.UTF-8',
    'pa_in': 'pa_IN.UTF-8',
    'pa_pk': 'pa_PK.UTF-8',
    'pap_an': 'pap_AN.UTF-8',
    'pap_aw': 'pap_AW.UTF-8',
    'pap_cw': 'pap_CW.UTF-8',
    'pd': 'pd_US.ISO8859-1',
    'pd_de': 'pd_DE.ISO8859-1',
    'pd_us': 'pd_US.ISO8859-1',
    'ph': 'ph_PH.ISO8859-1',
    'ph_ph': 'ph_PH.ISO8859-1',
    'pl': 'pl_PL.ISO8859-2',
    'pl_pl': 'pl_PL.ISO8859-2',
    'polish': 'pl_PL.ISO8859-2',
    'portuguese': 'pt_PT.ISO8859-1',
    'portuguese_brazil': 'pt_BR.ISO8859-1',
    'posix': 'C',
    'posix-utf2': 'C',
    'pp': 'pp_AN.ISO8859-1',
    'pp_an': 'pp_AN.ISO8859-1',
    'ps_af': 'ps_AF.UTF-8',
    'pt': 'pt_PT.ISO8859-1',
    'pt_br': 'pt_BR.ISO8859-1',
    'pt_pt': 'pt_PT.ISO8859-1',
    'quz_pe': 'quz_PE.UTF-8',
    'raj_in': 'raj_IN.UTF-8',
    'ro': 'ro_RO.ISO8859-2',
    'ro_ro': 'ro_RO.ISO8859-2',
    'romanian': 'ro_RO.ISO8859-2',
    'ru': 'ru_RU.UTF-8',
    'ru_ru': 'ru_RU.UTF-8',
    'ru_ua': 'ru_UA.KOI8-U',
    'rumanian': 'ro_RO.ISO8859-2',
    'russian': 'ru_RU.KOI8-R',
    'rw': 'rw_RW.ISO8859-1',
    'rw_rw': 'rw_RW.ISO8859-1',
    'sa_in': 'sa_IN.UTF-8',
    'sat_in': 'sat_IN.UTF-8',
    'sc_it': 'sc_IT.UTF-8',
    'sd': 'sd_IN.UTF-8',
    'sd_in': 'sd_IN.UTF-8',
    'sd_in@devanagari.utf8': 'sd_IN.UTF-8@devanagari',
    'sd_pk': 'sd_PK.UTF-8',
    'se_no': 'se_NO.UTF-8',
    'serbocroatian': 'sr_RS.UTF-8@latin',
    'sgs_lt': 'sgs_LT.UTF-8',
    'sh': 'sr_RS.UTF-8@latin',
    'sh_ba.iso88592@bosnia': 'sr_CS.ISO8859-2',
    'sh_hr': 'sh_HR.ISO8859-2',
    'sh_hr.iso88592': 'hr_HR.ISO8859-2',
    'sh_sp': 'sr_CS.ISO8859-2',
    'sh_yu': 'sr_RS.UTF-8@latin',
    'shn_mm': 'shn_MM.UTF-8',
    'shs_ca': 'shs_CA.UTF-8',
    'si': 'si_LK.UTF-8',
    'si_lk': 'si_LK.UTF-8',
    'sid_et': 'sid_ET.UTF-8',
    'sinhala': 'si_LK.UTF-8',
    'sk': 'sk_SK.ISO8859-2',
    'sk_sk': 'sk_SK.ISO8859-2',
    'sl': 'sl_SI.ISO8859-2',
    'sl_cs': 'sl_CS.ISO8859-2',
    'sl_si': 'sl_SI.ISO8859-2',
    'slovak': 'sk_SK.ISO8859-2',
    'slovene': 'sl_SI.ISO8859-2',
    'slovenian': 'sl_SI.ISO8859-2',
    'sm_ws': 'sm_WS.UTF-8',
    'so_dj': 'so_DJ.ISO8859-1',
    'so_et': 'so_ET.UTF-8',
    'so_ke': 'so_KE.ISO8859-1',
    'so_so': 'so_SO.ISO8859-1',
    'sp': 'sr_CS.ISO8859-5',
    'sp_yu': 'sr_CS.ISO8859-5',
    'spanish': 'es_ES.ISO8859-1',
    'spanish_spain': 'es_ES.ISO8859-1',
    'sq': 'sq_AL.ISO8859-2',
    'sq_al': 'sq_AL.ISO8859-2',
    'sq_mk': 'sq_MK.UTF-8',
    'sr': 'sr_RS.UTF-8',
    'sr@cyrillic': 'sr_RS.UTF-8',
    'sr@latn': 'sr_CS.UTF-8@latin',
    'sr_cs': 'sr_CS.UTF-8',
    'sr_cs.iso88592@latn': 'sr_CS.ISO8859-2',
    'sr_cs@latn': 'sr_CS.UTF-8@latin',
    'sr_me': 'sr_ME.UTF-8',
    'sr_rs': 'sr_RS.UTF-8',
    'sr_rs@latn': 'sr_RS.UTF-8@latin',
    'sr_sp': 'sr_CS.ISO8859-2',
    'sr_yu': 'sr_RS.UTF-8@latin',
    'sr_yu.cp1251@cyrillic': 'sr_CS.CP1251',
    'sr_yu.iso88592': 'sr_CS.ISO8859-2',
    'sr_yu.iso88595': 'sr_CS.ISO8859-5',
    'sr_yu.iso88595@cyrillic': 'sr_CS.ISO8859-5',
    'sr_yu.microsoftcp1251@cyrillic': 'sr_CS.CP1251',
    'sr_yu.utf8': 'sr_RS.UTF-8',
    'sr_yu.utf8@cyrillic': 'sr_RS.UTF-8',
    'sr_yu@cyrillic': 'sr_RS.UTF-8',
    'ss': 'ss_ZA.ISO8859-1',
    'ss_za': 'ss_ZA.ISO8859-1',
    'st': 'st_ZA.ISO8859-1',
    'st_za': 'st_ZA.ISO8859-1',
    'sv': 'sv_SE.ISO8859-1',
    'sv_fi': 'sv_FI.ISO8859-1',
    'sv_se': 'sv_SE.ISO8859-1',
    'sw_ke': 'sw_KE.UTF-8',
    'sw_tz': 'sw_TZ.UTF-8',
    'swedish': 'sv_SE.ISO8859-1',
    'szl_pl': 'szl_PL.UTF-8',
    'ta': 'ta_IN.TSCII-0',
    'ta_in': 'ta_IN.TSCII-0',
    'ta_in.tscii': 'ta_IN.TSCII-0',
    'ta_in.tscii0': 'ta_IN.TSCII-0',
    'ta_lk': 'ta_LK.UTF-8',
    'tcy_in.utf8': 'tcy_IN.UTF-8',
    'te': 'te_IN.UTF-8',
    'te_in': 'te_IN.UTF-8',
    'tg': 'tg_TJ.KOI8-C',
    'tg_tj': 'tg_TJ.KOI8-C',
    'th': 'th_TH.ISO8859-11',
    'th_th': 'th_TH.ISO8859-11',
    'th_th.tactis': 'th_TH.TIS620',
    'th_th.tis620': 'th_TH.TIS620',
    'thai': 'th_TH.ISO8859-11',
    'the_np': 'the_NP.UTF-8',
    'ti_er': 'ti_ER.UTF-8',
    'ti_et': 'ti_ET.UTF-8',
    'tig_er': 'tig_ER.UTF-8',
    'tk_tm': 'tk_TM.UTF-8',
    'tl': 'tl_PH.ISO8859-1',
    'tl_ph': 'tl_PH.ISO8859-1',
    'tn': 'tn_ZA.ISO8859-15',
    'tn_za': 'tn_ZA.ISO8859-15',
    'to_to': 'to_TO.UTF-8',
    'tpi_pg': 'tpi_PG.UTF-8',
    'tr': 'tr_TR.ISO8859-9',
    'tr_cy': 'tr_CY.ISO8859-9',
    'tr_tr': 'tr_TR.ISO8859-9',
    'ts': 'ts_ZA.ISO8859-1',
    'ts_za': 'ts_ZA.ISO8859-1',
    'tt': 'tt_RU.TATAR-CYR',
    'tt_ru': 'tt_RU.TATAR-CYR',
    'tt_ru.tatarcyr': 'tt_RU.TATAR-CYR',
    'tt_ru@iqtelif': 'tt_RU.UTF-8@iqtelif',
    'turkish': 'tr_TR.ISO8859-9',
    'ug_cn': 'ug_CN.UTF-8',
    'uk': 'uk_UA.KOI8-U',
    'uk_ua': 'uk_UA.KOI8-U',
    'univ': 'en_US.UTF-8',
    'universal': 'en_US.UTF-8',
    'universal.utf8@ucs4': 'en_US.UTF-8',
    'unm_us': 'unm_US.UTF-8',
    'ur': 'ur_PK.CP1256',
    'ur_in': 'ur_IN.UTF-8',
    'ur_pk': 'ur_PK.CP1256',
    'uz': 'uz_UZ.UTF-8',
    'uz_uz': 'uz_UZ.UTF-8',
    'uz_uz@cyrillic': 'uz_UZ.UTF-8',
    've': 've_ZA.UTF-8',
    've_za': 've_ZA.UTF-8',
    'vi': 'vi_VN.TCVN',
    'vi_vn': 'vi_VN.TCVN',
    'vi_vn.tcvn': 'vi_VN.TCVN',
    'vi_vn.tcvn5712': 'vi_VN.TCVN',
    'vi_vn.viscii': 'vi_VN.VISCII',
    'vi_vn.viscii111': 'vi_VN.VISCII',
    'wa': 'wa_BE.ISO8859-1',
    'wa_be': 'wa_BE.ISO8859-1',
    'wae_ch': 'wae_CH.UTF-8',
    'wal_et': 'wal_ET.UTF-8',
    'wo_sn': 'wo_SN.UTF-8',
    'xh': 'xh_ZA.ISO8859-1',
    'xh_za': 'xh_ZA.ISO8859-1',
    'yi': 'yi_US.CP1255',
    'yi_us': 'yi_US.CP1255',
    'yo_ng': 'yo_NG.UTF-8',
    'yue_hk': 'yue_HK.UTF-8',
    'yuw_pg': 'yuw_PG.UTF-8',
    'zh': 'zh_CN.eucCN',
    'zh_cn': 'zh_CN.gb2312',
    'zh_cn.big5': 'zh_TW.big5',
    'zh_cn.euc': 'zh_CN.eucCN',
    'zh_hk': 'zh_HK.big5hkscs',
    'zh_hk.big5hk': 'zh_HK.big5hkscs',
    'zh_sg': 'zh_SG.GB2312',
    'zh_sg.gbk': 'zh_SG.GBK',
    'zh_tw': 'zh_TW.big5',
    'zh_tw.euc': 'zh_TW.eucTW',
    'zh_tw.euctw': 'zh_TW.eucTW',
    'zu': 'zu_ZA.ISO8859-1',
    'zu_za': 'zu_ZA.ISO8859-1'}

On the system itself, I only get:

$ locale -a
C
C.UTF-8
en_AG
en_AG.utf8
en_AU.utf8
en_BW.utf8
en_CA.utf8
en_DK.utf8
en_GB.utf8
en_HK.utf8
en_IE.utf8
en_IL
en_IL.utf8
en_IN
en_IN.utf8
en_NG
en_NG.utf8
en_NZ.utf8
en_PH.utf8
en_SG.utf8
en_US.utf8
en_ZA.utf8
en_ZM
en_ZM.utf8
en_ZW.utf8
POSIX

@fermulator
Copy link
Contributor

In #48, op attempted a fix, but it reverts/breaks the fix for #30.

I think this is a bug/gap we missed during review/consideration of the change.

Not all docker containers I suppose would have locales-all package installed?
https://stackoverflow.com/questions/1259971/os-locale-support-for-use-in-python

This post suggests that it is kind of expected that environments have "all locales" available.
https://mike632t.wordpress.com/2018/04/08/dates-and-locale-settings-in-python/
(dpkg-reconfigure locales)

My system is

>>> locale.getlocale()
('en_CA', 'UTF-8')

Not exactly sure the best way to proceed. Perhaps the interim workaround is to ONLY apply this workaround if the en_US locale is available on the system? (this would prevent breakage for environments that don't have locales installed/configured ... but then would NOT FIX #30 for those systems). - sigh?

If we want to proceed with that kind of fix, we should wrap it in a try/except catching locale.Error and logging that we couldn't apply the workaround on that system.

@dshokouhi
Copy link
Contributor

Wouldn't this be more of a local issue where we ask the user to install/setup whatever they need and just move on? In the case of Home Assistant the Docker image is updated and other users are asked to make any necessary changes if that is the case. There are not many cases where locale is missing or not set at all.

@fermulator
Copy link
Contributor

fermulator commented Aug 14, 2019

Wouldn't this be more of a local issue where we ask the user to install/setup whatever they need and just move on? In the case of Home Assistant the Docker image is updated and other users are asked to make any necessary changes if that is the case. There are not many cases where locale is missing or not set at all.

It isn't unreasonable to ask environments to have their locales setup properly, I agree. But it might be unreasonable to ask a system to have the en_US locale installed when not in the US. (I'm not sure).

For home-assistant, home-assistant/core#25759, it looks like the qualms raised there were rejected. (and I don't see any subsequent issues for HASS/docker envs to have their locales fixed) - we probably don't want to rely on this happening

[EDIT]: actually I see that the docker image was fixed to include locales in https://github.com/home-assistant/home-assistant/pull/25791/files by @Santobert; (however the hasbian OS probably isn't fixed, and any1 in dietpi or other tiny envs might be broken too)

Regardless, I think the right thing to do here is to add the try/except like I suggested above. (because the call to setlocale() is not safe, and exception handling is needed) @dshokouhi - if you agree, I can submit the PR for that.

@Santobert
Copy link
Contributor

Santobert commented Aug 14, 2019

@fermulator do you have a deeper understanding why the error occurs in #30? We set the locale, call strftime and set the locale back. So it's only the date that is affected by the locale, not the creation of the header itself.

When we set a date that is created with UTC, it should not depend on the locale setting of the user. Therefore, error #30 should not occur here. What do you think? Please correct me if I am wrong.

Personally I thing that the try/except solution is just a workaround for a problem that can be solved. Furthermore try/except is a expensive operation and especially the case of except should be avoided when possible.

@fermulator
Copy link
Contributor

fermulator commented Aug 15, 2019

Hey @Santobert , thanks for jumping back into this. Appreciate the feedback!

RE: "So it's only the date that is affected by the locale, not the creation of the header itself."

Note that the date string is FOR the header. In __call__() for Auth class, we do:
https://github.com/stianaske/pybotvac/blob/master/pybotvac/robot.py#L262

date = time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime()) + ' GMT'
# ... (snip) ...
msg = '\n'.join([self.serial.lower(), date, request.body.decode('utf8')])
        signing = hmac.new(key=self.secret.encode('utf8'),
                           msg=msg.encode('utf8'),
                           digestmod=hashlib.sha256)
# ... (snip) ...
request.headers['Date'] = date
request.headers['Authorization'] = "NEATOAPP " + signing.hexdigest()

, so it is relevant to the header itself. In both the Date portion (raw), and encoded into the Authorization portion.

Also the creation of the date-time-string itself IS relevant to the system's locale as per:

%a | Locale’s abbreviated weekday name.
%b | Locale’s abbreviated month name.

As noted at the end of #30, I have submitted a request into Neato R&D to understand why this is happening ... (5 weeks ago) no response as of yet.

  • The PROPER fix here is to root cause the API AUTH dependency on en_US for some robot models. (but we are beholden to Neato's response unless someone in the community can ascertain RCA w/out their input?) - perhaps @ostinelli might be able to help chase?

RE understanding of the workaround itself,

In fact no at least on my en_CA systems I cannot see an observable (printable) difference between en_CA and en_US in terms of the resulting date-time-string. See #30 (comment) -- re-sharing here for posterity:

>>> import time
>>> import locale
>>> locale.getlocale()
('en_CA', 'UTF-8')
>>> date = time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime()) + ' GMT'
>>> print(date)
Thu, 15 Aug 2019 12:16:50 GMT
>>> 
>>> locale.setlocale(locale.LC_ALL, 'en_US.utf8')
'en_US.utf8'
>>> date = time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime()) + ' GMT'
>>> print(date)
Thu, 15 Aug 2019 12:17:02 GMT

, I see no difference; YET, we proved that the header request is broken without it in non-US locales on newer bots. (lots of evidence in #30).

So the TLDR is:

Finally, RE this issue specifically (#45, the delivery of workaround in #30 breaks systems without proper locale installation/config)

From a pure code robustness perspective, we MUST handle this using either EAFP or LBYL. It is incorrect to write code that calls into a library that MIGHT throw an error, and to not handle it. (I take the blame here, as I did not notice that setlocale() could throw an exception.)

So our two options are:

  1. The try/except implementation is EAFP (easier to ask for forgiveness than permission) -- https://docs.python.org/3/glossary.html#term-eafp. -- just try it, and if the locale isn't present gracefully move on.
  2. The alternative LBYL (look before you leap) - https://docs.python.org/3/glossary.html#term-lbyl -- would could be something like making a sys call into locale -a to determine if en_US is present, and only apply this workaround if that locale is present on the system.

I would hazard a guess, that a syscall is more expensive than exception handling. (only a guess though); If someone has a proposal for how to check if a system locale exists without that expensive sys call (option 2); in a pythonic way, that could be our winner here.

PS: (so that I can learn) what evidence/experience do you have that supports try/except as being expensive? As I understand it, the happy path through a try-except is extremely efficient/fast. Handling of an exception is more expensive, but exceptions are supposed to be rare anyway. I suppose you may be arguing the point that on systems without locale, the exception would no longer be rare, and thusly an undesired solution?

@Santobert
Copy link
Contributor

Wow, great summary. Thank you for the clarification.

https://docs.python.org/3/library/time.html#time.strftime
Our string is: '%a, %d %b %Y %H:%M:%S', so we are affected by it for:

%a | Locale’s abbreviated weekday name.
%b | Locale’s abbreviated month name.

Oh, I was just thinking about the date and time, not the string representations.
Could pytz solve this problem without using the systems locale? I'm not familiar with this library.
https://pypi.org/project/pytz/

From a pure code robustness perspective, we MUST handle this using either EAFP or LBYL. It is incorrect to write code that calls into a library that MIGHT throw an error, and to not handle it. (I take the blame here, as I did not notice that setlocale() could throw an exception.)

I agree, that the syscall might be more expensive than the except-case (which is the only part of try/except that is expensive). If there is no other solution (like pytz?), I would vote for the first solution (try/except) for performance reasons. The second solutions looks more clean to me.

@fermulator
Copy link
Contributor

Hey @Santobert , I gave this a shot.

BASELINE
.. wait ... did Neato change/fix something? I'm no longer able to produce the ORIGINAL problem. If I revert to the OLD code (v13) without any locale changing:

date = time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime()) + ' GMT'

, it doesn't bork (like it used to) and works:

~/projects/pybotvac$ python3
Python 3.5.2 (default, Nov 12 2018, 13:43:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pybotvac import Account
>>> for robot in Account('USERNAME_SNIP', 'PASSWORD_SNIP').robots: print(robot)
... 
Name: boyle, Serial: OPS<SNIP>, Secret: <SNIP> Traits: ['maps']

huh?

Confirming I'm still in en_CA locale

>>> import locale
>>> locale.getlocale()
('en_CA', 'UTF-8')

Retest v15 (the one with the locale switching)

...
>>> for robot in Account('USERNAME_SNIP', 'PASSWORD_SNIP').robots: print(robot)
... 
Name: boyle, Serial: OPS<SNIP>, Secret: <SNIP> Traits: ['maps']

Works as expected (still).

If I try with another method ...

>>> fmt='%a, %d %b %Y %H:%M:%S'
>>> from datetime import datetime
>>> date = datetime.utcnow().strftime(fmt) + ' GMT'
>>> print(date)
Tue, 20 Aug 2019 01:05:32 GMT

I hacked up a local instance of pybotvac robot.py with changes to the Auth.__call()__ method to use that, and it works as well.

...
>>> for robot in Account('USERNAME_SNIP', 'PASSWORD_SNIP').robots: print(robot)
... 
Name: boyle, Serial: OPS<SNIP>, Secret: <SNIP> Traits: ['maps']

If i use UTC as a suffix, it doesn't work (robot incorrectly reports as offline).

We need other testers to confirm/deny this.

SUSPECT that Neato changed something recently (FIXED) their API... ?
I can no longer test fixes here ...

@Santobert
Copy link
Contributor

Santobert commented Aug 20, 2019

It works for me, too.

>>> import locale
>>> locale.getlocale()
('de_DE', 'UTF-8')

Maybe thats the reason, why I couldn't reproduce that error while testing #48

@stianaske
Copy link
Owner

So, if I understand everything correctly, #48 should resolve both the #30 and #45, right?

This does make a lot of sense to me, as I'm unable to understand how the temporary locale could somehow affect the header encoding (except for the date of course). Note that in v0.15 we do reset the locale immediately after creating the datetime string.

saved_locale = locale.getlocale(locale.LC_TIME)
locale.setlocale(locale.LC_TIME, 'en_US.utf8')

date = time.strftime('%a, %d %b %Y %H:%M:%S', time.gmtime()) + ' GMT'

locale.setlocale(locale.LC_TIME, saved_locale)

I.e. the locale is only used to correctly format the current datetime, and setting it using datetime's

Have anybody reproduced #30 when using datetime.utctime() from #48?

@Santobert
Copy link
Contributor

So, if I understand everything correctly, #48 should resolve both the #30 and #45, right?

For me (de_DE.UTF-8) it works.

Have anybody reproduced #30 when using datetime.utctime() from #48?

I didn't notice anything wrong while testing #48, so I guess not.

@fermulator What do you think?

@ostinelli
Copy link

Sorry for late response, I was AFK.

Nothing was changed on Neato servers. If I understand correctly, you are experiencing some issues in encoding the date header properly.

As specified by the official docs, the Date Header is to be defined as per RFC 2616 Sec 14.18 which states that the reference is RFC 1123, that in turn references RFC 822, more specifically here. You can see in this section that days MUST be "Mon"..."Sun" and months "Jan"..."Dec" (in english).

This is BTW standard http header encoding. Cannot help in python because I'm not up to date on libraries / functions I'd recommend for the scope. :)

@Santobert
Copy link
Contributor

If there are still issues after #48 we could consider using Babel's format_datetime function. But on all my systems (Linux and Windows with different location locales) datetime.utcnow() always returns english representations of dates.

@fermulator
Copy link
Contributor

So, if I understand everything correctly, #48 should resolve both the #30 and #45, right?
@stianaske

no, please delete pull #48, it is not the right fix

@fermulator
Copy link
Contributor

fermulator commented Sep 5, 2019

Sorry for late response, I was AFK.

Nothing was changed on Neato servers. If I understand correctly, you are experiencing some issues in encoding the date header properly.

As specified by the official docs, the Date Header is to be defined as per RFC 2616 Sec 14.18 which states that the reference is RFC 1123, that in turn references RFC 822, more specifically here. You can see in this section that days MUST be "Mon"..."Sun" and months "Jan"..."Dec" (in english).

This is BTW standard http header encoding. Cannot help in python because I'm not up to date on libraries / functions I'd recommend for the scope. :)

Hi @ostinelli , thank you for the reply. I will look into the specifics of the RFCs, however at a glance it seems the code even originally was in compliance.

Can you please specifically consider
#30 (comment)
?

Users were having problems when using the date formulation for header with non-US locales, but for me if I compared "Canada vs. US", there was no human legible difference ...

ALSO, please specifically answer, will Neato's servers accept ANY locale weekday/month abbreviations for the date header? Or en_US only? (RFC 2616 of course assumes English, and slightly biases towards en_US I suppose)

I believe it is correct that it MUST be in UTC or GMT as well. (this is likely what Neato would assume)

@fermulator
Copy link
Contributor

fermulator commented Sep 5, 2019

I tried searching all over for a python module that would automatically format us to to RFC 2616/822 per the HTTP header date requirements, but surprising nothing exists! Not even the http module has something. There are several functions that do the OPPOSITE (from an http header, parse out the date), but nothing to create it.

Looking at the existing string format we have, this format seems to align as per RFC 2616 and per the semantics of RFC 822:

>>> fmt='%a, %d %b %Y %H:%M:%S'

(less the GMT suffix required)
but/however AGAIN %a and %b are tied to the system's locale. :(, so this is subject to breakage

There's really no way around this.
https://docs.python.org/3/library/time.html#time.strftime

Python's only map to "weekday abbreviated name" and "month abbreviated name" requires locale set.

(this is, at the root, the problem as I see it)


as a side-note, for the time conversion itself, I think we're into one of these solutions

(A)
https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow

>>> from datetime import datetime
>>> from time import strftime
datetime.utcnow().strftime(fmt) + ' GMT'
'Thu, 05 Sep 2019 12:14:05 GMT'

(B)
https://docs.python.org/3/library/time.html#time.gmtime

>>> from time import gmtime, strftime
>>> strftime(fmt, gmtime()) + ' GMT'
'Thu, 05 Sep 2019 12:08:06 GMT'

Which one to use? Well gmtime() considers DST, but utcnow() does not ...
https://stackoverflow.com/questions/25022745/why-is-there-1-hour-difference-between-gmtime-and-utcnow-datetime

or

(C)

>>> from babel.dates import format_date, format_datetime, format_time
>>> format_datetime(datetime.utcnow(), 'EEE, d MMM yyyy HH:mm:ss', locale='en') + ' GMT'
'Thu, 5 Sep 2019 14:54:53 GMT'

IMO, we should keep the change in #30 , but add/fix it for the try/except + warning. (if a system DOES NOT have a locale, then log a warning, as it is REQUIRED to build the HTTP header).
-> @stianaske if you concur, I will submit a PR to fix everything up

My proposal is:

  1. Continue with HTTPError: 400 Client Error: Bad Request for url [neatocloud url] #30 's solution, but add try/except handling.
  2. If we detect the system does not have the en_US locale installed, log a warning
  3. Use either gmtime() or utcnow() or babel.format_datetime() (TBD)

@fermulator
Copy link
Contributor

fermulator commented Sep 5, 2019

@Santobert , the babel package looks useful too, thanks for sharing that. I experimented with it.
http://babel.pocoo.org/en/latest/dates.html

do you know tho if it can simulate a locale EVEN IF the system doesn't have the target locale installed?

of note, this might be a hint into why some systems don't work (and validation that we depend on a valid locale...)

consider this from babel

>>> format_datetime(datetime.utcnow(), 'EEE, d MMM yyyy, HH:mm:ss', locale='en')
'Thu, 5 Sep 2019, 14:54:53'

OK, but what if we want it to print per RFC 822?

http://babel.pocoo.org/en/latest/dates.html#time-fields, see "TimeZone"

>>> format_datetime(datetime.utcnow(), 'EEE, d MMM yyyy, HH:mm:ss ZZZ', locale='en_US')
'Thu, 5 Sep 2019, 15:03:50 +0000'
>>> format_datetime(datetime.utcnow(), 'EEE, d MMM yyyy, HH:mm:ss ZZZZ', locale='en_US')
'Thu, 5 Sep 2019, 15:03:53 GMT+00:00'
>>> format_datetime(datetime.utcnow(), 'EEE, d MMM yyyy, HH:mm:ss V', locale='en_US')
'Thu, 5 Sep 2019, 15:03:56 Unknown Region (GMT) Time'

,,, hm

@fermulator
Copy link
Contributor

fermulator commented Sep 5, 2019

But on all my systems (Linux and Windows with different location locales) datetime.utcnow() always returns english representations of dates.

PS: the representation of dates is determined by the format time stuff (has nothing to do with datetime.utcnow() as that is returning a datetime object :)

For example, if you try to use a non-English locale, it definitely comes out per requested (and isn't tied to English)

>>> format_datetime(datetime.utcnow(), 'EEE, d MMM yyyy, HH:mm:ss V', locale='de_DE')
'Do., 5 Sep. 2019, 15:02:49 Unbekannte Region (GMT) Zeit'

@Santobert
Copy link
Contributor

Thanks @fermulator for digging into this. Are you sure that setting the systems locale doesn't have any side effects? Is the locale bound to the session or can it influence other programs behavior?
I'm still not happy with setting the locale, but I see that there is possibly no other way.

To simulate a locale I would consider the pytz package, but I'm not an expert.
https://pypi.org/project/pytz/

Unfortunately I can't help you at the moment because I'm quite busy, but I prefer solution C.

@fermulator
Copy link
Contributor

i agree setting the locale is risky (in fact it is riskier than I thought)

  • it does not affect the whole system (as I knew)
  • however it is not thread safe, as it affects the entire program (instance of python running)

Read: https://docs.python.org/3/library/locale.html#background-details-hints-tips-and-caveats
, so if there are other major systems using pybotvac (i.e. home-assistent), and they have threads, and they too rely on locale in some way, we could screw them up;

I was fiddling with pytz a month ago, but ran into a blocker that I can't remember ... retrying

>>> import pytz
>>> pytz.timezone('GMT')
<StaticTzInfo 'GMT'>
>>> pytz.timezone('UTC')
<UTC>

I remember now, I can't figure out how to get a raw datetime object for "utcnow()" that is easily usable into the datatime constructor...

their example is

>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
>>> loc_dt = utc_dt.astimezone(eastern)
>>> loc_dt.strftime(fmt)
'2002-10-27 01:00:00 EST-0500'

but we need the date-time of NOW (in UTC or GMT), and I'm also not sure how datetime.strftime() behaves if the tzinfo is set to something, and it doesn't match the system locale :(

@fermulator
Copy link
Contributor

I'm not going to spend any more time fiddling until I get a clear set of answers/requirements from Neato @ostinelli . #45 (comment)

@Santobert
Copy link
Contributor

Santobert commented Sep 21, 2019

I closed the #48 in order to create a new pr with the right solution. I have digged a little for Python datetime stuff. Thanks to @fermulator for his great comments. That helped me a lot.

I see two different approaches:
https://github.com/Santobert/pybotvac/blob/avoid_missing_locale_1/pybotvac/robot.py#L248
This approach uses the babel package. We have to create a datetime object with datetime.now(timezone.utc). This is just a more advanced version of datetime.utcnow(), that takes care about tzinfo. Babel's format_datetime() function creates the string from it using english abbreviations. The format EEE, dd LLL yyyy hh:mm:ss matches the given RFC.

https://github.com/Santobert/pybotvac/blob/avoid_missing_locale_2/pybotvac/robot.py#L248
The other approach uses the native package email.utils, which also has a function format_datetime(). This function creates a formatted string based on a datetime object that has utc as tzinfo. The function creates the string according to RFC 2822 that is also based on RFC 822 and RFC 1123. You can read about the differences here.

What do you think? Which of these approaches do you like? Do you see any problems?

@Santobert
Copy link
Contributor

I will create a PR as soon as we have decided on one of the given solutions.

@stianaske
Copy link
Owner

stianaske commented Oct 4, 2019

Going with the solution in PR #52 from @Santobert for now. Reopen if you experience any issues with this.

@fermulator
Copy link
Contributor

fermulator commented Oct 5, 2019

Great @Santobert in #52, i tested your changes locally (hacked in the patch/diff), and it seems to work!

for robot in Account('username', 'password').robots: print(robot)
Your 'boyle-ORIGINAL' robot is offline.
Name: boyle, Serial: OPS26517-SNIP, Secret: <SNIP> Traits: ['maps']

I'm glad you went with the builtin email.utils option, I much prefer this.

>>> from datetime import datetime, timezone
>>> from email.utils import format_datetime 
>>> now = datetime.now(timezone.utc)
>>> print(now)
2019-10-05 11:45:41.429244+00:00
>>> date = format_datetime(now, True)
>>> print(date)
Sat, 05 Oct 2019 11:45:41 GMT

Once we rebuild and re-release i'll be sure to track the fix doesn't regress anything. (as others will too I'm sure!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants