From d0f2ffd682c0183af605b70db525379f6924314b Mon Sep 17 00:00:00 2001 From: Philipp Spitzer Date: Thu, 27 Feb 2014 21:57:11 +0100 Subject: [PATCH 001/123] Added a test to TestURLValidator that fails (but should not). An invalid URL should be reported as an Invald exception. However, it is reported as ValueError. --- formencode/tests/test_validators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/formencode/tests/test_validators.py b/formencode/tests/test_validators.py index cb0ed6cf..360e3fa1 100644 --- a/formencode/tests/test_validators.py +++ b/formencode/tests/test_validators.py @@ -704,6 +704,10 @@ def test_ip_validator(self): "http://65.18.195.155/cgi-ordoro/bo/start.cgi"), "http://65.18.195.155/cgi-ordoro/bo/start.cgi") + def test_invalid_url(self): + self.assertRaises(validators.Invalid, self.validator.to_python, + '[http://domain.co.jp') + class TestRequireIfMissingValidator(unittest.TestCase): From d806910395e7d1d6b4b851e4c38035fced55a9df Mon Sep 17 00:00:00 2001 From: Philipp Spitzer Date: Thu, 27 Feb 2014 22:48:48 +0100 Subject: [PATCH 002/123] That should fix issue #61. --- formencode/validators.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/formencode/validators.py b/formencode/validators.py index 2ce59142..2f0bd212 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1527,8 +1527,11 @@ def _encode_idna(self, url): global urlparse if urlparse is None: import urlparse - scheme, netloc, path, params, query, fragment = urlparse.urlparse( - url) + try: + scheme, netloc, path, params, query, fragment = urlparse.urlparse( + url) + except ValueError: + return url try: netloc = netloc.encode('idna') if unicode is str: # Python 3 From ec715ce1a87c9651eaa6b8411637a47db90b5b55 Mon Sep 17 00:00:00 2001 From: Keith Yang Date: Wed, 22 Oct 2014 16:16:09 +0800 Subject: [PATCH 003/123] Complete zh_TW translation --- .../i18n/zh_TW/LC_MESSAGES/FormEncode.mo | Bin 7440 -> 11161 bytes .../i18n/zh_TW/LC_MESSAGES/FormEncode.po | 123 +++++++++--------- 2 files changed, 61 insertions(+), 62 deletions(-) diff --git a/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo b/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo index da78e02f692c9a65fccb480f46accdac9b307769..fea058d8c2fc0d5810a23a9ece56fc79b761990d 100644 GIT binary patch literal 11161 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 delta 1766 zcmaLWe@xV69Ki8MBb5`7P=0&(J@MrDad^oucjX3CWG+%tN6suExDW-tb%$iF1}+JM z+NzH@txTukKmEZbXW@d{%ITVIHCArAaxFQ)MYE<`Q(X1_#BKf8?cnu156|=YJkRHO zIQHb*{?6-3G5vUSm4VIU2Buoa)e4ot@})Iigy0a6{tEI}Wxz)I8; z?Lj?2Pt*Y{qyH6Zew{aDwESe3t z0rl@Kn1{Wnl{$yH_-)j?=%t^{1JXE8pvG^<4D3s!{zWv-Fwi~6m}|(_)N#T2_yXz% zkE53Q3hG;#K|Q&Thp`&Bquy~JlGB878D5AwjygYvd(#aS;7Z)Fg!=2i0R|qyLDT@h zqLyw3$z@zT5VaU}BUPvYTTtg8M!l+!P)mFf^~!EwHQq;k{Tn&H&JUpW_jS^Emc~1% z3+|u>iswP-8(4*!coXV|>ha%4hDhMIU6YK3joE9vZ_ zp#e@u2hO8T{4we@>Z`qvnxK%2-i9?%2T<4FLrs*(#TMZjWXQCjUd8LES9%6_;u!J| z(P>su2Mz2;J?T!=05)dhY19OlkgxfPU+l%)MSXlp)LRp;#R3eV#(Nj_AYs&vTtGd@ zbu?~wMS3Y&H5Ka~tEj59YAd&F-MD#^TuSfB+z<>jTGh>M z)}u}JA-koyt-PtVzPY8a!ETkk8J{?Oje!ntu%x)uA@K{GGJk0}E}#>v$nenAu@h3{ z+@BKpqWk8_L*t_#+&nNmAw6blsAuBHF}dc%YQ*z$^Yd1N-5B5sJkQy|*829` zQ11WD3gyc0*_Y+ovN*ZDV!iBg+cIzEH{!?%%1};*RC=oAYfnRr<, 2008. +# Fred Lin , 2010. +# Keith Yang , 2014. # msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: FormEncode \n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-05-16 19:11+0200\n" "PO-Revision-Date: 2010-05-16 04:32+0200\n" -"Last-Translator: Fred Lin \n" +"Last-Translator: Keith Yang \n" "Language-Team: zh_TW \n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" @@ -18,7 +20,7 @@ msgstr "" #: formencode/api.py:394 formencode/validators.py:418 msgid "Please enter a value" -msgstr "請輸入一個值" +msgstr "請輸入值" #: formencode/api.py:395 #, python-format @@ -31,62 +33,62 @@ msgstr "輸入必須為字串 (不能為空白)" #: formencode/national.py:29 msgid "Belarus" -msgstr "" +msgstr "Belarus" #: formencode/national.py:30 msgid "Montenegro" -msgstr "" +msgstr "Montenegro" #: formencode/national.py:31 msgid "Tasmania" -msgstr "" +msgstr "Tasmania" #: formencode/national.py:37 msgid "Britain" -msgstr "" +msgstr "Britain" #: formencode/national.py:38 msgid "Great Britain" -msgstr "" +msgstr "Great Britain" #: formencode/national.py:39 msgid "Cote de Ivoire" -msgstr "" +msgstr "Cote de Ivoire" #: formencode/national.py:144 #, python-format msgid "%d digits" -msgstr "" +msgstr "%d 個數字" #: formencode/national.py:165 #, python-format msgid "Please enter a zip code (%(format)s)" -msgstr "請輸入區碼(zip code, %(format)s)" +msgstr "請輸入區碼 %(format)s" #: formencode/national.py:229 formencode/national.py:263 #, python-format msgid "Please enter a zip code (%s)" -msgstr "請輸入區碼(zip code, %s)" +msgstr "請輸入區碼 %s" #: formencode/national.py:229 msgid "LnnnnLLL" -msgstr "" +msgstr "LnnnnLLL" #: formencode/national.py:263 msgid "LnL nLn" -msgstr "" +msgstr "LnL nLn" #: formencode/national.py:297 msgid "Please enter a valid postal code (for format see BS 7666)" -msgstr "" +msgstr "請輸入一個正確格式的郵遞區號" #: formencode/national.py:338 msgid "That country is not listed in ISO 3166" -msgstr "" +msgstr "國家未列於 ISO 3166 標準中" #: formencode/national.py:393 msgid "Given postal code does not match the country's format." -msgstr "" +msgstr "郵遞區號未符合國家的格式" #: formencode/national.py:484 msgid "Please enter a state code" @@ -107,27 +109,25 @@ msgid "" msgstr "請輸入一個包含區域碼的數字, 格式為 ###-###-####, 可加上 \"ext.####\" 表示轉接" #: formencode/national.py:646 -#, fuzzy msgid "Please enter a number, with area code, in the form +##-###-#######." -msgstr "請輸入一個包含區域碼的數字, 格式為 ###-###-####, 可加上 \"ext.####\" 表示轉接" +msgstr "請輸入一個包含區域碼的數字, 格式為 +##-###-####" #: formencode/national.py:725 msgid "That language is not listed in ISO 639" -msgstr "" +msgstr "這個語言未列於 ISO 639 標準中" #: formencode/schema.py:63 #, python-format msgid "The input field %(name)s was not expected." -msgstr "" +msgstr "不符合的輸入欄 %(name)s" #: formencode/schema.py:64 msgid "Missing value" -msgstr "" +msgstr "未填入" #: formencode/schema.py:65 -#, fuzzy, python-format msgid "The input must be dict-like (not a %(type)s: %(value)r)" -msgstr "輸入必須為字串 (而非 %(type)s: %(value)r)" +msgstr "輸入必須為 dict-like (而非 %(type)s: %(value)r)" #: formencode/validators.py:147 #, python-format @@ -186,7 +186,7 @@ msgstr "值必須為 %(items)s 中一個 (而非 %(value)r)" #: formencode/validators.py:640 msgid "Choose something" -msgstr "必須作選擇" +msgstr "請選擇" #: formencode/validators.py:641 #, python-format @@ -202,7 +202,7 @@ msgstr "值不能辨識" msgid "" "Nothing in my dictionary goes by the value %(value)s. Choose one of: " "%(items)s" -msgstr "字典中沒有 %(value)s 這個值. 從 %(items)s 中選擇一個值輸入" +msgstr "我的字典中沒有 %(value)s 這個值。從 %(items)s 中選擇一個值輸入" #: formencode/validators.py:705 msgid "Must be an integer index" @@ -220,12 +220,12 @@ msgstr "列表中找不到 %(value)s" #: formencode/validators.py:777 #, python-format msgid "Date must be after %(date)s" -msgstr "資料必須在 %(date)s 之後" +msgstr "日期必須在 %(date)s 之後" #: formencode/validators.py:778 #, python-format msgid "Date must be before %(date)s" -msgstr "資料必須在 %(date)s 之前" +msgstr "日期必須在 %(date)s 之前" #: formencode/validators.py:780 #, python-format @@ -237,14 +237,14 @@ msgid "The date must be sometime in the future" msgstr "請選擇在今日之後的日期" #: formencode/validators.py:882 -#, fuzzy, python-format +#, python-format msgid "Please enter a number that is %(min)s or greater" -msgstr "請輸入一個年份數字" +msgstr "請輸入一大於 %(min)s 的數字" #: formencode/validators.py:883 -#, fuzzy, python-format +#, python-format msgid "Please enter a number that is %(max)s or smaller" -msgstr "請輸入一個年份數字" +msgstr "請輸入一小於或等於 %(max)s 的數字" #: formencode/validators.py:920 msgid "Please enter an integer value" @@ -255,14 +255,14 @@ msgid "Please enter a number" msgstr "請輸入數字" #: formencode/validators.py:1019 -#, fuzzy, python-format +#, python-format msgid "Enter a value not more than %(max)i characters long" -msgstr "請輸入少於 %(max)i 個字元的值" +msgstr "請輸入少於 %(max)i 個字" #: formencode/validators.py:1020 #, python-format msgid "Enter a value %(min)i characters long or more" -msgstr "請輸入大於 %(min)i 個字元的值" +msgstr "請輸入大於 %(min)i 個字" #: formencode/validators.py:1095 msgid "Invalid data or incorrect encoding" @@ -274,7 +274,7 @@ msgstr "請輸入 email 帳號" #: formencode/validators.py:1268 msgid "An email address must contain a single @" -msgstr "email 帳號必須包含一個 @ 符號" +msgstr "Email 位址必須包含一個 @ 符號" #: formencode/validators.py:1269 #, python-format @@ -327,46 +327,46 @@ msgstr "伺服器回應 壞的狀態碼(%(status)s)" #: formencode/validators.py:1417 #, python-format msgid "You must provide a full domain name (like %(domain)s.com)" -msgstr "" +msgstr "你必須提供一個完整的 domain 名(如 %(domain)s.com)" #: formencode/validators.py:1544 msgid "" "The type of i-name is not defined; it may be either individual or " "organizational" -msgstr "" +msgstr "i-name 的形態未定義;它可以是個人或組織" #: formencode/validators.py:1546 msgid "Dots and dashes may not be repeated consecutively" -msgstr "" +msgstr "英文句號破折號不可連續" #: formencode/validators.py:1547 #, python-format msgid "\"%(iname)s\" is an invalid i-name" -msgstr "" +msgstr "\"%(iname)s\" 是個不合法的 i-name" #: formencode/validators.py:1548 msgid "i-names may not start with numbers nor punctuation marks" -msgstr "" +msgstr "i-names 開頭字元不可為數字或符號" #: formencode/validators.py:1550 #, python-format msgid "\"%(inumber)s\" is an invalid i-number" -msgstr "" +msgstr "\"%(inumber)s\" 是個不合法的 i-number" #: formencode/validators.py:1551 -#, fuzzy, python-format +#, python-format msgid "The XRI must be a string (not a %(type)s: %(value)r)" -msgstr "輸入必須為字串 (而非 %(type)s: %(value)r)" +msgstr "XRI 輸入必須為字串 (而非 %(type)s: %(value)r)" #: formencode/validators.py:1552 #, python-format msgid "\"%(xri_type)s\" is not a valid type of XRI" -msgstr "" +msgstr "\"%(xri_type)s\" 不是一個合理的 XRI 形態" #: formencode/validators.py:1651 #, python-format msgid "\"%(id)s\" is not a valid OpenId (it is neither an URL nor an XRI)" -msgstr "" +msgstr "\"%(id)s\" 不是一個合格的 OpenId(它不是 URL 也不是 XRI)" #: formencode/validators.py:1882 formencode/validators.py:1890 #, python-format @@ -398,12 +398,11 @@ msgstr "未知的月份名稱:%(month)s" #: formencode/validators.py:1888 msgid "Please enter a number for the year" -msgstr "請輸入一個年份數字" +msgstr "請使用數字輸入一個年份" #: formencode/validators.py:1889 -#, fuzzy msgid "Please enter a four-digit year after 1899" -msgstr "請輸入一個四位元的年份" +msgstr "請輸入一個 1899 年後四位元的年份" #: formencode/validators.py:2070 msgid "You must indicate AM or PM" @@ -411,7 +410,7 @@ msgstr "您必須指定 AM 或 PM" #: formencode/validators.py:2071 msgid "There are too many :'s" -msgstr "太多 :'s" +msgstr "太多冒號 :" #: formencode/validators.py:2072 msgid "You may not enter seconds" @@ -462,42 +461,43 @@ msgid "Signature is not correct" msgstr "符號不正確" #: formencode/validators.py:2378 -#, fuzzy msgid "Please enter a valid IP address (a.b.c.d)" -msgstr "請輸入 email 帳號" +msgstr "請輸入合理的 IP 位址 (a.b.c.d)" #: formencode/validators.py:2379 #, python-format msgid "The octets must be within the range of 0-255 (not %(octet)r)" -msgstr "" +msgstr "八位元組必須在 --255 範圍裡(而非 %(octet)r)" #: formencode/validators.py:2426 msgid "Please enter a valid IP address (a.b.c.d) or IP network (a.b.c.d/e)" -msgstr "" +msgstr "請輸入一個合理的 IP 位址 (a.b.c.d) 或 IP 網路 (a.b.c.d/e)" #: formencode/validators.py:2428 #, python-format msgid "The network size (bits) must be within the range of 8-32 (not %(bits)r)" -msgstr "" +msgstr "頻寬 (bits) 必須在 8-32 範圍裡(而非 %(bits)r)" #: formencode/validators.py:2477 #, python-format msgid "" "A MAC address must contain 12 digits and A-F; the value you gave has " "%(length)s characters" -msgstr "" +msgstr "一個 MAC 位址必須包含 12 個數字以及 A-F;你給的值長度為 " +"%(length)s 字元" #: formencode/validators.py:2479 #, python-format msgid "" "MAC addresses may only contain 0-9 and A-F (and optionally :), not " "%(char)r" -msgstr "" +msgstr "MAC 只能包含 0-9 與 A-F(不一定含 : 符號),而非" +"%(char)r" #: formencode/validators.py:2586 #, python-format msgid "You must give a value for %s" -msgstr "" +msgstr "你必須提供一個值給 %s" #: formencode/validators.py:2619 #, python-format @@ -510,7 +510,7 @@ msgstr "欄位不符合" #: formencode/validators.py:2621 msgid "Fields should be a dictionary" -msgstr "" +msgstr "欄位應為 dictionary" #: formencode/validators.py:2697 msgid "Please enter only the number, no other characters" @@ -527,7 +527,7 @@ msgstr "數字不正確" #: formencode/validators.py:2700 #, python-format msgid "The field %(key)s is missing" -msgstr "" +msgstr "欄位 %(key)s 未填入" #: formencode/validators.py:2816 msgid "Please enter numbers only for month and year" @@ -544,4 +544,3 @@ msgstr "請輸入信用卡安全碼數字" #: formencode/validators.py:2893 msgid "Invalid credit card security code length" msgstr "不正確的信用卡安全碼長度" - From 871e7e96ae8a7d438a4a6d02345f3b02fd578da3 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 10 Jan 2015 14:22:44 +0100 Subject: [PATCH 004/123] Document the new DateConverter month_style aliases (closes #73). --- formencode/validators.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/formencode/validators.py b/formencode/validators.py index b3391f76..c7495de1 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1358,7 +1358,7 @@ def _validate_python(self, value, state): idna_domain = '.'.join(idna_domain) except UnicodeError: # UnicodeError: label empty or too long - # This exception might happen if we have an invalid domain name part + # This exception might happen if we have an invalid domain name part # (for example test@.foo.bar.com) raise Invalid( self.message('badDomain', state, domain=domain), @@ -1899,8 +1899,8 @@ class DateConverter(FancyValidator): """ Validates and converts a string date, like mm/yy, dd/mm/yy, dd-mm-yy, etc. Using ``month_style`` you can support - ``'mm/dd/yyyy'`` or ``'dd/mm/yyyy'``. Only these two general - styles are supported. + the three general styles `mdy` = `us` = `mm/dd/yyyy`, + `dmy` = `euro` = `dd/mm/yyyy` and `ymd` = `iso` = `yyyy/mm/dd`. Accepts English month names, also abbreviated. Returns value as a datetime object (you can get mx.DateTime objects if you use @@ -2715,7 +2715,7 @@ class RequireIfMatching(FormValidator): matches the value of ``expected_value``, then the validator will raise an ``Invalid`` exception for every field in ``required_fields`` that is missing. - + :: >>> from formencode import validators From c08551d10e58960735744e8e197d06a70462988a Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 10 Jan 2015 14:27:19 +0100 Subject: [PATCH 005/123] I always forget ReST requires double backticks. --- formencode/validators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/formencode/validators.py b/formencode/validators.py index c7495de1..cbfc2490 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1899,8 +1899,9 @@ class DateConverter(FancyValidator): """ Validates and converts a string date, like mm/yy, dd/mm/yy, dd-mm-yy, etc. Using ``month_style`` you can support - the three general styles `mdy` = `us` = `mm/dd/yyyy`, - `dmy` = `euro` = `dd/mm/yyyy` and `ymd` = `iso` = `yyyy/mm/dd`. + the three general styles ``mdy`` = ``us`` = ``mm/dd/yyyy``, + ``dmy`` = ``euro`` = ``dd/mm/yyyy`` and + ``ymd`` = ``iso`` = ``yyyy/mm/dd``. Accepts English month names, also abbreviated. Returns value as a datetime object (you can get mx.DateTime objects if you use From fb5567625f1e69996549726367b5154c17c4090f Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Sat, 7 Feb 2015 13:27:00 -0500 Subject: [PATCH 006/123] Fix i18n test that fails because string changed --- formencode/tests/test_i18n.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/tests/test_i18n.py b/formencode/tests/test_i18n.py index a6534d0a..5c65891e 100644 --- a/formencode/tests/test_i18n.py +++ b/formencode/tests/test_i18n.py @@ -70,7 +70,7 @@ def test_pt_BR(): def test_zh_TW(): - _test_lang("zh_TW", u"請輸入一個值") + _test_lang("zh_TW", u"請輸入值") def test_sk(): From afd207f6f0855f59daa51eb16b948b5397187624 Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Sat, 7 Feb 2015 13:33:52 -0500 Subject: [PATCH 007/123] Release 1.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fdd032cc..e6204335 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ import sys from setuptools import setup, find_packages -version = '1.3.0a1' +version = '1.3.0' if not '2.6' <= sys.version < '3.0' and not '3.2' <= sys.version: raise ImportError('Python version not supported') From 11282b89c24e049567f40dd6c0bb073f276db7ab Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Fri, 3 Apr 2015 19:51:10 -0400 Subject: [PATCH 008/123] adding support for form selectors --- formencode/htmlfill.py | 34 ++++- formencode/tests/test_htmlfill_control.py | 177 ++++++++++++++++++++++ 2 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 formencode/tests/test_htmlfill_control.py diff --git a/formencode/htmlfill.py b/formencode/htmlfill.py index c06ddc9b..5c438786 100644 --- a/formencode/htmlfill.py +++ b/formencode/htmlfill.py @@ -17,7 +17,9 @@ def render(form, defaults=None, errors=None, use_all_keys=False, text_as_default=False, checkbox_checked_if_present=False, listener=None, encoding=None, error_class='error', prefix_error=True, - force_defaults=True, skip_passwords=False): + force_defaults=True, skip_passwords=False, + data_formencode_form=None, data_formencode_ignore=None, + ): """ Render the ``form`` (which should be a string) given the ``defaults`` and ``errors``. Defaults are the values that go in the input fields @@ -83,6 +85,19 @@ def render(form, defaults=None, errors=None, use_all_keys=False, rendering form-content. If disabled the password fields will not be filled with anything, which is useful when you don't want to return a user's password in plain-text source. + + ``data_formencode_form`` if a string is passed in (default `None`) only + fields with the html attribute `data-formencode-form` that matches this + string will be processed. For example: if a HTML fragment has two forms they + can be differentiated to Formencode by decorating the input elements with + attributes such as `data-formencode-form="a"` or `data-formencode-form="b"`, + then instructing `render()` to only process the "a" or "b" fields. + + ``data_formencode_ignore`` if True (default `None`) fields with the html + attribute `data-formencode-ignore` will not be processed. This attribute + need only be present in the tag: `data-formencode-ignore="1"`, + `data-formencode-ignore=""` and `data-formencode-ignore` without a value are + all valid signifiers. """ if defaults is None: defaults = {} @@ -101,6 +116,8 @@ def render(form, defaults=None, errors=None, use_all_keys=False, error_class=error_class, force_defaults=force_defaults, skip_passwords=skip_passwords, + data_formencode_form=data_formencode_form, + data_formencode_ignore=data_formencode_ignore, ) p.feed(form) p.close() @@ -214,7 +231,9 @@ def __init__(self, defaults, errors=None, use_all_keys=False, auto_error_formatter=None, text_as_default=False, checkbox_checked_if_present=False, encoding=None, prefix_error=True, - force_defaults=True, skip_passwords=False): + force_defaults=True, skip_passwords=False, + data_formencode_form=None, data_formencode_ignore=None, + ): RewritingParser.__init__(self) self.source = None self.lines = None @@ -247,6 +266,8 @@ def __init__(self, defaults, errors=None, use_all_keys=False, self.prefix_error = prefix_error self.force_defaults = force_defaults self.skip_passwords = skip_passwords + self.data_formencode_form = data_formencode_form + self.data_formencode_ignore = data_formencode_ignore def str_compare(self, str1, str2): """ @@ -312,6 +333,15 @@ def add_key(self, key): def handle_starttag(self, tag, attrs, startend=False): self.write_pos() + if self.data_formencode_form: + for a in attrs: + if a[0] == 'data-formencode-form': + if a[1] != self.data_formencode_form: + return + if self.data_formencode_ignore: + for a in attrs: + if a[0] == 'data-formencode-ignore': + return if tag == 'input': self.handle_input(attrs, startend) elif tag == 'textarea': diff --git a/formencode/tests/test_htmlfill_control.py b/formencode/tests/test_htmlfill_control.py new file mode 100644 index 00000000..442a3e97 --- /dev/null +++ b/formencode/tests/test_htmlfill_control.py @@ -0,0 +1,177 @@ +from formencode import htmlfill + + +# ============================================================================== + + +def test_defaults_legacy(): + html = """ + + + +""" + expected_html = """ + + + +""" + rendered_html = htmlfill.render(html, defaults={"foo": "bang"}, + force_defaults=True) + assert expected_html == rendered_html + + +def test_defaults_attr_ignore(): + html = """ + + + + + +""" + expected_html = """ + + + + + +""" + rendered_html = htmlfill.render(html, defaults={"foo": "bang"}, + force_defaults=True, + data_formencode_ignore=True) + assert expected_html == rendered_html + + +def test_defaults_attr_form(): + html = """ + + + +""" + expected_html = """ + + + +""" + rendered_html = htmlfill.render(html, defaults={"foo": "bang"}, + force_defaults=True, + data_formencode_form="b",) + assert expected_html == rendered_html + + +# ============================================================================== + +def test_error_legacy(): + html = """ + + + +""" + expected_html = """ + +bang
+ + + +""" + rendered_html = htmlfill.render(html, errors={"foo": "bang"}, + prefix_error=True) + assert expected_html == rendered_html + + +def test_error_attr_ignore(): + html = """ + + + +""" + expected_html = """ + + +bang
+ + +""" + rendered_html = htmlfill.render(html, errors={"foo": "bang"}, + force_defaults=True, + data_formencode_form="b",) + + +def test_error_attr_form(): + html = """ + + + +""" + expected_html = """ + + +bang
+ + +""" + rendered_html = htmlfill.render(html, errors={"foo": "bang"}, + force_defaults=True, + data_formencode_form="b",) + assert expected_html == rendered_html + + +def test_error_attr_form_alt(): + """note that formencode doesn't keep an indent on the replacement + mixes concepts + note a few things: + 1. we expect a leading "" block, because we are ignoring that tag + 1. we expect the leading "" block to not have an initial newline (\n) + """ + html = """ +
+ + +
+
+ + +
+
+ + + +
+""" + expected_html = """ +orange
+ +
+ + +
+
+ + +
+
+ + +bang
+ + +
+""" + rendered_html = htmlfill.render(html, defaults={"bar": "bang"}, + errors={"foo": "bang", "apple": "orange"}, + force_defaults=True, + data_formencode_form="c", + data_formencode_ignore=True, + ) + assert expected_html == rendered_html + + +if __name__ == '__main__': + test_defaults_legacy() + test_defaults_attr_ignore() + test_defaults_attr_form() + + test_error_legacy() + test_error_attr_ignore() + test_error_attr_form() + test_error_attr_form_alt() + \ No newline at end of file From 40c71314055621c02a5b9e8953ac2b233853efa7 Mon Sep 17 00:00:00 2001 From: Felix Wolfsteller Date: Thu, 9 Jul 2015 11:32:50 +0200 Subject: [PATCH 009/123] Fix typo in docs - although pirates are appropriate. HARR. --- docs/Validator.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Validator.txt b/docs/Validator.txt index 91e68deb..e40edf8e 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -510,7 +510,7 @@ Localization of Error Messages (i18n) When a failed validation occurs FormEncode tries to output the error message in the appropriate language. For this it uses the standard `gettext `_ mechanism of -python. To translate the message in the appropirate message FormEncode +python. To translate the message in the appropriate message FormEncode has to find a gettext function that translates the string. The language to be translated into and the used domain is determined by the found gettext function. To serve a standard translation mechanism From a19174cdd9860324c2d18b69e2ffead6e9a71f66 Mon Sep 17 00:00:00 2001 From: Bo Du Date: Mon, 13 Jul 2015 15:36:11 -0400 Subject: [PATCH 010/123] Appropriately handle zip codes that are longer than the specified partition lengths. Throw an error if a longer zip code is submitted instead of just taking the first few digits and calling it a day. --- formencode/national.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/national.py b/formencode/national.py index a8514ab3..205e57f6 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -150,7 +150,7 @@ def assembly_formatstring(self, partition_lengths, delimiter): def assembly_regex(self, partition_lengths, delimiter): mg = [r'(\d{%d})' % l for l in partition_lengths] rd = r'\%s?' % delimiter - return rd.join(mg) + return "^" + rd.join(mg) + "$" def __init__(self, partition_lengths, delimiter=None, *args, **kw): From 8bb6cbbc7e0838732b851e8cecb274d1f1b03fa9 Mon Sep 17 00:00:00 2001 From: Bo Du Date: Mon, 13 Jul 2015 16:45:23 -0400 Subject: [PATCH 011/123] Add a flag "strict" for stricter enforcement of zip code length. --- formencode/national.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/formencode/national.py b/formencode/national.py index 205e57f6..e78a5f30 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -147,19 +147,26 @@ def assembly_formatstring(self, partition_lengths, delimiter): else: return delimiter.join('n' * l for l in partition_lengths) - def assembly_regex(self, partition_lengths, delimiter): + def assembly_regex(self, partition_lengths, delimiter, strict): mg = [r'(\d{%d})' % l for l in partition_lengths] rd = r'\%s?' % delimiter - return "^" + rd.join(mg) + "$" + regex = rd.join(mg) - def __init__(self, partition_lengths, delimiter=None, + # anchor the regex so that it wont take zip codes w/ longer + # input than what is specified by the partition_lengths + if strict: + regex = "^" + regex + "$" + + return regex + + def __init__(self, partition_lengths, delimiter=None, strict=False, *args, **kw): if isinstance(partition_lengths, (int, long)): partition_lengths = [partition_lengths] if not delimiter: delimiter = '' self.format = self.assembly_formatstring(partition_lengths, delimiter) - self.regex = self.assembly_regex(partition_lengths, delimiter) + self.regex = self.assembly_regex(partition_lengths, delimiter, strict) self.partition_lengths, self.delimiter = partition_lengths, delimiter Regex.__init__(self, *args, **kw) From eda3aac0b745485b015b17a2ac0c1da3058f5d71 Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Wed, 9 Sep 2015 09:44:06 -0400 Subject: [PATCH 012/123] Create LICENSE.txt Refs #91and #93 --- LICENSE.txt | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..e788899e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,194 @@ +Python License, Version 2 (Python-2.0) + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python +alone or in any derivative version, provided, however, that PSF's +License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights +Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + +CNRI OPEN SOURCE LICENSE AGREEMENT (for Python 1.6b1) +-------------------------------------------------- + +IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY. + +BY CLICKING ON "ACCEPT" WHERE INDICATED BELOW, OR BY COPYING, +INSTALLING OR OTHERWISE USING PYTHON 1.6, beta 1 SOFTWARE, YOU ARE +DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS LICENSE +AGREEMENT. + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6, beta 1 +software in source or binary form and its associated documentation, +as released at the www.python.org Internet site on August 4, 2000 +("Python 1.6b1"). + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a non-exclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display +publicly, prepare derivative works, distribute, and otherwise use +Python 1.6b1 alone or in any derivative version, provided, however, +that CNRIs License Agreement is retained in Python 1.6b1, alone or +in any derivative version prepared by Licensee. + +Alternately, in lieu of CNRIs License Agreement, Licensee may +substitute the following text (omitting the quotes): "Python 1.6, +beta 1, is made available subject to the terms and conditions in +CNRIs License Agreement. This Agreement may be located on the +Internet using the following unique, persistent identifier (known +as a handle): 1895.22/1011. This Agreement may also be obtained +from a proxy server on the Internet using the +URL:http://hdl.handle.net/1895.22/1011". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6b1 or any part thereof, and wants to make +the derivative work available to the public as provided herein, +then Licensee hereby agrees to indicate in any such work the nature +of the modifications made to Python 1.6b1. + +4. CNRI is making Python 1.6b1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR +FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6b1 +WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR +LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING PYTHON 1.6b1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY +THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of Virginia, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed +to create any relationship of agency, partnership, or joint venture +between CNRI and Licensee. This License Agreement does not grant +permission to use CNRI trademarks or trade name in a trademark +sense to endorse or promote products or services of Licensee, or +any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6b1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + +ACCEPT + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. From 852459d0e418ad4b21753565b6a958831c5224de Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 12:24:06 +0200 Subject: [PATCH 013/123] Ignore .eggs directory with packages for testing. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 841a9115..80a9068a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.pyo *.tox +.eggs build dist/ docs/_build From 0146761a7a9ee9ea524f52a5d572095638a5ac58 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 12:24:21 +0200 Subject: [PATCH 014/123] PEP8 nitpicking. --- formencode/variabledecode.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/formencode/variabledecode.py b/formencode/variabledecode.py index f2461cee..909f72ef 100644 --- a/formencode/variabledecode.py +++ b/formencode/variabledecode.py @@ -26,9 +26,7 @@ def variable_decode(d, dict_char='.', list_char='-'): - """ - Decode the flat dictionary d into a nested structure. - """ + """Decode the flat dictionary d into a nested structure.""" result = {} dicts_to_sort = set() known_lengths = {} @@ -93,9 +91,9 @@ def variable_decode(d, dict_char='.', list_char='-'): last_key = sub_key to_sort = to_sort[sub_key] if None in to_sort: - noneVals = [(0, x) for x in to_sort.pop(None)] - noneVals.extend(to_sort.iteritems()) - to_sort = noneVals + none_values = [(0, x) for x in to_sort.pop(None)] + none_values.extend(to_sort.iteritems()) + to_sort = none_values else: to_sort = to_sort.iteritems() to_sort = [x[1] for x in sorted(to_sort)] @@ -109,9 +107,7 @@ def variable_decode(d, dict_char='.', list_char='-'): def variable_encode(d, prepend='', result=None, add_repetitions=True, dict_char='.', list_char='-'): - """ - Encode a nested structure into a flat dictionary. - """ + """Encode a nested structure into a flat dictionary.""" if result is None: result = {} if isinstance(d, dict): @@ -129,9 +125,9 @@ def variable_encode(d, prepend='', result=None, add_repetitions=True, variable_encode(value, "%s%s%i" % (prepend, list_char, i), result, add_repetitions, dict_char=dict_char, list_char=list_char) if add_repetitions: - repName = ('%s--repetitions' % prepend + rep_name = ('%s--repetitions' % prepend if prepend else '__repetitions__') - result[repName] = str(len(d)) + result[rep_name] = str(len(d)) else: result[prepend] = d return result From 7b25c583c50e1e6974c3d6343dfc1894d2efea4e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 12:34:11 +0200 Subject: [PATCH 015/123] Fix #92 - sorting behavior across Python 2 and 3. --- formencode/tests/test_variabledecode.py | 6 ++++++ formencode/variabledecode.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/formencode/tests/test_variabledecode.py b/formencode/tests/test_variabledecode.py index a281ddf9..6fe53f58 100644 --- a/formencode/tests/test_variabledecode.py +++ b/formencode/tests/test_variabledecode.py @@ -47,6 +47,12 @@ def test_dict_list_dict(self): self.assertEqual(expect, variable_decode(src)) + def test_list_dict_bad_key(self): + src = {'a-0.good': 'a', 'a.0.bad': 'b', 'a-1.good': 'c'} + expect = {'a': [{'good': 'a'}, {'good': 'c'}, {'bad': 'b'}]} + + self.assertEqual(expect, variable_decode(src)) + class TestVariableEncode(unittest.TestCase): diff --git a/formencode/variabledecode.py b/formencode/variabledecode.py index 909f72ef..da35535c 100644 --- a/formencode/variabledecode.py +++ b/formencode/variabledecode.py @@ -25,6 +25,16 @@ __all__ = ['variable_decode', 'variable_encode', 'NestedVariables'] +def _int_key(key): + """Robust sort key for numbers that sorts invalid keys last. + + This is used to make sorting behave the same across Python 2 and 3. + """ + if isinstance(key, int): + return 0, key + return 1, 0 + + def variable_decode(d, dict_char='.', list_char='-'): """Decode the flat dictionary d into a nested structure.""" result = {} @@ -96,7 +106,7 @@ def variable_decode(d, dict_char='.', list_char='-'): to_sort = none_values else: to_sort = to_sort.iteritems() - to_sort = [x[1] for x in sorted(to_sort)] + to_sort = [x[1] for x in sorted(to_sort, key=_int_key)] if key in known_lengths: if len(to_sort) < known_lengths[key]: to_sort.extend([''] * (known_lengths[key] - len(to_sort))) From 4cb6d0e8ba37afe29b65cb415e782d8081063cdf Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 13:15:39 +0200 Subject: [PATCH 016/123] Fix sort key - only first component is relevant. --- formencode/variabledecode.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/formencode/variabledecode.py b/formencode/variabledecode.py index da35535c..41c285b3 100644 --- a/formencode/variabledecode.py +++ b/formencode/variabledecode.py @@ -25,14 +25,13 @@ __all__ = ['variable_decode', 'variable_encode', 'NestedVariables'] -def _int_key(key): - """Robust sort key for numbers that sorts invalid keys last. +def _sort_key(item): + """Robust sort key that sorts items with invalid keys last. This is used to make sorting behave the same across Python 2 and 3. """ - if isinstance(key, int): - return 0, key - return 1, 0 + key = item[0] + return not isinstance(key, int), key def variable_decode(d, dict_char='.', list_char='-'): @@ -106,7 +105,7 @@ def variable_decode(d, dict_char='.', list_char='-'): to_sort = none_values else: to_sort = to_sort.iteritems() - to_sort = [x[1] for x in sorted(to_sort, key=_int_key)] + to_sort = [x[1] for x in sorted(to_sort, key=_sort_key)] if key in known_lengths: if len(to_sort) < known_lengths[key]: to_sort.extend([''] * (known_lengths[key] - len(to_sort))) From 74ebc5cf2ba0540ccd31965f6bbaa93379c7f522 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 16:44:31 +0200 Subject: [PATCH 017/123] Account for incompatible API change in pycountry 1.12 --- formencode/national.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/formencode/national.py b/formencode/national.py index e78a5f30..fe7a422e 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -92,12 +92,26 @@ def get_countries(): def get_country(code): return _c(pycountry.countries.get(alpha2=code).name) - def get_languages(): - return [(e.alpha2, _l(e.name)) for e in pycountry.languages - if e.name and getattr(e, 'alpha2', None)] + try: + if not pycountry.languages.get(iso639_1_code='en'): + raise KeyError + except KeyError: # pycountry < 1.12 - def get_language(code): - return _l(pycountry.languages.get(alpha2=code).name) + def get_languages(): + return [(e.alpha2, _l(e.name)) for e in pycountry.languages + if e.name and getattr(e, 'alpha2', None)] + + def get_language(code): + return _l(pycountry.languages.get(alpha2=code).name) + + else: + + def get_languages(): + return [(e.iso639_1_code, _l(e.name)) for e in pycountry.languages + if e.name and getattr(e, 'iso639_1_code', None)] + + def get_language(code): + return _l(pycountry.languages.get(iso639_1_code=code).name) ############################################################ From fd53da73e6341db8df66772a7a9aa84ac7f466a1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 17:07:22 +0200 Subject: [PATCH 018/123] Avoid deprecation warning in Python 3.4 and newer. --- formencode/rewritingparser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/formencode/rewritingparser.py b/formencode/rewritingparser.py index 9d1daefd..96c594bb 100644 --- a/formencode/rewritingparser.py +++ b/formencode/rewritingparser.py @@ -31,7 +31,10 @@ class RewritingParser(HTMLParser.HTMLParser): def __init__(self): self._content = [] - HTMLParser.HTMLParser.__init__(self) + try: + HTMLParser.HTMLParser.__init__(self, convert_charrefs=False) + except TypeError: # Python < 3.4 + HTMLParser.HTMLParser.__init__(self) def feed(self, data): self.data_is_str = isinstance(data, str) From a178c733bdf24d644391ce537368320d5307a31c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 17:40:34 +0200 Subject: [PATCH 019/123] Avoid expected warnings in test output. --- formencode/tests/test_subclassing_old.py | 102 ++++++++++++----------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/formencode/tests/test_subclassing_old.py b/formencode/tests/test_subclassing_old.py index c34a87f0..77c5efe7 100644 --- a/formencode/tests/test_subclassing_old.py +++ b/formencode/tests/test_subclassing_old.py @@ -167,48 +167,51 @@ def test_is_validator(self): def test_to_python(self): nov = DeprecatedNotOneValidator() - self.assertEqual(nov.to_python('0'), 0) - try: - nov.to_python('1') - except Invalid as e: - self.assertTrue( - 'must not be 1' in str(e), e) - else: - self.fail("1 should be invalid") - self.assertEqual(nov.to_python('2'), 2) - self.assertEqual(nov.to_python('42'), 42) + with warnings.catch_warnings(record=True): + self.assertEqual(nov.to_python('0'), 0) + try: + nov.to_python('1') + except Invalid as e: + self.assertTrue( + 'must not be 1' in str(e), e) + else: + self.fail("1 should be invalid") + self.assertEqual(nov.to_python('2'), 2) + self.assertEqual(nov.to_python('42'), 42) def test_to_python_number(self): nov = DeprecatedNotOneValidator(number=42) - self.assertEqual(nov.to_python('0'), 0) - self.assertEqual(nov.to_python('1'), 1) - self.assertEqual(nov.to_python('2'), 2) - try: - nov.to_python('42') - except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) - else: - self.fail("42 should be invalid") + with warnings.catch_warnings(record=True): + self.assertEqual(nov.to_python('0'), 0) + self.assertEqual(nov.to_python('1'), 1) + self.assertEqual(nov.to_python('2'), 2) + try: + nov.to_python('42') + except Invalid as e: + self.assertTrue( + 'must not be 42' in str(e), e) + else: + self.fail("42 should be invalid") def test_to_python_range(self): nov = DeprecatedNotOneValidator(min=40, max=49, number=42) - self.assertRaises(Invalid, nov.to_python, '0') - self.assertRaises(Invalid, nov.to_python, '1') - self.assertRaises(Invalid, nov.to_python, '2') - self.assertRaises(Invalid, nov.to_python, '39') - self.assertEqual(nov.to_python('40'), 40) - self.assertEqual(nov.to_python('41'), 41) - try: - nov.to_python('42') - except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) - else: - self.fail("42 should be invalid") - self.assertEqual(nov.to_python('43'), 43) - self.assertEqual(nov.to_python('49'), 49) - self.assertRaises(Invalid, nov.to_python, '50') + with warnings.catch_warnings(record=True): + self.assertRaises(Invalid, nov.to_python, '0') + self.assertRaises(Invalid, nov.to_python, '1') + self.assertRaises(Invalid, nov.to_python, '2') + self.assertRaises(Invalid, nov.to_python, '39') + self.assertEqual(nov.to_python('40'), 40) + self.assertEqual(nov.to_python('41'), 41) + try: + nov.to_python('42') + except Invalid as e: + self.assertTrue( + 'must not be 42' in str(e), e) + else: + self.fail("42 should be invalid") + self.assertEqual(nov.to_python('43'), 43) + self.assertEqual(nov.to_python('49'), 49) + self.assertRaises(Invalid, nov.to_python, '50') with warnings.catch_warnings(record=True) as custom_compound_warnings: @@ -286,16 +289,17 @@ def test_is_validator(self): def test_to_python(self): cav = self.validator - self.assertRaises(Invalid, cav.to_python, '1') - self.assertRaises(Invalid, cav.to_python, '2') - self.assertEqual(cav.to_python('3'), 3) - try: - cav.to_python('4') - except Invalid as e: - self.assertTrue( - 'must not be 4' in str(e), e) - else: - self.fail("4 should be invalid") - self.assertEqual(cav.to_python('5'), 5) - self.assertRaises(Invalid, cav.to_python, '6') - self.assertRaises(Invalid, cav.to_python, '7') + with warnings.catch_warnings(record=True): + self.assertRaises(Invalid, cav.to_python, '1') + self.assertRaises(Invalid, cav.to_python, '2') + self.assertEqual(cav.to_python('3'), 3) + try: + cav.to_python('4') + except Invalid as e: + self.assertTrue( + 'must not be 4' in str(e), e) + else: + self.fail("4 should be invalid") + self.assertEqual(cav.to_python('5'), 5) + self.assertRaises(Invalid, cav.to_python, '6') + self.assertRaises(Invalid, cav.to_python, '7') From 609902445c6f0129e19babd61877c7e456e635ef Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 18:03:07 +0200 Subject: [PATCH 020/123] Remove unnecessary code. --- formencode/validators.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/formencode/validators.py b/formencode/validators.py index c332ec9b..411887df 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1375,10 +1375,10 @@ def _validate_python(self, value, state): try: try: dns.resolver.query(domain, 'MX') - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e: + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): try: dns.resolver.query(domain, 'A') - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e: + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): raise Invalid( self.message('domainDoesNotExist', state, domain=domain), value, state) @@ -2280,9 +2280,9 @@ def _to_python_tuple(self, value, state): else: second = None if second is None: - return (hour, minute) + return hour, minute else: - return (hour, minute, second) + return hour, minute, second def _convert_from_python(self, value, state): if isinstance(value, basestring): From da6d30830bf8372ec5050ce26edde4379c03d4a9 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 18:34:33 +0200 Subject: [PATCH 021/123] Properly close http connections, avoid warning in Python 3. --- formencode/validators.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/formencode/validators.py b/formencode/validators.py index 411887df..2fc800d9 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1551,18 +1551,18 @@ def _check_url_exists(self, url, state): import socket scheme, netloc, path, params, query, fragment = urlparse.urlparse( url, 'http') - if scheme == 'https': - ConnClass = httplib.HTTPSConnection - else: - ConnClass = httplib.HTTPConnection + if params: + path += ';' + params + if query: + path += '?' + query try: - conn = ConnClass(netloc) - if params: - path += ';' + params - if query: - path += '?' + query - conn.request('HEAD', path) - res = conn.getresponse() + conn = (httplib.HTTPSConnection if scheme == 'https' + else httplib.HTTPConnection)(netloc) + try: + conn.request('HEAD', path) + res = conn.getresponse() + finally: + conn.close() except httplib.HTTPException as e: raise Invalid( self.message('httpError', state, error=e), state, url) From e32051d899ba57f01812b2d944247b51180af57b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 22:11:09 +0200 Subject: [PATCH 022/123] Make doctest pass with Python 2.6 --- formencode/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/validators.py b/formencode/validators.py index 2fc800d9..a93fa029 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -2727,7 +2727,7 @@ class RequireIfMatching(FormValidator): >>> v.to_python(dict(phone_type='mobile')) Traceback (most recent call last): ... - formencode.api.Invalid: You must give a value for mobile + Invalid: You must give a value for mobile >>> v.to_python(dict(phone_type='someothervalue')) {'phone_type': 'someothervalue'} """ From fb0b22ac1bd5d92de727d8e461324ea6076e1163 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 23:28:44 +0200 Subject: [PATCH 023/123] Simplify tox configuration and add Python 3.5. --- .gitignore | 12 +++++++----- tox.ini | 31 ++++++++++--------------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 80a9068a..0e6e090a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ *.egg *.egg-info +.eggs + *.pyc *.pyo -*.tox -.eggs +.tox + +.coverage +coverage/ + build dist/ docs/_build @@ -14,6 +19,3 @@ docs/_build .project .pydevproject .settings - -.coverage -coverage/ diff --git a/tox.ini b/tox.ini index db74de4a..f6b2237b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,24 +1,13 @@ [tox] -envlist=py26,py27,pypy,py33,py34 +envlist=py26,py27,pypy,py33,py34,py35 [testenv] -deps=nose - dnspython - pycountry -commands=nosetests - -[testenv:py33] -deps=nose - dnspython3 - pycountry -# Need to run nosetests in a different directory so it picks up 2to3-translated -# files and not the untranslated files in the source directory -commands=nosetests -w /tmp formencode - -[testenv:py34] -deps=nose - dnspython3 - pycountry -# Need to run nosetests in a different directory so it picks up 2to3-translated -# files and not the untranslated files in the source directory -commands=nosetests -w /tmp formencode +deps= + nose + wheel + pycountry + py{26,27},pypy: dnspython + py{33,34,35}: dnspython3 +commands= + python setup.py clean --all + python setup.py nosetests From 2f759a12db99198323f52e3fc21e7136f9fcf455 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 23:30:08 +0200 Subject: [PATCH 024/123] Handle non-ascii socket error messages --- formencode/validators.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/formencode/validators.py b/formencode/validators.py index a93fa029..164b372f 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1564,9 +1564,27 @@ def _check_url_exists(self, url, state): finally: conn.close() except httplib.HTTPException as e: + e = str(e) + if str is not unicode: # Python 2 + try: + e = e.decode('utf-8') + except UnicodeDecodeError: + try: + e = e.decode('latin-1') + except UnicodeDecodeError: + e = e.decode('ascii', 'replace') raise Invalid( self.message('httpError', state, error=e), state, url) except socket.error as e: + e = str(e) + if str is not unicode: # Python 2 + try: + e = e.decode('utf-8') + except UnicodeDecodeError: + try: + e = e.decode('latin-1') + except UnicodeDecodeError: + e = e.decode('ascii', 'replace') raise Invalid( self.message('socketError', state, error=e), state, url) else: From 25185503f39d65c358652006d69c62afd6e6a9ef Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 23:37:31 +0200 Subject: [PATCH 025/123] Add note about testing (see issue #82). --- README.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 335b4ef3..7c7a764c 100644 --- a/README.rst +++ b/README.rst @@ -20,11 +20,19 @@ Documentation The latest documentation is available at http://www.formencode.org/ + +Testing +------- + +Use `python setup.py nosetests` to run the test suite. +Use `tox` to run the test suite for all supported Python versions. + + Changes ------- Added a validator that can require one or more fields based on the value of another field. -A german howto can be found here: http://techblog.auf-nach-mallorca.info/2014/08/19/dynamische_formulare_validieren_mit_formencode/ +A German howto can be found here: http://techblog.auf-nach-mallorca.info/2014/08/19/dynamische_formulare_validieren_mit_formencode/ Courtesy of the developers of http://www.auf-nach-mallorca.info From 85be89a89c3f7a17a04e4b87cb593f6e8354fbed Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 23:42:19 +0200 Subject: [PATCH 026/123] Add Python 3.5 to Travis configuration, use nose to run all tests. --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7cbe05a1..c8cc5753 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,12 @@ python: - 2.7 - 3.2 - 3.3 + - 3.4 + - 3.5 - pypy -install: python setup.py install +install: + - pip install . -script: python setup.py test +script: python setup.py nosetests From 135208dab74df8f382ac2250c95b6d5b16bf5d85 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 23:51:26 +0200 Subject: [PATCH 027/123] Travis does not yet support the new Python 3.5 release. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8cc5753..37c39c3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,10 @@ python: - 3.2 - 3.3 - 3.4 - - 3.5 + - 3.5-dev - pypy -install: - - pip install . +install: pip install . script: python setup.py nosetests From f840ed8c9b5926e68491f4ff710cc26d8c9f3ee0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 13 Sep 2015 23:56:44 +0200 Subject: [PATCH 028/123] Travis does not support Python 3.5-dev with nose. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 37c39c3a..1884f4d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ python: - 3.2 - 3.3 - 3.4 - - 3.5-dev - pypy install: pip install . From 7e6d491d6208fac68008d57c5e0e3d4dbb680f45 Mon Sep 17 00:00:00 2001 From: Elmer de Looff Date: Wed, 4 Nov 2015 16:47:01 +0100 Subject: [PATCH 029/123] Correct spacing on _most_ UK postal codes. --- formencode/national.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/formencode/national.py b/formencode/national.py index fe7a422e..1cc1fdf0 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -316,9 +316,9 @@ class UKPostalCode(Regex): """ regex = re.compile(r'^((ASCN|BBND|BIQQ|FIQQ|PCRN|SIQQ|STHL|TDCU|TKCA)' - ' 1ZZ|BFPO (c\/o )?[1-9]{1,4}|GIR 0AA|[A-PR-UWYZ]' + '\s?1ZZ|BFPO (c\/o )?[1-9]{1,4}|GIR\s?0AA|[A-PR-UWYZ]' '([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))' - '|[0-9][A-HJKS-UW]) [0-9][ABD-HJLNP-UW-Z]{2})$', re.I) + '|[0-9][A-HJKS-UW])\s?[0-9][ABD-HJLNP-UW-Z]{2})$', re.I) strip = True messages = dict( @@ -331,7 +331,11 @@ def _convert_to_python(self, value, state): raise Invalid( self.message('invalid', state), value, state) - return match.group(1).upper() + value = match.group(1).upper() + if not value.startswith('BFPO'): + value = value.replace(' ', '') + value = '%s %s' % (value[:-3], value[-3:]) + return value class CountryValidator(FancyValidator): From 306b6a1a34ea64fd16637548100ca37620fe14fa Mon Sep 17 00:00:00 2001 From: Elmer de Looff Date: Wed, 4 Nov 2015 16:59:28 +0100 Subject: [PATCH 030/123] Added validator for Dutch postal codes. --- formencode/national.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/formencode/national.py b/formencode/national.py index 1cc1fdf0..ca474796 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -297,6 +297,39 @@ def _convert_to_python(self, value, state): return '%s %s' % (match.group(1).upper(), match.group(2).upper()) +class DutchPostalCode(Regex): + """ + Dutch Postal codes. + + :: + + >>> DutchPostalCode.to_python('1011 PN') + '1011PN' + >>> DutchPostalCode.to_python('3400ac') + '3400AC' + >>> DutchPostalCode.to_python('12345') + Traceback (most recent call last): + ... + Invalid: Please enter a zip code (nnnnLL) + """ + + format = _('nnnnLL') + regex = re.compile(r'^(\d{4})\s?([a-zA-Z]{2})$') + strip = True + + messages = dict( + invalid=_('Please enter a zip code (%(format)s)')) + + def _convert_to_python(self, value, state): + self.assert_string(value, state) + match = self.regex.search(value) + if not match: + raise Invalid( + self.message('invalid', state, format=self.format), + value, state) + return '%s%s' % (match.group(1), match.group(2).upper()) + + class UKPostalCode(Regex): """ UK Postal codes. Please see BS 7666. @@ -454,6 +487,7 @@ class PostalCodeInCountryFormat(FancyValidator): 'LU': FourDigitsPostalCode, 'MC': lambda: DelimitedDigitsPostalCode(5), 'NI': lambda: DelimitedDigitsPostalCode([3, 3, 1], '-'), + 'NL': DutchPostalCode, 'NO': FourDigitsPostalCode, 'PL': PolishPostalCode, 'PT': lambda: DelimitedDigitsPostalCode([4, 3], '-'), From 8142cafaa92bfaf8de2ff190c9423abd009b94e3 Mon Sep 17 00:00:00 2001 From: Satoshi KOBAYASHI Date: Wed, 11 Nov 2015 18:26:51 +0900 Subject: [PATCH 031/123] can't build universal wheel Present setup.cfg is marked universal flag in wheel section. Therefore, setup.py generates universal (= python2/3 compatible) wheel. $ python --version Python 2.7.10 $ python setup.py bdist_wheel $ ls dist FormEncode-1.3.0-py2.py3-none-any.whl However, this package can't work in python3. It throws an exception when imported. $ python --version Python 3.5.0 $ pip install dist/FormEncode-1.3.0-py2.py3-none-any.whl Processing ./dist/FormEncode-1.3.0-py2.py3-none-any.whl Installing collected packages: FormEncode Successfully installed FormEncode-1.3.0 $ (cd; python -c "import formencode") Traceback (most recent call last): File "", line 1, in File "/Users/satoshi-k/.virtualenvs/py35/lib/python3.5/site-packages/formencode/__init__.py", line 3, in from formencode.api import ( File "/Users/satoshi-k/.virtualenvs/py35/lib/python3.5/site-packages/formencode/api.py", line 109, in class Invalid(Exception): File "/Users/satoshi-k/.virtualenvs/py35/lib/python3.5/site-packages/formencode/api.py", line 153, in Invalid if unicode is not str: # Python 2 NameError: name 'unicode' is not defined It is because 2to3 is not applied when building. But this issue is not resolved even if 2to3 is applied. An applied package can't work in python2. The setting and behavior don't match at present. So we turn off universal flag in setup.cfg. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 25332a75..8eae3a33 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,5 +26,5 @@ output_dir = formencode/i18n previous = true [wheel] -universal = 1 +universal = 0 From eca27f6815e7fad9f970a5a8635c4b3bbf8d061a Mon Sep 17 00:00:00 2001 From: systemctl Date: Wed, 23 Sep 2015 14:49:43 +0200 Subject: [PATCH 032/123] Added Serbian latin translation --- .../i18n/sr@latin/LC_MESSAGES/FormEncode.mo | Bin 0 -> 10876 bytes .../i18n/sr@latin/LC_MESSAGES/FormEncode.po | 543 ++++++++++++++++++ 2 files changed, 543 insertions(+) create mode 100644 formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo create mode 100644 formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo b/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo new file mode 100644 index 0000000000000000000000000000000000000000..a5cbb6e8464164078c31a0e3ff7ee3a522e75cc8 GIT binary patch literal 10876 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, 2012. +# Updated by Miloš Gavrilov, 2015 +# +msgid "" +msgstr "" +"Project-Id-Version: FormEncode 1.2.4\n" +"Report-Msgid-Bugs-To: formencode-discuss@lists.sf.net\n" +"POT-Creation-Date: 2012-09-14 03:22+0400\n" +"PO-Revision-Date: 2013-05-14 16:11+0100\n" +"Last-Translator: Miloš Gavrilov\n" +"Language-Team: Andrija Zaric \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#: formencode/api.py:401 +#: formencode/validators.py:432 +msgid "Please enter a value" +msgstr "Molim, unesite vrednost" + +#: formencode/api.py:402 +#, python-format +msgid "The input must be a string (not a %(type)s: %(value)r)" +msgstr "Ulaz mora biti string (ne %(type)s: %(value)r)" + +#: formencode/api.py:403 +msgid "The input must be a string (not None)" +msgstr "Ulaz mora biti string (ne None)" + +#: formencode/national.py:31 +msgid "Belarus" +msgstr "" + +#: formencode/national.py:32 +msgid "Montenegro" +msgstr "" + +#: formencode/national.py:33 +msgid "Tasmania" +msgstr "" + +#: formencode/national.py:39 +msgid "Britain" +msgstr "" + +#: formencode/national.py:40 +msgid "Great Britain" +msgstr "" + +#: formencode/national.py:41 +msgid "Cote de Ivoire" +msgstr "" + +#: formencode/national.py:146 +#, python-format +msgid "%d digits" +msgstr "" + +#: formencode/national.py:167 +#, python-format +msgid "Please enter a zip code (%(format)s)" +msgstr "" + +#: formencode/national.py:231 +#: formencode/national.py:265 +#, python-format +msgid "Please enter a zip code (%s)" +msgstr "Molim, unesite poštanski broj (%s)" + +#: formencode/national.py:231 +msgid "LnnnnLLL" +msgstr "" + +#: formencode/national.py:265 +msgid "LnL nLn" +msgstr "" + +#: formencode/national.py:299 +msgid "Please enter a valid postal code (for format see BS 7666)" +msgstr "" + +#: formencode/national.py:340 +msgid "That country is not listed in ISO 3166" +msgstr "" + +#: formencode/national.py:395 +msgid "Given postal code does not match the country's format." +msgstr "" + +#: formencode/national.py:486 +msgid "Please enter a state code" +msgstr "" + +#: formencode/national.py:487 +msgid "Please enter a state code with TWO letters" +msgstr "" + +#: formencode/national.py:488 +msgid "That is not a valid state code" +msgstr "To nije ispravan kod države" + +#: formencode/national.py:541 +msgid "Please enter a number, with area code, in the form ###-###-####, optionally with \"ext.####\"" +msgstr "" + +#: formencode/national.py:648 +msgid "Please enter a number, with area code, in the form +##-###-#######." +msgstr "" + +#: formencode/national.py:727 +msgid "That language is not listed in ISO 639" +msgstr "Jezik nije naveden u ISO 639" + +#: formencode/schema.py:68 +#, python-format +msgid "The input field %(name)s was not expected." +msgstr "Polje ulaza %(name)s nije očekivano." + +#: formencode/schema.py:69 +msgid "Missing value" +msgstr "Nedostaje vrednost" + +#: formencode/schema.py:70 +#, python-format +msgid "The input must be dict-like (not a %(type)s: %(value)r)" +msgstr "Unos mora biti sličan rečniku (ne %(type)s: %(value)r)" + +#: formencode/schema.py:72 +msgid "Please provide only one value" +msgstr "Molim, prosledite samo jednu vrednost" + +#: formencode/validators.py:150 +#, python-format +msgid "%(object)r is not a subclass of %(subclass)s" +msgstr "%(object)r nije potklasa %(subclass)s" + +#: formencode/validators.py:151 +#, python-format +msgid "%(object)r is not a subclass of one of the types %(subclassList)s" +msgstr "%(object)r nije potklasa jedne od tipova %(subclassList)s" + +#: formencode/validators.py:152 +#, python-format +msgid "%(object)r must be one of the types %(typeList)s" +msgstr "%(object)r mora biti jedan od tipova %(typeList)s" + +#: formencode/validators.py:153 +#, python-format +msgid "%(object)r must be of the type %(type)s" +msgstr "%(object)r mora biti tipa %(type)s" + +#: formencode/validators.py:344 +#, python-format +msgid "Enter a value less than %(maxLength)i characters long" +msgstr "Unesite vrednost, kraću od %(maxLength)i znakova" + +#: formencode/validators.py:345 +#: formencode/validators.py:399 +msgid "Invalid value (value with length expected)" +msgstr "Nevaljan unos (očekivana je vrednost sa dužinom)" + +#: formencode/validators.py:398 +#, python-format +msgid "Enter a value at least %(minLength)i characters long" +msgstr "Unesite vrednost, dužine najmanje %(minLength)i znakova" + +#: formencode/validators.py:455 +msgid "You cannot enter a value here" +msgstr "Ovde ne možete uneti vrednost" + +#: formencode/validators.py:504 +msgid "The input is not valid" +msgstr "Unos nije valjan" + +#: formencode/validators.py:557 +msgid "Enter only letters, numbers, or _ (underscore)" +msgstr "Unesite samo slova, cifre ili _ (podvlaku)" + +#: formencode/validators.py:595 +msgid "Invalid value" +msgstr "Nevaljana vrednost" + +#: formencode/validators.py:596 +#, python-format +msgid "Value must be one of: %(items)s (not %(value)r)" +msgstr "Vrednost mora biti jedna od: %(items)s (ne %(value)r)" + +#: formencode/validators.py:657 +msgid "Choose something" +msgstr "Izaberite nešto" + +#: formencode/validators.py:658 +#, python-format +msgid "Enter a value from: %(items)s" +msgstr "Unesite jednu od vrednosti: %(items)s" + +#: formencode/validators.py:659 +msgid "That value is not known" +msgstr "Nije poznata ta vrednost" + +#: formencode/validators.py:660 +#, python-format +msgid "Nothing in my dictionary goes by the value %(value)s. Choose one of: %(items)s" +msgstr "Rečnik ne sadrži %(value)s. Izaberite jednu od: %(items)s" + +#: formencode/validators.py:724 +msgid "Must be an integer index" +msgstr "Indeks mora biti celobrojan" + +#: formencode/validators.py:725 +msgid "Index out of range" +msgstr "Indeks je izvan opsega" + +#: formencode/validators.py:726 +#, python-format +msgid "Item %(value)s was not found in the list" +msgstr "Element %(value)s nije nađen u listi" + +#: formencode/validators.py:796 +#, python-format +msgid "Date must be after %(date)s" +msgstr "Datum mora biti kasnije od %(date)s" + +#: formencode/validators.py:797 +#, python-format +msgid "Date must be before %(date)s" +msgstr "Datum mora prethoditi %(date)s" + +#: formencode/validators.py:799 +#, python-format +msgid "%%A, %%d %%B %%Y" +msgstr "%%A, %%d %%B %%Y" + +#: formencode/validators.py:800 +msgid "The date must be sometime in the future" +msgstr "Datum mora biti budući" + +#: formencode/validators.py:901 +#, python-format +msgid "Please enter a number that is %(min)s or greater" +msgstr "Molim, unesite broj koji je %(min)s ili veći" + +#: formencode/validators.py:902 +#, python-format +msgid "Please enter a number that is %(max)s or smaller" +msgstr "Molim, unesite broj koji je %(max)s ili manji" + +#: formencode/validators.py:939 +msgid "Please enter an integer value" +msgstr "Molim, unesite celobrojnu vrednost" + +#: formencode/validators.py:975 +msgid "Please enter a number" +msgstr "Molim, unesite broj" + +#: formencode/validators.py:1036 +#, python-format +msgid "Enter a value not more than %(max)i characters long" +msgstr "Unesite vrednost dugu najviše %(max)i znakova" + +#: formencode/validators.py:1037 +#, python-format +msgid "Enter a value %(min)i characters long or more" +msgstr "Unesite vrednost dugu najmanje %(min)i znakova" + +#: formencode/validators.py:1119 +msgid "Invalid data or incorrect encoding" +msgstr "Nevaljani podaci ili nepravilno kodiranje" + +#: formencode/validators.py:1290 +msgid "Please enter an email address" +msgstr "Molim, unesite adresu e-pošte" + +#: formencode/validators.py:1291 +msgid "An email address must contain a single @" +msgstr "Adresa e-pošte mora sadržati jedan @" + +#: formencode/validators.py:1292 +#, python-format +msgid "The username portion of the email address is invalid (the portion before the @: %(username)s)" +msgstr "Korisničko ime u adresi e-pošte nije valjano (deo pre @): %(username)s)" + +#: formencode/validators.py:1294 +#: formencode/validators.py:1457 +#, python-format +msgid "An error occured when trying to connect to the server: %(error)s" +msgstr "Pojavila se greška prilikom povezivanja na server: %(error)s" + +#: formencode/validators.py:1296 +#, python-format +msgid "The domain portion of the email address is invalid (the portion after the @: %(domain)s)" +msgstr "Domen u adresi e-pošte nije valjan (deo posle @): %(domain)s" + +#: formencode/validators.py:1298 +#, python-format +msgid "The domain of the email address does not exist (the portion after the @: %(domain)s)" +msgstr "Domen adrese e-pošte ne postoji (deo posle @): %(domain)s" + +#: formencode/validators.py:1453 +msgid "You must start your URL with http://, https://, etc" +msgstr "URL mora početi sa http://, https://, itd" + +#: formencode/validators.py:1454 +msgid "That is not a valid URL" +msgstr "Ovo nije valjan URL" + +#: formencode/validators.py:1455 +#, python-format +msgid "An error occurred when trying to access the URL: %(error)s" +msgstr "Pojavila se greška prilikom dosezanja URL-a: %(error)s" + +#: formencode/validators.py:1459 +msgid "The server responded that the page could not be found" +msgstr "Server je odgovorio da strana nije pronađena" + +#: formencode/validators.py:1460 +#, python-format +msgid "The server responded with a bad status code (%(status)s)" +msgstr "Server je odgovorio sa kodom lošeg statusa (%(status)s)" + +#: formencode/validators.py:1461 +#, python-format +msgid "You must provide a full domain name (like %(domain)s.com)" +msgstr "Morate proslediti puno ime domena (kao %(domain)s.com)" + +#: formencode/validators.py:1602 +msgid "The type of i-name is not defined; it may be either individual or organizational" +msgstr "Tip i-name nije definisan" + +#: formencode/validators.py:1604 +msgid "Dots and dashes may not be repeated consecutively" +msgstr "Tačke i crtice se ne smeju neprekidno ponavljati" + +#: formencode/validators.py:1605 +#, python-format +msgid "\"%(iname)s\" is an invalid i-name" +msgstr "\"%(iname)s\" nije valjano i-name" + +#: formencode/validators.py:1606 +msgid "i-names may not start with numbers nor punctuation marks" +msgstr "i-names ne mogu počinjati brojevima i znakovima interpunkcije" + +#: formencode/validators.py:1608 +#, python-format +msgid "\"%(inumber)s\" is an invalid i-number" +msgstr "\"%(inumber)s\" nije valjan i-number" + +#: formencode/validators.py:1609 +#, python-format +msgid "The XRI must be a string (not a %(type)s: %(value)r)" +msgstr "XRI mora biti string (ne %(type)s: %(value)r)" + +#: formencode/validators.py:1610 +#, python-format +msgid "\"%(xri_type)s\" is not a valid type of XRI" +msgstr "\"%(xri_type)s\" nije valjan XRI tip" + +#: formencode/validators.py:1709 +#, python-format +msgid "\"%(id)s\" is not a valid OpenId (it is neither an URL nor an XRI)" +msgstr "\"%(id)s\" nije valjan OpenId (nije ni URL ni XRI )" + +#: formencode/validators.py:1941 +#: formencode/validators.py:1949 +#, python-format +msgid "Please enter the date in the form %(format)s" +msgstr "Molim, unesite datum u obliku %(format)s" + +#: formencode/validators.py:1942 +msgid "Please enter a month from 1 to 12" +msgstr "Molim, unesite dautm u obliku %(format)s" + +#: formencode/validators.py:1943 +msgid "Please enter a valid day" +msgstr "Molim, unesite ispravan dan" + +#: formencode/validators.py:1944 +#, python-format +msgid "That month only has %(days)i days" +msgstr "Taj mesec ima samo %(days)i dana" + +#: formencode/validators.py:1945 +#, python-format +msgid "That is not a valid day (%(exception)s)" +msgstr "Dan nije valjan (%(exception)s)" + +#: formencode/validators.py:1946 +#, python-format +msgid "Unknown month name: %(month)s" +msgstr "Nepoznato ime meseca: %(month)s" + +#: formencode/validators.py:1947 +msgid "Please enter a number for the year" +msgstr "Neznano ime meseca: %(month)s" + +#: formencode/validators.py:1948 +msgid "Please enter a four-digit year after 1899" +msgstr "Molim, unesite četvorocifrenu godinu" + +#: formencode/validators.py:2131 +msgid "You must indicate AM or PM" +msgstr "Morate naznačiti pre ili posle podne (AM ili PM)" + +#: formencode/validators.py:2132 +msgid "There are too many :'s" +msgstr "Previše dvotački" + +#: formencode/validators.py:2133 +msgid "You may not enter seconds" +msgstr "Ne možete uneti sekunde" + +#: formencode/validators.py:2134 +msgid "You must enter seconds" +msgstr "Morate uneti sekunde" + +#: formencode/validators.py:2135 +msgid "You must enter minutes (after a :)" +msgstr "Morate uneti minute (posle dvotačke)" + +#: formencode/validators.py:2136 +#, python-format +msgid "The %(part)s value you gave is not a number: %(number)r" +msgstr "%(part)s vrednost nije broj: %(number)r" + +#: formencode/validators.py:2137 +#, python-format +msgid "You must enter an hour in the range %(range)s" +msgstr "Morate uneti sat u opsegu %(range)s" + +#: formencode/validators.py:2138 +msgid "You must enter a minute in the range 0-59" +msgstr "Morate uneti minute u opsegu 0-59" + +#: formencode/validators.py:2139 +msgid "You must enter a second in the range 0-59" +msgstr "Morate uneti sekunde u opsegu 0-59" + +#: formencode/validators.py:2285 +#, python-format +msgid "The name %(name)s is missing" +msgstr "Nedostaje ime %(name)s" + +#: formencode/validators.py:2328 +#, python-format +msgid "Value should be %(true)r or %(false)r" +msgstr "Vrednost treba da bude %(true)r ili %(false)r" + +#: formencode/validators.py:2363 +msgid "Value does not contain a signature" +msgstr "Vrednost ne sadrži potpis" + +#: formencode/validators.py:2364 +msgid "Signature is not correct" +msgstr "Potpis je nepravilan" + +#: formencode/validators.py:2439 +msgid "Please enter a valid IP address (a.b.c.d)" +msgstr "Molim, unesite ispravnu IP adresu (a.b.c.d)" + +#: formencode/validators.py:2440 +msgid "The octets must not have leading zeros" +msgstr "Okteti ne mogu imati vodeće nule" + +#: formencode/validators.py:2441 +#, python-format +msgid "The octets must be within the range of 0-255 (not %(octet)r)" +msgstr "Okteti moraju biti u opsegu 0-255 (ne %(octet)r)" + +#: formencode/validators.py:2496 +msgid "Please enter a valid IP address (a.b.c.d) or IP network (a.b.c.d/e)" +msgstr "Molim, unesite ispravnu IP adresu (a.b.c.d) ili IP mrežu (a.b.c.d/e)" + +#: formencode/validators.py:2498 +#, python-format +msgid "The network size (bits) must be within the range of 8-32 (not %(bits)r)" +msgstr "Veličina mreže (u bitovima) mora biti u opsegu 8-32 (ne %(bits)r)" + +#: formencode/validators.py:2547 +#, python-format +msgid "A MAC address must contain 12 digits and A-F; the value you gave has %(length)s characters" +msgstr "MAC adresa mora sadržati 12 cifara i A-F; vrednost koju ste dali ima %(length)s karaktera" + +#: formencode/validators.py:2549 +#, python-format +msgid "MAC addresses may only contain 0-9 and A-F (and optionally :), not %(char)r" +msgstr "MAC adrese mogu sadržati samo karaktere 0-9 i A--F (i opciono :), ne %(char)r" + +#: formencode/validators.py:2657 +#, python-format +msgid "You must give a value for %s" +msgstr "Morate dati vrednost za %s" + +#: formencode/validators.py:2691 +#, python-format +msgid "Fields do not match (should be %(match)s)" +msgstr "Polja se ne poklapaju (mora biti %(match)s)" + +#: formencode/validators.py:2692 +msgid "Fields do not match" +msgstr "Polja se ne poklapaju" + +#: formencode/validators.py:2693 +msgid "Fields should be a dictionary" +msgstr "Polja bi trebalo da budu rečnik (dict)" + +#: formencode/validators.py:2769 +msgid "Please enter only the number, no other characters" +msgstr "Molim, unesite samo broj, bez drugih znakova" + +#: formencode/validators.py:2770 +msgid "You did not enter a valid number of digits" +msgstr "Niste uneli potreban broj cifara" + +#: formencode/validators.py:2771 +msgid "That number is not valid" +msgstr "Taj broj nije valjan" + +#: formencode/validators.py:2772 +#, python-format +msgid "The field %(key)s is missing" +msgstr "Nedostaje polje %(key)s" + +#: formencode/validators.py:2888 +msgid "Please enter numbers only for month and year" +msgstr "Molim, unesite samo brojeve za mesec i godinu" + +#: formencode/validators.py:2889 +msgid "Invalid Expiration Date" +msgstr "Nevaljan rok isteka" + +#: formencode/validators.py:2964 +msgid "Please enter numbers only for credit card security code" +msgstr "Molim, unesite cifre samo za sigurnosni kod kartice" + +#: formencode/validators.py:2965 +msgid "Invalid credit card security code length" +msgstr "Nevaljana dužina sigurnosnog koda kartice" + From ea7a612061032a7f6253b0763bdadbf7ea78ac35 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 14 Nov 2015 15:22:12 -0500 Subject: [PATCH 033/123] Update License. Add contributors file --- CONTRIBUTORS.txt | 95 ++++++++++++++++++++++ LICENSE.txt | 201 ++++------------------------------------------- setup.py | 2 +- 3 files changed, 112 insertions(+), 186 deletions(-) create mode 100644 CONTRIBUTORS.txt diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 00000000..3b8056e0 --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,95 @@ +List of Contributors +==================== + +The below-signed are contributors to a code repository that is part of the +project named "FormEncode". Each below-signed contributor has agreed to +license their contributions under the MIT license (see LICENSE.txt) as of the +date listed by their name. + +Contributors +------------ + - Ian Bicking, 2015-11-14 + + - Christoph Zwerschke, 2015-11-14 + + - Christopher Lambacher, 2015-11-14 + + - Philip Jenvey, 2015-11-14 + + - Ian Wilson, 2015-11-14 + + - W. Mark Kubacki, 2015-11-14 + + - Greg Horvath, 2015-11-14 + + - Maxim Avanov, 2015-11-14 + + - Ben Bangert, 2015-11-14 + + - Matthew Desmarais, 2015-11-14 + + - Ethan Jucovy, 2015-11-14 + + - Marc Abramowitz, 2015-11-14 + + - Benjamin Behringer, 2015-11-14 + + - Richard Flosi, 2015-11-14 + + - Juliusz Gonera, 2015-11-14 + + - John Kraal, 2015-11-14 + + - Alessandro Molina, 2015-11-14 + + - Neil Muller, 2015-11-14 + + - Philipp Spitzer, 2015-11-14 + + - Vitaly Babiy, 2015-11-14 + + - David Stanek, 2015-11-14 + + - Ira, 2015-11-14 + + - Hong Minhee, 2015-11-14 + + - Felix Wolfsteller, 2015-11-14 + + - Keith Yang, 2015-11-14 + + - Elmer de Looff, 2015-11-14 + + - Jonathan Vanasco, 2015-11-14 + + - Luke Tucker, 2015-11-14 + + - Kevin Dangoor, 2015-11-14 + + - Mark Lipscombe, 2015-11-14 + + - Nina Plakalovic, 2015-11-14 + + - Oleg Broytman, 2015-11-14 + + - Christophe Benz, 2015-11-14 + + - Chris George, 2015-11-14 + + - Philipp Hagemeister, 2015-11-14 + + - Remco Verhoef, 2015-11-14 + + - Ryan P. Kelly, 2015-11-14 + + - Jason R. Coombs, 2015-11-14 + +Contributors that Have Not Agreed to MIT License + + - Patrick Hunt, 2015-11-14 + + - Jeff Breidenbach, 2015-11-14 + + - Bo Du, 2015-11-14 + + - Aleksey Sivokon, 2015-11-14 diff --git a/LICENSE.txt b/LICENSE.txt index e788899e..a611276f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,194 +1,25 @@ -Python License, Version 2 (Python-2.0) +Copyright (c) 2015 Ian Bicking and FormEncode Contributors -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. -2. Subject to the terms and conditions of this License Agreement, PSF -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python -alone or in any derivative version, provided, however, that PSF's -License Agreement and PSF's notice of copyright, i.e., "Copyright (c) -2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights -Reserved" are retained in Python alone or in any derivative version -prepared by Licensee. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - -CNRI OPEN SOURCE LICENSE AGREEMENT (for Python 1.6b1) --------------------------------------------------- - -IMPORTANT: PLEASE READ THE FOLLOWING AGREEMENT CAREFULLY. - -BY CLICKING ON "ACCEPT" WHERE INDICATED BELOW, OR BY COPYING, -INSTALLING OR OTHERWISE USING PYTHON 1.6, beta 1 SOFTWARE, YOU ARE -DEEMED TO HAVE AGREED TO THE TERMS AND CONDITIONS OF THIS LICENSE -AGREEMENT. - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6, beta 1 -software in source or binary form and its associated documentation, -as released at the www.python.org Internet site on August 4, 2000 -("Python 1.6b1"). - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a non-exclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display -publicly, prepare derivative works, distribute, and otherwise use -Python 1.6b1 alone or in any derivative version, provided, however, -that CNRIs License Agreement is retained in Python 1.6b1, alone or -in any derivative version prepared by Licensee. - -Alternately, in lieu of CNRIs License Agreement, Licensee may -substitute the following text (omitting the quotes): "Python 1.6, -beta 1, is made available subject to the terms and conditions in -CNRIs License Agreement. This Agreement may be located on the -Internet using the following unique, persistent identifier (known -as a handle): 1895.22/1011. This Agreement may also be obtained -from a proxy server on the Internet using the -URL:http://hdl.handle.net/1895.22/1011". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6b1 or any part thereof, and wants to make -the derivative work available to the public as provided herein, -then Licensee hereby agrees to indicate in any such work the nature -of the modifications made to Python 1.6b1. - -4. CNRI is making Python 1.6b1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR -FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6b1 -WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR -LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING PYTHON 1.6b1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY -THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of Virginia, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed -to create any relationship of agency, partnership, or joint venture -between CNRI and Licensee. This License Agreement does not grant -permission to use CNRI trademarks or trade name in a trademark -sense to endorse or promote products or services of Licensee, or -any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6b1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - -ACCEPT - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/setup.py b/setup.py index e6204335..b3d3837c 100755 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", - "License :: OSI Approved :: Python Software Foundation License", + "License :: OSI Approved :: MIT", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", From 08d32150e54d491884763f95c8e57c568bfb24f1 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 21 Nov 2015 12:40:46 -0500 Subject: [PATCH 034/123] Add new agreements and new found contributors --- CONTRIBUTORS.txt | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3b8056e0..b6836a5f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -84,12 +84,26 @@ Contributors - Jason R. Coombs, 2015-11-14 + - Joshua Bronson, 2015-11-14 + + - Aleksey Sivokon, 2015-11-14 + Contributors that Have Not Agreed to MIT License - - Patrick Hunt, 2015-11-14 + - Patrick Hunt - - Jeff Breidenbach, 2015-11-14 + - Bo Du - - Bo Du, 2015-11-14 + - Dennis Muhlestein - - Aleksey Sivokon, 2015-11-14 + - Marius Gedminas + + - Jason Culverhouse + + - Michele Cella + + - Jacob Smullyan + + - Stphane Raimbault + + - Arnaud Fontaine From a77d1c2edb45a4f006d8c359487d28935396b519 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 28 Nov 2015 16:43:01 -0500 Subject: [PATCH 035/123] Add more contributors --- CONTRIBUTORS.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b6836a5f..2e180e39 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -88,6 +88,11 @@ Contributors - Aleksey Sivokon, 2015-11-14 + - Stphane Raimbault 2015-11-23 + + - Jason Culverhouse, 2015-11-24 + + Contributors that Have Not Agreed to MIT License - Patrick Hunt @@ -98,12 +103,8 @@ Contributors that Have Not Agreed to MIT License - Marius Gedminas - - Jason Culverhouse - - Michele Cella - Jacob Smullyan - - Stphane Raimbault - - Arnaud Fontaine From f2427037cb0ab27aed4f958f455f2d7a83a7fd3c Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Tue, 1 Dec 2015 22:29:21 -0500 Subject: [PATCH 036/123] More acceptance --- CONTRIBUTORS.txt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 2e180e39..b97aac4c 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -92,8 +92,16 @@ Contributors - Jason Culverhouse, 2015-11-24 + - Michele Cella, 2015-11-30 -Contributors that Have Not Agreed to MIT License + - Arnaud Fontaine, 2015-12-01 + + +The following people made their contributions under the PSF license and did +not respond to requests to approve the change to the MIT license. If you are +one of these contributor and you do not want you changes licensed under MIT +please contact the project managers and we will work on backing out your +contributions. - Patrick Hunt @@ -103,8 +111,4 @@ Contributors that Have Not Agreed to MIT License - Marius Gedminas - - Michele Cella - - Jacob Smullyan - - - Arnaud Fontaine From 92baee07582cfc7e57b42eef708a27fe8400e895 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 5 Dec 2015 12:28:30 -0500 Subject: [PATCH 037/123] Contributors update --- CONTRIBUTORS.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b97aac4c..fba9bfeb 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -99,8 +99,8 @@ Contributors The following people made their contributions under the PSF license and did not respond to requests to approve the change to the MIT license. If you are -one of these contributor and you do not want you changes licensed under MIT -please contact the project managers and we will work on backing out your +one of these contributors and you do not want you changes licensed under MIT +please contact the project managers and we will work on removing your contributions. - Patrick Hunt From b89d28313d9a6909cd86ff1510d72d86a34dc3ab Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 5 Dec 2015 13:23:20 -0500 Subject: [PATCH 038/123] Add python 3.5 to travis builds --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1884f4d2..4bf41ffa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: - 3.2 - 3.3 - 3.4 + - 3.5 - pypy install: pip install . From 276666b6b141b205a4ecf30f63c4aff0b6ee2754 Mon Sep 17 00:00:00 2001 From: Bradley Arsenault Date: Fri, 20 Dec 2013 16:12:54 -0400 Subject: [PATCH 039/123] Added in a very simple valdiator for combined date/time objects represented in ISO format. --- formencode/validators.py | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/formencode/validators.py b/formencode/validators.py index 164b372f..ff085de1 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -2330,6 +2330,48 @@ def _convert_from_python(self, value, state): return '%i:%02i%s' % (hour, minute, ampm) + + +class ISODateTimeConverter(FancyValidator): + """ + Converts fields which contain both date and time, in the + ISO 8601 standard format YYYY-MM-DDTHH:MM:SS. + + Stores in a datetime.datetime object. + + Examples:: + + >>> tim = ISODateTimeConverter() + >>> tim.to_python('2012-06-25T05:30:25') + datetime.datetime(2012, 6, 25, 5, 30, 25) + >>> tim.to_python('1999-12-01T12:00:00') + datetime.datetime(1999, 12, 1, 12, 0, 0) + >>> tim.to_python('2012-06-25 05:30:25') + Traceback (most recent call last): + ... + Invalid: The must enter your date & time in the format YYYY-MM-DDTHH:MM:SS + + """ + + # This can be set to make it prefer mxDateTime: + datetime_module = None + + messages = dict( + invalidFormat=_('The must enter your date & time in the format YYYY-MM-DDTHH:MM:SS'),) + + def _convert_to_python(self, value, state): + dt_mod = import_datetime(self.datetime_module) + datetime_class = dt_mod.datetime + try: + datetime = datetime_class.strptime(value, r"%Y-%m-%dT%H:%M:%S") + except ValueError: + raise Invalid(self.message('invalidFormat', state), value, state) + + def _convert_from_python(self, value, state): + return value.isoformat("T") + + + def PostalCode(*kw, **kwargs): deprecation_warning("please use formencode.national.USPostalCode") from formencode.national import USPostalCode From 6c80141078b1d35ca74512e747bd76d161b71778 Mon Sep 17 00:00:00 2001 From: Bradley Arsenault Date: Sat, 5 Dec 2015 15:15:30 -0500 Subject: [PATCH 040/123] Added myself to contributors --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index fba9bfeb..ab0bcbb0 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -96,6 +96,8 @@ Contributors - Arnaud Fontaine, 2015-12-01 + - Bradley Arsenault, 2015-12-05 + The following people made their contributions under the PSF license and did not respond to requests to approve the change to the MIT license. If you are From 0804dba24bc41f042f8b280d6e8e1deac78af49f Mon Sep 17 00:00:00 2001 From: Bradley Arsenault Date: Sat, 5 Dec 2015 15:22:55 -0500 Subject: [PATCH 041/123] Minor fix to return the variable. --- formencode/validators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/formencode/validators.py b/formencode/validators.py index ff085de1..f5fb97a0 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -2366,6 +2366,7 @@ def _convert_to_python(self, value, state): datetime = datetime_class.strptime(value, r"%Y-%m-%dT%H:%M:%S") except ValueError: raise Invalid(self.message('invalidFormat', state), value, state) + return datetime def _convert_from_python(self, value, state): return value.isoformat("T") From 9b17d47225939dbdf8a0e48ec006dc9d8be69cd7 Mon Sep 17 00:00:00 2001 From: Bradley Arsenault Date: Sat, 5 Dec 2015 15:24:14 -0500 Subject: [PATCH 042/123] Return the variable in a slightly different place. --- formencode/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/validators.py b/formencode/validators.py index f5fb97a0..8c8dd28e 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -2364,9 +2364,9 @@ def _convert_to_python(self, value, state): datetime_class = dt_mod.datetime try: datetime = datetime_class.strptime(value, r"%Y-%m-%dT%H:%M:%S") + return datetime except ValueError: raise Invalid(self.message('invalidFormat', state), value, state) - return datetime def _convert_from_python(self, value, state): return value.isoformat("T") From 5fab89a4c59b921ea5f9cc35951fd6a1e1fc4498 Mon Sep 17 00:00:00 2001 From: Bradley Arsenault Date: Sat, 5 Dec 2015 15:29:42 -0500 Subject: [PATCH 043/123] Minor change to the test, I guess python is not outputting the seconds on datetime objects in this case. --- formencode/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/validators.py b/formencode/validators.py index 8c8dd28e..66f1aacc 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -2345,7 +2345,7 @@ class ISODateTimeConverter(FancyValidator): >>> tim.to_python('2012-06-25T05:30:25') datetime.datetime(2012, 6, 25, 5, 30, 25) >>> tim.to_python('1999-12-01T12:00:00') - datetime.datetime(1999, 12, 1, 12, 0, 0) + datetime.datetime(1999, 12, 1, 12, 0) >>> tim.to_python('2012-06-25 05:30:25') Traceback (most recent call last): ... From 4a3d053f889dde480f6eb1aba78f3aa244fe986e Mon Sep 17 00:00:00 2001 From: Milos Date: Sun, 6 Dec 2015 19:52:12 +0100 Subject: [PATCH 044/123] Added myself to contributors --- CONTRIBUTORS.txt | 119 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 CONTRIBUTORS.txt diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 00000000..bfeee148 --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,119 @@ +List of Contributors +==================== + +The below-signed are contributors to a code repository that is part of the +project named "FormEncode". Each below-signed contributor has agreed to +license their contributions under the MIT license (see LICENSE.txt) as of the +date listed by their name. + +Contributors +------------ + - Ian Bicking, 2015-11-14 + + - Christoph Zwerschke, 2015-11-14 + + - Christopher Lambacher, 2015-11-14 + + - Philip Jenvey, 2015-11-14 + + - Ian Wilson, 2015-11-14 + + - W. Mark Kubacki, 2015-11-14 + + - Greg Horvath, 2015-11-14 + + - Maxim Avanov, 2015-11-14 + + - Ben Bangert, 2015-11-14 + + - Matthew Desmarais, 2015-11-14 + + - Ethan Jucovy, 2015-11-14 + + - Marc Abramowitz, 2015-11-14 + + - Benjamin Behringer, 2015-11-14 + + - Richard Flosi, 2015-11-14 + + - Juliusz Gonera, 2015-11-14 + + - John Kraal, 2015-11-14 + + - Alessandro Molina, 2015-11-14 + + - Neil Muller, 2015-11-14 + + - Philipp Spitzer, 2015-11-14 + + - Vitaly Babiy, 2015-11-14 + + - David Stanek, 2015-11-14 + + - Ira, 2015-11-14 + + - Hong Minhee, 2015-11-14 + + - Felix Wolfsteller, 2015-11-14 + + - Keith Yang, 2015-11-14 + + - Elmer de Looff, 2015-11-14 + + - Jonathan Vanasco, 2015-11-14 + + - Luke Tucker, 2015-11-14 + + - Kevin Dangoor, 2015-11-14 + + - Mark Lipscombe, 2015-11-14 + + - Nina Plakalovic, 2015-11-14 + + - Oleg Broytman, 2015-11-14 + + - Christophe Benz, 2015-11-14 + + - Chris George, 2015-11-14 + + - Philipp Hagemeister, 2015-11-14 + + - Remco Verhoef, 2015-11-14 + + - Ryan P. Kelly, 2015-11-14 + + - Jason R. Coombs, 2015-11-14 + + - Joshua Bronson, 2015-11-14 + + - Aleksey Sivokon, 2015-11-14 + + - Stéphane Raimbault 2015-11-23 + + - Jason Culverhouse, 2015-11-24 + + - Michele Cella, 2015-11-30 + + - Arnaud Fontaine, 2015-12-01 + + - Bradley Arsenault, 2015-12-05 + + - Miloš Gavrilov, 2015-12-06 + + +The following people made their contributions under the PSF license and did +not respond to requests to approve the change to the MIT license. If you are +one of these contributors and you do not want you changes licensed under MIT +please contact the project managers and we will work on removing your +contributions. + + - Patrick Hunt + + - Bo Du + + - Dennis Muhlestein + + - Marius Gedminas + + - Jacob Smullyan + From c1a88e900043adeaaec530053ca896d34823f8df Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Wed, 9 Dec 2015 00:02:16 -0500 Subject: [PATCH 045/123] Remove 2to3 in favour using six --- docs/Validator.txt | 13 +-- docs/conf.py | 14 +-- examples/WebwareExamples/index.py | 11 +- formencode/__init__.py | 1 + formencode/api.py | 26 ++--- formencode/compound.py | 6 +- formencode/context.py | 4 +- formencode/declarative.py | 24 +++-- formencode/doctest_xml_compare.py | 11 +- formencode/fieldstorage.py | 1 + formencode/foreach.py | 6 +- formencode/htmlfill.py | 24 +++-- formencode/htmlfill_schemabuilder.py | 1 + formencode/htmlgen.py | 25 +++-- formencode/htmlrename.py | 1 + formencode/national.py | 12 ++- formencode/rewritingparser.py | 19 ++-- formencode/schema.py | 32 +++--- formencode/tests/__init__.py | 1 + formencode/tests/test_cc.py | 1 + formencode/tests/test_compound.py | 1 + formencode/tests/test_context.py | 1 + formencode/tests/test_declarative.py | 1 + formencode/tests/test_doctest_xml_compare.py | 1 + formencode/tests/test_doctests.py | 4 +- formencode/tests/test_email.py | 9 +- formencode/tests/test_htmlfill.py | 32 +++--- formencode/tests/test_htmlfill_control.py | 1 + formencode/tests/test_htmlgen.py | 15 +-- formencode/tests/test_htmlrename.py | 1 + formencode/tests/test_i18n.py | 53 +++++----- formencode/tests/test_schema.py | 20 ++-- formencode/tests/test_subclassing.py | 1 + formencode/tests/test_subclassing_old.py | 2 + formencode/tests/test_validators.py | 105 +++++++++--------- formencode/tests/test_variabledecode.py | 1 + formencode/validators.py | 106 ++++++++++--------- formencode/variabledecode.py | 11 +- setup.py | 3 +- 39 files changed, 342 insertions(+), 259 deletions(-) diff --git a/docs/Validator.txt b/docs/Validator.txt index e40edf8e..1c31c429 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -61,11 +61,12 @@ sort of feedback. Like: >>> raw_input_input = [] >>> def raw_input(prompt): ... value = raw_input_input.pop(0) - ... print '%s%s' % (prompt, value) + ... print ('%s%s' % (prompt, value)) ... return value >>> raw_input_input.extend(['ten', '10']) >>> raw_input_input.extend(['bob', 'bob@nowhere.com']) - >>> if unicode is str: # Python 3 + >>> import six + >>> if six.PY3: # Python 3 ... input = raw_input :: @@ -75,8 +76,8 @@ sort of feedback. Like: ... try: ... value = raw_input('Enter a number: ') ... return validator.to_python(value) - ... except formencode.Invalid, e: - ... print e + ... except formencode.Invalid as e: + ... print (e) ... >>> get_integer() Enter a number: ten @@ -91,8 +92,8 @@ We can also generalize this kind of function:: ... try: ... value = raw_input(prompt) ... return validator.to_python(value) - ... except formencode.Invalid, e: - ... print e + ... except formencode.Invalid as e: + ... print (e) >>> valid_input('Enter your email: ', validators.Email()) Enter your email: bob An email address must contain a single @ diff --git a/docs/conf.py b/docs/conf.py index 4bb92a93..5cb0d37a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # -*- coding: utf-8 -*- # # FormEncode documentation build configuration file, created by @@ -12,6 +13,7 @@ # serve to show the default. import sys, os +import six # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -40,8 +42,8 @@ master_doc = 'index' # General information about the project. -project = u'FormEncode' -copyright = u'2008-2012, Ian Bicking and Contributors' +project = six.u('FormEncode') +copyright = six.u('2008-2012, Ian Bicking and Contributors') # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -179,8 +181,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'FormEncode.tex', u'FormEncode Documentation', - u'Ian Bicking', 'manual'), + ('index', 'FormEncode.tex', six.u('FormEncode Documentation'), + six.u('Ian Bicking'), 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -212,6 +214,6 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'formencode', u'FormEncode Documentation', - [u'Ian Bicking'], 1) + ('index', 'formencode', six.u('FormEncode Documentation'), + [six.u('Ian Bicking')], 1) ] diff --git a/examples/WebwareExamples/index.py b/examples/WebwareExamples/index.py index b261e6b5..c8b1a1ea 100644 --- a/examples/WebwareExamples/index.py +++ b/examples/WebwareExamples/index.py @@ -1,6 +1,9 @@ +from __future__ import absolute_import +from __future__ import print_function from formencode import Invalid, htmlfill, Schema, validators from WebKit.Page import Page +import six page_style = ''' @@ -72,10 +75,10 @@ def save(self): fields = self.request().fields() try: fields = FormSchema.to_python(fields, self) - except Invalid, e: + except Invalid as e: errors = dict((k, v.encode('utf-8')) - for k, v in e.unpack_errors().iteritems()) - print "Errors:", errors + for k, v in six.iteritems(e.unpack_errors())) + print("Errors:", errors) self.rendered_form = htmlfill.render(form_template, defaults=fields, errors=errors) self.writeHTML() @@ -83,7 +86,7 @@ def save(self): self.doAction(fields) def doAction(self, fields): - print "Fields:", fields + print("Fields:", fields) self.rendered_form = response_template % fields self.writeHTML() diff --git a/formencode/__init__.py b/formencode/__init__.py index de1de205..421d44a5 100644 --- a/formencode/__init__.py +++ b/formencode/__init__.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # formencode package from formencode.api import ( diff --git a/formencode/api.py b/formencode/api.py index 4f7655cd..c097006f 100644 --- a/formencode/api.py +++ b/formencode/api.py @@ -1,6 +1,7 @@ """ Core classes for validation. """ +from __future__ import absolute_import from . import declarative import gettext @@ -8,6 +9,7 @@ import re import textwrap import warnings +import six try: from pkg_resources import resource_filename @@ -150,15 +152,15 @@ def __str__(self): val = self.msg return val - if unicode is not str: # Python 2 + if six.text_type is not str: # Python 2 def __unicode__(self): - if isinstance(self.msg, unicode): + if isinstance(self.msg, six.text_type): return self.msg elif isinstance(self.msg, str): return self.msg.decode('utf8') else: - return unicode(self.msg) + return six.text_type(self.msg) def unpack_errors(self, encode_variables=False, dict_char='.', list_char='-'): @@ -177,15 +179,15 @@ def unpack_errors(self, encode_variables=False, dict_char='.', for item in self.error_list] if self.error_dict: result = {} - for name, item in self.error_dict.iteritems(): + for name, item in six.iteritems(self.error_dict): result[name] = item if isinstance( - item, basestring) else item.unpack_errors() + item, six.string_types) else item.unpack_errors() if encode_variables: from . import variabledecode result = variabledecode.variable_encode( result, add_repetitions=False, dict_char=dict_char, list_char=list_char) - for key in result.keys(): + for key in list(result.keys()): if not result[key]: del result[key] return result @@ -245,8 +247,8 @@ def message(self, msgName, state, **kw): except AttributeError: try: if self.use_builtins_gettext: - import __builtin__ - trans = __builtin__._ + import six.moves.builtins + trans = six.moves.builtins._ else: trans = _stdtrans except AttributeError: @@ -311,7 +313,7 @@ def _initialize_docstring(cls): """ doc = cls.__doc__ or '' doc = [textwrap.dedent(doc).rstrip()] - messages = sorted(cls._messages.iteritems()) + messages = sorted(six.iteritems(cls._messages)) doc.append('\n\n**Messages**\n\n') for name, default in messages: default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default) @@ -456,7 +458,7 @@ def __classinit__(cls, new_attrs): def to_python(self, value, state=None): try: - if self.strip and isinstance(value, basestring): + if self.strip and isinstance(value, six.string_types): value = value.strip() elif hasattr(value, 'mixed'): # Support Paste's MultiDict @@ -484,7 +486,7 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): try: - if self.strip and isinstance(value, basestring): + if self.strip and isinstance(value, six.string_types): value = value.strip() if not self.accept_python: if self.is_empty(value): @@ -520,7 +522,7 @@ def empty_value(self, value): return None def assert_string(self, value, state): - if not isinstance(value, basestring): + if not isinstance(value, six.string_types): raise Invalid(self.message('badType', state, type=type(value), value=value), value, state) diff --git a/formencode/compound.py b/formencode/compound.py index e01c4e00..6005f1a3 100644 --- a/formencode/compound.py +++ b/formencode/compound.py @@ -1,9 +1,11 @@ """ Validators for applying validations in sequence. """ +from __future__ import absolute_import from .api import (FancyValidator, Identity, Invalid, NoDefault, Validator, is_validator) +import six __all__ = ['CompoundValidator', 'Any', 'All', 'Pipe'] @@ -39,7 +41,7 @@ class CompoundValidator(FancyValidator): def __classinit__(cls, new_attrs): FancyValidator.__classinit__(cls, new_attrs) toAdd = [] - for name, value in new_attrs.iteritems(): + for name, value in six.iteritems(new_attrs): if is_validator(value) and value is not Identity: toAdd.append((name, value)) # @@: Should we really delete too? @@ -200,7 +202,7 @@ def join(cls, *validators): filtering out None and trying to keep `All` validators from being nested (which isn't needed). """ - validators = filter(lambda v: v and v is not Identity, validators) + validators = [v for v in validators if v and v is not Identity] if not validators: return Identity if len(validators) == 1: diff --git a/formencode/context.py b/formencode/context.py index 8bd8c72a..128c9122 100644 --- a/formencode/context.py +++ b/formencode/context.py @@ -51,10 +51,12 @@ def do_stuff(): And ``page`` will be set to ``'view'`` only inside that ``with`` block. """ +from __future__ import absolute_import import threading from itertools import count +from six.moves import range __all__ = ['Context', 'ContextRestoreError'] @@ -96,7 +98,7 @@ def __setattr__(self, attr, value): "You can only write attribute on context object with the .set() method") def set(self, **kw): - state_id = _restore_ids.next() + state_id = next(_restore_ids) try: stack = self._local.stack except AttributeError: diff --git a/formencode/declarative.py b/formencode/declarative.py index 504c44c9..2375b3b9 100644 --- a/formencode/declarative.py +++ b/formencode/declarative.py @@ -17,11 +17,15 @@ Also, you can define a __classinit__(cls, new_attrs) method, which will be called when the class is created (including subclasses). """ +from __future__ import absolute_import import copy import types from itertools import count +import six +from six.moves import map +from six.moves import zip class classinstancemethod(object): @@ -55,9 +59,9 @@ def __call__(self, *args, **kw): def __repr__(self): if self.obj is None: return ('' - % (self.cls.__name__, self.func.func_name)) + % (self.cls.__name__, self.func.__name__)) return ('' - % (self.cls.__name__, self.func.func_name, self.obj)) + % (self.cls.__name__, self.func.__name__, self.obj)) class DeclarativeMeta(type): @@ -66,11 +70,11 @@ def __new__(meta, class_name, bases, new_attrs): cls = type.__new__(meta, class_name, bases, new_attrs) for name in cls.__mutableattributes__: setattr(cls, name, copy.copy(getattr(cls, name))) - cls.declarative_count = cls.counter.next() + cls.declarative_count = next(cls.counter) if ('__classinit__' in new_attrs and not isinstance(cls.__classinit__, (staticmethod, types.FunctionType))): setattr(cls, '__classinit__', - staticmethod(cls.__classinit__.im_func)) + staticmethod(cls.__classinit__.__func__)) cls.__classinit__(cls, new_attrs) names = getattr(cls, '__singletonmethods__', None) if names: @@ -97,14 +101,12 @@ def __get__(self, obj, cls=None): return types.MethodType(self.func, obj) -class Declarative(object): +class Declarative(six.with_metaclass(DeclarativeMeta, object)): __unpackargs__ = () __mutableattributes__ = () - __metaclass__ = DeclarativeMeta - __singletonmethods__ = () counter = count() @@ -143,10 +145,10 @@ def __init__(self, *args, **kw): for name in self.__mutableattributes__: if name not in kw: setattr(self, name, copy.copy(getattr(self, name))) - for name, value in kw.iteritems(): + for name, value in six.iteritems(kw): setattr(self, name, value) if 'declarative_count' not in kw: - self.declarative_count = self.counter.next() + self.declarative_count = next(self.counter) self.__initargs__(kw) def __initargs__(self, new_attrs): @@ -179,7 +181,7 @@ def __sourcerepr__(self, source, binding=None): and self.__unpackargs__[1] in vals): v = vals[self.__unpackargs__[1]] if isinstance(v, (list, int)): - args.extend(map(source.makeRepr, v)) + args.extend(list(map(source.makeRepr, v))) del v[self.__unpackargs__[1]] for name in self.__unpackargs__: if name in vals: @@ -188,7 +190,7 @@ def __sourcerepr__(self, source, binding=None): else: break args.extend('%s=%s' % (name, source.makeRepr(value)) - for (name, value) in vals.iteritems()) + for (name, value) in six.iteritems(vals)) return '%s(%s)' % (self.__class__.__name__, ', '.join(args)) diff --git a/formencode/doctest_xml_compare.py b/formencode/doctest_xml_compare.py index 7f3a8bf9..ebac496d 100644 --- a/formencode/doctest_xml_compare.py +++ b/formencode/doctest_xml_compare.py @@ -1,6 +1,11 @@ +from __future__ import absolute_import +from __future__ import print_function import doctest import xml.etree.ElementTree as ET +import six +from six.moves import map +from six.moves import zip try: XMLParseError = ET.ParseError except AttributeError: # Python < 2.7 @@ -11,7 +16,7 @@ def debug(*msg): import sys - print >> sys.stderr, ' '.join(map(str, msg)) + print(' '.join(map(str, msg)), file=sys.stderr) class HTMLOutputChecker(RealOutputChecker): @@ -69,7 +74,7 @@ def xml_compare(x1, x2, reporter=None): if reporter: reporter('Tags do not match: %s and %s' % (x1.tag, x2.tag)) return False - for name, value in x1.attrib.iteritems(): + for name, value in six.iteritems(x1.attrib): if x2.attrib.get(name) != value: if reporter: reporter('Attributes do not match: %s=%r, %s=%r' @@ -120,7 +125,7 @@ def make_xml(s): def make_string(xml): - if isinstance(xml, basestring): + if isinstance(xml, six.string_types): xml = make_xml(xml) s = ET.tostring(xml) if s == '': diff --git a/formencode/fieldstorage.py b/formencode/fieldstorage.py index a581320a..40a4d1b6 100644 --- a/formencode/fieldstorage.py +++ b/formencode/fieldstorage.py @@ -3,6 +3,7 @@ """ Wrapper class for use with cgi.FieldStorage types for file uploads """ +from __future__ import absolute_import import cgi diff --git a/formencode/foreach.py b/formencode/foreach.py index b81e2957..ce3cf07e 100644 --- a/formencode/foreach.py +++ b/formencode/foreach.py @@ -1,9 +1,11 @@ """ Validator for repeating items. """ +from __future__ import absolute_import from .api import NoDefault, Invalid from .compound import CompoundValidator, from_python +import six __all__ = ['ForEach'] @@ -83,7 +85,7 @@ def _attempt_convert(self, value, state, validate): new_list = set(new_list) return new_list else: - raise Invalid('Errors:\n%s' % '\n'.join(unicode(e) + raise Invalid('Errors:\n%s' % '\n'.join(six.text_type(e) for e in errors if e), value, state, error_list=errors) finally: if state is not None: @@ -125,7 +127,7 @@ def __delete__(self, obj): del _IfMissing def _convert_to_list(self, value): - if isinstance(value, basestring): + if isinstance(value, six.string_types): return [value] elif value is None: return [] diff --git a/formencode/htmlfill.py b/formencode/htmlfill.py index 5c438786..90f4beda 100644 --- a/formencode/htmlfill.py +++ b/formencode/htmlfill.py @@ -1,10 +1,12 @@ """ Parser for HTML forms, that fills in defaults and errors. See ``render``. """ +from __future__ import absolute_import import re from formencode.rewritingparser import RewritingParser, html_quote +import six __all__ = ['render', 'htmlliteral', 'default_formatter', 'none_formatter', 'escape_formatter', @@ -206,7 +208,7 @@ class FillingParser(RewritingParser): ... ... ''') >>> parser.close() - >>> print parser.text() # doctest: +NORMALIZE_WHITESPACE + >>> print (parser.text()) # doctest: +NORMALIZE_WHITESPACE ', {}, {}) == '' % rarr) assert (htmlfill.render('', {}, {}) == '') assert (htmlfill.render('', {}, {}) == - u'') + '') def test_xhtml(): @@ -180,7 +184,7 @@ def test_checkbox(): def test_unicode(): - assert (htmlfill.render(u'', + assert (htmlfill.render('', dict(tags=[])) == '') @@ -455,10 +459,10 @@ def test_error_class_textarea(): def test_mix_str_and_unicode(): html = '' - uhtml = unicode(html) + uhtml = six.text_type(html) cheese = dict(cheese='Käse') - ucheese = dict(cheese=u'Käse') - expected = u'' + ucheese = dict(cheese='Käse') + expected = '' rendered = htmlfill.render(html, defaults=cheese, encoding='utf-8') assert expected == rendered rendered = htmlfill.render(html, defaults=ucheese, encoding='utf-8') diff --git a/formencode/tests/test_htmlfill_control.py b/formencode/tests/test_htmlfill_control.py index 442a3e97..4ee4fa6c 100644 --- a/formencode/tests/test_htmlfill_control.py +++ b/formencode/tests/test_htmlfill_control.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from formencode import htmlfill diff --git a/formencode/tests/test_htmlgen.py b/formencode/tests/test_htmlgen.py index cfc07198..3006466d 100644 --- a/formencode/tests/test_htmlgen.py +++ b/formencode/tests/test_htmlgen.py @@ -1,11 +1,14 @@ +from __future__ import absolute_import +from __future__ import print_function import doctest from formencode.htmlgen import html +import six # A test value that can't be encoded as ascii: -uni_value = u'\xff' -str_value = uni_value if str is unicode else uni_value.encode('utf-8') +uni_value = six.u('\xff') +str_value = uni_value if str is six.text_type else uni_value.encode('utf-8') def test_basic(): @@ -37,7 +40,7 @@ def test_unicode(): assert False, ( "We need something that can't be ASCII-encoded: %r (%r)" % (uni_value, uni_value.encode('ascii'))) - assert unicode(html.b(uni_value)) == u'%s' % uni_value + assert six.text_type(html.b(uni_value)) == six.u('%s') % uni_value def test_quote(): @@ -76,10 +79,10 @@ def test_namespace(): if __name__ == '__main__': # It's like a super-mini py.test... - for name, value in globals().iteritems(): + for name, value in six.iteritems(globals()): if name.startswith('test'): - print name + print(name) value() from formencode import htmlgen doctest.testmod(htmlgen) - print 'doctest' + print('doctest') diff --git a/formencode/tests/test_htmlrename.py b/formencode/tests/test_htmlrename.py index 86a6f92d..5f93c4a7 100644 --- a/formencode/tests/test_htmlrename.py +++ b/formencode/tests/test_htmlrename.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from formencode.htmlrename import rename, add_prefix diff --git a/formencode/tests/test_i18n.py b/formencode/tests/test_i18n.py index 5c65891e..5998f96b 100644 --- a/formencode/tests/test_i18n.py +++ b/formencode/tests/test_i18n.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import unicode_literals import formencode +import six ne = formencode.validators.NotEmpty() @@ -8,15 +11,15 @@ def _test_builtins(func): def dummy(s): return "builtins dummy" - import __builtin__ - __builtin__._ = dummy + import six.moves.builtins + six.moves.builtins._ = dummy try: ne.to_python("") except formencode.api.Invalid as e: func(e) - del __builtin__._ + del six.moves.builtins._ def test_builtins(): @@ -52,90 +55,90 @@ def _test_lang(language, notemptytext): try: ne.to_python("") except formencode.api.Invalid as e: - assert unicode(e) == notemptytext + assert six.text_type(e) == notemptytext formencode.api.set_stdtranslation() # set back to defaults def test_de(): - _test_lang("de", u"Bitte einen Wert eingeben") + _test_lang("de", "Bitte einen Wert eingeben") def test_es(): - _test_lang("es", u"Por favor introduzca un valor") + _test_lang("es", "Por favor introduzca un valor") def test_pt_BR(): - _test_lang("pt_BR", u"Por favor digite um valor") + _test_lang("pt_BR", "Por favor digite um valor") def test_zh_TW(): - _test_lang("zh_TW", u"請輸入值") + _test_lang("zh_TW", "請輸入值") def test_sk(): - _test_lang("sk", u"Zadajte hodnotu, prosím") + _test_lang("sk", "Zadajte hodnotu, prosím") def test_ru(): - _test_lang("ru", u"Необходимо ввести значение") + _test_lang("ru", "Необходимо ввести значение") def test_sl(): - _test_lang("sl", u"Prosim, izpolnite polje") + _test_lang("sl", "Prosim, izpolnite polje") def test_pt_PT(): - _test_lang("pt_PT", u"Por favor insira um valor") + _test_lang("pt_PT", "Por favor insira um valor") def test_fr(): - _test_lang("fr", u"Saisissez une valeur") + _test_lang("fr", "Saisissez une valeur") def test_nl(): - _test_lang("nl", u"Voer een waarde in") + _test_lang("nl", "Voer een waarde in") def test_pl(): - _test_lang("pl", u"Proszę podać wartość") + _test_lang("pl", "Proszę podać wartość") def test_el(): - _test_lang("el", u"Παρακαλούμε εισάγετε μια τιμή") + _test_lang("el", "Παρακαλούμε εισάγετε μια τιμή") def test_zh_CN(): - _test_lang("zh_CN", u"请输入一个值") + _test_lang("zh_CN", "请输入一个值") def test_cs(): - _test_lang("cs", u"Prosím zadejte hodnotu") + _test_lang("cs", "Prosím zadejte hodnotu") def test_fi(): - _test_lang("fi", u"Anna arvo") + _test_lang("fi", "Anna arvo") def test_nb_NO(): - _test_lang("nb_NO", u"Venligst fyll inn en verdi") + _test_lang("nb_NO", "Venligst fyll inn en verdi") def test_it(): - _test_lang("it", u"Inserire un dato") + _test_lang("it", "Inserire un dato") def test_et(): - _test_lang("et", u"Palun sisestada väärtus") + _test_lang("et", "Palun sisestada väärtus") def test_lt(): - _test_lang("lt", u"Prašome įvesti reikšmę") + _test_lang("lt", "Prašome įvesti reikšmę") def test_ja(): - _test_lang("ja", u"入力してください") + _test_lang("ja", "入力してください") def test_tr(): - _test_lang("tr", u"Lütfen bir değer giriniz") + _test_lang("tr", "Lütfen bir değer giriniz") diff --git a/formencode/tests/test_schema.py b/formencode/tests/test_schema.py index 49dc3a76..56b153cb 100644 --- a/formencode/tests/test_schema.py +++ b/formencode/tests/test_schema.py @@ -1,6 +1,8 @@ +from __future__ import absolute_import +from __future__ import print_function import unittest -from urlparse import parse_qsl +from six.moves.urllib.parse import parse_qsl from formencode import Invalid, Validator, compound, foreach, validators from formencode.schema import Schema, merge_dicts, SimpleFormValidator @@ -14,14 +16,14 @@ def _notranslation(s): def setup_module(module): """Disable i18n translation""" - import __builtin__ - __builtin__._ = _notranslation + import six.moves.builtins + six.moves.builtins._ = _notranslation def teardown_module(module): """Remove translation function""" - import __builtin__ - del __builtin__._ + import six.moves.builtins + del six.moves.builtins._ def d(**kw): @@ -56,9 +58,9 @@ def __init__(self, schema, input, **output): all_cases.append(self) def test(self): - print 'input', repr(self.input) + print('input', repr(self.input)) actual = self.schema.to_python(self.input) - print 'output', repr(actual) + print('output', repr(actual)) assert actual == self.output @@ -72,9 +74,9 @@ def __init__(self, *args, **kw): self.output = self.output['text'] def test(self): - print repr(self.raw_input) + print(repr(self.raw_input)) try: - print repr(self.schema.to_python(self.input)) + print(repr(self.schema.to_python(self.input))) except Invalid as e: actual = e.unpack_errors() assert actual == self.output diff --git a/formencode/tests/test_subclassing.py b/formencode/tests/test_subclassing.py index 1a908d42..3f620797 100644 --- a/formencode/tests/test_subclassing.py +++ b/formencode/tests/test_subclassing.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # -*- coding: utf-8 -*- import unittest diff --git a/formencode/tests/test_subclassing_old.py b/formencode/tests/test_subclassing_old.py index 77c5efe7..23b62df9 100644 --- a/formencode/tests/test_subclassing_old.py +++ b/formencode/tests/test_subclassing_old.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # -*- coding: utf-8 -*- import unittest @@ -6,6 +7,7 @@ from formencode.api import is_validator, FancyValidator, Invalid from formencode.compound import CompoundValidator, All from formencode.validators import Int +from six.moves import map with warnings.catch_warnings(record=True) as custom_warnings: diff --git a/formencode/tests/test_validators.py b/formencode/tests/test_validators.py index 892d257a..d588fe6c 100644 --- a/formencode/tests/test_validators.py +++ b/formencode/tests/test_validators.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import unicode_literals import datetime import unittest @@ -10,6 +12,10 @@ from formencode.schema import Schema from formencode.foreach import ForEach from formencode.api import NoDefault +import six + +if six.PY3: + unicode = str def validate(validator, value): @@ -49,7 +55,7 @@ def setUp(self): self.messages = self.validator.message def test_alias(self): - if str is not unicode: # Python 2 + if str is not six.text_type: # Python 2 self.assertTrue(self.validator is validators.String) def test_docstring(self): @@ -87,7 +93,7 @@ def setUp(self): self.validator = validators.UnicodeString def test_alias(self): - if str is unicode: # Python 3 + if str is six.text_type: # Python 3 self.assertTrue(self.validator is validators.String) def test_docstring(self): @@ -96,47 +102,47 @@ def test_docstring(self): def test_unicode(self): un = self.validator() - self.assertEqual(un.to_python(12), u'12') - self.assertTrue(type(un.to_python(12)) is unicode) - self.assertEqual(un.from_python(12), u'12'.encode('ascii')) + self.assertEqual(un.to_python(12), '12') + self.assertTrue(type(un.to_python(12)) is six.text_type) + self.assertEqual(un.from_python(12), '12'.encode('ascii')) self.assertTrue(type(un.from_python(12)) is bytes) def test_unicode_encoding(self): uv = self.validator() - us = u'käse' + us = 'käse' u7s, u8s = us.encode('utf-7'), us.encode('utf-8') self.assertEqual(uv.to_python(u8s), us) - self.assertTrue(type(uv.to_python(u8s)) is unicode) + self.assertTrue(type(uv.to_python(u8s)) is six.text_type) self.assertEqual(uv.from_python(us), u8s) self.assertTrue(type(uv.from_python(us)) is bytes) uv = self.validator(encoding='utf-7') self.assertEqual(uv.to_python(u7s), us) - self.assertTrue(type(uv.to_python(u7s)) is unicode) + self.assertTrue(type(uv.to_python(u7s)) is six.text_type) self.assertEqual(uv.from_python(us), u7s) self.assertTrue(type(uv.from_python(us)) is bytes) uv = self.validator(inputEncoding='utf-7') self.assertEqual(uv.to_python(u7s), us) - self.assertTrue(type(uv.to_python(u7s)) is unicode) + self.assertTrue(type(uv.to_python(u7s)) is six.text_type) uv = self.validator(outputEncoding='utf-7') self.assertEqual(uv.from_python(us), u7s) self.assertTrue(type(uv.from_python(us)) is bytes) uv = self.validator(inputEncoding=None) self.assertEqual(uv.to_python(us), us) - self.assertTrue(type(uv.to_python(us)) is unicode) + self.assertTrue(type(uv.to_python(us)) is six.text_type) self.assertEqual(uv.from_python(us), u8s) self.assertTrue(type(uv.from_python(us)) is bytes) uv = self.validator(outputEncoding=None) self.assertEqual(uv.to_python(u8s), us) - self.assertTrue(type(uv.to_python(u8s)) is unicode) + self.assertTrue(type(uv.to_python(u8s)) is six.text_type) self.assertEqual(uv.from_python(us), us) - self.assertTrue(type(uv.from_python(us)) is unicode) + self.assertTrue(type(uv.from_python(us)) is six.text_type) def test_unicode_empty(self): iv = self.validator() - for value in [None, "", u""]: + for value in [None, b"", ""]: result = iv.to_python(value) - self.assertEqual(result, u"") - self.assertTrue(isinstance(result, unicode)) + self.assertEqual(result, "") + self.assertTrue(isinstance(result, six.text_type)) class TestIntValidator(unittest.TestCase): @@ -207,14 +213,14 @@ def test_bad_dates(self): dc.to_python('20/12/150') except Invalid as e: self.assertTrue( - 'Please enter a four-digit year after 1899' in str(e)) + 'Please enter a four-digit year after 1899' in unicode(e)) else: self.fail('Date should be invalid') try: dc.to_python('oh/happy/day') except Invalid as e: self.assertTrue( - 'Please enter the date in the form DD/MM/YYYY' in str(e)) + 'Please enter the date in the form DD/MM/YYYY' in unicode(e)) else: self.fail('Date should be invalid') @@ -234,14 +240,14 @@ def test_month_style_alias(self): dc = self.validator(month_style='Klingon') except TypeError as e: self.assertTrue( - str(e) == "Bad month_style: 'klingon'") + unicode(e).replace("u'", "'") == "Bad month_style: 'klingon'") else: self.fail('month_style should be invalid') try: dc = self.validator(month_style='ydm') except TypeError as e: self.assertTrue( - str(e) == "Bad month_style: 'ydm'") + unicode(e).replace("u'", "'") == "Bad month_style: 'ydm'") else: self.fail('month_style should be invalid') @@ -255,14 +261,14 @@ def test_us_style(self): try: self.assertEqual(dc.to_python('20/12/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in str(e)) + self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('12/Dec/2007'), d) except Invalid as e: self.assertTrue( - 'Please enter the date in the form MM/DD/YYYY' in str(e)) + 'Please enter the date in the form MM/DD/YYYY' in unicode(e)) else: self.fail('Date should be invalid') @@ -276,14 +282,14 @@ def test_euro_style(self): try: self.assertEqual(dc.to_python('12/20/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in str(e)) + self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('Dec/12/2007'), d) except Invalid as e: self.assertTrue( - 'Please enter the date in the form DD/MM/YYYY' in str(e)) + 'Please enter the date in the form DD/MM/YYYY' in unicode(e)) else: self.fail('Date should be invalid') @@ -297,14 +303,14 @@ def test_iso_style(self): try: self.assertEqual(dc.to_python('2013/30/06'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in str(e)) + self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('2013/06/Jun'), d) except Invalid as e: self.assertTrue( - 'Please enter the date in the form YYYY/MM/DD' in str(e)) + 'Please enter the date in the form YYYY/MM/DD' in unicode(e)) else: self.fail('Date should be invalid') @@ -318,21 +324,21 @@ def test_no_day(self): try: self.assertEqual(dc.to_python('20/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in str(e)) + self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('12/20/2007'), d) except Invalid as e: self.assertTrue( - 'Please enter the date in the form MM/YYYY' in str(e)) + 'Please enter the date in the form MM/YYYY' in unicode(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('2007/Dec'), d) except Invalid as e: self.assertTrue( - 'Please enter the date in the form MM/YYYY' in str(e)) + 'Please enter the date in the form MM/YYYY' in unicode(e)) else: self.fail('Date should be invalid') @@ -383,29 +389,29 @@ def test_time(self): tc.to_python('25:30:15') except Invalid as e: self.assertTrue( - 'You must enter an hour in the range 0-23' in str(e)) + 'You must enter an hour in the range 0-23' in unicode(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:75:15') except Invalid as e: self.assertTrue( - 'You must enter a minute in the range 0-59' in str(e)) + 'You must enter a minute in the range 0-59' in unicode(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:30:75') except Invalid as e: self.assertTrue( - 'You must enter a second in the range 0-59' in str(e)) + 'You must enter a second in the range 0-59' in unicode(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:30:zx') except Invalid as e: self.assertTrue( - 'The second value you gave is not a number' in str(e)) - self.assertTrue('zx' in str(e)) + 'The second value you gave is not a number' in unicode(e)) + self.assertTrue('zx' in unicode(e)) else: self.fail('Time should be invalid') @@ -437,7 +443,7 @@ class State: values = validator.to_python(start_values, state) except Invalid as e: self.assertEqual(e.unpack_errors(), - {'people': u'Please add a person'}) + {'people': 'Please add a person'}) else: raise Exception("Shouldn't be valid data", values, start_values) @@ -534,8 +540,8 @@ def test_numerals_at_beggining(self): def test_non_english_inames(self): """i-names with non-English characters are valid""" - self.validator.to_python(u'=Gustavo.Narea.García') - self.validator.to_python(u'@名前.例') + self.validator.to_python('=Gustavo.Narea.García') + self.validator.to_python('@名前.例') def test_inames_plus_paths(self): """i-names with paths are valid but not supported""" @@ -544,9 +550,9 @@ def test_inames_plus_paths(self): def test_communities(self): """i-names may have so-called 'communities'""" - self.validator.to_python(u'=María*Yolanda*Liliana*Gustavo') - self.validator.to_python(u'=Gustavo*Andreina') - self.validator.to_python(u'@IBM*Lenovo') + self.validator.to_python('=María*Yolanda*Liliana*Gustavo') + self.validator.to_python('=Gustavo*Andreina') + self.validator.to_python('@IBM*Lenovo') class TestINumberValidator(unittest.TestCase): @@ -619,8 +625,8 @@ def test_docstring(self): 'Value must be one of: ``%(items)s`` (not ``%(value)r``)' in doc) def test_unicode_list(self): - o = validators.OneOf([u'ö', u'a']) - self.assertRaises(Invalid, o.to_python, u"ä") + o = validators.OneOf(['ö', 'a']) + self.assertRaises(Invalid, o.to_python, "ä") def test_ascii_list(self): o = validators.OneOf(['a', 'b']) @@ -659,13 +665,13 @@ def test_leading_zeros(self): try: validate('1.2.3.037') except Invalid as e: - self.assertTrue('The octets must not have leading zeros' in str(e)) + self.assertTrue('The octets must not have leading zeros' in unicode(e)) else: self.fail('IP address octets with leading zeros should be invalid') try: validate('1.2.3.0377') except Invalid as e: - self.assertTrue('The octets must not have leading zeros' in str(e)) + self.assertTrue('The octets must not have leading zeros' in unicode(e)) else: self.fail('IP octets with leading zeros should be invalid') @@ -679,7 +685,7 @@ def test_leading_zeros_allowed(self): validate('1.2.3.0377') except Invalid as e: self.assertTrue("The octets must be within the range of 0-255" - " (not '377')" in str(e)) + " (not '377')" in unicode(e).replace("u'", "'")) else: self.fail( 'IP address octets should not be interpreted as octal numbers') @@ -718,7 +724,7 @@ def test_missing(self): v = self.validator('phone_type', missing='mail') self.assertEqual( validate(v, dict(phone_type='')), - dict(phone_type=u'Please enter a value')) + dict(phone_type='Please enter a value')) self.assertEqual( validate(v, dict(phone_type='', mail='foo@bar.org')), dict(phone_type='', mail='foo@bar.org')) @@ -727,7 +733,7 @@ def test_present(self): v = self.validator('phone_type', present='phone') self.assertEqual( validate(v, dict(phone_type='', phone='510 420 4577')), - dict(phone_type=u'Please enter a value')) + dict(phone_type='Please enter a value')) self.assertEqual( validate(v, dict(phone='')), dict(phone='')) @@ -735,7 +741,8 @@ def test_zero(self): v = self.validator('operator', present='operand') self.assertEqual( validate(v, dict(operator='', operand=0)), - dict(operator=u'Please enter a value')) + dict(operator='Please enter a value')) + class TestRequireIfMatchingValidator(unittest.TestCase): @@ -753,14 +760,14 @@ def test_matching(self): v = self.validator('phone_type', 'mobile', required_fields=['mobile']) self.assertEqual( validate(v, dict(phone_type='mobile')), - dict(mobile=u'Please enter a value') + dict(mobile='Please enter a value') ) def test_matching_multiple_required(self): v = self.validator('phone_type', 'mobile', required_fields=['mobile', 'code']) self.assertEqual( validate(v, dict(phone_type='mobile')), - dict(mobile=u'Please enter a value') + dict(mobile='Please enter a value') ) def test_not_matching(self): diff --git a/formencode/tests/test_variabledecode.py b/formencode/tests/test_variabledecode.py index 6fe53f58..d7b6583b 100644 --- a/formencode/tests/test_variabledecode.py +++ b/formencode/tests/test_variabledecode.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import unittest from formencode.variabledecode import variable_decode, variable_encode diff --git a/formencode/validators.py b/formencode/validators.py index 66f1aacc..966b92dc 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -4,12 +4,16 @@ """ Validator/Converters for use with FormEncode. """ +from __future__ import absolute_import import cgi import locale import re import warnings from encodings import idna +import six +from six.moves import map +from six.moves import range try: # import dnspython import dns.resolver @@ -513,13 +517,13 @@ class Regex(FancyValidator): def __init__(self, *args, **kw): FancyValidator.__init__(self, *args, **kw) - if isinstance(self.regex, basestring): + if isinstance(self.regex, six.string_types): ops = 0 - assert not isinstance(self.regexOps, basestring), ( + assert not isinstance(self.regexOps, six.string_types), ( "regexOps should be a list of options from the re module " "(names, or actual values)") for op in self.regexOps: - if isinstance(op, basestring): + if isinstance(op, six.string_types): ops |= getattr(re, op) else: ops |= op @@ -527,13 +531,13 @@ def __init__(self, *args, **kw): def _validate_python(self, value, state): self.assert_string(value, state) - if self.strip and isinstance(value, basestring): + if self.strip and isinstance(value, six.string_types): value = value.strip() if not self.regex.search(value): raise Invalid(self.message('invalid', state), value, state) def _convert_to_python(self, value, state): - if self.strip and isinstance(value, basestring): + if self.strip and isinstance(value, six.string_types): return value.strip() return value @@ -615,7 +619,7 @@ def _validate_python(self, value, state): try: items = '; '.join(map(str, self.list)) except UnicodeError: - items = '; '.join(map(unicode, self.list)) + items = '; '.join(map(six.text_type, self.list)) raise Invalid( self.message('notIn', state, items=items, value=value), value, state) @@ -690,13 +694,13 @@ def _convert_to_python(self, value, state): state, items=items), value, state) def _convert_from_python(self, value, state): - for k, v in self.dict.iteritems(): + for k, v in six.iteritems(self.dict): if value == v: return k if self.hideDict: raise Invalid(self.message('valueNotFound', state), value, state) else: - items = '; '.join(map(repr, self.dict.itervalues())) + items = '; '.join(map(repr, six.itervalues(self.dict))) raise Invalid( self.message('chooseValue', state, value=repr(value), items=items), value, state) @@ -812,8 +816,8 @@ class DateValidator(FancyValidator): def _validate_python(self, value, state): date_format = self.message('date_format', state) - if (str is not unicode # Python 2 - and isinstance(date_format, unicode)): + if (str is not six.text_type # Python 2 + and isinstance(date_format, six.text_type)): # strftime uses the locale encoding, not Unicode encoding = locale.getlocale(locale.LC_TIME)[1] or 'utf-8' date_format = date_format.encode(encoding) @@ -1061,27 +1065,27 @@ def __initargs__(self, new_attrs): def _convert_to_python(self, value, state): if value is None: value = '' - elif not isinstance(value, basestring): + elif not isinstance(value, six.string_types): try: value = bytes(value) except UnicodeEncodeError: - value = unicode(value) - if self.encoding is not None and isinstance(value, unicode): + value = six.text_type(value) + if self.encoding is not None and isinstance(value, six.text_type): value = value.encode(self.encoding) return value def _convert_from_python(self, value, state): if value is None: value = '' - elif not isinstance(value, basestring): + elif not isinstance(value, six.string_types): if isinstance(value, (list, tuple)): value = self.list_joiner.join( self._convert_from_python(v, state) for v in value) try: value = str(value) except UnicodeEncodeError: - value = unicode(value) - if self.encoding is not None and isinstance(value, unicode): + value = six.text_type(value) + if self.encoding is not None and isinstance(value, six.text_type): value = value.encode(self.encoding) if self.strip: value = value.strip() @@ -1092,11 +1096,11 @@ def _validate_other(self, value, state): return if value is None: value = '' - elif not isinstance(value, basestring): + elif not isinstance(value, six.string_types): try: value = str(value) except UnicodeEncodeError: - value = unicode(value) + value = six.text_type(value) if self.max is not None and len(value) > self.max: raise Invalid( self.message('tooLong', state, max=self.max), value, state) @@ -1148,19 +1152,19 @@ def __init__(self, **kw): def _convert_to_python(self, value, state): if not value: - return u'' - if isinstance(value, unicode): + return six.u('') + if isinstance(value, six.text_type): return value - if not isinstance(value, unicode): + if not isinstance(value, six.text_type): if hasattr(value, '__unicode__'): - value = unicode(value) + value = six.text_type(value) return value - if not (unicode is str # Python 3 + if not (six.text_type is str # Python 3 and isinstance(value, bytes) and self.inputEncoding): value = str(value) - if self.inputEncoding and not isinstance(value, unicode): + if self.inputEncoding and not isinstance(value, six.text_type): try: - value = unicode(value, self.inputEncoding) + value = six.text_type(value, self.inputEncoding) except UnicodeDecodeError: raise Invalid(self.message('badEncoding', state), value, state) except TypeError: @@ -1170,22 +1174,22 @@ def _convert_to_python(self, value, state): return value def _convert_from_python(self, value, state): - if not isinstance(value, unicode): + if not isinstance(value, six.text_type): if hasattr(value, '__unicode__'): - value = unicode(value) + value = six.text_type(value) else: value = str(value) - if self.outputEncoding and isinstance(value, unicode): + if self.outputEncoding and isinstance(value, six.text_type): value = value.encode(self.outputEncoding) return value def empty_value(self, value): - return u'' + return six.u('') # Provide proper alias for native strings -String = UnicodeString if str is unicode else ByteString +String = UnicodeString if str is six.text_type else ByteString class Set(FancyValidator): @@ -1353,7 +1357,7 @@ def _validate_python(self, value, state): value, state) try: idna_domain = [idna.ToASCII(p) for p in domain.split('.')] - if unicode is str: # Python 3 + if six.text_type is str: # Python 3 idna_domain = [p.decode('ascii') for p in idna_domain] idna_domain = '.'.join(idna_domain) except UnicodeError: @@ -1526,7 +1530,7 @@ def _convert_to_python(self, value, state): def _encode_idna(self, url): global urlparse if urlparse is None: - import urlparse + from six.moves.urllib import parse as urlparse try: scheme, netloc, path, params, query, fragment = urlparse.urlparse( url) @@ -1534,7 +1538,7 @@ def _encode_idna(self, url): return url try: netloc = netloc.encode('idna') - if unicode is str: # Python 3 + if six.text_type is str: # Python 3 netloc = netloc.decode('ascii') return str(urlparse.urlunparse((scheme, netloc, path, params, query, fragment))) @@ -1544,9 +1548,9 @@ def _encode_idna(self, url): def _check_url_exists(self, url, state): global httplib, urlparse, socket if httplib is None: - import httplib + import six.moves.http_client if urlparse is None: - import urlparse + from six.moves.urllib import parse as urlparse if socket is None: import socket scheme, netloc, path, params, query, fragment = urlparse.urlparse( @@ -1556,16 +1560,16 @@ def _check_url_exists(self, url, state): if query: path += '?' + query try: - conn = (httplib.HTTPSConnection if scheme == 'https' - else httplib.HTTPConnection)(netloc) + conn = (six.moves.http_client.HTTPSConnection if scheme == 'https' + else six.moves.http_client.HTTPConnection)(netloc) try: conn.request('HEAD', path) res = conn.getresponse() finally: conn.close() - except httplib.HTTPException as e: + except six.moves.http_client.HTTPException as e: e = str(e) - if str is not unicode: # Python 2 + if str is not six.text_type: # Python 2 try: e = e.decode('utf-8') except UnicodeDecodeError: @@ -1577,7 +1581,7 @@ def _check_url_exists(self, url, state): self.message('httpError', state, error=e), state, url) except socket.error as e: e = str(e) - if str is not unicode: # Python 2 + if str is not six.text_type: # Python 2 try: e = e.decode('utf-8') except UnicodeDecodeError: @@ -1706,7 +1710,7 @@ def _validate_python(self, value, state=None): is not valid. """ - if not isinstance(value, basestring): + if not isinstance(value, six.string_types): raise Invalid( self.message('badType', state, type=str(type(value)), value=value), value, state) @@ -1886,7 +1890,7 @@ def _convert_to_python(self, value, state): if isinstance(upload, cgi.FieldStorage): filename = upload.filename content = upload.value - elif isinstance(upload, basestring) and upload: + elif isinstance(upload, six.string_types) and upload: filename = None # @@: Should this encode upload if it is unicode? content = upload @@ -2303,7 +2307,7 @@ def _to_python_tuple(self, value, state): return hour, minute, second def _convert_from_python(self, value, state): - if isinstance(value, basestring): + if isinstance(value, six.string_types): return value if hasattr(value, 'hour'): hour, minute = value.hour, value.minute @@ -2444,7 +2448,7 @@ class StringBool(FancyValidator): # originally from TurboGears 1 string=_('Value should be %(true)r or %(false)r')) def _convert_to_python(self, value, state): - if isinstance(value, basestring): + if isinstance(value, six.string_types): value = value.strip().lower() if value in self.true_values: return True @@ -2514,7 +2518,7 @@ def make_nonce(self): if not random: import random return ''.join(chr(random.randrange(256)) - for _i in xrange(self.nonce_length)) + for _i in range(self.nonce_length)) class IPAddress(FancyValidator): @@ -2872,7 +2876,7 @@ def _validate_python(self, field_dict, state): else: errors[name] = self.message('invalidNoMatch', state) if errors: - error_list = sorted(errors.iteritems()) + error_list = sorted(six.iteritems(errors)) error_message = '
\n'.join( '%s: %s' % (name, value) for name, value in error_list) raise Invalid(error_message, field_dict, state, error_dict=errors) @@ -2931,7 +2935,7 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(errors.iteritems()) + error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join('%s: %s' % (name, value) for name, value in error_list), @@ -2948,7 +2952,7 @@ def _validateReturn(self, field_dict, state): number = number.replace(' ', '') number = number.replace('-', '') try: - long(number) + int(number) except ValueError: return {self.cc_number_field: self.message('notANumber', state)} assert ccType in self._cardInfo, ( @@ -3046,7 +3050,7 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(errors.iteritems()) + error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join('%s: %s' % (name, value) for name, value in error_list), @@ -3120,7 +3124,7 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(errors.iteritems()) + error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join('%s: %s' % (name, value) for name, value in error_list), @@ -3143,7 +3147,7 @@ def _validateReturn(self, field_dict, state): def validators(): """Return the names of all validators in this module.""" - return [name for name, value in globals().iteritems() + return [name for name, value in six.iteritems(globals()) if isinstance(value, type) and issubclass(value, Validator)] __all__ = ['Invalid'] + validators() diff --git a/formencode/variabledecode.py b/formencode/variabledecode.py index 41c285b3..7f93416c 100644 --- a/formencode/variabledecode.py +++ b/formencode/variabledecode.py @@ -19,8 +19,11 @@ and list_char keyword args. For example, to have the GET/POST variables, ``a_1=something`` as a list, you would use a ``list_char='_'``. """ +from __future__ import absolute_import from .api import FancyValidator +import six +from six.moves import range __all__ = ['variable_decode', 'variable_encode', 'NestedVariables'] @@ -39,7 +42,7 @@ def variable_decode(d, dict_char='.', list_char='-'): result = {} dicts_to_sort = set() known_lengths = {} - for key, value in d.iteritems(): + for key, value in six.iteritems(d): keys = key.split(dict_char) new_keys = [] was_repetition_count = False @@ -101,10 +104,10 @@ def variable_decode(d, dict_char='.', list_char='-'): to_sort = to_sort[sub_key] if None in to_sort: none_values = [(0, x) for x in to_sort.pop(None)] - none_values.extend(to_sort.iteritems()) + none_values.extend(six.iteritems(to_sort)) to_sort = none_values else: - to_sort = to_sort.iteritems() + to_sort = six.iteritems(to_sort) to_sort = [x[1] for x in sorted(to_sort, key=_sort_key)] if key in known_lengths: if len(to_sort) < known_lengths[key]: @@ -120,7 +123,7 @@ def variable_encode(d, prepend='', result=None, add_repetitions=True, if result is None: result = {} if isinstance(d, dict): - for key, value in d.iteritems(): + for key, value in six.iteritems(d): if key is None: name = prepend elif not prepend: diff --git a/setup.py b/setup.py index b3d3837c..7ec8e337 100755 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ The official repo is at GitHub: https://github.com/formencode/formencode """ +from __future__ import absolute_import import sys from setuptools import setup, find_packages @@ -43,8 +44,8 @@ include_package_data=True, package_data={'formencode': ['../docs/*.txt']}, test_suite='formencode.tests', + install_requires=['six'], tests_require=tests_require, extras_require={'testing': tests_require}, - use_2to3=True, convert_2to3_doctests=doctests, ) From 65cabae608ac5c8835f9ab73d77b9cf03bbc6c08 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Wed, 9 Dec 2015 00:09:45 -0500 Subject: [PATCH 046/123] Universal wheel --- setup.cfg | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8eae3a33..719fafca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,5 +26,4 @@ output_dir = formencode/i18n previous = true [wheel] -universal = 0 - +universal = 1 From 8712812e434aefd8e00c29700c62c2001e775c22 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Wed, 9 Dec 2015 20:12:22 -0500 Subject: [PATCH 047/123] Fix some failing tests for 2.6, 3.1 and 3.2 --- formencode/rewritingparser.py | 4 ++-- formencode/tests/test_htmlfill.py | 2 +- formencode/validators.py | 22 +++++++++++----------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/formencode/rewritingparser.py b/formencode/rewritingparser.py index 29bf6b7b..539c3f21 100644 --- a/formencode/rewritingparser.py +++ b/formencode/rewritingparser.py @@ -62,7 +62,7 @@ def _sub_entityref(self, match): # If we don't recognize it, pass it through as though it # wasn't an entity ref at all return match.group(0) - return unichr(name2codepoint[name]) + return six.unichr(name2codepoint[name]) def _sub_charref(self, match): num = match.group(1) @@ -70,7 +70,7 @@ def _sub_charref(self, match): num = int(num[1:], 16) else: num = int(num) - return unichr(num) + return six.unichr(num) def handle_misc(self, whatever): self.write_pos() diff --git a/formencode/tests/test_htmlfill.py b/formencode/tests/test_htmlfill.py index 1a67d597..431e03bf 100644 --- a/formencode/tests/test_htmlfill.py +++ b/formencode/tests/test_htmlfill.py @@ -48,7 +48,7 @@ def run_filename(filename): data_content = '' namespace = {} if data_content: - exec(data_content, namespace) + six.exec_(data_content, namespace) data = namespace.copy() data['defaults'] = data.get('defaults', {}) if 'check' in data: diff --git a/formencode/validators.py b/formencode/validators.py index 966b92dc..4d1ef650 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1129,12 +1129,12 @@ class UnicodeString(ByteString): :: - >>> UnicodeString().to_python(None) - u'' - >>> UnicodeString().to_python([]) - u'' - >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') - u'Ni Ni Ni' + >>> UnicodeString().to_python(None) == six.u('') + True + >>> UnicodeString().to_python([]) == six.u('') + True + >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') == six.u('Ni Ni Ni') + True """ encoding = 'utf-8' @@ -1304,8 +1304,8 @@ class Email(FancyValidator): Traceback (most recent call last): ... Invalid: The domain of the email address does not exist (the portion after the @: thisdomaindoesnotexistithinkforsure.com) - >>> e.to_python(u'test@google.com') - u'test@google.com' + >>> e.to_python('test@google.com') + 'test@google.com' >>> e = Email(not_empty=False) >>> e.to_python('') @@ -1460,13 +1460,13 @@ class URL(FancyValidator): You may set allow_idna to False to change this behavior:: >>> URL(allow_idna=True).to_python( - ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') + ... six.u('http://\u0433\u0443\u0433\u043b.\u0440\u0444')) 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=True, add_http=True).to_python( - ... u'\u0433\u0443\u0433\u043b.\u0440\u0444') + ... six.u('\u0433\u0443\u0433\u043b.\u0440\u0444')) 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=False).to_python( - ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') + ... six.u('http://\u0433\u0443\u0433\u043b.\u0440\u0444')) Traceback (most recent call last): ... Invalid: That is not a valid URL From da7f8fb3aa3382d563141e653dbfa81147d8d72c Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 2 Jan 2016 20:04:09 -0500 Subject: [PATCH 048/123] Fix some six imports and update trove classifiers --- formencode/rewritingparser.py | 10 +++++----- formencode/validators.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/formencode/rewritingparser.py b/formencode/rewritingparser.py index 539c3f21..a9d62e58 100644 --- a/formencode/rewritingparser.py +++ b/formencode/rewritingparser.py @@ -1,6 +1,6 @@ from __future__ import absolute_import -import six.moves.html_parser +from six.moves import html_parser import re import six from six.moves import range @@ -27,7 +27,7 @@ def html_quote(v): return escape(v, True) -class RewritingParser(six.moves.html_parser.HTMLParser): +class RewritingParser(html_parser.HTMLParser): listener = None skip_next = False @@ -35,9 +35,9 @@ class RewritingParser(six.moves.html_parser.HTMLParser): def __init__(self): self._content = [] try: - six.moves.html_parser.HTMLParser.__init__(self, convert_charrefs=False) + html_parser.HTMLParser.__init__(self, convert_charrefs=False) except TypeError: # Python < 3.4 - six.moves.html_parser.HTMLParser.__init__(self) + html_parser.HTMLParser.__init__(self) def feed(self, data): self.data_is_str = isinstance(data, str) @@ -46,7 +46,7 @@ def feed(self, data): self.source_pos = 1, 0 if self.listener: self.listener.reset() - six.moves.html_parser.HTMLParser.feed(self, data) + html_parser.HTMLParser.feed(self, data) _entityref_re = re.compile('&([a-zA-Z][-.a-zA-Z\d]*);') _charref_re = re.compile('&#(\d+|[xX][a-fA-F\d]+);') diff --git a/formencode/validators.py b/formencode/validators.py index 4d1ef650..0e158024 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -25,7 +25,7 @@ # These are only imported when needed -httplib = None +http_client = None random = None sha1 = None socket = None @@ -1546,9 +1546,9 @@ def _encode_idna(self, url): return url def _check_url_exists(self, url, state): - global httplib, urlparse, socket - if httplib is None: - import six.moves.http_client + global http_client, urlparse, socket + if http_client is None: + from six.moves import http_client if urlparse is None: from six.moves.urllib import parse as urlparse if socket is None: @@ -1560,14 +1560,14 @@ def _check_url_exists(self, url, state): if query: path += '?' + query try: - conn = (six.moves.http_client.HTTPSConnection if scheme == 'https' - else six.moves.http_client.HTTPConnection)(netloc) + conn = (http_client.HTTPSConnection if scheme == 'https' + else http_client.HTTPConnection)(netloc) try: conn.request('HEAD', path) res = conn.getresponse() finally: conn.close() - except six.moves.http_client.HTTPException as e: + except http_client.HTTPException as e: e = str(e) if str is not six.text_type: # Python 2 try: From 4d2e010d681ca401c39f5397e72f9dc1a0a6b680 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 2 Jan 2016 22:11:29 -0500 Subject: [PATCH 049/123] Trove classifiers for Python versions --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index 7ec8e337..2ed10762 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,12 @@ "License :: OSI Approved :: MIT", "Programming Language :: Python", "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries :: Python Modules", ], author='Ian Bicking', From 844fdff3c02dd619c09cccbee064ca0ab7858986 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sun, 3 Jan 2016 20:44:39 -0500 Subject: [PATCH 050/123] Add Bo Du contributor license change consent --- CONTRIBUTORS.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index ab0bcbb0..d368b291 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -98,6 +98,8 @@ Contributors - Bradley Arsenault, 2015-12-05 + - Bo Du, 2015-12-28 + The following people made their contributions under the PSF license and did not respond to requests to approve the change to the MIT license. If you are @@ -107,7 +109,6 @@ contributions. - Patrick Hunt - - Bo Du - Dennis Muhlestein From 3a1ae8e8530087953d6f9d14c168f82babecd66a Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 23 Jan 2016 12:41:54 -0500 Subject: [PATCH 051/123] Remove six.u('') in favour of u'' --- docs/conf.py | 13 ++++++------- formencode/tests/test_htmlgen.py | 4 ++-- formencode/validators.py | 19 ++++++++++--------- setup.cfg | 6 ++++++ 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5cb0d37a..9a8ca9a1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,6 @@ # serve to show the default. import sys, os -import six # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -42,8 +41,8 @@ master_doc = 'index' # General information about the project. -project = six.u('FormEncode') -copyright = six.u('2008-2012, Ian Bicking and Contributors') +project = u'FormEncode' +copyright = u'2008-2012, Ian Bicking and Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -181,8 +180,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'FormEncode.tex', six.u('FormEncode Documentation'), - six.u('Ian Bicking'), 'manual'), + ('index', 'FormEncode.tex', u'FormEncode Documentation', + u'Ian Bicking', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -214,6 +213,6 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'formencode', six.u('FormEncode Documentation'), - [six.u('Ian Bicking')], 1) + ('index', 'formencode', u'FormEncode Documentation', + [u'Ian Bicking'], 1) ] diff --git a/formencode/tests/test_htmlgen.py b/formencode/tests/test_htmlgen.py index 3006466d..9dccf5bf 100644 --- a/formencode/tests/test_htmlgen.py +++ b/formencode/tests/test_htmlgen.py @@ -7,7 +7,7 @@ import six # A test value that can't be encoded as ascii: -uni_value = six.u('\xff') +uni_value = u'\xff' str_value = uni_value if str is six.text_type else uni_value.encode('utf-8') @@ -40,7 +40,7 @@ def test_unicode(): assert False, ( "We need something that can't be ASCII-encoded: %r (%r)" % (uni_value, uni_value.encode('ascii'))) - assert six.text_type(html.b(uni_value)) == six.u('%s') % uni_value + assert six.text_type(html.b(uni_value)) == u'%s' % uni_value def test_quote(): diff --git a/formencode/validators.py b/formencode/validators.py index 0e158024..bc5a8721 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1129,11 +1129,11 @@ class UnicodeString(ByteString): :: - >>> UnicodeString().to_python(None) == six.u('') + >>> UnicodeString().to_python(None) == u'' True - >>> UnicodeString().to_python([]) == six.u('') + >>> UnicodeString().to_python([]) == u'' True - >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') == six.u('Ni Ni Ni') + >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') == u'Ni Ni Ni' True """ @@ -1152,7 +1152,7 @@ def __init__(self, **kw): def _convert_to_python(self, value, state): if not value: - return six.u('') + return u'' if isinstance(value, six.text_type): return value if not isinstance(value, six.text_type): @@ -1184,7 +1184,7 @@ def _convert_from_python(self, value, state): return value def empty_value(self, value): - return six.u('') + return u'' # Provide proper alias for native strings @@ -1460,13 +1460,13 @@ class URL(FancyValidator): You may set allow_idna to False to change this behavior:: >>> URL(allow_idna=True).to_python( - ... six.u('http://\u0433\u0443\u0433\u043b.\u0440\u0444')) + ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=True, add_http=True).to_python( - ... six.u('\u0433\u0443\u0433\u043b.\u0440\u0444')) + ... u'\u0433\u0443\u0433\u043b.\u0440\u0444') 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=False).to_python( - ... six.u('http://\u0433\u0443\u0433\u043b.\u0440\u0444')) + ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') Traceback (most recent call last): ... Invalid: That is not a valid URL @@ -3126,7 +3126,8 @@ def _validate_python(self, field_dict, state): if errors: error_list = sorted(six.iteritems(errors)) raise Invalid( - '
\n'.join('%s: %s' % (name, value) + '
\n'.join( + '%s: %s' % (name, value) for name, value in error_list), field_dict, state, error_dict=errors) diff --git a/setup.cfg b/setup.cfg index 719fafca..7ada280c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,3 +27,9 @@ previous = true [wheel] universal = 1 + +[pep8] +max_line_length=100 + +[flake8] +max_line_length=100 From bfab9cce29ae1caca7f04a2843c213757ebd46c8 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 23 Jan 2016 12:48:28 -0500 Subject: [PATCH 052/123] Make travis python version coverage match tox --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4bf41ffa..662acb4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: python python: - 2.6 - 2.7 - - 3.2 - 3.3 - 3.4 - 3.5 From 8fee2d9682468269c7ad17da82b74dc29fbce438 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 23 Jan 2016 12:55:10 -0500 Subject: [PATCH 053/123] Check for PY2 not PY3 --- docs/Validator.txt | 2 +- formencode/tests/test_validators.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Validator.txt b/docs/Validator.txt index 1c31c429..044b9a89 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -66,7 +66,7 @@ sort of feedback. Like: >>> raw_input_input.extend(['ten', '10']) >>> raw_input_input.extend(['bob', 'bob@nowhere.com']) >>> import six - >>> if six.PY3: # Python 3 + >>> if not six.PY2: # Python 3 ... input = raw_input :: diff --git a/formencode/tests/test_validators.py b/formencode/tests/test_validators.py index d588fe6c..6549219e 100644 --- a/formencode/tests/test_validators.py +++ b/formencode/tests/test_validators.py @@ -14,7 +14,7 @@ from formencode.api import NoDefault import six -if six.PY3: +if not six.PY2: unicode = str From 3da7b5a2617b6e8feb64ef1b6c89a6b5c9b5801b Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 23 Jan 2016 12:55:28 -0500 Subject: [PATCH 054/123] Add install error on python 3.2 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2ed10762..d73e53e3 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ version = '1.3.0' -if not '2.6' <= sys.version < '3.0' and not '3.2' <= sys.version: +if not '2.6' <= sys.version < '3.0' and not '3.2' < sys.version: raise ImportError('Python version not supported') tests_require = ['nose', 'pycountry', @@ -23,7 +23,7 @@ setup(name='FormEncode', version=version, - # requires_python='>=2.6,!=3.0,!=3.1', # PEP345 + # requires_python='>=2.6,!=3.0,!=3.1',!=3.2, # PEP345 description="HTML form validation, generation, and conversion package", long_description=__doc__, classifiers=[ From e44443166232617386ad5519705da361fcb21013 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sat, 23 Jan 2016 13:41:23 -0500 Subject: [PATCH 055/123] Bump version and start on what's new docs --- docs/conf.py | 4 ++-- docs/whatsnew-2.0.txt | 17 +++++++++++++++++ setup.py | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 docs/whatsnew-2.0.txt diff --git a/docs/conf.py b/docs/conf.py index 9a8ca9a1..d5f8e417 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '1.3' +version = '2.0.0' # The full version, including alpha/beta/rc tags. -release = '1.3.0dev' +release = '2.0.0a1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt new file mode 100644 index 00000000..e9e5d116 --- /dev/null +++ b/docs/whatsnew-2.0.txt @@ -0,0 +1,17 @@ +What's New In FormEncode 2.0 +============================== + +This article explains the latest changes in `FormEncode` version 2.0 +compared to its predecessor, `FormEncode` 1.3.0 + + +Feature Additions +----------------- + + - `FormEncode` can now run on Python 3.3 and higher without needing to run 2to3 first. + + +Backwards Incompatibilities +--------------------------- + + - `FormEncode` 2.0 is no longer compatible with Python 3.2. If you need Python 3.2 compatibility please use FormEncode 1.3 diff --git a/setup.py b/setup.py index d73e53e3..831ce0fe 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ import sys from setuptools import setup, find_packages -version = '1.3.0' +version = '2.0.0a1' if not '2.6' <= sys.version < '3.0' and not '3.2' < sys.version: raise ImportError('Python version not supported') From 88c4e8cae3975565e65e7f5e751e16c09a5ae982 Mon Sep 17 00:00:00 2001 From: Milos Date: Tue, 23 Feb 2016 12:20:25 +0100 Subject: [PATCH 056/123] Corrected .po and .mo files --- formencode/i18n/sr/LC_MESSAGES/FormEncode.mo | Bin 12732 -> 13274 bytes formencode/i18n/sr/LC_MESSAGES/FormEncode.po | 4 ++-- .../i18n/sr@latin/LC_MESSAGES/FormEncode.mo | Bin 10876 -> 10871 bytes .../i18n/sr@latin/LC_MESSAGES/FormEncode.po | 7 +++---- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo b/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo index 1c4d146e69f491f58142320c0b699d130b9e7a85..a256f1fdf164f663228f09faeda19555cb07b629 100644 GIT binary patch delta 2239 zcmZA2drXye9LMo5nW!8AxyVH>K7yDSkR0V!DhD9}A;H8v2O}j!%$oU6)*r1$ySUm| zonvlY*5>BeoSXcClqJKh+HC%%kQ;64rp?PAxYZU@mu=Pi^PH#F`i<}N`aS1)&hPU5 z{eFk>zAFRKO!osbdnJ(zB|6tEgfVpRM_h_CSc*CG%ogB!EW<6>fCuq0{2o{0G0VHuu7WoX*J&rkE; zM>RNrn(0B*l6;DV_$@BO>!<-Ik`}%1E~la|G@v?)pi&+~HShy=;V&4*M)ItIeu2u^ zSKZ?ybjBD^7G9cH2vrZX~jO@VSJkV2e=#8vQvUMgpcD-_!Q1!hgM-dhVV6f0neaPy^wX$UfP2pJd0}g zA<~3}O5OPJ7+~63yThoCMo?>X3pJAr9%{tRr~$u>HTW5>#YyB{9t!;xV5 zEr!}7BdAkylS?~htc?Ev?Zj&C-61O4ELV|1S`OdRNSja{zJ=QLW2k}7pi);&n&{Jd zPy-r7?TPEC_p6!SUVIf3P@mC-Z6Q=x0ee*EU+LLEy4-%9uf&G9}Q-J7^8zi@owtI0#eVQ1o- biLWPgdt~w*r;=NzGWhXGQNd_x@o>uDeV*E5 delta 1667 zcmYk+U1-&17y$5Bi9FLx^YmAx$&Ll%-V7S#07m&f{+8c#PBeE%Us*ZIaRK;rE%RD2{^PI`|IGIC?g@0#M zVxot`rHuP~*v`X@_0ARyDh$puDv&8JW}YqC&Rc!|Fz50Jqe6qF`9;P~v)2?CY-j9r zBjZS(;tU?;Y<|i}_!mY3g)1CHJTRRRD9xRzgXJ!`HjCc1}Fu~&E_KVz2P zaS{Jvtd~>&*jNwm;tOoy5G7PdoXS=%=6W{s5F^1so*S3azl`ob zr;gDxIng5UCPsk$RJ8OVn|OxZyu|rjDaf7i{`DEW%+rho*Lo!$<6+*<%Z#0O%Wg4u zF&2D>>o~%Dm~+#E+{+gJ#8sTC0pj|0cJU)d#m3jDe{^HL!R_o}l7lzRD=|7^9cEbXpr, 2012. -# Updated by Nina Plakalovic , 2013 +# Updated by Nina Plakalovic , 2013, Miloš Gavrilov, 2016. # msgid "" msgstr "" @@ -373,7 +373,7 @@ msgstr "Молим, унесите датум у облику %(format)s" #: formencode/validators.py:1942 msgid "Please enter a month from 1 to 12" -msgstr "Молим, унесите даутм у облику %(format)s" +msgstr "Молим, унесите месец између 1 и 12" #: formencode/validators.py:1943 msgid "Please enter a valid day" diff --git a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo b/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo index a5cbb6e8464164078c31a0e3ff7ee3a522e75cc8..1b024e572ccffb67324d46a47bba27644be0b0f1 100644 GIT binary patch delta 550 zcmXZYJ4*vW5Ww*fM6n2J3~J(QV@y2b1J6eYLTtoB5UVRvh#+!Bz*I>jh;UeXq|mRR zR@wy8SXf#J5wsGtk6juYIk50QM7Z1E?lR2m%&+s~)X%!Tgov!gMTRhs6L^e^=>PkG z^PInM8^hBgW0=PgETiVB;Uw0PPhMG8@f$T?il7B_(8BYCc#>pu$3+C+P#@C5DGYEN zqcfd{WzcYTP~$5&iayTb4Qin#>caxuz(I=Sa2NI7N3DCE^h5^OJaf{5AGm}8X3u$-Y+g8N!Y`ae4;L{p%Fmd? zHN3!Sd_smH4b+4_vg`X*4B#1V;58cfjT*m!K{RN89uqo$4X}$kx(b?DMeXbjHBlXP zq+PMrsHadn*g~B|88z?~&fpy`;3tlw!Lou$Y(p(p8>yXiJO=}NS-Vs)ZL_#@u%EY0 SE1D}F6|$w+acw(ncK!j&*+t0! diff --git a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po b/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po index b44fffda..13e0fec5 100644 --- a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po +++ b/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po @@ -2,7 +2,7 @@ # Copyright (C) 2015 Laboratorijski informacioni sistemi # This file is distributed under the same license as the FormEncode project. # Andrija Zaric , 2012. -# Updated by Miloš Gavrilov, 2015 +# Updated by Miloš Gavrilov, 2016. # msgid "" msgstr "" @@ -373,7 +373,7 @@ msgstr "Molim, unesite datum u obliku %(format)s" #: formencode/validators.py:1942 msgid "Please enter a month from 1 to 12" -msgstr "Molim, unesite dautm u obliku %(format)s" +msgstr "Molim, unesite mesec između 1 i 12" #: formencode/validators.py:1943 msgid "Please enter a valid day" @@ -537,7 +537,6 @@ msgstr "Nevaljan rok isteka" msgid "Please enter numbers only for credit card security code" msgstr "Molim, unesite cifre samo za sigurnosni kod kartice" -#: formencode/validators.py:2965 +#: formencode/validators.py:2965 msgid "Invalid credit card security code length" msgstr "Nevaljana dužina sigurnosnog koda kartice" - From 7e6c0c97feccd8482b27b1dcdbe92c1783884bf1 Mon Sep 17 00:00:00 2001 From: Elmer de Looff Date: Thu, 7 Apr 2016 14:47:21 +0200 Subject: [PATCH 057/123] Corrected Greek postal code number grouping. --- formencode/national.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/national.py b/formencode/national.py index 7881f251..6b818aff 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -477,7 +477,7 @@ class PostalCodeInCountryFormat(FancyValidator): 'FR': lambda: DelimitedDigitsPostalCode(5), 'GB': UKPostalCode, 'GF': lambda: DelimitedDigitsPostalCode(5), - 'GR': lambda: DelimitedDigitsPostalCode([2, 3], ' '), + 'GR': lambda: DelimitedDigitsPostalCode([3, 2], ' '), 'HN': lambda: DelimitedDigitsPostalCode(5), 'HT': FourDigitsPostalCode, 'HU': FourDigitsPostalCode, From af14c6c16a2f049a5ddd73d98cb728169e38045a Mon Sep 17 00:00:00 2001 From: Elmer de Looff Date: Thu, 7 Apr 2016 15:10:29 +0200 Subject: [PATCH 058/123] Added postal code validation for Switzerland, Cyprus, Faroe Islands, San Marino, Ukraine and Vatican City. --- formencode/national.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/formencode/national.py b/formencode/national.py index 6b818aff..cecc4bcf 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -466,14 +466,17 @@ class PostalCodeInCountryFormat(FancyValidator): 'BE': FourDigitsPostalCode, 'BG': FourDigitsPostalCode, 'CA': CanadianPostalCode, + 'CH': FourDigitsPostalCode, 'CL': lambda: DelimitedDigitsPostalCode(7), 'CN': lambda: DelimitedDigitsPostalCode(6), 'CR': FourDigitsPostalCode, + 'CY': FourDigitsPostalCode, 'DE': GermanPostalCode, 'DK': FourDigitsPostalCode, 'DO': lambda: DelimitedDigitsPostalCode(5), 'ES': lambda: DelimitedDigitsPostalCode(5), 'FI': lambda: DelimitedDigitsPostalCode(5), + 'FO': lambda: DelimitedDigitsPostalCode(3), 'FR': lambda: DelimitedDigitsPostalCode(5), 'GB': UKPostalCode, 'GF': lambda: DelimitedDigitsPostalCode(5), @@ -497,8 +500,11 @@ class PostalCodeInCountryFormat(FancyValidator): 'RO': lambda: DelimitedDigitsPostalCode(6), 'SE': lambda: DelimitedDigitsPostalCode([3, 2], ' '), 'SG': lambda: DelimitedDigitsPostalCode(6), + 'SM': lambda: DelimitedDigitsPostalCode(5), 'US': USPostalCode, + 'UA': lambda: DelimitedDigitsPostalCode(5), 'UY': lambda: DelimitedDigitsPostalCode(5), + 'VA': lambda: DelimitedDigitsPostalCode(5), } def _validate_python(self, fields_dict, state): From 1fdeaabe07167b657db455b3053d7d09d6784f60 Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 6 Jun 2016 13:45:05 +0200 Subject: [PATCH 059/123] Fix a collection of errors from msgfmt. Fix a number of minor issues that msgfmt marks as errors. This includes missing format specifiers, invalid format strings due to translating the substiution name and typos in the translated format string. --- formencode/i18n/it/LC_MESSAGES/FormEncode.po | 2 +- formencode/i18n/ja/LC_MESSAGES/FormEncode.po | 2 +- formencode/i18n/ko/LC_MESSAGES/FormEncode.po | 2 +- formencode/i18n/nl/LC_MESSAGES/FormEncode.po | 8 +++++--- formencode/i18n/sl/LC_MESSAGES/FormEncode.po | 2 -- formencode/i18n/sv/LC_MESSAGES/FormEncode.po | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/formencode/i18n/it/LC_MESSAGES/FormEncode.po b/formencode/i18n/it/LC_MESSAGES/FormEncode.po index f9a23042..52908510 100644 --- a/formencode/i18n/it/LC_MESSAGES/FormEncode.po +++ b/formencode/i18n/it/LC_MESSAGES/FormEncode.po @@ -440,7 +440,7 @@ msgstr "Inserire i minuti ( dopo i : )" #: formencode/validators.py:2075 #, python-format msgid "The %(part)s value you gave is not a number: %(number)r" -msgstr "%(part), non e' un numero: %(number)r" +msgstr "%(part)s, non e' un numero: %(number)r" #: formencode/validators.py:2076 #, python-format diff --git a/formencode/i18n/ja/LC_MESSAGES/FormEncode.po b/formencode/i18n/ja/LC_MESSAGES/FormEncode.po index e6116866..3d1bdc89 100644 --- a/formencode/i18n/ja/LC_MESSAGES/FormEncode.po +++ b/formencode/i18n/ja/LC_MESSAGES/FormEncode.po @@ -494,7 +494,7 @@ msgstr "" #: formencode/validators.py:2586 #, python-format msgid "You must give a value for %s" -msgstr "% の値を入力してください" +msgstr "%s の値を入力してください" #: formencode/validators.py:2619 #, python-format diff --git a/formencode/i18n/ko/LC_MESSAGES/FormEncode.po b/formencode/i18n/ko/LC_MESSAGES/FormEncode.po index def49682..e2499925 100644 --- a/formencode/i18n/ko/LC_MESSAGES/FormEncode.po +++ b/formencode/i18n/ko/LC_MESSAGES/FormEncode.po @@ -327,7 +327,7 @@ msgstr "올바른 URL이 아닙니다" #: formencode/validators.py:1455 #, python-format msgid "An error occurred when trying to access the URL: %(error)s" -msgstr "URL에 접속 시도중 오류가 발생했습니다: $(error)s" +msgstr "URL에 접속 시도중 오류가 발생했습니다: %(error)s" #: formencode/validators.py:1459 msgid "The server responded that the page could not be found" diff --git a/formencode/i18n/nl/LC_MESSAGES/FormEncode.po b/formencode/i18n/nl/LC_MESSAGES/FormEncode.po index aa904afc..1fd58f20 100644 --- a/formencode/i18n/nl/LC_MESSAGES/FormEncode.po +++ b/formencode/i18n/nl/LC_MESSAGES/FormEncode.po @@ -292,7 +292,7 @@ msgstr "" msgid "" "The username portion of the email address is invalid (the portion before the " "@: %(username)s)" -msgstr "Ongeldig emailadres: het deel vóór de \"@\" is incorrect" +msgstr "Ongeldig emailadres: het deel vóór de \"@: %(username)s\" is incorrect" #: formencode/validators.py:1271 formencode/validators.py:1413 #, python-format @@ -305,7 +305,8 @@ msgid "" "The domain portion of the email address is invalid (the portion after the @: " "%(domain)s)" msgstr "" -"Ongeldig emailadres: het \"domain\" (het deel achter de \"@\") is incorrect" +"Ongeldig emailadres: het \"domain\" (het deel achter de \"@: %(domain)s\") " +"is incorrect" #: formencode/validators.py:1275 #, python-format @@ -313,7 +314,8 @@ msgid "" "The domain of the email address does not exist (the portion after the @: " "%(domain)s)" msgstr "" -"Ongeldig emailadres: het \"domain\" (het deel achter de \"@\") bestaat niet" +"Ongeldig emailadres: het \"domain\" (het deel achter de \"@: %(domain)s\") " +"bestaat niet" #: formencode/validators.py:1409 msgid "You must start your URL with http://, https://, etc" diff --git a/formencode/i18n/sl/LC_MESSAGES/FormEncode.po b/formencode/i18n/sl/LC_MESSAGES/FormEncode.po index 2eb35eed..56702f5e 100644 --- a/formencode/i18n/sl/LC_MESSAGES/FormEncode.po +++ b/formencode/i18n/sl/LC_MESSAGES/FormEncode.po @@ -302,8 +302,6 @@ msgstr "Domenski del e-poštnega naslova je neveljaven: %(domain)s" #: formencode/validators.py:1275 #, fuzzy, python-format -#| msgid "" "The domain portion of the email address is invalid (the portion -#| after the" " @: %(domain)s)" msgid "" "The domain of the email address does not exist (the portion after the @: " "%(domain)s)" diff --git a/formencode/i18n/sv/LC_MESSAGES/FormEncode.po b/formencode/i18n/sv/LC_MESSAGES/FormEncode.po index fcb3cf24..aa71a923 100644 --- a/formencode/i18n/sv/LC_MESSAGES/FormEncode.po +++ b/formencode/i18n/sv/LC_MESSAGES/FormEncode.po @@ -118,7 +118,7 @@ msgstr "Det språket inte är upptaget i ISO 639" #: formencode/schema.py:68 #, python-format msgid "The input field %(name)s was not expected." -msgstr "Inmatningsfältet %(namn) var inte förväntat." +msgstr "Inmatningsfältet %(name)s var inte förväntat." #: formencode/schema.py:69 msgid "Missing value" @@ -331,7 +331,7 @@ msgstr "" #: formencode/validators.py:1461 #, python-format msgid "You must provide a full domain name (like %(domain)s.com)" -msgstr "Du måste ange ett komplett domännamn (som %(domän)er.com" +msgstr "Du måste ange ett komplett domännamn (som %(domain)s.com" #: formencode/validators.py:1602 msgid "" From 9f683a6a68bf196a28eed18dcbdf29cb35f69719 Mon Sep 17 00:00:00 2001 From: Elmer de Looff Date: Tue, 12 Jul 2016 15:44:23 +0200 Subject: [PATCH 060/123] Updated postal code format of the Republic of Korea (South Korea). This is in line with announcement 2014-63 by Korea Post. Effective August 1st 2015, the Republic of Korea changed to a 5 digit postal code system. The full announcement can be found on the website of Korea Post: http://www.koreapost.go.kr/notice/eview.action?contId=e3010000&agencyCode=kpost&order=regDt%20desc&page=1&pagesize=10¬iIdx=24114 --- formencode/national.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/national.py b/formencode/national.py index 7881f251..0f1d7221 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -484,7 +484,7 @@ class PostalCodeInCountryFormat(FancyValidator): 'IS': lambda: DelimitedDigitsPostalCode(3), 'IT': lambda: DelimitedDigitsPostalCode(5), 'JP': lambda: DelimitedDigitsPostalCode([3, 4], '-'), - 'KR': lambda: DelimitedDigitsPostalCode([3, 3], '-'), + 'KR': lambda: DelimitedDigitsPostalCode(5), 'LI': FourDigitsPostalCode, 'LU': FourDigitsPostalCode, 'MC': lambda: DelimitedDigitsPostalCode(5), From 53ed3844c22ab5d899083061ca6d93a6a550bea9 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 8 Aug 2016 22:56:33 -0400 Subject: [PATCH 061/123] 2.0.0a1 release --- docs/index.txt | 1 + docs/whatsnew-1.3.txt | 4 +--- docs/whatsnew-2.0.txt | 21 +++++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 9c9f8a6a..c9efb354 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -21,6 +21,7 @@ What's New .. toctree:: :maxdepth: 1 + whatsnew-2.0 whatsnew-1.3 whatsnew-1.2.5 whatsnew-0-to-1.2.4 diff --git a/docs/whatsnew-1.3.txt b/docs/whatsnew-1.3.txt index e194b3d2..bea1951c 100644 --- a/docs/whatsnew-1.3.txt +++ b/docs/whatsnew-1.3.txt @@ -27,6 +27,4 @@ Backwards Incompatibilities - The `FancyValidator` methods `_to_python`, `_from_python`, `validate_python` and `validate_other` have been renamed to `_convert_to_python`, `_convert_from_python`, `_validate_python` and `_validate_other`, respectively. This has been done to clarify that while these methods are meant to be overridden by custom validators, they are not part of the external API. They are only helper methods that are called internally by the external methods `to_python` and `from_python`, which constitute the external API. Particularly, do not assume that `_validate_python` catches all validation errors that a call of `to_python` will catch. Please have a look at the `FancyValidator` docstring and source if you're unsure how these methods work together. For the same reason, the `CompoundValidator` method `attempt_convert` has been renamed to `_attempt_convert`. For now, the old method names will still work, but they will output deprecation warnings. -Documentation Enhancements --------------------------- - + - (1.3.1) Don't use universal wheel diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index e9e5d116..858a0e32 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -1,17 +1,22 @@ What's New In FormEncode 2.0 ============================== -This article explains the latest changes in `FormEncode` version 2.0 +This article explains the latest changes in `FormEncode` version 2.0.0 compared to its predecessor, `FormEncode` 1.3.0 -Feature Additions ------------------ +Changelog +--------- - `FormEncode` can now run on Python 3.3 and higher without needing to run 2to3 first. - - -Backwards Incompatibilities ---------------------------- - - `FormEncode` 2.0 is no longer compatible with Python 3.2. If you need Python 3.2 compatibility please use FormEncode 1.3 + - Add strict flag to USPostalCode to raise error on postal codes that has too + many digits instead of just truncating + - Various Python 3 fixes + - Serbian latin translation + - Changed License to MIT + - Dutch, UK, Greek and South Korean postal code format fixes + - Add postal code formats for Switzerland, Cyprus, Faroe Islands, San Marino, Ukraine and Vatican City. + - Add ISODateTimeConverter validator + - Add ability to target htmlfill to particular form or ignore a form + - Fix format errors in some translations From 6b0775cb71aa373cf15b7416148aa89524a72f50 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 8 Aug 2016 23:02:16 -0400 Subject: [PATCH 062/123] Trove classifier fix --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 831ce0fe..a3204f51 100755 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT", + "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", From 888dfd3f1374f42c5a37d16a9464716ba5f5c7a7 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 8 Aug 2016 23:08:30 -0400 Subject: [PATCH 063/123] Change license to MIT Closes #115 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a3204f51..bda9fdc8 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ author='Ian Bicking', author_email='ianb@colorstudy.com', url='http://formencode.org', - license='PSF', + license='MIT', zip_safe=False, packages=find_packages(), include_package_data=True, From f1819aed6976c76e6b8c4ac966fe165254862437 Mon Sep 17 00:00:00 2001 From: Elmer de Looff Date: Thu, 19 Jan 2017 18:18:49 +0100 Subject: [PATCH 064/123] Updated language/country getters for pycountry 16+ support. --- formencode/national.py | 64 +++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/formencode/national.py b/formencode/national.py index a6467a15..c96d439a 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -86,34 +86,60 @@ def get_language(code): gettext.bindtextdomain('iso639', pycountry.LOCALES_DIR) _l = lambda t: gettext.dgettext('iso639', t) - def get_countries(): - c1 = set([(e.alpha2, _c(e.name)) for e in pycountry.countries]) - ret = c1.union(country_additions + fuzzy_countrynames) - return ret - - def get_country(code): - return _c(pycountry.countries.get(alpha2=code).name) + # pycountry has gone through a bunch of API changes over its lifetime, + # so we detect a working one and roll with it try: - if not pycountry.languages.get(iso639_1_code='en'): - raise KeyError - except KeyError: # pycountry < 1.12 + pycountry.countries.get(alpha_2='US') + except KeyError: + # API for pycountry < 16.10 + def get_countries(): + c1 = set([(e.alpha2, _c(e.name)) for e in pycountry.countries]) + ret = c1.union(country_additions + fuzzy_countrynames) + return ret + + def get_country(code): + return _c(pycountry.countries.get(alpha2=code).name) + else: + # API for pycountry >= 16.10 + def get_countries(): + c1 = set([(e.alpha_2, _c(e.name)) for e in pycountry.countries]) + ret = c1.union(country_additions + fuzzy_countrynames) + return ret - def get_languages(): - return [(e.alpha2, _l(e.name)) for e in pycountry.languages - if e.name and getattr(e, 'alpha2', None)] + def get_country(code): + return _c(pycountry.countries.get(alpha_2=code).name) - def get_language(code): - return _l(pycountry.languages.get(alpha2=code).name) + try: + pycountry.languages.get(alpha_2='en') + except KeyError: + try: + if not pycountry.languages.get(iso639_1_code='en'): + raise KeyError + except KeyError: + # API for pycountry < 1.12 + def get_languages(): + return [(e.alpha2, _l(e.name)) for e in pycountry.languages + if e.name and getattr(e, 'alpha2', None)] - else: + def get_language(code): + return _l(pycountry.languages.get(alpha2=code).name) + else: + # API for pycountry 1.12 up to 16.10 + def get_languages(): + return [(e.iso639_1_code, _l(e.name)) for e in pycountry.languages + if e.name and getattr(e, 'iso639_1_code', None)] + def get_language(code): + return _l(pycountry.languages.get(iso639_1_code=code).name) + else: + # API for pycountry >= 16.10 def get_languages(): - return [(e.iso639_1_code, _l(e.name)) for e in pycountry.languages - if e.name and getattr(e, 'iso639_1_code', None)] + return [(e.alpha_2, _l(e.name)) for e in pycountry.languages + if e.name and getattr(e, 'alpha_2', None)] def get_language(code): - return _l(pycountry.languages.get(iso639_1_code=code).name) + return _l(pycountry.languages.get(alpha_2=code).name) ############################################################ From 37b41a8e6f6f6ec32553198715868575c1378769 Mon Sep 17 00:00:00 2001 From: Elmer de Looff Date: Thu, 19 Jan 2017 18:51:18 +0100 Subject: [PATCH 065/123] Allow for arbitrary delimiters in DelimitedDigitsPostalCode validator. --- formencode/national.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/formencode/national.py b/formencode/national.py index c96d439a..7735d481 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -152,11 +152,17 @@ class DelimitedDigitsPostalCode(Regex): With constant amount of digits. By providing a single digit as partition you can obtain a trivial 'x digits' postal code validator. + For flexibility, input may use additional delimiters or delimters in a + bad position. Only the minimum (or if strict, exact) number of digits has + to be provided. + :: >>> german = DelimitedDigitsPostalCode(5) >>> german.to_python('55555') '55555' + >>> german.to_python('55 55-5') + '55555' >>> german.to_python('5555') Traceback (most recent call last): ... @@ -166,6 +172,8 @@ class DelimitedDigitsPostalCode(Regex): '55-555' >>> polish.to_python('55-555') '55-555' + >>> polish.to_python('555-55') + '55-555' >>> polish.to_python('5555') Traceback (most recent call last): ... @@ -189,10 +197,12 @@ def assembly_formatstring(self, partition_lengths, delimiter): else: return delimiter.join('n' * l for l in partition_lengths) + def assembly_grouping(self, partition_lengths, delimiter): + digit_groups = ['%s' * length for length in partition_lengths] + return delimiter.join(digit_groups) + def assembly_regex(self, partition_lengths, delimiter, strict): - mg = [r'(\d{%d})' % l for l in partition_lengths] - rd = r'\%s?' % delimiter - regex = rd.join(mg) + regex = r'\D*(\d)\D*' * sum(partition_lengths) # anchor the regex so that it wont take zip codes w/ longer # input than what is specified by the partition_lengths @@ -208,6 +218,7 @@ def __init__(self, partition_lengths, delimiter=None, strict=False, if not delimiter: delimiter = '' self.format = self.assembly_formatstring(partition_lengths, delimiter) + self.grouping = self.assembly_grouping(partition_lengths, delimiter) self.regex = self.assembly_regex(partition_lengths, delimiter, strict) self.partition_lengths, self.delimiter = partition_lengths, delimiter Regex.__init__(self, *args, **kw) @@ -222,7 +233,7 @@ def _convert_to_python(self, value, state): raise Invalid( self.message('invalid', state, format=self.format), value, state) - return self.delimiter.join(match.groups()) + return self.grouping % match.groups() def USPostalCode(*args, **kw): From 7f262ac7947331436861fd6e9af77138b1097812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 3 Jan 2017 14:55:35 +0100 Subject: [PATCH 066/123] Updated error message of PlainText validator to reflect the acceptance of the hyphen character --- formencode/validators.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/formencode/validators.py b/formencode/validators.py index bc5a8721..ff58642c 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -556,7 +556,7 @@ class PlainText(Regex): >>> PlainText(accept_python=False).from_python(' this ') Traceback (most recent call last): ... - Invalid: Enter only letters, numbers, or _ (underscore) + Invalid: Enter only letters, numbers, - (hyphen) or _ (underscore) >>> PlainText(strip=True).to_python(' this ') 'this' >>> PlainText(strip=True).from_python(' this ') @@ -566,7 +566,7 @@ class PlainText(Regex): regex = r"^[a-zA-Z_\-0-9]*$" messages = dict( - invalid=_('Enter only letters, numbers, or _ (underscore)')) + invalid=_('Enter only letters, numbers, - (hyphen) or _ (underscore)')) class OneOf(FancyValidator): @@ -612,7 +612,7 @@ def _validate_python(self, value, state): for v in value: self._validate_python(v, state) else: - if not value in self.list: + if value not in self.list: if self.hideList: raise Invalid(self.message('invalid', state), value, state) else: @@ -2340,7 +2340,7 @@ class ISODateTimeConverter(FancyValidator): """ Converts fields which contain both date and time, in the ISO 8601 standard format YYYY-MM-DDTHH:MM:SS. - + Stores in a datetime.datetime object. Examples:: @@ -2356,10 +2356,10 @@ class ISODateTimeConverter(FancyValidator): Invalid: The must enter your date & time in the format YYYY-MM-DDTHH:MM:SS """ - + # This can be set to make it prefer mxDateTime: datetime_module = None - + messages = dict( invalidFormat=_('The must enter your date & time in the format YYYY-MM-DDTHH:MM:SS'),) @@ -2772,6 +2772,7 @@ def _convert_to_python(self, value_dict, state): RequireIfPresent = RequireIfMissing + class RequireIfMatching(FormValidator): """ Require a list of fields based on the value of another field. @@ -2820,6 +2821,7 @@ def _convert_to_python(self, value_dict, state): value_dict.get(required_field), state)}) return value_dict + class FieldsMatch(FormValidator): """ Tests that the given fields match, i.e., are identical. Useful From 92f17bb258189577ffa52b7b0ab37428d42eee8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Gavrilov?= Date: Wed, 15 Mar 2017 14:14:49 +0100 Subject: [PATCH 067/123] Added Macedonian translation --- formencode/i18n/mk/LC_MESSAGES/FormEncode.mo | Bin 0 -> 13895 bytes formencode/i18n/mk/LC_MESSAGES/FormEncode.po | 542 +++++++++++++++++++ 2 files changed, 542 insertions(+) create mode 100644 formencode/i18n/mk/LC_MESSAGES/FormEncode.mo create mode 100644 formencode/i18n/mk/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo b/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo new file mode 100644 index 0000000000000000000000000000000000000000..7fed059648f61096de34c60649c6261e8fa85c78 GIT binary patch literal 13895 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 literal 0 HcmV?d00001 diff --git a/formencode/i18n/mk/LC_MESSAGES/FormEncode.po b/formencode/i18n/mk/LC_MESSAGES/FormEncode.po new file mode 100644 index 00000000..c73c542a --- /dev/null +++ b/formencode/i18n/mk/LC_MESSAGES/FormEncode.po @@ -0,0 +1,542 @@ +# Macedonian strings for FormEncode. +# Copyright (C) 2013 Laboratorijski informacioni sistemi +# This file is distributed under the same license as the FormEncode project. +# Andrija Zaric , 2012. +# Updated by Nina Plakalovic , 2013, Miloš Gavrilov, 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: FormEncode 1.2.4\n" +"Report-Msgid-Bugs-To: formencode-discuss@lists.sf.net\n" +"POT-Creation-Date: 2012-09-14 03:22+0400\n" +"PO-Revision-Date: 2013-05-14 16:11+0100\n" +"Last-Translator: Nina Plakalovic \n" +"Language-Team: Andrija Zaric \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#: formencode/api.py:401 +#: formencode/validators.py:432 +msgid "Please enter a value" +msgstr "Ве молам, внесете вредност" + +#: formencode/api.py:402 +#, python-format +msgid "The input must be a string (not a %(type)s: %(value)r)" +msgstr "Внесот мора да биде стринг (не %(type)s: %(value)r)" + +#: formencode/api.py:403 +msgid "The input must be a string (not None)" +msgstr "Внесот мора да биде стринг (не None)" + +#: formencode/national.py:31 +msgid "Belarus" +msgstr "" + +#: formencode/national.py:32 +msgid "Montenegro" +msgstr "" + +#: formencode/national.py:33 +msgid "Tasmania" +msgstr "" + +#: formencode/national.py:39 +msgid "Britain" +msgstr "" + +#: formencode/national.py:40 +msgid "Great Britain" +msgstr "" + +#: formencode/national.py:41 +msgid "Cote de Ivoire" +msgstr "" + +#: formencode/national.py:146 +#, python-format +msgid "%d digits" +msgstr "%d цифри" + +#: formencode/national.py:167 +#, python-format +msgid "Please enter a zip code (%(format)s)" +msgstr "" + +#: formencode/national.py:231 +#: formencode/national.py:265 +#, python-format +msgid "Please enter a zip code (%s)" +msgstr "Ве молам, внесете го поштенскиот број (%s)" + +#: formencode/national.py:231 +msgid "LnnnnLLL" +msgstr "" + +#: formencode/national.py:265 +msgid "LnL nLn" +msgstr "" + +#: formencode/national.py:299 +msgid "Please enter a valid postal code (for format see BS 7666)" +msgstr "" + +#: formencode/national.py:340 +msgid "That country is not listed in ISO 3166" +msgstr "" + +#: formencode/national.py:395 +msgid "Given postal code does not match the country's format." +msgstr "" + +#: formencode/national.py:486 +msgid "Please enter a state code" +msgstr "" + +#: formencode/national.py:487 +msgid "Please enter a state code with TWO letters" +msgstr "" + +#: formencode/national.py:488 +msgid "That is not a valid state code" +msgstr "Тоа не е точен код на дрзавата" + +#: formencode/national.py:541 +msgid "Please enter a number, with area code, in the form ###-###-####, optionally with \"ext.####\"" +msgstr "" + +#: formencode/national.py:648 +msgid "Please enter a number, with area code, in the form +##-###-#######." +msgstr "" + +#: formencode/national.py:727 +msgid "That language is not listed in ISO 639" +msgstr "Јазикот не е наведен во ISO 639" + +#: formencode/schema.py:68 +#, python-format +msgid "The input field %(name)s was not expected." +msgstr "Поле за внес %(name)s не е очекувано." + +#: formencode/schema.py:69 +msgid "Missing value" +msgstr "Недостасува вредност" + +#: formencode/schema.py:70 +#, python-format +msgid "The input must be dict-like (not a %(type)s: %(value)r)" +msgstr "Внесот мора да биде сличен со речникот (не %(type)s: %(value)r)" + +#: formencode/schema.py:72 +msgid "Please provide only one value" +msgstr "Ве молам проследете само една вредност" + +#: formencode/validators.py:150 +#, python-format +msgid "%(object)r is not a subclass of %(subclass)s" +msgstr "%(object)r не е подкласа %(subclass)s" + +#: formencode/validators.py:151 +#, python-format +msgid "%(object)r is not a subclass of one of the types %(subclassList)s" +msgstr "%(object)r не е подкласата една од типовите %(subclassList)s" + +#: formencode/validators.py:152 +#, python-format +msgid "%(object)r must be one of the types %(typeList)s" +msgstr "%(object)r мора да биде една од типовите %(typeList)s" + +#: formencode/validators.py:153 +#, python-format +msgid "%(object)r must be of the type %(type)s" +msgstr "%(object)r мора да биде од типот %(type)s" + +#: formencode/validators.py:344 +#, python-format +msgid "Enter a value less than %(maxLength)i characters long" +msgstr "Внесете вредност, пократка од %(maxLength)i знаци" + +#: formencode/validators.py:345 +#: formencode/validators.py:399 +msgid "Invalid value (value with length expected)" +msgstr "Неправилен влез (очекувана е вредност со должина)" + +#: formencode/validators.py:398 +#, python-format +msgid "Enter a value at least %(minLength)i characters long" +msgstr "Внесете вредност, во должина најмалку %(minLength)i знаци" + +#: formencode/validators.py:455 +msgid "You cannot enter a value here" +msgstr "Овде не можете да внесете вредност" + +#: formencode/validators.py:504 +msgid "The input is not valid" +msgstr "Внесот не е добар" + +#: formencode/validators.py:557 +msgid "Enter only letters, numbers, or _ (underscore)" +msgstr "Внесете само буква, број или _ (подвлечете)" + +#: formencode/validators.py:595 +msgid "Invalid value" +msgstr "Непосакувана вредност" + +#: formencode/validators.py:596 +#, python-format +msgid "Value must be one of: %(items)s (not %(value)r)" +msgstr "Вредноста мора да биде една од: %(items)s (не %(value)r)" + +#: formencode/validators.py:657 +msgid "Choose something" +msgstr "Изберете нешто" + +#: formencode/validators.py:658 +#, python-format +msgid "Enter a value from: %(items)s" +msgstr "Внесете една од вредностите: %(items)s" + +#: formencode/validators.py:659 +msgid "That value is not known" +msgstr "Вредноста не е позната" + +#: formencode/validators.py:660 +#, python-format +msgid "Nothing in my dictionary goes by the value %(value)s. Choose one of: %(items)s" +msgstr "Речникот не содржи %(value)s. Изберете една од: %(items)s" + +#: formencode/validators.py:724 +msgid "Must be an integer index" +msgstr "Индексот мора да биде цел број" + +#: formencode/validators.py:725 +msgid "Index out of range" +msgstr "Индексот е надвор од опсег" + +#: formencode/validators.py:726 +#, python-format +msgid "Item %(value)s was not found in the list" +msgstr "Елемент %(value)s не е пронајден на листата" + +#: formencode/validators.py:796 +#, python-format +msgid "Date must be after %(date)s" +msgstr "Датумот мора да биде подоцна од %(date)s" + +#: formencode/validators.py:797 +#, python-format +msgid "Date must be before %(date)s" +msgstr "Датумот мора да претходи %(date)s" + +#: formencode/validators.py:799 +#, python-format +msgid "%%A, %%d %%B %%Y" +msgstr "%%A, %%d %%B %%Y" + +#: formencode/validators.py:800 +msgid "The date must be sometime in the future" +msgstr "Датумот мора да биде иден" + +#: formencode/validators.py:901 +#, python-format +msgid "Please enter a number that is %(min)s or greater" +msgstr "Ве молам да внесете број кој е %(min)s или поголем" + +#: formencode/validators.py:902 +#, python-format +msgid "Please enter a number that is %(max)s or smaller" +msgstr "Ве молам да внесете број кој е %(max)s или помал" + +#: formencode/validators.py:939 +msgid "Please enter an integer value" +msgstr "Ве молам внесете цела вредност" + +#: formencode/validators.py:975 +msgid "Please enter a number" +msgstr "Ве молам внесете број" + +#: formencode/validators.py:1036 +#, python-format +msgid "Enter a value not more than %(max)i characters long" +msgstr "Внесете вредност која е најмногу долга %(max)i знаци" + +#: formencode/validators.py:1037 +#, python-format +msgid "Enter a value %(min)i characters long or more" +msgstr "Внесете вредност која е најмалку долга %(min)i знаци" + +#: formencode/validators.py:1119 +msgid "Invalid data or incorrect encoding" +msgstr "Неправилни податоци или неправилно кодирање" + +#: formencode/validators.py:1290 +msgid "Please enter an email address" +msgstr "Ве молам, внесете е-маил адресата" + +#: formencode/validators.py:1291 +msgid "An email address must contain a single @" +msgstr "Е-маил адресата мора да содржи еден @" + +#: formencode/validators.py:1292 +#, python-format +msgid "The username portion of the email address is invalid (the portion before the @: %(username)s)" +msgstr "Корисничкото име во адресата на е-маилот не е правилно (дел пред @): %(username)s)" + +#: formencode/validators.py:1294 +#: formencode/validators.py:1457 +#, python-format +msgid "An error occured when trying to connect to the server: %(error)s" +msgstr "Се појавила грешка при поврзување на серверот: %(error)s" + +#: formencode/validators.py:1296 +#, python-format +msgid "The domain portion of the email address is invalid (the portion after the @: %(domain)s)" +msgstr "Доменот во е-маил адресата не е правилен (делот после @): %(domain)s" + +#: formencode/validators.py:1298 +#, python-format +msgid "The domain of the email address does not exist (the portion after the @: %(domain)s)" +msgstr "Доменот на адресата на е-маилот не постои (делот после @): %(domain)s" + +#: formencode/validators.py:1453 +msgid "You must start your URL with http://, https://, etc" +msgstr "URL мора да започнува со http://, http://, итн" + +#: formencode/validators.py:1454 +msgid "That is not a valid URL" +msgstr "Оваа не е правилна URL" + +#: formencode/validators.py:1455 +#, python-format +msgid "An error occurred when trying to access the URL: %(error)s" +msgstr "Се појавила грешка при повикување на URL-а: %(error)s" + +#: formencode/validators.py:1459 +msgid "The server responded that the page could not be found" +msgstr "Серверот одговара дека страната не е пронајдена" + +#: formencode/validators.py:1460 +#, python-format +msgid "The server responded with a bad status code (%(status)s)" +msgstr "Серверот одговара со грешка за лош статус (%(status)s)" + +#: formencode/validators.py:1461 +#, python-format +msgid "You must provide a full domain name (like %(domain)s.com)" +msgstr "Мора да го впишете однапред полното име на доменот (на пр. %(domain)s.com)" + +#: formencode/validators.py:1602 +msgid "The type of i-name is not defined; it may be either individual or organizational" +msgstr "Не е дефиниран типот i-name" + +#: formencode/validators.py:1604 +msgid "Dots and dashes may not be repeated consecutively" +msgstr "Точки и црти не смеат последователно да се повторуваат" + +#: formencode/validators.py:1605 +#, python-format +msgid "\"%(iname)s\" is an invalid i-name" +msgstr "\"%(iname)s\" не е правилно i-name" + +#: formencode/validators.py:1606 +msgid "i-names may not start with numbers nor punctuation marks" +msgstr "i-names не може да се започне со броеви или интерпункциски знаци" + +#: formencode/validators.py:1608 +#, python-format +msgid "\"%(inumber)s\" is an invalid i-number" +msgstr "\"%(inumber)s\" не е правилно i-number" + +#: formencode/validators.py:1609 +#, python-format +msgid "The XRI must be a string (not a %(type)s: %(value)r)" +msgstr "XRI мора да биде низа (не %(type)s: %(value)r)" + +#: formencode/validators.py:1610 +#, python-format +msgid "\"%(xri_type)s\" is not a valid type of XRI" +msgstr "\"%(xri_type)s\" не е точен XRI типот" + +#: formencode/validators.py:1709 +#, python-format +msgid "\"%(id)s\" is not a valid OpenId (it is neither an URL nor an XRI)" +msgstr "\"%(id)s\" не е точен OpenId (не е ни URL ниту XRI )" + +#: formencode/validators.py:1941 +#: formencode/validators.py:1949 +#, python-format +msgid "Please enter the date in the form %(format)s" +msgstr "Ве молам, внесете датум во облик %(format)s" + +#: formencode/validators.py:1942 +msgid "Please enter a month from 1 to 12" +msgstr "Ве молам, внесете месец измеѓу 1 и 12" + +#: formencode/validators.py:1943 +msgid "Please enter a valid day" +msgstr "Ве молам, внесете точен ден" + +#: formencode/validators.py:1944 +#, python-format +msgid "That month only has %(days)i days" +msgstr "Тој месец има само %(days)i денови" + +#: formencode/validators.py:1945 +#, python-format +msgid "That is not a valid day (%(exception)s)" +msgstr "Денот не е правилен (%(exception)s)" + +#: formencode/validators.py:1946 +#, python-format +msgid "Unknown month name: %(month)s" +msgstr "Непознато име месеца: %(month)s" + +#: formencode/validators.py:1947 +msgid "Please enter a number for the year" +msgstr "Ве молим, внесете број за година" + +#: formencode/validators.py:1948 +msgid "Please enter a four-digit year after 1899" +msgstr "Ве молам, внесете четирицифрена година" + +#: formencode/validators.py:2131 +msgid "You must indicate AM or PM" +msgstr "Мора да назначите пред или после пладне (AM или PM)" + +#: formencode/validators.py:2132 +msgid "There are too many :'s" +msgstr "Има премногу :'s" + +#: formencode/validators.py:2133 +msgid "You may not enter seconds" +msgstr "Не можете да внесете секунди" + +#: formencode/validators.py:2134 +msgid "You must enter seconds" +msgstr "Морате да внесете секунди" + +#: formencode/validators.py:2135 +msgid "You must enter minutes (after a :)" +msgstr "Мора да внесете минути (после :)" + +#: formencode/validators.py:2136 +#, python-format +msgid "The %(part)s value you gave is not a number: %(number)r" +msgstr "%(part)s вредноста не е број: %(number)r" + +#: formencode/validators.py:2137 +#, python-format +msgid "You must enter an hour in the range %(range)s" +msgstr "Морате да внесете саат во опесегот %(range)s" + +#: formencode/validators.py:2138 +msgid "You must enter a minute in the range 0-59" +msgstr "Мора да внесете минути во опсег 0-59" + +#: formencode/validators.py:2139 +msgid "You must enter a second in the range 0-59" +msgstr "Мора да внесете секунди во опсег 0-59" + +#: formencode/validators.py:2285 +#, python-format +msgid "The name %(name)s is missing" +msgstr "Недостасува име %(name)s" + +#: formencode/validators.py:2328 +#, python-format +msgid "Value should be %(true)r or %(false)r" +msgstr "Вредноста треба да биде %(true)r или %(false)r" + +#: formencode/validators.py:2363 +msgid "Value does not contain a signature" +msgstr "Вредноста не содржи потпис" + +#: formencode/validators.py:2364 +msgid "Signature is not correct" +msgstr "Потписот не е точен" + +#: formencode/validators.py:2439 +msgid "Please enter a valid IP address (a.b.c.d)" +msgstr "Ве молам, да ја внесете правилната IP адреса (a.b.c.d)" + +#: formencode/validators.py:2440 +msgid "The octets must not have leading zeros" +msgstr "Октетите неможат да имаат водечки нули" + +#: formencode/validators.py:2441 +#, python-format +msgid "The octets must be within the range of 0-255 (not %(octet)r)" +msgstr "Октетите мора да бидат во опсег 0-255 (не %(octet)r)" + +#: formencode/validators.py:2496 +msgid "Please enter a valid IP address (a.b.c.d) or IP network (a.b.c.d/e)" +msgstr "Ве молам, внесете точна IP адреса (a.b.c.d) или IP мрежа (a.b.c.d/e)" + +#: formencode/validators.py:2498 +#, python-format +msgid "The network size (bits) must be within the range of 8-32 (not %(bits)r)" +msgstr "Големината на мрежата (во битови) мора да биде во опсег 8-32 (не %(bits)r)" + +#: formencode/validators.py:2547 +#, python-format +msgid "A MAC address must contain 12 digits and A-F; the value you gave has %(length)s characters" +msgstr "MAC адреса мора да содржи 12 цифри и A-F; редноста која сте ја дале има %(length)s знакова" + +#: formencode/validators.py:2549 +#, python-format +msgid "MAC addresses may only contain 0-9 and A-F (and optionally :), not %(char)r" +msgstr "MAC адресата мора да содржи само карактеристики 0-9 и А--F (и опционално :), не %(char)r" + +#: formencode/validators.py:2657 +#, python-format +msgid "You must give a value for %s" +msgstr "Мора да дадете вредност за %s" + +#: formencode/validators.py:2691 +#, python-format +msgid "Fields do not match (should be %(match)s)" +msgstr "Полината не се поклопуваат (мора да бидат %(match)s)" + +#: formencode/validators.py:2692 +msgid "Fields do not match" +msgstr "Полината не се софпаѓаат" + +#: formencode/validators.py:2693 +msgid "Fields should be a dictionary" +msgstr "Полето би требало да биде речник (dict)" + +#: formencode/validators.py:2769 +msgid "Please enter only the number, no other characters" +msgstr "Ве молам, внесете само број, без други знаци" + +#: formencode/validators.py:2770 +msgid "You did not enter a valid number of digits" +msgstr "Не сте внесле потребен број на цифри" + +#: formencode/validators.py:2771 +msgid "That number is not valid" +msgstr "Тој број не е правилен" + +#: formencode/validators.py:2772 +#, python-format +msgid "The field %(key)s is missing" +msgstr "Недостига поле %(key)s" + +#: formencode/validators.py:2888 +msgid "Please enter numbers only for month and year" +msgstr "Ве молам, внесете само броеви за месец и година" + +#: formencode/validators.py:2889 +msgid "Invalid Expiration Date" +msgstr "Неправилен рок на истек" + +#: formencode/validators.py:2964 +msgid "Please enter numbers only for credit card security code" +msgstr "Ве молам, внесете само цифри за сигурносни код картички" + +#: formencode/validators.py:2965 +msgid "Invalid credit card security code length" +msgstr "Неправилна сигурносна код картичка" From 36b3ff60a39a9523b896fec1ab886048d62b9048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Gavrilov?= Date: Wed, 5 Apr 2017 16:05:15 +0200 Subject: [PATCH 068/123] Repair sr and sr@latin translation --- formencode/i18n/sr/LC_MESSAGES/FormEncode.mo | Bin 13274 -> 13271 bytes formencode/i18n/sr/LC_MESSAGES/FormEncode.po | 4 ++-- .../i18n/sr@latin/LC_MESSAGES/FormEncode.mo | Bin 10871 -> 10866 bytes .../i18n/sr@latin/LC_MESSAGES/FormEncode.po | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo b/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo index a256f1fdf164f663228f09faeda19555cb07b629..47c90bc31c7d42cdcd9066dc8b59b47da99a5c0b 100644 GIT binary patch delta 554 zcmXZYKS+W>7{~Ev5TT(6p|A)M?aegHO8RF8mNY00713ZAO-dF~v;++{CbSvkP(wqs z#Y-v~SS_u=2|*zZZ8bIp2|?e3;N|^1_r7=c{O;=Mm$Z3g$qtIhh#?Zg)W0UqGHzfB zBRwL+c!_;j$4Rt$Sc@|l!985XTUL(;-JWQMSPe;-Kc~E z_<=!e=cgOv*pFK{hNn1%&&X7Q(W+20YA}& zA-be6i7|Xcec~2U>668A1Do*dE}kf9JX@ZQB2|}p081aDPEC~ z(#9&CbQ*QTeH_Od)Iq;cUtN%EqNgNK2g>0xzM-BUqGt sQFp4GZ#BB}%CHp+Mx)7IP*r6*)kEz-mDRPX)DBhI8S?BF&4ADH2RwpZ&;S4c delta 558 zcmXZYy-Pw-7{~Dkl7b8xBnk?X#4OY7g)}KivJjym8ybuv2(%Zdme!1hUce=6DTp9k z1Y+OwMkK=9PPI060#cIA%CH0^xDz7S; us@I+gJ3BKTM^o|C(b8moE>K7n>jz}@ATDiV<{{mnW)G(`u|?z82@j$ zh^+%65nRSj+(i@1*oT+MC-)2q{6am?WKkSbIE)8q;RUwigDE9xqj+VaAM4nI9fKmh zm_P$ls2lRwg+9jc6gB7$^isKFJ65Y|xho~)9{1Vx>RS+s@-$5mXw zB2MBB(hT`P4QwV|*C(+X3uxmBYQQsUz&mP(n|S^-nyC9%P&?Gqiby1|B^rVdhdVSyLU*_n24RIz zFfbw###QXYB1X}}A*>*uyfDmS1NFQ(izd)Q6Av(k*Vu!vQ7K6`MU9DJY~lchMn&{u zDKyYR-LQ*6EMo#MQG=dPFV@6)>>?$DE2#5j)Zhz-PJBeodykbwrYM?Bq|uCv^kNiAm2zO21p$QRDO6T#|XB Date: Mon, 14 Aug 2017 16:33:51 -0400 Subject: [PATCH 069/123] adding `_message_vars_decode` to Validator class with tests --- formencode/api.py | 29 ++++++++++++++++++++++++++++ formencode/tests/test_validators.py | 30 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/formencode/api.py b/formencode/api.py index c097006f..ca1fbb38 100644 --- a/formencode/api.py +++ b/formencode/api.py @@ -240,6 +240,31 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): return value + _message_vars_decode = None + if six.text_type is not str: + def _message_vars_decode(self, message_vars): + """ + Under python2, a form value in web frameworks may be encoded as + UTF8 unicode. The standard error templates use a ``%(value)r`` + string formatting, which will render the error as ``u"Foo"`` instead + of just ``"Foo"``. This decoder, which can be overridden, will + encode the value back to a python string and render ``"Foo"``. This + decoder will only update the error dictionary if the unicode and + string values are equal by comparison. + + v = message_vars.items()[0][1] + """ + if six.text_type is not str: + for (k, v) in message_vars.items(): + if isinstance(v, six.text_type): + try: + v2 = v.encode('utf-8') + except Exception as e: + v2 = v + if v == v2: + message_vars[k] = v2 + return message_vars + def message(self, msgName, state, **kw): # determine translation function try: @@ -260,6 +285,10 @@ def message(self, msgName, state, **kw): msg = self._messages[msgName] msg = trans(msg, **self.gettextargs) + if self._message_vars_decode: + # handle custom decoding for message vars + kw = self._message_vars_decode(kw) + try: return msg % kw except KeyError as e: diff --git a/formencode/tests/test_validators.py b/formencode/tests/test_validators.py index 6549219e..cc653192 100644 --- a/formencode/tests/test_validators.py +++ b/formencode/tests/test_validators.py @@ -642,6 +642,36 @@ class foo(Schema): self.assertEqual(expected, value) + def test_mixed_list(self): + """before `_message_vars_decode`, the errors were: + Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'c') + Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'\xa7') + """ + o = validators.OneOf(['a', 'b', 'ö', '™', 1, 2, 3]) + try: + o.to_python('c') + raise ValueError("exception expected") + except Invalid as e: + self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'c')" in unicode(e)) + try: + o.to_python(9) + raise ValueError("exception expected") + except Invalid as e: + self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 9)" in unicode(e)) + try: + o.to_python(u'd') + raise ValueError("exception expected") + except Invalid as e: + self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'd')" in unicode(e)) + try: + o.to_python('§') + raise ValueError("exception expected") + except Invalid as e: + if six.PY2: + self.assertTrue(r"Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'\xa7')" in unicode(e)) + else: + self.assertTrue(r"Value must be one of: a; b; ö; ™; 1; 2; 3 (not '§')" in unicode(e)) + class TestIPAddressValidator(unittest.TestCase): From 41d86765c03efe19319f29443778273f1d456c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 11 Oct 2018 23:30:23 +0200 Subject: [PATCH 070/123] Fix doc of ForEach validator that was mentioning AsInt and InList --- formencode/foreach.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/formencode/foreach.py b/formencode/foreach.py index ce3cf07e..70abfc2b 100644 --- a/formencode/foreach.py +++ b/formencode/foreach.py @@ -16,13 +16,13 @@ class ForEach(CompoundValidator): For instance:: - ForEach(AsInt(), InList([1, 2, 3])) + ForEach(Int(), OneOf([1, 2, 3])) Will take a list of values and try to convert each of them to an integer, and then check if each integer is 1, 2, or 3. Using multiple arguments is equivalent to:: - ForEach(All(AsInt(), InList([1, 2, 3]))) + ForEach(All(Int(), OneOf([1, 2, 3]))) Use convert_to_list=True if you want to force the input to be a list. This will turn non-lists into one-element lists, and None From 987d29922b2a37eb969fb40658a1057bacbe1129 Mon Sep 17 00:00:00 2001 From: Valery Febvre Date: Mon, 17 Dec 2018 10:54:06 +0100 Subject: [PATCH 071/123] Fix issue #101 --- formencode/api.py | 2 +- formencode/schema.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/formencode/api.py b/formencode/api.py index c097006f..a9eb114d 100644 --- a/formencode/api.py +++ b/formencode/api.py @@ -98,7 +98,7 @@ class NoDefault(object): def is_empty(value): """Check whether the given value should be considered "empty".""" - return value is None or value == '' or ( + return value is None or value == '' or value == b'' or ( isinstance(value, (list, tuple, dict)) and not value) diff --git a/formencode/schema.py b/formencode/schema.py index ff72d57c..f7584971 100644 --- a/formencode/schema.py +++ b/formencode/schema.py @@ -342,7 +342,7 @@ def empty_value(self, value): return {} def _value_is_iterator(self, value): - if isinstance(value, six.string_types): + if isinstance(value, six.string_types + (six.binary_type, )): return False elif isinstance(value, (list, tuple)): return True From 98c508dd0f4872facf8faa2486fdd677eb384d4c Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Thu, 13 Feb 2020 12:12:07 +0200 Subject: [PATCH 072/123] Fix 'invalid escape' DeprecationWarning In Python 3.6+, invalid escape sequences generate a DeprecationWarning and will become a SyntaxError at some point. This fixes these warnings by explicitly marking the correct strings as raw strings --- formencode/national.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/formencode/national.py b/formencode/national.py index 7735d481..a7a76ed9 100644 --- a/formencode/national.py +++ b/formencode/national.py @@ -388,9 +388,9 @@ class UKPostalCode(Regex): """ regex = re.compile(r'^((ASCN|BBND|BIQQ|FIQQ|PCRN|SIQQ|STHL|TDCU|TKCA)' - '\s?1ZZ|BFPO (c\/o )?[1-9]{1,4}|GIR\s?0AA|[A-PR-UWYZ]' - '([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))' - '|[0-9][A-HJKS-UW])\s?[0-9][ABD-HJLNP-UW-Z]{2})$', re.I) + r'\s?1ZZ|BFPO (c\/o )?[1-9]{1,4}|GIR\s?0AA|[A-PR-UWYZ]' + r'([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))' + r'|[0-9][A-HJKS-UW])\s?[0-9][ABD-HJLNP-UW-Z]{2})$', re.I) strip = True messages = dict( @@ -646,7 +646,7 @@ class USPhoneNumber(FancyValidator): # for emacs: " _phoneRE = re.compile(r'^\s*(?:1-)?(\d\d\d)[\- \.]?(\d\d\d)[\- \.]?' - '(\d\d\d\d)(?:\s*ext\.?\s*(\d+))?\s*$', re.I) + r'(\d\d\d\d)(?:\s*ext\.?\s*(\d+))?\s*$', re.I) messages = dict( phoneFormat=_('Please enter a number, with area code,' From f592f4449e6b5856e462ceae88cefb06a8b5f2a8 Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Thu, 13 Feb 2020 12:17:30 +0200 Subject: [PATCH 073/123] Fix Deprecation warning in tests --- formencode/tests/test_declarative.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/formencode/tests/test_declarative.py b/formencode/tests/test_declarative.py index 248210ee..ffce388a 100644 --- a/formencode/tests/test_declarative.py +++ b/formencode/tests/test_declarative.py @@ -34,13 +34,13 @@ def test_repr(self): obj_bar = D(foo='bar') obj_baz = D(foo='baz') obj = D(bar=obj_bar, baz=obj_baz) - self.assertTrue(re.match("" - " baz=>", repr(obj))) + self.assertTrue(re.match(r"" + r" baz=>", repr(obj))) def test_repr_recursive(self): D = declarative.Declarative obj = D(foo='bar') obj.bar = obj - self.assertTrue(re.match("", repr(obj))) + self.assertTrue(re.match(r"", repr(obj))) From 401bda3db8b09ab7916ee9b4c7d693521aae8b3e Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Thu, 13 Feb 2020 12:17:42 +0200 Subject: [PATCH 074/123] Fix DeprecationWarning --- formencode/rewritingparser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/formencode/rewritingparser.py b/formencode/rewritingparser.py index a9d62e58..da852006 100644 --- a/formencode/rewritingparser.py +++ b/formencode/rewritingparser.py @@ -48,8 +48,8 @@ def feed(self, data): self.listener.reset() html_parser.HTMLParser.feed(self, data) - _entityref_re = re.compile('&([a-zA-Z][-.a-zA-Z\d]*);') - _charref_re = re.compile('&#(\d+|[xX][a-fA-F\d]+);') + _entityref_re = re.compile(r'&([a-zA-Z][-.a-zA-Z\d]*);') + _charref_re = re.compile(r'&#(\d+|[xX][a-fA-F\d]+);') def unescape(self, s): s = self._entityref_re.sub(self._sub_entityref, s) From 2428a610983c22097cd7c444287d403f92c3aee9 Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Fri, 7 Aug 2020 10:18:29 +0200 Subject: [PATCH 075/123] Fix pkg_resources DeprecationWarning pkg_resources now warns on absolute paths in resource_filename. Because of how resource_filename handles this specific case (discarding the leading '/' because of how it uses split and os.path.joini to construct the actual name), the leading '/' here doesn't actually do anything, so we can remove it without consequence. --- formencode/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formencode/api.py b/formencode/api.py index c097006f..b7fbcf66 100644 --- a/formencode/api.py +++ b/formencode/api.py @@ -32,7 +32,7 @@ def get_localedir(): # Check the egg first if resource_filename is not None: try: - locale_dir = resource_filename(__name__, "/i18n") + locale_dir = resource_filename(__name__, "i18n") except NotImplementedError: # resource_filename doesn't work with non-egg zip files pass From 65416af9acd13a09a2a023ae01bd2e8867018f64 Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Fri, 2 Oct 2020 13:15:14 -0400 Subject: [PATCH 076/123] Update Python Version support in tests --- .travis.yml | 6 +++--- formencode/tests/test_email.py | 2 ++ setup.py | 10 +++++----- tox.ini | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 662acb4a..247b92c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: python python: - - 2.6 - 2.7 - - 3.3 - - 3.4 - 3.5 + - 3.6 + - 3.7 + - 3.8 - pypy install: pip install . diff --git a/formencode/tests/test_email.py b/formencode/tests/test_email.py index 1722a7e2..f79aea80 100644 --- a/formencode/tests/test_email.py +++ b/formencode/tests/test_email.py @@ -80,5 +80,7 @@ def test_unicode_ascii_subgroup(self): 'foo@yandex.com'), 'foo@yandex.com') def test_cyrillic_email(self): + return + # NOTE test failing because domain is expired. Need a new example domain self.assertEqual(self.validator.to_python( 'me@письмо.рф'), 'me@письмо.рф') diff --git a/setup.py b/setup.py index bda9fdc8..995b528c 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ version = '2.0.0a1' -if not '2.6' <= sys.version < '3.0' and not '3.2' < sys.version: +if not '2.7' <= sys.version < '3.0' and not '3.5' <= sys.version: raise ImportError('Python version not supported') tests_require = ['nose', 'pycountry', @@ -23,7 +23,7 @@ setup(name='FormEncode', version=version, - # requires_python='>=2.6,!=3.0,!=3.1',!=3.2, # PEP345 + # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,' # PEP345 description="HTML form validation, generation, and conversion package", long_description=__doc__, classifiers=[ @@ -32,12 +32,12 @@ "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: Software Development :: Libraries :: Python Modules", ], author='Ian Bicking', diff --git a/tox.ini b/tox.ini index f6b2237b..a0fd4cdd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] -envlist=py26,py27,pypy,py33,py34,py35 +envlist=py27,pypy,py35,py36,py37,py38 [testenv] deps= nose wheel pycountry - py{26,27},pypy: dnspython - py{33,34,35}: dnspython3 + py27,pypy: dnspython + py{35,36,37,38}: dnspython3 commands= python setup.py clean --all python setup.py nosetests From 5e3ac14041312e0b12d7b1d977ec2b015ce7c223 Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Fri, 2 Oct 2020 13:31:00 -0400 Subject: [PATCH 077/123] Fix Python 2.7 tests --- .travis.yml | 1 - setup.py | 7 +++---- tox.ini | 8 ++++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 247b92c2..637e3bea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python python: - 2.7 - - 3.5 - 3.6 - 3.7 - 3.8 diff --git a/setup.py b/setup.py index 995b528c..8591193e 100755 --- a/setup.py +++ b/setup.py @@ -12,11 +12,11 @@ version = '2.0.0a1' -if not '2.7' <= sys.version < '3.0' and not '3.5' <= sys.version: +if not '2.7' <= sys.version < '3.0' and not '3.6' <= sys.version: raise ImportError('Python version not supported') -tests_require = ['nose', 'pycountry', - 'dnspython' if sys.version < '3.0' else 'dnspython3'] +tests_require = ['nose', 'dnspython==1.16.0' if sys.version < '3.0' else 'dnspython>=2.0.0', + 'pycountry<19' if sys.version < '3.0' else 'pycountry'] doctests = ['docs/htmlfill.txt', 'docs/Validator.txt', 'formencode/tests/non_empty.txt'] @@ -34,7 +34,6 @@ "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", diff --git a/tox.ini b/tox.ini index a0fd4cdd..3b05face 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] -envlist=py27,pypy,py35,py36,py37,py38 +envlist=py27,pypy,py36,py37,py38 [testenv] deps= nose wheel - pycountry - py27,pypy: dnspython - py{35,36,37,38}: dnspython3 + py27,pypy: pycountry < 19 + py27,pypy: dnspython < 2.0.0 + py{36,37,38}: pycountry dnspython commands= python setup.py clean --all python setup.py nosetests From c5a7e1e9ca8f507e14f6cad5941dd9748407595b Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Fri, 2 Oct 2020 19:24:38 -0400 Subject: [PATCH 078/123] Add License To Distribution Files --- .gitattributes | 2 ++ MANIFEST.in | 13 +++---------- docs/conf.py | 6 ++++-- formencode/__init__.py | 7 +++++++ setup.cfg | 3 +++ setup.py | 8 ++++---- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.gitattributes b/.gitattributes index 2c53400b..4aa54ca2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,3 +17,5 @@ *.mo binary *.png binary + +.git_archival.txt export-subst diff --git a/MANIFEST.in b/MANIFEST.in index 8e84270e..44df65a8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,3 @@ -recursive-include . *.py -recursive-include docs *.html *.txt *.css -recursive-include formencode/javascript *.js -recursive-include formencode/i18n *.py *.pot *.po *.mo -recursive-include formencode/tests *.txt -recursive-exclude . *.pyc *.pyo *~ -prune docs/_build -prune **/.svn -prune .hg -prune .git +exclude .gitignore +exclude .gitattributes +exclude .travis.yml diff --git a/docs/conf.py b/docs/conf.py index d5f8e417..9fde57c5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,8 +50,10 @@ # # The short X.Y version. version = '2.0.0' -# The full version, including alpha/beta/rc tags. -release = '2.0.0a1' +from pkg_resources import get_distribution +release = get_distribution('formencode').version +# for example take major/minor +version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/formencode/__init__.py b/formencode/__init__.py index 421d44a5..b4d46e7d 100644 --- a/formencode/__init__.py +++ b/formencode/__init__.py @@ -1,5 +1,6 @@ from __future__ import absolute_import # formencode package +from pkg_resources import get_distribution, DistributionNotFound from formencode.api import ( NoDefault, Invalid, Validator, Identity, @@ -10,3 +11,9 @@ from formencode import validators from formencode import national from formencode.variabledecode import NestedVariables + +try: + __version__ = get_distribution(__name__).version +except DistributionNotFound: + # package is not installed + __version__ = 'local-test' diff --git a/setup.cfg b/setup.cfg index 7ada280c..b2aba26e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,3 +33,6 @@ max_line_length=100 [flake8] max_line_length=100 + +[metadata] +license_files = LICENSE.txt diff --git a/setup.py b/setup.py index 8591193e..f0ec674d 100755 --- a/setup.py +++ b/setup.py @@ -10,8 +10,6 @@ import sys from setuptools import setup, find_packages -version = '2.0.0a1' - if not '2.7' <= sys.version < '3.0' and not '3.6' <= sys.version: raise ImportError('Python version not supported') @@ -22,7 +20,6 @@ 'formencode/tests/non_empty.txt'] setup(name='FormEncode', - version=version, # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,' # PEP345 description="HTML form validation, generation, and conversion package", long_description=__doc__, @@ -43,6 +40,7 @@ author_email='ianb@colorstudy.com', url='http://formencode.org', license='MIT', + data_files = [("", ["LICENSE.txt"])], zip_safe=False, packages=find_packages(), include_package_data=True, @@ -50,6 +48,8 @@ test_suite='formencode.tests', install_requires=['six'], tests_require=tests_require, + use_scm_version=True, + setup_requires=['setuptools_scm', 'setuptools_scm_git_archive'], + extras_require={'testing': tests_require}, - convert_2to3_doctests=doctests, ) From e8b0427d3188641f9f43bb44e483a024312b270c Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Fri, 2 Oct 2020 20:13:42 -0400 Subject: [PATCH 079/123] Update 2.0 release docs --- docs/whatsnew-2.0.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 858a0e32..7a7127fe 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -8,8 +8,12 @@ compared to its predecessor, `FormEncode` 1.3.0 Changelog --------- - - `FormEncode` can now run on Python 3.3 and higher without needing to run 2to3 first. - - `FormEncode` 2.0 is no longer compatible with Python 3.2. If you need Python 3.2 compatibility please use FormEncode 1.3 + - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. + - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. If + you need Python 2.6 or 3.2 to 3.5 compatibility please use FormEncode 1.3. +You might also try FormEncode 2.0.0a1 which supports Python 2.6 and Python +3.3-3.5 + - This will be the last major version to support Python 2.7 - Add strict flag to USPostalCode to raise error on postal codes that has too many digits instead of just truncating - Various Python 3 fixes From 952221b30cbfebde5af4356d1de480bb264c640f Mon Sep 17 00:00:00 2001 From: Christopher Lambacher Date: Fri, 2 Oct 2020 20:20:19 -0400 Subject: [PATCH 080/123] Add note to release docs about formencode.__version__ --- docs/whatsnew-2.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 7a7127fe..55b6233b 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -24,3 +24,4 @@ You might also try FormEncode 2.0.0a1 which supports Python 2.6 and Python - Add ISODateTimeConverter validator - Add ability to target htmlfill to particular form or ignore a form - Fix format errors in some translations + - The version of the library can be checked using formencode.__version__ From e0c0b99a44401a63963083ed3572ffb690c73525 Mon Sep 17 00:00:00 2001 From: Neil Muller Date: Thu, 26 Nov 2020 11:56:46 +0200 Subject: [PATCH 081/123] Use formencode.Invalid in all the examples for consistency --- docs/Validator.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/Validator.txt b/docs/Validator.txt index 044b9a89..22c0984f 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -325,14 +325,14 @@ that a little more. Here's a more complete implementation of ... ... def _validate_python(self, value, state): ... if len(value) < self.min: - ... raise Invalid(self.message("too_few", state, - ... min=self.min), - ... value, state) + ... raise formencode.Invalid(self.message("too_few", state, + ... min=self.min), + ... value, state) ... non_letters = self.letter_regex.sub('', value) ... if len(non_letters) < self.non_letter: - ... raise Invalid(self.message("non_letter", state, - ... non_letter=self.non_letter), - ... value, state) + ... raise formencode.Invalid(self.message("non_letter", state, + ... non_letter=self.non_letter), + ... value, state) With all validators, any arguments you pass to the constructor will be used to set instance variables. So :class:`SecureValidator(min=5)` will @@ -502,8 +502,8 @@ use the :meth:`message` method, like:: } def _validate_python(self, value, state): - raise Invalid(self.message('key', state, substitution='apples'), - value, state) + raise formencode.Invalid(self.message('key', state, substitution='apples'), + value, state) Localization of Error Messages (i18n) ------------------------------------- From f8f62b4c0b35f7bf7c08fd85b912ba6cfe7b5f74 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 26 Nov 2020 11:30:50 +0100 Subject: [PATCH 082/123] Fix a minor ReST issue in the new changelog --- docs/whatsnew-2.0.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 55b6233b..1923babb 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -1,5 +1,5 @@ What's New In FormEncode 2.0 -============================== +============================ This article explains the latest changes in `FormEncode` version 2.0.0 compared to its predecessor, `FormEncode` 1.3.0 @@ -9,10 +9,9 @@ Changelog --------- - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. If - you need Python 2.6 or 3.2 to 3.5 compatibility please use FormEncode 1.3. -You might also try FormEncode 2.0.0a1 which supports Python 2.6 and Python -3.3-3.5 + - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. + If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. + You might also try `FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. - This will be the last major version to support Python 2.7 - Add strict flag to USPostalCode to raise error on postal codes that has too many digits instead of just truncating From 74bbd2ea74aa39792136fbdc0aa5fd40da1510e2 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Sun, 29 Nov 2020 17:01:48 -0500 Subject: [PATCH 083/123] Declare 3.9 support and prep for 3.10 support Closes #160 --- .travis.yml | 1 + setup.py | 9 +++++---- tox.ini | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 637e3bea..2e115089 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - 3.6 - 3.7 - 3.8 + - 3.9 - pypy install: pip install . diff --git a/setup.py b/setup.py index f0ec674d..976d3ff7 100755 --- a/setup.py +++ b/setup.py @@ -10,17 +10,17 @@ import sys from setuptools import setup, find_packages -if not '2.7' <= sys.version < '3.0' and not '3.6' <= sys.version: +if not (2,7) <= sys.version_info[:2] < (3,0) and not (3,6) <= sys.version_info[:2]: raise ImportError('Python version not supported') -tests_require = ['nose', 'dnspython==1.16.0' if sys.version < '3.0' else 'dnspython>=2.0.0', - 'pycountry<19' if sys.version < '3.0' else 'pycountry'] +tests_require = ['nose', 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', + 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] doctests = ['docs/htmlfill.txt', 'docs/Validator.txt', 'formencode/tests/non_empty.txt'] setup(name='FormEncode', - # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,' # PEP345 + # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,!=3.5' # PEP345 description="HTML form validation, generation, and conversion package", long_description=__doc__, classifiers=[ @@ -34,6 +34,7 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", ], author='Ian Bicking', diff --git a/tox.ini b/tox.ini index 3b05face..c53dd196 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,pypy,py36,py37,py38 +envlist=py27,pypy,py36,py37,py38,py39 [testenv] deps= From d3e76b1d777736c63197eb429443ce0a009d50cd Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 7 Dec 2020 08:41:51 -0500 Subject: [PATCH 084/123] Add 3.10 and pypy3 to test matrix --- .travis.yml | 2 ++ tox.ini | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e115089..c799dec3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ python: - 3.7 - 3.8 - 3.9 + - 3.10-dev - pypy + - pypy3 install: pip install . diff --git a/tox.ini b/tox.ini index c53dd196..4acbd71f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,pypy,py36,py37,py38,py39 +envlist=py27,pypy,py36,py37,py38,py39,py310,pypy3 [testenv] deps= @@ -7,7 +7,7 @@ deps= wheel py27,pypy: pycountry < 19 py27,pypy: dnspython < 2.0.0 - py{36,37,38}: pycountry dnspython + py{36,37,38,39,310},pypy3: pycountry dnspython commands= python setup.py clean --all python setup.py nosetests From 9cb3cdbc24502bdf216dbf6484c74419b5585bc6 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 4 Oct 2021 21:32:48 -0400 Subject: [PATCH 085/123] pytest instead of nosetest and github actions --- .github/workflows/run-tests.yml | 31 +++++++++++++++++++++++++++++ .gitignore | 1 + .travis.yml | 16 --------------- Makefile | 22 ++++++++++++++++++++ README.rst | 2 +- formencode/tests/test_context.py | 5 +++-- formencode/tests/test_doctests.py | 15 +++++++++----- formencode/tests/test_htmlfill.py | 10 ++++++---- formencode/tests/test_schema.py | 12 ++++++++--- formencode/tests/test_validators.py | 4 ++-- formencode/validators.py | 2 +- pytest.ini | 3 +++ requirements-dev.txt | 9 +++++++++ run-tests-generate-coverage-html | 3 --- setup.cfg | 3 --- setup.py | 14 ++++++++++--- tox.ini | 6 +++--- 17 files changed, 112 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/run-tests.yml delete mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 pytest.ini create mode 100644 requirements-dev.txt delete mode 100755 run-tests-generate-coverage-html diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000..ad2000ab --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,31 @@ +name: Tests + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10-dev] + os: [ubuntu-18.04, macOS-latest, windows-latest] + include: + # pypy3 on Windows currently fails trying to + # run dnspython tests. Moving pypy3 to only test linux. + - python-version: pypy3 + os: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + make + - name: Run tests + run: | + make ci diff --git a/.gitignore b/.gitignore index 0e6e090a..619c6219 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ docs/_build .project .pydevproject .settings +.tool-versions diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c799dec3..00000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: python - -python: - - 2.7 - - 3.6 - - 3.7 - - 3.8 - - 3.9 - - 3.10-dev - - pypy - - pypy3 - -install: pip install . - -script: python setup.py nosetests - diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8603ed14 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +.PHONY: docs +init: + pip install -e . + pip install -r requirements-dev.txt +ci: + pytest formencode + +flake8: + flake8 --ignore=E501,F401,E128,E402,E731,F821 formencode + +coverage: + pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode + +publish: + pip install 'twine>=1.5.0' + python setup.py sdist bdist_wheel + twine upload dist/* + rm -fr build dist .egg formencode.egg-info + +docs: + cd docs && make html + @echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m" diff --git a/README.rst b/README.rst index 7c7a764c..79fc8cb4 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ The latest documentation is available at http://www.formencode.org/ Testing ------- -Use `python setup.py nosetests` to run the test suite. +Use `pytest formencode` to run the test suite. Use `tox` to run the test suite for all supported Python versions. diff --git a/formencode/tests/test_context.py b/formencode/tests/test_context.py index 5fcd52d5..4c2a28c9 100644 --- a/formencode/tests/test_context.py +++ b/formencode/tests/test_context.py @@ -1,5 +1,5 @@ from __future__ import absolute_import -from nose.tools import assert_raises +import pytest from formencode.context import Context, ContextRestoreError @@ -34,7 +34,8 @@ def test_fail(): c3 = Context() res1 = c3.set(a=1) res2 = c3.set(b=2) - assert_raises(ContextRestoreError, res1.restore) + with pytest.raises(ContextRestoreError): + res1.restore() assert c3.b == 2 assert c3.a == 1 res2.restore() diff --git a/formencode/tests/test_doctests.py b/formencode/tests/test_doctests.py index 800eb04b..a3288ae9 100644 --- a/formencode/tests/test_doctests.py +++ b/formencode/tests/test_doctests.py @@ -11,6 +11,8 @@ from formencode import validators import six +import pytest + """Modules that will have their doctests tested.""" modules = [compound, htmlfill, htmlgen, national, schema, validators] @@ -66,16 +68,13 @@ def doctest_module(document, verbose, raise_error): def set_func_description(fn, description): - """Wrap function and set description attr for nosetests to display.""" + """Wrap function and set description attr for pytest to display.""" def _wrapper(*a_test_args): fn(*a_test_args) _wrapper.description = description return _wrapper - -def test_doctests(): - """Generate each doctest.""" - # TODO Can we resolve this from nose? +def collect_functions(): verbose = False raise_error = True for document in text_files + modules: @@ -91,6 +90,12 @@ def test_doctests(): verbose, raise_error +@pytest.mark.parametrize("testfn,document,verbose,raise_error", list(collect_functions())) +def test_doctests(testfn,document,verbose,raise_error): + """Generate each doctest.""" + testfn(document, verbose, raise_error) + + if __name__ == '__main__': # Call this file directly if you want to test doctests. args = sys.argv[1:] diff --git a/formencode/tests/test_htmlfill.py b/formencode/tests/test_htmlfill.py index 431e03bf..ecade0a1 100644 --- a/formencode/tests/test_htmlfill.py +++ b/formencode/tests/test_htmlfill.py @@ -24,16 +24,18 @@ from formencode import htmlfill, htmlfill_schemabuilder from formencode.doctest_xml_compare import xml_compare +import pytest -def test_inputoutput(): + +def collect_values(): data_dir = os.path.join(os.path.dirname(__file__), 'htmlfill_data') for fn in os.listdir(data_dir): if fn.startswith('data-'): fn = os.path.join(data_dir, fn) - yield run_filename, fn - + yield fn -def run_filename(filename): +@pytest.mark.parametrize('filename', list(collect_values())) +def test_runfile(filename): f = open(filename) content = f.read() f.close() diff --git a/formencode/tests/test_schema.py b/formencode/tests/test_schema.py index 56b153cb..2ed349a2 100644 --- a/formencode/tests/test_schema.py +++ b/formencode/tests/test_schema.py @@ -8,6 +8,8 @@ from formencode.schema import Schema, merge_dicts, SimpleFormValidator from formencode.variabledecode import NestedVariables +import pytest + def _notranslation(s): return s @@ -146,10 +148,14 @@ class schema(Schema): text="The input field 'whatever' was not expected.") -def test_this(): - +def collect_cases(): for case in all_cases: - yield (case.test,) + yield case.test + + +@pytest.mark.parametrize('func', list(collect_cases())) +def test_this(func): + func() def test_merge(): diff --git a/formencode/tests/test_validators.py b/formencode/tests/test_validators.py index cc653192..fd7d186d 100644 --- a/formencode/tests/test_validators.py +++ b/formencode/tests/test_validators.py @@ -4,7 +4,7 @@ import datetime import unittest -from nose.plugins.skip import SkipTest +import pytest from formencode import validators from formencode.validators import Invalid @@ -198,7 +198,7 @@ def test_inf(self): try: inf = float('infinity') except ValueError: - raise SkipTest + pytest.skip('skipping incompatible float test') self.assertEqual(self.validator.to_python('infinity'), inf) diff --git a/formencode/validators.py b/formencode/validators.py index ff58642c..11222878 100644 --- a/formencode/validators.py +++ b/formencode/validators.py @@ -1395,7 +1395,7 @@ def _convert_to_python(self, value, state): class URL(FancyValidator): - """ + r""" Validate a URL, either http://... or https://. If check_exists is true, then we'll actually make a request for the page. diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..787b78f4 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +# content of pytest.ini +[pytest] +#addopts = --doctest-modules diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..cfd0c5e0 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,9 @@ +pytest<4.7; python_version == '2.7' +pytest-cov<2.13; python_version == '2.7' +pytest; python_version > '2.7' +pytest-cov; python_version > '2.7' +wheel +pycountry<19; python_version == '2.7' +dnspython<2.0.0; python_version == '2.7' +pycountry; python_version > '2.7' +dnspython; python_version > '2.7' diff --git a/run-tests-generate-coverage-html b/run-tests-generate-coverage-html deleted file mode 100755 index a73069f1..00000000 --- a/run-tests-generate-coverage-html +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -nosetests --with-coverage --cover-package=formencode --cover-erase --cover-html --cover-html-dir=coverage \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index b2aba26e..d757f58b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[nosetests] -detailed-errors = 1 - # Babel configuration [compile_catalog] domain = FormEncode diff --git a/setup.py b/setup.py index 976d3ff7..99ae4692 100755 --- a/setup.py +++ b/setup.py @@ -9,12 +9,20 @@ import sys from setuptools import setup, find_packages +import platform if not (2,7) <= sys.version_info[:2] < (3,0) and not (3,6) <= sys.version_info[:2]: raise ImportError('Python version not supported') -tests_require = ['nose', 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', - 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] +tests_require = [ + 'pytest<4.7' if sys.version_info[:2] < (3,0) else 'pytest', + 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', + 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] + +setup_requires = [ + 'setuptools_scm<6.0' if sys.version_info[:2] < (3,0) else 'setuptools_scm', + 'setuptools_scm_git_archive', +] doctests = ['docs/htmlfill.txt', 'docs/Validator.txt', 'formencode/tests/non_empty.txt'] @@ -50,7 +58,7 @@ install_requires=['six'], tests_require=tests_require, use_scm_version=True, - setup_requires=['setuptools_scm', 'setuptools_scm_git_archive'], + setup_requires=setup_requires, extras_require={'testing': tests_require}, ) diff --git a/tox.ini b/tox.ini index 4acbd71f..73dc6c9f 100644 --- a/tox.ini +++ b/tox.ini @@ -3,11 +3,11 @@ envlist=py27,pypy,py36,py37,py38,py39,py310,pypy3 [testenv] deps= - nose + pytest wheel py27,pypy: pycountry < 19 py27,pypy: dnspython < 2.0.0 py{36,37,38,39,310},pypy3: pycountry dnspython commands= - python setup.py clean --all - python setup.py nosetests + pip install -e . + pytest formencode From 483b404a6a8bca03f376627c1178cbb1563991fe Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Tue, 5 Oct 2021 08:31:49 -0400 Subject: [PATCH 086/123] Prep for 2.0.1 release to support Python 3.10 --- .github/workflows/run-tests.yml | 8 ++++++-- Makefile | 2 +- README.rst | 12 ++++-------- docs/whatsnew-2.0.txt | 13 +++++++++++-- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ad2000ab..c1e62132 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,6 +1,10 @@ name: Tests -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: {} jobs: build: @@ -9,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10-dev] + python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10.0] os: [ubuntu-18.04, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to diff --git a/Makefile b/Makefile index 8603ed14..e0ff995d 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ publish: pip install 'twine>=1.5.0' python setup.py sdist bdist_wheel twine upload dist/* - rm -fr build dist .egg formencode.egg-info + rm -fr build dist .egg FormEncode.egg-info docs: cd docs && make html diff --git a/README.rst b/README.rst index 79fc8cb4..9b556630 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,9 @@ FormEncode ========== -.. image:: https://secure.travis-ci.org/formencode/formencode.png?branch=master - :target: https://travis-ci.org/formencode/formencode - :alt: Travis CI continuous integration status +.. image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg + :target: https://github.com/formencode/formencode/actions + :alt: Test Status Introduction @@ -31,8 +31,4 @@ Use `tox` to run the test suite for all supported Python versions. Changes ------- -Added a validator that can require one or more fields based on the value of another field. - -A German howto can be found here: http://techblog.auf-nach-mallorca.info/2014/08/19/dynamische_formulare_validieren_mit_formencode/ - -Courtesy of the developers of http://www.auf-nach-mallorca.info +See the `What's new section of the documentation `_. diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 1923babb..7b5adcc8 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -5,8 +5,17 @@ This article explains the latest changes in `FormEncode` version 2.0.0 compared to its predecessor, `FormEncode` 1.3.0 -Changelog ---------- +2.0.1 +----- + - Add support for 3.10 + - use Pytest instead of Nose and Github Actions instead of Travis for tests + - Documentation updates + - Note this will be the last version to support Python 2.7. The next version + will be 2.1 to signal this change. If you want to keep support for Python + 2.7 update your dependencies spec to be below 2.1 + +2.0.0 +----- - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. From 25749f8ebb5d0aebf484f901dcdefa6017a5bf7c Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Mon, 3 Jan 2022 21:01:38 -0500 Subject: [PATCH 087/123] Python 3.11 and setup.cfg instead of setup.py --- .github/workflows/run-tests.yml | 6 +- Makefile | 12 ++-- pyproject.toml | 5 ++ requirements-dev.txt | 12 ++-- requirements-test.txt | 5 ++ setup.cfg | 56 ++++++++++++--- setup.py | 58 +--------------- {formencode => src/formencode}/__init__.py | 0 {formencode => src/formencode}/api.py | 0 {formencode => src/formencode}/compound.py | 0 {formencode => src/formencode}/context.py | 0 {formencode => src/formencode}/declarative.py | 0 .../formencode}/doctest_xml_compare.py | 0 {formencode => src/formencode}/exc.py | 0 .../formencode}/fieldstorage.py | 0 {formencode => src/formencode}/foreach.py | 0 {formencode => src/formencode}/htmlfill.py | 0 .../formencode}/htmlfill_schemabuilder.py | 0 {formencode => src/formencode}/htmlgen.py | 0 {formencode => src/formencode}/htmlrename.py | 0 .../formencode}/i18n/FormEncode.pot | 0 .../i18n/cs/LC_MESSAGES/FormEncode.mo | Bin .../i18n/cs/LC_MESSAGES/FormEncode.po | 0 .../i18n/da/LC_MESSAGES/FormEncode.mo | Bin .../i18n/da/LC_MESSAGES/FormEncode.po | 0 .../i18n/de/LC_MESSAGES/FormEncode.mo | Bin .../i18n/de/LC_MESSAGES/FormEncode.po | 0 .../i18n/el/LC_MESSAGES/FormEncode.mo | Bin .../i18n/el/LC_MESSAGES/FormEncode.po | 0 .../i18n/es/LC_MESSAGES/FormEncode.mo | Bin .../i18n/es/LC_MESSAGES/FormEncode.po | 0 .../i18n/et/LC_MESSAGES/FormEncode.mo | Bin .../i18n/et/LC_MESSAGES/FormEncode.po | 0 .../i18n/fi/LC_MESSAGES/FormEncode.mo | Bin .../i18n/fi/LC_MESSAGES/FormEncode.po | 0 .../i18n/fr/LC_MESSAGES/FormEncode.mo | Bin .../i18n/fr/LC_MESSAGES/FormEncode.po | 0 .../i18n/it/LC_MESSAGES/FormEncode.mo | Bin .../i18n/it/LC_MESSAGES/FormEncode.po | 0 .../i18n/ja/LC_MESSAGES/FormEncode.mo | Bin .../i18n/ja/LC_MESSAGES/FormEncode.po | 0 .../i18n/ko/LC_MESSAGES/FormEncode.mo | Bin .../i18n/ko/LC_MESSAGES/FormEncode.po | 0 .../i18n/lt/LC_MESSAGES/FormEncode.mo | Bin .../i18n/lt/LC_MESSAGES/FormEncode.po | 0 .../i18n/mk/LC_MESSAGES/FormEncode.mo | Bin .../i18n/mk/LC_MESSAGES/FormEncode.po | 0 .../i18n/nb_NO/LC_MESSAGES/FormEncode.mo | Bin .../i18n/nb_NO/LC_MESSAGES/FormEncode.po | 0 .../i18n/nl/LC_MESSAGES/FormEncode.mo | Bin .../i18n/nl/LC_MESSAGES/FormEncode.po | 0 .../i18n/pl/LC_MESSAGES/FormEncode.mo | Bin .../i18n/pl/LC_MESSAGES/FormEncode.po | 0 .../i18n/pt_BR/LC_MESSAGES/FormEncode.mo | Bin .../i18n/pt_BR/LC_MESSAGES/FormEncode.po | 0 .../i18n/pt_PT/LC_MESSAGES/FormEncode.mo | Bin .../i18n/pt_PT/LC_MESSAGES/FormEncode.po | 0 .../i18n/ru/LC_MESSAGES/FormEncode.mo | Bin .../i18n/ru/LC_MESSAGES/FormEncode.po | 0 .../i18n/sk/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sk/LC_MESSAGES/FormEncode.po | 0 .../i18n/sl/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sl/LC_MESSAGES/FormEncode.po | 0 .../i18n/sr/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sr/LC_MESSAGES/FormEncode.po | 0 .../i18n/sr@latin/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sr@latin/LC_MESSAGES/FormEncode.po | 0 .../i18n/sv/LC_MESSAGES/FormEncode.mo | Bin .../i18n/sv/LC_MESSAGES/FormEncode.po | 0 .../i18n/tr/LC_MESSAGES/FormEncode.mo | Bin .../i18n/tr/LC_MESSAGES/FormEncode.po | 0 .../i18n/zh_CN/LC_MESSAGES/FormEncode.mo | Bin .../i18n/zh_CN/LC_MESSAGES/FormEncode.po | 0 .../i18n/zh_TW/LC_MESSAGES/FormEncode.mo | Bin .../i18n/zh_TW/LC_MESSAGES/FormEncode.po | 0 {formencode => src/formencode}/interfaces.py | 0 .../formencode}/javascript/ordering.js | 0 {formencode => src/formencode}/national.py | 0 .../formencode}/rewritingparser.py | 0 {formencode => src/formencode}/schema.py | 0 {formencode => src/formencode}/validators.py | 0 .../formencode}/variabledecode.py | 0 {formencode/tests => tests}/__init__.py | 0 .../htmlfill_data/data-error1.txt | 0 .../htmlfill_data/data-fill1.txt | 0 .../htmlfill_data/data-fill2.txt | 0 .../htmlfill_data/data-fill3.txt | 0 .../htmlfill_data/data-fill4.txt | 0 .../htmlfill_data/data-form-last-element.txt | 0 .../htmlfill_data/data-schema1.txt | 0 {formencode/tests => tests}/non_empty.txt | 0 {formencode/tests => tests}/test_cc.py | 0 {formencode/tests => tests}/test_compound.py | 0 {formencode/tests => tests}/test_context.py | 0 .../tests => tests}/test_declarative.py | 0 .../test_doctest_xml_compare.py | 0 {formencode/tests => tests}/test_doctests.py | 65 +++++++++++------- {formencode/tests => tests}/test_email.py | 0 {formencode/tests => tests}/test_htmlfill.py | 0 .../tests => tests}/test_htmlfill_control.py | 0 {formencode/tests => tests}/test_htmlgen.py | 0 .../tests => tests}/test_htmlrename.py | 0 {formencode/tests => tests}/test_i18n.py | 0 {formencode/tests => tests}/test_schema.py | 0 .../tests => tests}/test_subclassing.py | 0 .../tests => tests}/test_subclassing_old.py | 0 .../tests => tests}/test_validators.py | 0 .../tests => tests}/test_variabledecode.py | 0 tox.ini | 7 +- 109 files changed, 116 insertions(+), 110 deletions(-) create mode 100644 pyproject.toml create mode 100644 requirements-test.txt rename {formencode => src/formencode}/__init__.py (100%) rename {formencode => src/formencode}/api.py (100%) rename {formencode => src/formencode}/compound.py (100%) rename {formencode => src/formencode}/context.py (100%) rename {formencode => src/formencode}/declarative.py (100%) rename {formencode => src/formencode}/doctest_xml_compare.py (100%) rename {formencode => src/formencode}/exc.py (100%) rename {formencode => src/formencode}/fieldstorage.py (100%) rename {formencode => src/formencode}/foreach.py (100%) rename {formencode => src/formencode}/htmlfill.py (100%) rename {formencode => src/formencode}/htmlfill_schemabuilder.py (100%) rename {formencode => src/formencode}/htmlgen.py (100%) rename {formencode => src/formencode}/htmlrename.py (100%) rename {formencode => src/formencode}/i18n/FormEncode.pot (100%) rename {formencode => src/formencode}/i18n/cs/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/cs/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/da/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/da/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/de/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/de/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/el/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/el/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/es/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/es/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/et/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/et/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/fi/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/fi/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/fr/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/fr/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/it/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/it/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/ja/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/ja/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/ko/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/ko/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/lt/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/lt/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/mk/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/mk/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/nb_NO/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/nb_NO/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/nl/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/nl/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/pl/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/pl/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/pt_BR/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/pt_BR/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/pt_PT/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/pt_PT/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/ru/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/ru/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sk/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sk/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sl/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sl/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sr/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sr/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sr@latin/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sr@latin/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/sv/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/sv/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/tr/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/tr/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/zh_CN/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/zh_CN/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/i18n/zh_TW/LC_MESSAGES/FormEncode.mo (100%) rename {formencode => src/formencode}/i18n/zh_TW/LC_MESSAGES/FormEncode.po (100%) rename {formencode => src/formencode}/interfaces.py (100%) rename {formencode => src/formencode}/javascript/ordering.js (100%) rename {formencode => src/formencode}/national.py (100%) rename {formencode => src/formencode}/rewritingparser.py (100%) rename {formencode => src/formencode}/schema.py (100%) rename {formencode => src/formencode}/validators.py (100%) rename {formencode => src/formencode}/variabledecode.py (100%) rename {formencode/tests => tests}/__init__.py (100%) rename {formencode/tests => tests}/htmlfill_data/data-error1.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill1.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill2.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill3.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-fill4.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-form-last-element.txt (100%) rename {formencode/tests => tests}/htmlfill_data/data-schema1.txt (100%) rename {formencode/tests => tests}/non_empty.txt (100%) rename {formencode/tests => tests}/test_cc.py (100%) rename {formencode/tests => tests}/test_compound.py (100%) rename {formencode/tests => tests}/test_context.py (100%) rename {formencode/tests => tests}/test_declarative.py (100%) rename {formencode/tests => tests}/test_doctest_xml_compare.py (100%) rename {formencode/tests => tests}/test_doctests.py (65%) rename {formencode/tests => tests}/test_email.py (100%) rename {formencode/tests => tests}/test_htmlfill.py (100%) rename {formencode/tests => tests}/test_htmlfill_control.py (100%) rename {formencode/tests => tests}/test_htmlgen.py (100%) rename {formencode/tests => tests}/test_htmlrename.py (100%) rename {formencode/tests => tests}/test_i18n.py (100%) rename {formencode/tests => tests}/test_schema.py (100%) rename {formencode/tests => tests}/test_subclassing.py (100%) rename {formencode/tests => tests}/test_subclassing_old.py (100%) rename {formencode/tests => tests}/test_validators.py (100%) rename {formencode/tests => tests}/test_variabledecode.py (100%) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c1e62132..6635747e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,12 +13,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10.0] + python-version: [3.7, 3.8, 3.9, '3.10.0 - 3.10.99', '3.11.0-alpha - 3.11.0'] os: [ubuntu-18.04, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. - - python-version: pypy3 + - python-version: pypy-3.7 + os: ubuntu-latest + - python-version: pypy-3.8 os: ubuntu-latest steps: diff --git a/Makefile b/Makefile index e0ff995d..eb9713c4 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,21 @@ .PHONY: docs init: pip install -e . - pip install -r requirements-dev.txt + pip install -r requirements-test.txt ci: - pytest formencode + pytest flake8: - flake8 --ignore=E501,F401,E128,E402,E731,F821 formencode + flake8 --ignore=E501,F401,E128,E402,E731,F821 src/formencode coverage: pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode publish: - pip install 'twine>=1.5.0' - python setup.py sdist bdist_wheel + pip install 'twine>=1.5.0' build + python -m build twine upload dist/* - rm -fr build dist .egg FormEncode.egg-info + rm -fr build dist .egg src/FormEncode.egg-info docs: cd docs && make html diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c19dbc32 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[build-system] +requires = ["setuptools", "wheel", "setuptools_scm>=6.2"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] diff --git a/requirements-dev.txt b/requirements-dev.txt index cfd0c5e0..c40fa96e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,9 +1,5 @@ -pytest<4.7; python_version == '2.7' -pytest-cov<2.13; python_version == '2.7' -pytest; python_version > '2.7' -pytest-cov; python_version > '2.7' +build wheel -pycountry<19; python_version == '2.7' -dnspython<2.0.0; python_version == '2.7' -pycountry; python_version > '2.7' -dnspython; python_version > '2.7' +black +flake8 +-r requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 00000000..d858886e --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,5 @@ +pytest +pytest-cov +wheel +pycountry +dnspython diff --git a/setup.cfg b/setup.cfg index d757f58b..de307aea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,48 @@ +[metadata] +name = FormEncode +description = "HTML form validation, generation, and conversion package" +long_description = file: README.rst +long_description_content_type = text/x-rst +author = Ian Bicking +author_email = ianb@colorstudy.com +maintainer = Chris Lambacher +maintainer_email = chris@kateandchris.net +url = "http://formencode.org" +license = MIT +license_file = LICENSE.txt +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy + Topic :: Software Development :: Libraries :: Python Modules + +[options] +packages = find: +package_dir = + =src +python_requires = >=3.7 +install_requires = six +include_package_data = True + +[options.packages.find] +where = src + +[options.extras_require] +testing = + pytest + dnspython + pycountry + # Babel configuration [compile_catalog] domain = FormEncode @@ -22,14 +67,9 @@ input_file = formencode/i18n/FormEncode.pot output_dir = formencode/i18n previous = true -[wheel] -universal = 1 - [pep8] -max_line_length=100 +max_line_length=88 [flake8] -max_line_length=100 - -[metadata] -license_files = LICENSE.txt +max-line-length = 88 +extend-ignore = E203 diff --git a/setup.py b/setup.py index 99ae4692..0b62acc5 100755 --- a/setup.py +++ b/setup.py @@ -5,60 +5,6 @@ The official repo is at GitHub: https://github.com/formencode/formencode """ -from __future__ import absolute_import +from setuptools import setup -import sys -from setuptools import setup, find_packages -import platform - -if not (2,7) <= sys.version_info[:2] < (3,0) and not (3,6) <= sys.version_info[:2]: - raise ImportError('Python version not supported') - -tests_require = [ - 'pytest<4.7' if sys.version_info[:2] < (3,0) else 'pytest', - 'dnspython==1.16.0' if sys.version_info[:2] < (3,0) else 'dnspython>=2.0.0', - 'pycountry<19' if sys.version_info < (3,0) else 'pycountry'] - -setup_requires = [ - 'setuptools_scm<6.0' if sys.version_info[:2] < (3,0) else 'setuptools_scm', - 'setuptools_scm_git_archive', -] - -doctests = ['docs/htmlfill.txt', 'docs/Validator.txt', - 'formencode/tests/non_empty.txt'] - -setup(name='FormEncode', - # requires_python='>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,!=3.5' # PEP345 - description="HTML form validation, generation, and conversion package", - long_description=__doc__, - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - author='Ian Bicking', - author_email='ianb@colorstudy.com', - url='http://formencode.org', - license='MIT', - data_files = [("", ["LICENSE.txt"])], - zip_safe=False, - packages=find_packages(), - include_package_data=True, - package_data={'formencode': ['../docs/*.txt']}, - test_suite='formencode.tests', - install_requires=['six'], - tests_require=tests_require, - use_scm_version=True, - setup_requires=setup_requires, - - extras_require={'testing': tests_require}, - ) +setup() diff --git a/formencode/__init__.py b/src/formencode/__init__.py similarity index 100% rename from formencode/__init__.py rename to src/formencode/__init__.py diff --git a/formencode/api.py b/src/formencode/api.py similarity index 100% rename from formencode/api.py rename to src/formencode/api.py diff --git a/formencode/compound.py b/src/formencode/compound.py similarity index 100% rename from formencode/compound.py rename to src/formencode/compound.py diff --git a/formencode/context.py b/src/formencode/context.py similarity index 100% rename from formencode/context.py rename to src/formencode/context.py diff --git a/formencode/declarative.py b/src/formencode/declarative.py similarity index 100% rename from formencode/declarative.py rename to src/formencode/declarative.py diff --git a/formencode/doctest_xml_compare.py b/src/formencode/doctest_xml_compare.py similarity index 100% rename from formencode/doctest_xml_compare.py rename to src/formencode/doctest_xml_compare.py diff --git a/formencode/exc.py b/src/formencode/exc.py similarity index 100% rename from formencode/exc.py rename to src/formencode/exc.py diff --git a/formencode/fieldstorage.py b/src/formencode/fieldstorage.py similarity index 100% rename from formencode/fieldstorage.py rename to src/formencode/fieldstorage.py diff --git a/formencode/foreach.py b/src/formencode/foreach.py similarity index 100% rename from formencode/foreach.py rename to src/formencode/foreach.py diff --git a/formencode/htmlfill.py b/src/formencode/htmlfill.py similarity index 100% rename from formencode/htmlfill.py rename to src/formencode/htmlfill.py diff --git a/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py similarity index 100% rename from formencode/htmlfill_schemabuilder.py rename to src/formencode/htmlfill_schemabuilder.py diff --git a/formencode/htmlgen.py b/src/formencode/htmlgen.py similarity index 100% rename from formencode/htmlgen.py rename to src/formencode/htmlgen.py diff --git a/formencode/htmlrename.py b/src/formencode/htmlrename.py similarity index 100% rename from formencode/htmlrename.py rename to src/formencode/htmlrename.py diff --git a/formencode/i18n/FormEncode.pot b/src/formencode/i18n/FormEncode.pot similarity index 100% rename from formencode/i18n/FormEncode.pot rename to src/formencode/i18n/FormEncode.pot diff --git a/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/cs/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/cs/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/cs/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/cs/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/cs/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/cs/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/da/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/da/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/da/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/da/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/da/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/da/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/da/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/da/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/de/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/de/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/de/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/de/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/de/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/de/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/de/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/de/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/el/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/el/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/el/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/el/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/el/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/el/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/el/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/el/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/es/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/es/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/es/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/es/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/es/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/es/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/es/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/es/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/et/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/et/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/et/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/et/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/et/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/et/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/et/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/et/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/fi/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/fi/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/fi/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/fi/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/fi/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/fi/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/fr/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/fr/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/fr/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/fr/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/fr/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/fr/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/it/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/it/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/it/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/it/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/it/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/it/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/it/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/it/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/ja/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/ja/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/ja/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/ja/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/ja/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/ja/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/ko/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/ko/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/ko/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/ko/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/ko/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/ko/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/ko/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/ko/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/lt/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/lt/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/lt/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/lt/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/lt/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/lt/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/mk/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/mk/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/mk/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/mk/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/mk/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/mk/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/nb_NO/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/nl/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/nl/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/nl/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/nl/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/nl/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/nl/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/nl/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/nl/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/pl/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/pl/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/pl/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/pl/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/pl/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/pl/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/pl/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/pl/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/pt_BR/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/pt_PT/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/ru/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/ru/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/ru/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/ru/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/ru/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/ru/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/ru/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/ru/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sk/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sk/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sk/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sk/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sk/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sk/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sk/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sk/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sl/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sl/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sl/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sl/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sl/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sl/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sl/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sl/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sr/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sr/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sr/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sr/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sr/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sr/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sr@latin/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/sv/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/sv/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/sv/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/sv/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/tr/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/tr/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/tr/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/tr/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/tr/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/tr/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/tr/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/tr/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/zh_CN/LC_MESSAGES/FormEncode.po diff --git a/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo similarity index 100% rename from formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo rename to src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.mo diff --git a/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po b/src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po similarity index 100% rename from formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po rename to src/formencode/i18n/zh_TW/LC_MESSAGES/FormEncode.po diff --git a/formencode/interfaces.py b/src/formencode/interfaces.py similarity index 100% rename from formencode/interfaces.py rename to src/formencode/interfaces.py diff --git a/formencode/javascript/ordering.js b/src/formencode/javascript/ordering.js similarity index 100% rename from formencode/javascript/ordering.js rename to src/formencode/javascript/ordering.js diff --git a/formencode/national.py b/src/formencode/national.py similarity index 100% rename from formencode/national.py rename to src/formencode/national.py diff --git a/formencode/rewritingparser.py b/src/formencode/rewritingparser.py similarity index 100% rename from formencode/rewritingparser.py rename to src/formencode/rewritingparser.py diff --git a/formencode/schema.py b/src/formencode/schema.py similarity index 100% rename from formencode/schema.py rename to src/formencode/schema.py diff --git a/formencode/validators.py b/src/formencode/validators.py similarity index 100% rename from formencode/validators.py rename to src/formencode/validators.py diff --git a/formencode/variabledecode.py b/src/formencode/variabledecode.py similarity index 100% rename from formencode/variabledecode.py rename to src/formencode/variabledecode.py diff --git a/formencode/tests/__init__.py b/tests/__init__.py similarity index 100% rename from formencode/tests/__init__.py rename to tests/__init__.py diff --git a/formencode/tests/htmlfill_data/data-error1.txt b/tests/htmlfill_data/data-error1.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-error1.txt rename to tests/htmlfill_data/data-error1.txt diff --git a/formencode/tests/htmlfill_data/data-fill1.txt b/tests/htmlfill_data/data-fill1.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill1.txt rename to tests/htmlfill_data/data-fill1.txt diff --git a/formencode/tests/htmlfill_data/data-fill2.txt b/tests/htmlfill_data/data-fill2.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill2.txt rename to tests/htmlfill_data/data-fill2.txt diff --git a/formencode/tests/htmlfill_data/data-fill3.txt b/tests/htmlfill_data/data-fill3.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill3.txt rename to tests/htmlfill_data/data-fill3.txt diff --git a/formencode/tests/htmlfill_data/data-fill4.txt b/tests/htmlfill_data/data-fill4.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-fill4.txt rename to tests/htmlfill_data/data-fill4.txt diff --git a/formencode/tests/htmlfill_data/data-form-last-element.txt b/tests/htmlfill_data/data-form-last-element.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-form-last-element.txt rename to tests/htmlfill_data/data-form-last-element.txt diff --git a/formencode/tests/htmlfill_data/data-schema1.txt b/tests/htmlfill_data/data-schema1.txt similarity index 100% rename from formencode/tests/htmlfill_data/data-schema1.txt rename to tests/htmlfill_data/data-schema1.txt diff --git a/formencode/tests/non_empty.txt b/tests/non_empty.txt similarity index 100% rename from formencode/tests/non_empty.txt rename to tests/non_empty.txt diff --git a/formencode/tests/test_cc.py b/tests/test_cc.py similarity index 100% rename from formencode/tests/test_cc.py rename to tests/test_cc.py diff --git a/formencode/tests/test_compound.py b/tests/test_compound.py similarity index 100% rename from formencode/tests/test_compound.py rename to tests/test_compound.py diff --git a/formencode/tests/test_context.py b/tests/test_context.py similarity index 100% rename from formencode/tests/test_context.py rename to tests/test_context.py diff --git a/formencode/tests/test_declarative.py b/tests/test_declarative.py similarity index 100% rename from formencode/tests/test_declarative.py rename to tests/test_declarative.py diff --git a/formencode/tests/test_doctest_xml_compare.py b/tests/test_doctest_xml_compare.py similarity index 100% rename from formencode/tests/test_doctest_xml_compare.py rename to tests/test_doctest_xml_compare.py diff --git a/formencode/tests/test_doctests.py b/tests/test_doctests.py similarity index 65% rename from formencode/tests/test_doctests.py rename to tests/test_doctests.py index a3288ae9..6efb38fb 100644 --- a/formencode/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -20,15 +20,14 @@ """Text files that will have their doctests tested.""" text_files = [ - 'docs/htmlfill.txt', - 'docs/Validator.txt', - 'formencode/tests/non_empty.txt', - ] + "docs/htmlfill.txt", + "docs/Validator.txt", + "tests/non_empty.txt", +] """Used to resolve text files to absolute paths.""" -base = os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__)))) +base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if six.text_type is str: # Python 3 @@ -36,32 +35,39 @@ OutputChecker = doctest.OutputChecker class OutputChecker3(OutputChecker): - def check_output(self, want, got, optionflags): if want.startswith("u'"): want = want[1:] - elif want.startswith('set(['): - want = want[3:].replace( - '([', '{').replace('])', '}').replace('{}', 'set()') + elif want.startswith("set(["): + want = ( + want[3:] + .replace("([", "{") + .replace("])", "}") + .replace("{}", "set()") + ) return OutputChecker.check_output(self, want, got, optionflags) doctest.OutputChecker = OutputChecker3 def doctest_file(document, verbose, raise_error): - failure_count, test_count = doctest.testfile(document, - module_relative=False, - optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, - verbose=verbose) + failure_count, test_count = doctest.testfile( + document, + module_relative=False, + optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, + verbose=verbose, + ) if raise_error: assert test_count > 0 assert failure_count == 0 def doctest_module(document, verbose, raise_error): - failure_count, test_count = doctest.testmod(document, - optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, - verbose=verbose) + failure_count, test_count = doctest.testmod( + document, + optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL, + verbose=verbose, + ) if raise_error: assert test_count > 0 assert failure_count == 0 @@ -69,11 +75,14 @@ def doctest_module(document, verbose, raise_error): def set_func_description(fn, description): """Wrap function and set description attr for pytest to display.""" + def _wrapper(*a_test_args): fn(*a_test_args) + _wrapper.description = description return _wrapper + def collect_functions(): verbose = False raise_error = True @@ -82,26 +91,30 @@ def collect_functions(): name = "Doctests for %s" % (document,) if not document.startswith(os.sep): document = os.path.join(base, document) - yield set_func_description(doctest_file, name), document, \ - verbose, raise_error + yield set_func_description( + doctest_file, name + ), document, verbose, raise_error else: name = "Doctests for %s" % (document.__name__,) - yield set_func_description(doctest_module, name), document, \ - verbose, raise_error + yield set_func_description( + doctest_module, name + ), document, verbose, raise_error -@pytest.mark.parametrize("testfn,document,verbose,raise_error", list(collect_functions())) -def test_doctests(testfn,document,verbose,raise_error): +@pytest.mark.parametrize( + "testfn,document,verbose,raise_error", list(collect_functions()) +) +def test_doctests(testfn, document, verbose, raise_error): """Generate each doctest.""" testfn(document, verbose, raise_error) -if __name__ == '__main__': +if __name__ == "__main__": # Call this file directly if you want to test doctests. args = sys.argv[1:] verbose = False - if '-v' in args: - args.remove('-v') + if "-v" in args: + args.remove("-v") verbose = True if not args: args = text_files + modules diff --git a/formencode/tests/test_email.py b/tests/test_email.py similarity index 100% rename from formencode/tests/test_email.py rename to tests/test_email.py diff --git a/formencode/tests/test_htmlfill.py b/tests/test_htmlfill.py similarity index 100% rename from formencode/tests/test_htmlfill.py rename to tests/test_htmlfill.py diff --git a/formencode/tests/test_htmlfill_control.py b/tests/test_htmlfill_control.py similarity index 100% rename from formencode/tests/test_htmlfill_control.py rename to tests/test_htmlfill_control.py diff --git a/formencode/tests/test_htmlgen.py b/tests/test_htmlgen.py similarity index 100% rename from formencode/tests/test_htmlgen.py rename to tests/test_htmlgen.py diff --git a/formencode/tests/test_htmlrename.py b/tests/test_htmlrename.py similarity index 100% rename from formencode/tests/test_htmlrename.py rename to tests/test_htmlrename.py diff --git a/formencode/tests/test_i18n.py b/tests/test_i18n.py similarity index 100% rename from formencode/tests/test_i18n.py rename to tests/test_i18n.py diff --git a/formencode/tests/test_schema.py b/tests/test_schema.py similarity index 100% rename from formencode/tests/test_schema.py rename to tests/test_schema.py diff --git a/formencode/tests/test_subclassing.py b/tests/test_subclassing.py similarity index 100% rename from formencode/tests/test_subclassing.py rename to tests/test_subclassing.py diff --git a/formencode/tests/test_subclassing_old.py b/tests/test_subclassing_old.py similarity index 100% rename from formencode/tests/test_subclassing_old.py rename to tests/test_subclassing_old.py diff --git a/formencode/tests/test_validators.py b/tests/test_validators.py similarity index 100% rename from formencode/tests/test_validators.py rename to tests/test_validators.py diff --git a/formencode/tests/test_variabledecode.py b/tests/test_variabledecode.py similarity index 100% rename from formencode/tests/test_variabledecode.py rename to tests/test_variabledecode.py diff --git a/tox.ini b/tox.ini index 73dc6c9f..14545ba4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,12 @@ [tox] -envlist=py27,pypy,py36,py37,py38,py39,py310,pypy3 +envlist=py37,py38,py39,py310,py311,pypy3 [testenv] deps= pytest wheel - py27,pypy: pycountry < 19 - py27,pypy: dnspython < 2.0.0 - py{36,37,38,39,310},pypy3: pycountry dnspython + pycountry + dnspython commands= pip install -e . pytest formencode From da0671f3e126077ed28fa6e3a16c8628d280d804 Mon Sep 17 00:00:00 2001 From: Falibur Date: Fri, 21 Apr 2023 21:05:44 +0200 Subject: [PATCH 088/123] Fix binary of swedish translation (#178) --- .../i18n/sv/LC_MESSAGES/FormEncode.mo | Bin 2145 -> 2605 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo b/src/formencode/i18n/sv/LC_MESSAGES/FormEncode.mo index aa132e37fd29618f4a88111b00f5ef47c9d49b89..5f3abb7706062b535af09da026a5615b6bed7473 100644 GIT binary patch delta 878 zcmYk(O-~a+7zgl&whF8-V5u*O8pnuAg;@$T2y0ZRRBd2s(=sO5(a)o#u7xQ&`-n5FbDO5Rd@xyfEVFss1MwZz7HJOJ#Y-(K=E}rj_)525?z2l zBL6~N_}N}vcu2H}#!U=_P*?s1X5khbgt0y%28W<7EF1N0IEKClN8meX!mp6QKkx)h z^%G^_XyhZ9L;n>#kMrpp8W#Kw^{eZ@VlVX5={SbOe%(M_gvaUzme6q|9-(^}E=Pxv zdvO#EJjCur;1Kcs$Uaq z!kjd-Mp?A|Kyh0(+#1hy8j@Fhi`6krbf@u}E2|wT=bElm(v)@66UrzRd0DKvdQZOK zC}FV&`S~)pi+o|8-7Q*nVf0~GT+SPI!OriWK4E5zTfV1+r+8(fjb%!#tMPWzalLD- z`osyOP}e)E&S&=L1&$}{BH-P!U9VxWnH5(VcZ4Sb9F3|sES7UtM3ZIAS##Q0awN77 z=CH2mC_k_mhP+0{X$W2sPV2wE#pIfCpGSM?sU=_3T*anP>E?FgeJn20^7VS}j)4-> J(yaO|`Ukg5nL7Xg delta 417 zcmXZYKT88a5QgzLcS$swK#&jwEfhVIh>3y{5exr-C zQ(7I?Dz<`+jfDq(1{<}?Gu$lPoPi&%7}DaqGPbWsO~DO_U-AJIO1;WYlEhh8#j zxQ$jnLu+`8v-pVuTZ&QEcM{0?n1 z?^wg%xL)duwz!2h$Ue^D1+L>Wj-%TzEu)8P!GN>p2Ho_H<1{Om8<}wM^}bnYv}*^g GBlQ4m#4POq From 1d99b293eab5705eda335b8d218cbb013bb4999d Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Sun, 5 Nov 2023 12:16:35 +0100 Subject: [PATCH 089/123] Support Python 3.12 (#180) --- .github/workflows/run-tests.yml | 4 ++-- Makefile | 1 + setup.cfg | 1 + src/formencode/__init__.py | 10 ++++++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6635747e..d75f3fc8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,8 +13,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10.0 - 3.10.99', '3.11.0-alpha - 3.11.0'] - os: [ubuntu-18.04, macOS-latest, windows-latest] + python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] + os: [ubuntu-latest, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. diff --git a/Makefile b/Makefile index eb9713c4..0275f9c4 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ init: pip install -e . pip install -r requirements-test.txt + ci: pytest diff --git a/setup.cfg b/setup.cfg index de307aea..0fcb8b19 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index b4d46e7d..162a067f 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -1,6 +1,12 @@ from __future__ import absolute_import # formencode package -from pkg_resources import get_distribution, DistributionNotFound +try: + from importlib.metadata import version, PackageNotFoundError +except ImportError: + from pkg_resources import get_distribution + from pkg_resources import DistributionNotFound as PackageNotFoundError + def version(pkg): + return get_distribution(__name__).version from formencode.api import ( NoDefault, Invalid, Validator, Identity, @@ -13,7 +19,7 @@ from formencode.variabledecode import NestedVariables try: - __version__ = get_distribution(__name__).version + __version__ = version(__name__) except DistributionNotFound: # package is not installed __version__ = 'local-test' From c62e59cee1ec2ecbdcb8e1303d7121b5da45b2ec Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 12:34:40 +0100 Subject: [PATCH 090/123] Some cleanup in package init module --- src/formencode/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index 162a067f..b5b8daf3 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -1,12 +1,15 @@ +"""The formencode package""" + from __future__ import absolute_import -# formencode package + try: from importlib.metadata import version, PackageNotFoundError -except ImportError: +except ImportError: # Python < 3.8 from pkg_resources import get_distribution from pkg_resources import DistributionNotFound as PackageNotFoundError - def version(pkg): - return get_distribution(__name__).version + + def version(distribution_name): + return get_distribution(distribution_name).version from formencode.api import ( NoDefault, Invalid, Validator, Identity, @@ -20,6 +23,5 @@ def version(pkg): try: __version__ = version(__name__) -except DistributionNotFound: - # package is not installed +except DistributionNotFound: # package is not installed __version__ = 'local-test' From d682cac4c457ce780f82978dde4974b428cf2423 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 12:34:55 +0100 Subject: [PATCH 091/123] Add Python 3.12 to tox tests --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 14545ba4..8b5ffd82 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py37,py38,py39,py310,py311,pypy3 +envlist=py37,py38,py39,py310,py311,py312,pypy3 [testenv] deps= From aeb9271c10f3725798506f0d4688919d896226ce Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 12:43:44 +0100 Subject: [PATCH 092/123] Remove now unnecessary future imports and encoding --- docs/conf.py | 6 ++---- examples/WebwareExamples/index.py | 2 -- src/formencode/__init__.py | 2 -- src/formencode/api.py | 1 - src/formencode/compound.py | 2 -- src/formencode/context.py | 1 - src/formencode/declarative.py | 1 - src/formencode/doctest_xml_compare.py | 3 --- src/formencode/fieldstorage.py | 2 +- src/formencode/foreach.py | 1 - src/formencode/htmlfill.py | 1 - src/formencode/htmlfill_schemabuilder.py | 2 -- src/formencode/htmlgen.py | 1 - src/formencode/htmlrename.py | 2 -- src/formencode/national.py | 1 - src/formencode/rewritingparser.py | 2 -- src/formencode/schema.py | 1 - src/formencode/validators.py | 1 - src/formencode/variabledecode.py | 1 - tests/__init__.py | 1 - tests/test_cc.py | 3 --- tests/test_compound.py | 3 --- tests/test_context.py | 1 - tests/test_declarative.py | 2 -- tests/test_doctest_xml_compare.py | 1 - tests/test_doctests.py | 1 - tests/test_email.py | 3 --- tests/test_htmlfill.py | 5 ----- tests/test_htmlfill_control.py | 2 -- tests/test_htmlgen.py | 3 --- tests/test_htmlrename.py | 1 - tests/test_i18n.py | 4 ---- tests/test_schema.py | 2 -- tests/test_subclassing.py | 3 --- tests/test_subclassing_old.py | 3 --- tests/test_validators.py | 4 ---- tests/test_variabledecode.py | 1 - 37 files changed, 3 insertions(+), 73 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9fde57c5..1e4d14de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- -# # FormEncode documentation build configuration file, created by # sphinx-quickstart on Tue Aug 30 2011. # @@ -12,7 +9,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the diff --git a/examples/WebwareExamples/index.py b/examples/WebwareExamples/index.py index c8b1a1ea..9faf7f53 100644 --- a/examples/WebwareExamples/index.py +++ b/examples/WebwareExamples/index.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function from formencode import Invalid, htmlfill, Schema, validators from WebKit.Page import Page diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index b5b8daf3..18ca699b 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -1,7 +1,5 @@ """The formencode package""" -from __future__ import absolute_import - try: from importlib.metadata import version, PackageNotFoundError except ImportError: # Python < 3.8 diff --git a/src/formencode/api.py b/src/formencode/api.py index da321fa2..3dfd5b29 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -1,7 +1,6 @@ """ Core classes for validation. """ -from __future__ import absolute_import from . import declarative import gettext diff --git a/src/formencode/compound.py b/src/formencode/compound.py index 6005f1a3..bf2a0c8e 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -1,8 +1,6 @@ """ Validators for applying validations in sequence. """ -from __future__ import absolute_import - from .api import (FancyValidator, Identity, Invalid, NoDefault, Validator, is_validator) import six diff --git a/src/formencode/context.py b/src/formencode/context.py index 128c9122..d898d4b8 100644 --- a/src/formencode/context.py +++ b/src/formencode/context.py @@ -51,7 +51,6 @@ def do_stuff(): And ``page`` will be set to ``'view'`` only inside that ``with`` block. """ -from __future__ import absolute_import import threading diff --git a/src/formencode/declarative.py b/src/formencode/declarative.py index 2375b3b9..770ce6cc 100644 --- a/src/formencode/declarative.py +++ b/src/formencode/declarative.py @@ -17,7 +17,6 @@ Also, you can define a __classinit__(cls, new_attrs) method, which will be called when the class is created (including subclasses). """ -from __future__ import absolute_import import copy import types diff --git a/src/formencode/doctest_xml_compare.py b/src/formencode/doctest_xml_compare.py index ebac496d..94b490c8 100644 --- a/src/formencode/doctest_xml_compare.py +++ b/src/formencode/doctest_xml_compare.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function - import doctest import xml.etree.ElementTree as ET import six diff --git a/src/formencode/fieldstorage.py b/src/formencode/fieldstorage.py index 40a4d1b6..9894bf7f 100644 --- a/src/formencode/fieldstorage.py +++ b/src/formencode/fieldstorage.py @@ -3,7 +3,7 @@ """ Wrapper class for use with cgi.FieldStorage types for file uploads """ -from __future__ import absolute_import + import cgi diff --git a/src/formencode/foreach.py b/src/formencode/foreach.py index 70abfc2b..1bdc46a1 100644 --- a/src/formencode/foreach.py +++ b/src/formencode/foreach.py @@ -1,7 +1,6 @@ """ Validator for repeating items. """ -from __future__ import absolute_import from .api import NoDefault, Invalid from .compound import CompoundValidator, from_python diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index 90f4beda..ce7bdeab 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -1,7 +1,6 @@ """ Parser for HTML forms, that fills in defaults and errors. See ``render``. """ -from __future__ import absolute_import import re diff --git a/src/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py index 2059ff79..36937a59 100644 --- a/src/formencode/htmlfill_schemabuilder.py +++ b/src/formencode/htmlfill_schemabuilder.py @@ -6,8 +6,6 @@ ``listen`` argument), or call ``parse_schema`` to just parse out a ``Schema`` object. """ -from __future__ import absolute_import - from . import validators from . import schema from . import compound diff --git a/src/formencode/htmlgen.py b/src/formencode/htmlgen.py index 6ab61736..94c56c52 100644 --- a/src/formencode/htmlgen.py +++ b/src/formencode/htmlgen.py @@ -55,7 +55,6 @@ 'return to top' """ -from __future__ import absolute_import import xml.etree.ElementTree as ET import six diff --git a/src/formencode/htmlrename.py b/src/formencode/htmlrename.py index e8a5c38f..d884848e 100644 --- a/src/formencode/htmlrename.py +++ b/src/formencode/htmlrename.py @@ -1,8 +1,6 @@ """ Module to rename form fields """ -from __future__ import absolute_import - from formencode.rewritingparser import RewritingParser __all__ = ['rename', 'add_prefix'] diff --git a/src/formencode/national.py b/src/formencode/national.py index a7a76ed9..8d23a5cf 100644 --- a/src/formencode/national.py +++ b/src/formencode/national.py @@ -1,7 +1,6 @@ """ Country specific validators for use with FormEncode. """ -from __future__ import absolute_import import re from .api import FancyValidator diff --git a/src/formencode/rewritingparser.py b/src/formencode/rewritingparser.py index da852006..0342a6c4 100644 --- a/src/formencode/rewritingparser.py +++ b/src/formencode/rewritingparser.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from six.moves import html_parser import re import six diff --git a/src/formencode/schema.py b/src/formencode/schema.py index f7584971..86fe0fa9 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import warnings from .api import _, is_validator, FancyValidator, Invalid, NoDefault diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 11222878..b5fa6585 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -4,7 +4,6 @@ """ Validator/Converters for use with FormEncode. """ -from __future__ import absolute_import import cgi import locale diff --git a/src/formencode/variabledecode.py b/src/formencode/variabledecode.py index 7f93416c..fc0542a0 100644 --- a/src/formencode/variabledecode.py +++ b/src/formencode/variabledecode.py @@ -19,7 +19,6 @@ and list_char keyword args. For example, to have the GET/POST variables, ``a_1=something`` as a list, you would use a ``list_char='_'``. """ -from __future__ import absolute_import from .api import FancyValidator import six diff --git a/tests/__init__.py b/tests/__init__.py index 24f33978..a380a390 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import sys import os diff --git a/tests/test_cc.py b/tests/test_cc.py index c0e5319b..580f4ae8 100644 --- a/tests/test_cc.py +++ b/tests/test_cc.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest from formencode import Invalid diff --git a/tests/test_compound.py b/tests/test_compound.py index d74f8d61..928163a1 100644 --- a/tests/test_compound.py +++ b/tests/test_compound.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest from formencode import compound, Invalid diff --git a/tests/test_context.py b/tests/test_context.py index 4c2a28c9..6e203f4d 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import pytest from formencode.context import Context, ContextRestoreError diff --git a/tests/test_declarative.py b/tests/test_declarative.py index ffce388a..f985ad6e 100644 --- a/tests/test_declarative.py +++ b/tests/test_declarative.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- import unittest import re diff --git a/tests/test_doctest_xml_compare.py b/tests/test_doctest_xml_compare.py index 316abac1..b6fa5e56 100644 --- a/tests/test_doctest_xml_compare.py +++ b/tests/test_doctest_xml_compare.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import sys import formencode.doctest_xml_compare as dxml diff --git a/tests/test_doctests.py b/tests/test_doctests.py index 6efb38fb..d69e3737 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import os import sys import doctest diff --git a/tests/test_email.py b/tests/test_email.py index f79aea80..0fefb200 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals import unittest diff --git a/tests/test_htmlfill.py b/tests/test_htmlfill.py index ecade0a1..b3eb86ef 100644 --- a/tests/test_htmlfill.py +++ b/tests/test_htmlfill.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import os import re import sys diff --git a/tests/test_htmlfill_control.py b/tests/test_htmlfill_control.py index 4ee4fa6c..e678115f 100644 --- a/tests/test_htmlfill_control.py +++ b/tests/test_htmlfill_control.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from formencode import htmlfill @@ -175,4 +174,3 @@ def test_error_attr_form_alt(): test_error_attr_ignore() test_error_attr_form() test_error_attr_form_alt() - \ No newline at end of file diff --git a/tests/test_htmlgen.py b/tests/test_htmlgen.py index 9dccf5bf..05c7f3a5 100644 --- a/tests/test_htmlgen.py +++ b/tests/test_htmlgen.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function - import doctest from formencode.htmlgen import html diff --git a/tests/test_htmlrename.py b/tests/test_htmlrename.py index 5f93c4a7..86a6f92d 100644 --- a/tests/test_htmlrename.py +++ b/tests/test_htmlrename.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from formencode.htmlrename import rename, add_prefix diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 5998f96b..6f4c8f21 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals - import formencode import six diff --git a/tests/test_schema.py b/tests/test_schema.py index 2ed349a2..a2afba2a 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function import unittest from six.moves.urllib.parse import parse_qsl diff --git a/tests/test_subclassing.py b/tests/test_subclassing.py index 3f620797..6fb23db2 100644 --- a/tests/test_subclassing.py +++ b/tests/test_subclassing.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest from formencode.api import is_validator, FancyValidator, Invalid diff --git a/tests/test_subclassing_old.py b/tests/test_subclassing_old.py index 23b62df9..92b34007 100644 --- a/tests/test_subclassing_old.py +++ b/tests/test_subclassing_old.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -# -*- coding: utf-8 -*- - import unittest import warnings diff --git a/tests/test_validators.py b/tests/test_validators.py index fd7d186d..cfbf4814 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals - import datetime import unittest import pytest diff --git a/tests/test_variabledecode.py b/tests/test_variabledecode.py index d7b6583b..6fe53f58 100644 --- a/tests/test_variabledecode.py +++ b/tests/test_variabledecode.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import unittest from formencode.variabledecode import variable_decode, variable_encode From f3c89e5f2346bc2b6be4777b932ed71fa970d027 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:46:56 +0100 Subject: [PATCH 093/123] Add logo --- docs/_static/formencode.png | Bin 0 -> 23643 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/_static/formencode.png diff --git a/docs/_static/formencode.png b/docs/_static/formencode.png new file mode 100644 index 0000000000000000000000000000000000000000..6595c523cb69441bceba268252daf2619f8730c8 GIT binary patch literal 23643 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 Yk`oj?F*m8t!n__mG}bMCVE^WS0O3XT`v3p{ literal 0 HcmV?d00001 From 7723798ab7fe555d717c3bbe2e6af0d58b6b486c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:47:34 +0100 Subject: [PATCH 094/123] Add flake8 to tests and fix flake8 issues --- .github/workflows/run-qa.yml | 22 +++++++++ .github/workflows/run-tests.yml | 17 ++++--- Makefile | 11 +++-- setup.cfg | 5 +- src/formencode/api.py | 16 +++---- src/formencode/compound.py | 4 +- src/formencode/declarative.py | 2 +- src/formencode/exc.py | 2 +- src/formencode/fieldstorage.py | 5 -- src/formencode/foreach.py | 3 +- src/formencode/htmlfill.py | 2 +- src/formencode/htmlfill_schemabuilder.py | 7 ++- src/formencode/htmlgen.py | 1 + src/formencode/national.py | 24 ++++------ src/formencode/schema.py | 10 +--- src/formencode/validators.py | 61 +++++++++++------------- tests/test_email.py | 8 ++-- tests/test_htmlfill.py | 1 + tests/test_htmlfill_control.py | 1 + tests/test_schema.py | 5 +- tests/test_subclassing_old.py | 2 +- tests/test_validators.py | 12 +++-- tox.ini | 7 +++ 23 files changed, 120 insertions(+), 108 deletions(-) create mode 100644 .github/workflows/run-qa.yml diff --git a/.github/workflows/run-qa.yml b/.github/workflows/run-qa.yml new file mode 100644 index 00000000..222b7cfe --- /dev/null +++ b/.github/workflows/run-qa.yml @@ -0,0 +1,22 @@ +name: Code quality + +on: + push: + branches: + - main + pull_request: {} + +jobs: + build: + runs-on: ubutu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install dependencies + run: make + - name: Run code quality checks + run: make flake8 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d75f3fc8..13d8d2ab 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,25 +13,24 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. - - python-version: pypy-3.7 + - python-version: 'pypy-3.9' os: ubuntu-latest - - python-version: pypy-3.8 + - python-version: 'pypy-3.10' os: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Check out code + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - make + run: make - name: Run tests - run: | - make ci + run: make ci diff --git a/Makefile b/Makefile index 0275f9c4..707329a9 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,26 @@ -.PHONY: docs init: pip install -e . pip install -r requirements-test.txt -ci: +.PHONY: tests + +tests: pytest flake8: - flake8 --ignore=E501,F401,E128,E402,E731,F821 src/formencode + flake8 src tests coverage: pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode publish: - pip install 'twine>=1.5.0' build + pip install "twine>=4" build python -m build twine upload dist/* rm -fr build dist .egg src/FormEncode.egg-info +.PHONY: docs + docs: cd docs && make html @echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m" diff --git a/setup.cfg b/setup.cfg index 0fcb8b19..7a0fe578 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ include_package_data = True where = src [options.extras_require] -testing = +testing = pytest dnspython pycountry @@ -73,4 +73,5 @@ max_line_length=88 [flake8] max-line-length = 88 -extend-ignore = E203 +extend-ignore = E203,E128,E402,E501,E731,F821,F401 + diff --git a/src/formencode/api.py b/src/formencode/api.py index 3dfd5b29..fcb2d441 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -63,6 +63,7 @@ def set_stdtranslation(domain="FormEncode", languages=None, except AttributeError: # Python 3 _stdtrans = t.gettext + set_stdtranslation() # Dummy i18n translation function, nothing is translated here. @@ -195,9 +196,7 @@ def unpack_errors(self, encode_variables=False, dict_char='.', return self.msg -############################################################ -## Base Classes -############################################################ +# Base Classes class Validator(declarative.Declarative): @@ -239,8 +238,9 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): return value - _message_vars_decode = None - if six.text_type is not str: + if six.text_type is str: + _message_vars_decode = None + else: def _message_vars_decode(self, message_vars): """ Under python2, a form value in web frameworks may be encoded as @@ -258,7 +258,7 @@ def _message_vars_decode(self, message_vars): if isinstance(v, six.text_type): try: v2 = v.encode('utf-8') - except Exception as e: + except Exception: v2 = v if v == v2: message_vars[k] = v2 @@ -355,6 +355,7 @@ class _Identity(Validator): def __repr__(self): return 'validators.Identity' + Identity = _Identity() @@ -481,8 +482,7 @@ def __classinit__(cls, new_attrs): stacklevel=cls._inheritance_level + 2) setattr(cls, new, new_attrs[old]) elif new in new_attrs: - setattr(cls, old, deprecated(old=old, new=new)( - new_attrs[new])) + setattr(cls, old, deprecated(old=old, new=new)(new_attrs[new])) def to_python(self, value, state=None): try: diff --git a/src/formencode/compound.py b/src/formencode/compound.py index bf2a0c8e..72bc6dfd 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -7,10 +7,8 @@ __all__ = ['CompoundValidator', 'Any', 'All', 'Pipe'] -############################################################ -## Compound Validators -############################################################ +# Compound Validators def to_python(validator, value, state): return validator.to_python(value, state) diff --git a/src/formencode/declarative.py b/src/formencode/declarative.py index 770ce6cc..c4a7000d 100644 --- a/src/formencode/declarative.py +++ b/src/formencode/declarative.py @@ -189,7 +189,7 @@ def __sourcerepr__(self, source, binding=None): else: break args.extend('%s=%s' % (name, source.makeRepr(value)) - for (name, value) in six.iteritems(vals)) + for (name, value) in six.iteritems(vals)) return '%s(%s)' % (self.__class__.__name__, ', '.join(args)) diff --git a/src/formencode/exc.py b/src/formencode/exc.py index 8bc75298..3abdaf4a 100644 --- a/src/formencode/exc.py +++ b/src/formencode/exc.py @@ -2,7 +2,7 @@ Custom exceptions and warnings. """ -__all__ = ['FERunTimeWarning'] +__all__ = ['FERuntimeWarning'] class FERuntimeWarning(RuntimeWarning): diff --git a/src/formencode/fieldstorage.py b/src/formencode/fieldstorage.py index 9894bf7f..93b2ccd8 100644 --- a/src/formencode/fieldstorage.py +++ b/src/formencode/fieldstorage.py @@ -1,12 +1,7 @@ -## FormEncode, a Form processor -## Copyright (C) 2003, Ian Bicking """ Wrapper class for use with cgi.FieldStorage types for file uploads """ -import cgi - - def convert_fieldstorage(fs): return fs if fs.filename else None diff --git a/src/formencode/foreach.py b/src/formencode/foreach.py index 1bdc46a1..f3b45886 100644 --- a/src/formencode/foreach.py +++ b/src/formencode/foreach.py @@ -136,6 +136,5 @@ def _convert_to_list(self, value): for _n in value: break return value - ## @@: Should this catch any other errors?: - except TypeError: + except TypeError: # @@: Should this catch any other errors? return [value] diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index ce7bdeab..5c919d93 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -280,7 +280,7 @@ def str_compare(self, str1, str2): str1 = six.text_type(str1) else: str1 = str(str1) - if type(str1) == type(str2): + if type(str1) is type(str2): return str1 == str2 if isinstance(str1, six.text_type): str1 = str1.encode(self.encoding or self.default_encoding) diff --git a/src/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py index 36937a59..03f7ade9 100644 --- a/src/formencode/htmlfill_schemabuilder.py +++ b/src/formencode/htmlfill_schemabuilder.py @@ -27,9 +27,8 @@ def parse_schema(form): return listener.schema() -default_validators = dict( - [(name.lower(), getattr(validators, name)) - for name in dir(validators)]) +default_validators = { + name.lower(): getattr(validators, name) for name in dir(validators)} def get_messages(cls, message): @@ -95,7 +94,7 @@ def listen_input(self, parser, tag, attrs): if required: v.validators.append( validators.NotEmpty( - messages=get_messages(validators.NotEmpty, message))) + messages=get_messages(validators.NotEmpty, message))) else: v.validators[0].if_missing = False if add_to_end: diff --git a/src/formencode/htmlgen.py b/src/formencode/htmlgen.py index 94c56c52..4e0090a0 100644 --- a/src/formencode/htmlgen.py +++ b/src/formencode/htmlgen.py @@ -113,6 +113,7 @@ def str(self, arg, encoding=None): arg = arg.encode(default_encoding) return arg + html = _HTML() diff --git a/src/formencode/national.py b/src/formencode/national.py index 8d23a5cf..8c42fb1b 100644 --- a/src/formencode/national.py +++ b/src/formencode/national.py @@ -24,9 +24,7 @@ no_country = ('Please easy_install pycountry or validators handling' ' country names and/or languages will not work.') -############################################################ -## country lists and functions -############################################################ +# country lists and functions country_additions = [ ('BY', _('Belarus')), @@ -141,9 +139,7 @@ def get_language(code): return _l(pycountry.languages.get(alpha_2=code).name) -############################################################ -## country, state and postal code validators -############################################################ +# country, state and postal code validators class DelimitedDigitsPostalCode(Regex): """ @@ -193,8 +189,7 @@ class DelimitedDigitsPostalCode(Regex): def assembly_formatstring(self, partition_lengths, delimiter): if len(partition_lengths) == 1: return _('%d digits') % partition_lengths[0] - else: - return delimiter.join('n' * l for l in partition_lengths) + return delimiter.join('n' * n for n in partition_lengths) def assembly_grouping(self, partition_lengths, delimiter): digit_groups = ['%s' * length for length in partition_lengths] @@ -404,8 +399,8 @@ def _convert_to_python(self, value, state): value, state) value = match.group(1).upper() if not value.startswith('BFPO'): - value = value.replace(' ', '') - value = '%s %s' % (value[:-3], value[-3:]) + value = ''.join(value.split()) + value = '%s %s' % (value[:-3], value[-3:]) return value @@ -616,9 +611,7 @@ def _convert_to_python(self, value, state): return str(value).strip().upper() -############################################################ -## phone number validators -############################################################ +# phone number validators class USPhoneNumber(FancyValidator): """ @@ -801,9 +794,7 @@ def _convert_to_python(self, value, state): return value -############################################################ -## language validators -############################################################ +# language validators class LanguageValidator(FancyValidator): """ @@ -871,4 +862,5 @@ def validators(): return [name for name, value in list(globals().items()) if isinstance(value, type) and issubclass(value, FancyValidator)] + __all__ = ['Invalid'] + validators() diff --git a/src/formencode/schema.py b/src/formencode/schema.py index 86fe0fa9..362c9c07 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -248,9 +248,7 @@ def _convert_from_python(self, value_dict, state): previous_full_dict = getattr(state, 'full_dict', None) state.full_dict = value_dict try: - __traceback_info__ = None for name, value in six.iteritems(value_dict): - __traceback_info__ = 'for_python in %s' % name try: unused.remove(name) except ValueError: @@ -267,8 +265,6 @@ def _convert_from_python(self, value_dict, state): except Invalid as e: errors[name] = e - del __traceback_info__ - for name in unused: validator = self.fields[name] if state is not None: @@ -289,7 +285,6 @@ def _convert_from_python(self, value_dict, state): state.key = previous_key for validator in pre: - __traceback_info__ = 'for_python pre_validator %s' % validator new = validator.from_python(new, state) return new @@ -334,7 +329,7 @@ def subvalidators(self): return result def is_empty(self, value): - ## Generally nothing is empty for us + # generally nothing is empty for us return False def empty_value(self, value): @@ -350,8 +345,7 @@ def _value_is_iterator(self, value): for _v in value: break return True - ## @@: Should this catch any other errors?: - except TypeError: + except TypeError: # @@: should this catch any other errors? return False diff --git a/src/formencode/validators.py b/src/formencode/validators.py index b5fa6585..01d853e3 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1,5 +1,5 @@ -## FormEncode, a Form processor -## Copyright (C) 2003, Ian Bicking +# FormEncode, a Form processor +# Copyright (C) 2003, Ian Bicking """ Validator/Converters for use with FormEncode. @@ -36,18 +36,16 @@ assert Identity and Invalid and NoDefault # silence unused import warnings # Dummy i18n translation function, nothing is translated here. -# Instead this is actually done in api.message. +# Instead, this is actually done in api.message. # The surrounding _('string') of the strings is only for extracting # the strings automatically. # If you run pygettext with this source comment this function out temporarily. _ = lambda s: s -############################################################ -## Utility methods -############################################################ - +# Utility methods # These all deal with accepting both datetime and mxDateTime modules and types + datetime_module = None mxDateTime_module = None @@ -98,9 +96,7 @@ def datetime_isotime(module): return module.ISO.Time -############################################################ -## Wrapper Validators -############################################################ +# Wrapper Validators class ConfirmType(FancyValidator): """ @@ -313,9 +309,7 @@ def _convert_to_python(self, value, state): _convert_from_python = _convert_to_python -############################################################ -## Normal validators -############################################################ +# Normal validators class MaxLength(FancyValidator): """ @@ -1997,19 +1991,19 @@ class DateConverter(FancyValidator): _date_re = dict( dmy=re.compile( r'^\s*(\d\d?)[\-\./\\](\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), mdy=re.compile( r'^\s*(\d\d?|%s)[\-\./\\](\d\d?)[\-\./\\](\d\d\d?\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), ymd=re.compile( r'^\s*(\d\d\d?\d?)[\-\./\\](\d\d?|%s)[\-\./\\](\d\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), my=re.compile( r'^\s*(\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$' - % '|'.join(_month_names), re.I), + % '|'.join(_month_names), re.I), ym=re.compile( r'^\s*(\d\d\d?\d?)[\-\./\\](\d\d?|%s)\s*$' - % '|'.join(_month_names), re.I)) + % '|'.join(_month_names), re.I)) _formats = dict(d='%d', m='%m', y='%Y') @@ -2333,8 +2327,6 @@ def _convert_from_python(self, value, state): return '%i:%02i%s' % (hour, minute, ampm) - - class ISODateTimeConverter(FancyValidator): """ Converts fields which contain both date and time, in the @@ -2375,7 +2367,6 @@ def _convert_from_python(self, value, state): return value.isoformat("T") - def PostalCode(*kw, **kwargs): deprecation_warning("please use formencode.national.USPostalCode") from formencode.national import USPostalCode @@ -2462,6 +2453,7 @@ def _convert_to_python(self, value, state): def _convert_from_python(self, value, state): return (self.true_values if value else self.false_values)[0] + # Should deprecate: StringBoolean = StringBool @@ -2764,11 +2756,13 @@ def _convert_to_python(self, value_dict, state): raise Invalid( _('You must give a value for %s') % self.required, value_dict, state, - error_dict={self.required: - Invalid(self.message('empty', state), + error_dict={ + self.required: Invalid( + self.message('empty', state), value_dict.get(self.required), state)}) return value_dict + RequireIfPresent = RequireIfMissing @@ -3075,15 +3069,17 @@ def _validateReturn(self, field_dict, state): dt_mod, next_month_year, next_month, 1) assert expires_date > today except ValueError: - return {self.cc_expires_month_field: - self.message('notANumber', state), - self.cc_expires_year_field: - self.message('notANumber', state)} + return { + self.cc_expires_month_field: + self.message('notANumber', state), + self.cc_expires_year_field: + self.message('notANumber', state)} except AssertionError: - return {self.cc_expires_month_field: - self.message('invalidNumber', state), - self.cc_expires_year_field: - self.message('invalidNumber', state)} + return { + self.cc_expires_month_field: + self.message('invalidNumber', state), + self.cc_expires_year_field: + self.message('invalidNumber', state)} class CreditCardSecurityCode(FormValidator): @@ -3150,6 +3146,7 @@ def _validateReturn(self, field_dict, state): def validators(): """Return the names of all validators in this module.""" return [name for name, value in six.iteritems(globals()) - if isinstance(value, type) and issubclass(value, Validator)] + if isinstance(value, type) and issubclass(value, Validator)] + __all__ = ['Invalid'] + validators() diff --git a/tests/test_email.py b/tests/test_email.py index 0fefb200..4f9873c1 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -53,10 +53,10 @@ def test_valid_email_addresses(self): ('foo{bar}@example.com', 'foo{bar}@example.com'), # examples from RFC 3696 # punting on the difficult and extremely uncommon ones - #('"Abc\@def"@example.com', '"Abc\@def"@example.com'), - #('"Fred Bloggs"@example.com', '"Fred Bloggs"@example.com'), - #('"Joe\\Blow"@example.com', '"Joe\\Blow"@example.com'), - #('"Abc@def"@example.com', '"Abc@def"@example.com'), + # ('"Abc\@def"@example.com', '"Abc\@def"@example.com'), + # ('"Fred Bloggs"@example.com', '"Fred Bloggs"@example.com'), + # ('"Joe\\Blow"@example.com', '"Joe\\Blow"@example.com'), + # ('"Abc@def"@example.com', '"Abc@def"@example.com'), ('customer/department=shipping@example.com', 'customer/department=shipping@example.com'), ('$A12345@example.com', '$A12345@example.com'), diff --git a/tests/test_htmlfill.py b/tests/test_htmlfill.py index b3eb86ef..afa624b0 100644 --- a/tests/test_htmlfill.py +++ b/tests/test_htmlfill.py @@ -29,6 +29,7 @@ def collect_values(): fn = os.path.join(data_dir, fn) yield fn + @pytest.mark.parametrize('filename', list(collect_values())) def test_runfile(filename): f = open(filename) diff --git a/tests/test_htmlfill_control.py b/tests/test_htmlfill_control.py index e678115f..e6959594 100644 --- a/tests/test_htmlfill_control.py +++ b/tests/test_htmlfill_control.py @@ -94,6 +94,7 @@ def test_error_attr_ignore(): rendered_html = htmlfill.render(html, errors={"foo": "bang"}, force_defaults=True, data_formencode_form="b",) + assert expected_html == rendered_html def test_error_attr_form(): diff --git a/tests/test_schema.py b/tests/test_schema.py index a2afba2a..9188f4c8 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -176,8 +176,9 @@ class ChainedTest(Schema): b = validators.String() b_confirm = validators.String() - chained_validators = [validators.FieldsMatch('a', 'a_confirm'), - validators.FieldsMatch('b', 'b_confirm')] + chained_validators = [ + validators.FieldsMatch('a', 'a_confirm'), + validators.FieldsMatch('b', 'b_confirm')] def test_multiple_chained_validators_errors(): diff --git a/tests/test_subclassing_old.py b/tests/test_subclassing_old.py index 92b34007..54051c24 100644 --- a/tests/test_subclassing_old.py +++ b/tests/test_subclassing_old.py @@ -239,7 +239,7 @@ def test_is_validator(self): self.assertTrue(is_validator(self.validator)) def test_to_python(self): - with warnings.catch_warnings(record=True) as _ignore: + with warnings.catch_warnings(record=True): ccv = self.validator self.assertEqual(ccv.to_python('2'), 2) self.assertEqual(ccv.to_python('4'), 4) diff --git a/tests/test_validators.py b/tests/test_validators.py index cfbf4814..02f02b8f 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -705,13 +705,14 @@ def test_leading_zeros_allowed(self): validate = self.validator(leading_zeros=True).to_python try: validate('1.2.3.037') - except Invalid as e: + except Invalid: self.fail('IP address octets with leading zeros should be valid') try: validate('1.2.3.0377') except Invalid as e: - self.assertTrue("The octets must be within the range of 0-255" - " (not '377')" in unicode(e).replace("u'", "'")) + self.assertTrue( + "The octets must be within the range of 0-255 (not '377')" + in unicode(e).replace("u'", "'")) else: self.fail( 'IP address octets should not be interpreted as octal numbers') @@ -723,8 +724,9 @@ def setUp(self): self.validator = validators.URL() def test_cojp(self): - self.assertEqual(self.validator.to_python('http://domain.co.jp'), - 'http://domain.co.jp') + self.assertEqual( + self.validator.to_python('http://domain.co.jp'), + 'http://domain.co.jp') def test_1char_thirdlevel(self): self.assertEqual(self.validator.to_python( diff --git a/tox.ini b/tox.ini index 8b5ffd82..d578ee9c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,13 @@ [tox] envlist=py37,py38,py39,py310,py311,py312,pypy3 +[testenv:flake8] +basepython = python3.11 +deps = + flake8 +commands = + flake8 src tests + [testenv] deps= pytest From 134b4fc8748ddd0cb01f03f5d2db6b89835ddd6e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:55:54 +0100 Subject: [PATCH 095/123] Fix some more flake8 issues --- setup.cfg | 2 +- src/formencode/__init__.py | 9 ++++++++- src/formencode/api.py | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7a0fe578..75eb5b52 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,5 +73,5 @@ max_line_length=88 [flake8] max-line-length = 88 -extend-ignore = E203,E128,E402,E501,E731,F821,F401 +extend-ignore = E128,E402,E501,E731 diff --git a/src/formencode/__init__.py b/src/formencode/__init__.py index 18ca699b..fde4099a 100644 --- a/src/formencode/__init__.py +++ b/src/formencode/__init__.py @@ -19,7 +19,14 @@ def version(distribution_name): from formencode import national from formencode.variabledecode import NestedVariables +__all__ = [ + 'NoDefault', 'Invalid', 'Validator', 'Identity', 'FancyValidator', + 'is_empty', 'is_validator', + 'Schema', 'CompoundValidator', 'Any', 'All', 'Pipe', + 'ForEach', 'validators', 'national', 'NestedVariables', + '__version__'] + try: __version__ = version(__name__) -except DistributionNotFound: # package is not installed +except PackageNotFoundError: # package is not installed __version__ = 'local-test' diff --git a/src/formencode/api.py b/src/formencode/api.py index fcb2d441..88441c7e 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -67,10 +67,11 @@ def set_stdtranslation(domain="FormEncode", languages=None, set_stdtranslation() # Dummy i18n translation function, nothing is translated here. -# Instead this is actually done in api.Validator.message. +# Instead, this is actually done in api.Validator.message. # The surrounding _('string') of the strings is only for extracting # the strings automatically. -# If you run pygettext with this source comment this function out temporarily. +# If you run pygettext with this source, comment this function out temporarily. + _ = lambda s: s From 8b60178ee25e9e00d7f7fecae0c9ca020bc44a2e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 13:58:11 +0100 Subject: [PATCH 096/123] Fix GitHub actions --- .github/workflows/run-qa.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-qa.yml b/.github/workflows/run-qa.yml index 222b7cfe..25879ccd 100644 --- a/.github/workflows/run-qa.yml +++ b/.github/workflows/run-qa.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubutu-latest + runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 13d8d2ab..0aaad708 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -33,4 +33,4 @@ jobs: - name: Install dependencies run: make - name: Run tests - run: make ci + run: make tests From 399472d052e176a76483ecf90185e1b6106adbee Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 14:05:42 +0100 Subject: [PATCH 097/123] Fix GitHub action for flake8 --- .github/workflows/run-qa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-qa.yml b/.github/workflows/run-qa.yml index 25879ccd..0946cd2b 100644 --- a/.github/workflows/run-qa.yml +++ b/.github/workflows/run-qa.yml @@ -17,6 +17,6 @@ jobs: with: python-version: '3.12' - name: Install dependencies - run: make + run: pip install flake8 - name: Run code quality checks run: make flake8 From 98e9e97ea6e8a7574fb106d699c2b00e3a6bc5ae Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 16:15:24 +0100 Subject: [PATCH 098/123] Remove now unnecessary dependency on six --- Makefile | 2 +- docs/Validator.txt | 25 ++-- docs/conf.py | 15 +- docs/modules/htmlfill.txt | 1 - examples/WebwareExamples/index.py | 5 +- requirements-dev.txt | 2 +- requirements-test.txt | 4 +- setup.cfg | 1 - src/formencode/api.py | 77 ++--------- src/formencode/compound.py | 15 +- src/formencode/context.py | 15 +- src/formencode/declarative.py | 15 +- src/formencode/doctest_xml_compare.py | 13 +- src/formencode/foreach.py | 12 +- src/formencode/htmlfill.py | 26 ++-- src/formencode/htmlfill_schemabuilder.py | 2 +- src/formencode/htmlgen.py | 55 ++------ src/formencode/interfaces.py | 4 +- src/formencode/national.py | 24 ++-- src/formencode/rewritingparser.py | 33 ++--- src/formencode/schema.py | 59 +++----- src/formencode/validators.py | 169 ++++++++--------------- src/formencode/variabledecode.py | 12 +- tests/__init__.py | 2 +- tests/test_cc.py | 4 +- tests/test_compound.py | 12 +- tests/test_declarative.py | 4 +- tests/test_doctests.py | 31 ++--- tests/test_email.py | 3 +- tests/test_htmlfill.py | 15 +- tests/test_htmlgen.py | 12 +- tests/test_i18n.py | 11 +- tests/test_schema.py | 22 ++- tests/test_subclassing.py | 33 ++--- tests/test_subclassing_old.py | 43 ++---- tests/test_validators.py | 162 ++++++++++------------ tox.ini | 12 +- 37 files changed, 358 insertions(+), 594 deletions(-) diff --git a/Makefile b/Makefile index 707329a9..b27f4fd5 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ init: .PHONY: tests tests: - pytest + pytest tests flake8: flake8 src tests diff --git a/docs/Validator.txt b/docs/Validator.txt index 22c0984f..5166fdcd 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -56,25 +56,22 @@ The core of this validation process is two methods and an exception:: exception. Typically we'd catch that exception, and use it for some sort of feedback. Like: -.. comment (fake raw_input): +.. comment (fake input): - >>> raw_input_input = [] - >>> def raw_input(prompt): - ... value = raw_input_input.pop(0) + >>> input_values = [] + >>> def input(prompt): + ... value = input_values.pop(0) ... print ('%s%s' % (prompt, value)) ... return value - >>> raw_input_input.extend(['ten', '10']) - >>> raw_input_input.extend(['bob', 'bob@nowhere.com']) - >>> import six - >>> if not six.PY2: # Python 3 - ... input = raw_input + >>> input_values.extend(['ten', '10']) + >>> input_values.extend(['bob', 'bob@nowhere.com']) :: >>> def get_integer(): ... while 1: ... try: - ... value = raw_input('Enter a number: ') + ... value = input('Enter a number: ') ... return validator.to_python(value) ... except formencode.Invalid as e: ... print (e) @@ -90,10 +87,10 @@ We can also generalize this kind of function:: >>> def valid_input(prompt, validator): ... while 1: ... try: - ... value = raw_input(prompt) + ... value = input(prompt) ... return validator.to_python(value) ... except formencode.Invalid as e: - ... print (e) + ... print(e) >>> valid_input('Enter your email: ', validators.Email()) Enter your email: bob An email address must contain a single @ @@ -573,12 +570,12 @@ Optionally you can also add a test of your language to ne = formencode.validators.NotEmpty() [...] def test_de(): - _test_lang("de", u"Bitte einen Wert eingeben") + _test_lang("de", "Bitte einen Wert eingeben") And the test for your language:: def test_(): - _test_lang("", u"") + _test_lang("", "") HTTP/HTML Form Input -------------------- diff --git a/docs/conf.py b/docs/conf.py index 1e4d14de..88cc6407 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,15 +32,12 @@ # The suffix of source filenames. source_suffix = '.txt' -# The encoding of source files. -#source_encoding = 'utf-8-sig' - # The master toctree document. master_doc = 'index' # General information about the project. -project = u'FormEncode' -copyright = u'2008-2012, Ian Bicking and Contributors' +project = 'FormEncode' +copyright = '2008-2023, Ian Bicking and Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -180,8 +177,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'FormEncode.tex', u'FormEncode Documentation', - u'Ian Bicking', 'manual'), + ('index', 'FormEncode.tex', 'FormEncode Documentation', + 'Ian Bicking', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -213,6 +210,6 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'formencode', u'FormEncode Documentation', - [u'Ian Bicking'], 1) + ('index', 'formencode', 'FormEncode Documentation', + ['Ian Bicking'], 1) ] diff --git a/docs/modules/htmlfill.txt b/docs/modules/htmlfill.txt index ad13455c..0cff1dd1 100644 --- a/docs/modules/htmlfill.txt +++ b/docs/modules/htmlfill.txt @@ -13,5 +13,4 @@ Module Contents .. autofunction:: escape_formatter .. autofunction:: escapenl_formatter .. autofunction:: ignore_formatter -.. autofunction:: ignore_formatter .. autoclass:: FillingParser diff --git a/examples/WebwareExamples/index.py b/examples/WebwareExamples/index.py index 9faf7f53..4a3fdd7b 100644 --- a/examples/WebwareExamples/index.py +++ b/examples/WebwareExamples/index.py @@ -1,7 +1,6 @@ from formencode import Invalid, htmlfill, Schema, validators from WebKit.Page import Page -import six page_style = ''' @@ -74,8 +73,8 @@ def save(self): try: fields = FormSchema.to_python(fields, self) except Invalid as e: - errors = dict((k, v.encode('utf-8')) - for k, v in six.iteritems(e.unpack_errors())) + errors = {k: v.encode('utf-8') + for k, v in e.unpack_errors().items()} print("Errors:", errors) self.rendered_form = htmlfill.render(form_template, defaults=fields, errors=errors) diff --git a/requirements-dev.txt b/requirements-dev.txt index c40fa96e..7a52c582 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ build wheel -black flake8 +sphinx -r requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt index d858886e..556e04cd 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,5 @@ +dnspython +pycountry pytest pytest-cov wheel -pycountry -dnspython diff --git a/setup.cfg b/setup.cfg index 75eb5b52..d3fd4c90 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,7 +32,6 @@ packages = find: package_dir = =src python_requires = >=3.7 -install_requires = six include_package_data = True [options.packages.find] diff --git a/src/formencode/api.py b/src/formencode/api.py index 88441c7e..1f1cffc8 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -8,7 +8,6 @@ import re import textwrap import warnings -import six try: from pkg_resources import resource_filename @@ -58,10 +57,7 @@ def set_stdtranslation(domain="FormEncode", languages=None, languages=languages, localedir=localedir, fallback=True) global _stdtrans - try: - _stdtrans = t.ugettext - except AttributeError: # Python 3 - _stdtrans = t.gettext + _stdtrans = t.gettext set_stdtranslation() @@ -93,7 +89,7 @@ def inner(*args, **kwargs): return outer -class NoDefault(object): +class NoDefault: """A dummy value used for parameters with no default.""" @@ -150,18 +146,7 @@ def __init__(self, msg, % (self, self.error_list, self.error_dict)) def __str__(self): - val = self.msg - return val - - if six.text_type is not str: # Python 2 - - def __unicode__(self): - if isinstance(self.msg, six.text_type): - return self.msg - elif isinstance(self.msg, str): - return self.msg.decode('utf8') - else: - return six.text_type(self.msg) + return self.msg def unpack_errors(self, encode_variables=False, dict_char='.', list_char='-'): @@ -180,9 +165,9 @@ def unpack_errors(self, encode_variables=False, dict_char='.', for item in self.error_list] if self.error_dict: result = {} - for name, item in six.iteritems(self.error_dict): + for name, item in self.error_dict.items(): result[name] = item if isinstance( - item, six.string_types) else item.unpack_errors() + item, str) else item.unpack_errors() if encode_variables: from . import variabledecode result = variabledecode.variable_encode( @@ -239,32 +224,6 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): return value - if six.text_type is str: - _message_vars_decode = None - else: - def _message_vars_decode(self, message_vars): - """ - Under python2, a form value in web frameworks may be encoded as - UTF8 unicode. The standard error templates use a ``%(value)r`` - string formatting, which will render the error as ``u"Foo"`` instead - of just ``"Foo"``. This decoder, which can be overridden, will - encode the value back to a python string and render ``"Foo"``. This - decoder will only update the error dictionary if the unicode and - string values are equal by comparison. - - v = message_vars.items()[0][1] - """ - if six.text_type is not str: - for (k, v) in message_vars.items(): - if isinstance(v, six.text_type): - try: - v2 = v.encode('utf-8') - except Exception: - v2 = v - if v == v2: - message_vars[k] = v2 - return message_vars - def message(self, msgName, state, **kw): # determine translation function try: @@ -272,8 +231,8 @@ def message(self, msgName, state, **kw): except AttributeError: try: if self.use_builtins_gettext: - import six.moves.builtins - trans = six.moves.builtins._ + import builtins + trans = builtins._ else: trans = _stdtrans except AttributeError: @@ -282,12 +241,7 @@ def message(self, msgName, state, **kw): if not callable(trans): trans = _stdtrans - msg = self._messages[msgName] - msg = trans(msg, **self.gettextargs) - - if self._message_vars_decode: - # handle custom decoding for message vars - kw = self._message_vars_decode(kw) + msg = trans(self._messages[msgName], **self.gettextargs) try: return msg % kw @@ -340,11 +294,10 @@ def _initialize_docstring(cls): This changes the class's docstring to include information about all the messages this validator uses. """ - doc = cls.__doc__ or '' - doc = [textwrap.dedent(doc).rstrip()] - messages = sorted(six.iteritems(cls._messages)) - doc.append('\n\n**Messages**\n\n') - for name, default in messages: + doc = [ + textwrap.dedent(cls.__doc__ or '').rstrip(), + '\n\n**Messages**\n\n'] + for name, default in sorted(cls._messages.items()): default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default) doc.append('``' + name + '``:\n') doc.append(' ' + default + '\n\n') @@ -487,7 +440,7 @@ def __classinit__(cls, new_attrs): def to_python(self, value, state=None): try: - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): value = value.strip() elif hasattr(value, 'mixed'): # Support Paste's MultiDict @@ -515,7 +468,7 @@ def to_python(self, value, state=None): def from_python(self, value, state=None): try: - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): value = value.strip() if not self.accept_python: if self.is_empty(value): @@ -551,7 +504,7 @@ def empty_value(self, value): return None def assert_string(self, value, state): - if not isinstance(value, six.string_types): + if not isinstance(value, str): raise Invalid(self.message('badType', state, type=type(value), value=value), value, state) diff --git a/src/formencode/compound.py b/src/formencode/compound.py index 72bc6dfd..4b3be890 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -1,9 +1,8 @@ """ Validators for applying validations in sequence. """ -from .api import (FancyValidator, Identity, Invalid, NoDefault, Validator, - is_validator) -import six +from .api import ( + FancyValidator, Identity, Invalid, NoDefault, Validator, is_validator) __all__ = ['CompoundValidator', 'Any', 'All', 'Pipe'] @@ -36,14 +35,14 @@ class CompoundValidator(FancyValidator): @staticmethod def __classinit__(cls, new_attrs): FancyValidator.__classinit__(cls, new_attrs) - toAdd = [] - for name, value in six.iteritems(new_attrs): + to_add = [] + for name, value in new_attrs.items(): if is_validator(value) and value is not Identity: - toAdd.append((name, value)) + to_add.append((name, value)) # @@: Should we really delete too? delattr(cls, name) - toAdd.sort() - cls.validators.extend([value for _name, value in toAdd]) + to_add.sort() + cls.validators.extend([value for _name, value in to_add]) def __init__(self, *args, **kw): Validator.__init__(self, *args, **kw) diff --git a/src/formencode/context.py b/src/formencode/context.py index d898d4b8..1f3ac8ea 100644 --- a/src/formencode/context.py +++ b/src/formencode/context.py @@ -25,7 +25,7 @@ def do_stuff(): context object to pass information to another thread. In a single-thread environment it doesn't really matter. -Typically you will create ``Context`` instances for your application, +Typically, you will create ``Context`` instances for your application, environment, etc. These should be global module-level variables, that may be imported by any interested module; each instance is a namespace of its own. @@ -41,10 +41,10 @@ def do_stuff(): value. ``None`` is a typical value for that. Another is ``context.set_default(**vars)``, which will set only those -variables to default values. This will not effect the stack of -scopes, but will only add defaults. +variables to default values. This will not affect the stack of scopes, +but will only add defaults. -When Python 2.5 comes out, this syntax would certainly be useful:: +this syntax would certainly be useful:: with context(page='view'): do stuff... @@ -55,14 +55,13 @@ def do_stuff(): import threading from itertools import count -from six.moves import range __all__ = ['Context', 'ContextRestoreError'] _restore_ids = count() -class NoDefault(object): +class NoDefault: """A dummy value used for parameters with no default.""" @@ -70,7 +69,7 @@ class ContextRestoreError(Exception): """Raised when something is restored out-of-order.""" -class Context(object): +class Context: def __init__(self, default=NoDefault): self.__dict__['_local'] = threading.local() @@ -152,7 +151,7 @@ def __repr__(self): self.__class__.__name__, myid, ' '.join(varlist)) -class RestoreState(object): +class RestoreState: def __init__(self, context, state_id): self.state_id = state_id diff --git a/src/formencode/declarative.py b/src/formencode/declarative.py index c4a7000d..550a2703 100644 --- a/src/formencode/declarative.py +++ b/src/formencode/declarative.py @@ -22,12 +22,9 @@ import types from itertools import count -import six -from six.moves import map -from six.moves import zip -class classinstancemethod(object): +class classinstancemethod: """ Acts like a class method when called from a class, like an instance method when called by an instance. The method should @@ -42,7 +39,7 @@ def __get__(self, obj, cls=None): return _methodwrapper(self.func, obj=obj, cls=cls) -class _methodwrapper(object): +class _methodwrapper: def __init__(self, func, obj, cls): self.func = func @@ -84,7 +81,7 @@ def __new__(meta, class_name, bases, new_attrs): return cls -class singletonmethod(object): +class singletonmethod: """ For Declarative subclasses, this decorator will call the method on the cls.singleton() object if called as a class method (or @@ -100,7 +97,7 @@ def __get__(self, obj, cls=None): return types.MethodType(self.func, obj) -class Declarative(six.with_metaclass(DeclarativeMeta, object)): +class Declarative(metaclass=DeclarativeMeta): __unpackargs__ = () @@ -144,7 +141,7 @@ def __init__(self, *args, **kw): for name in self.__mutableattributes__: if name not in kw: setattr(self, name, copy.copy(getattr(self, name))) - for name, value in six.iteritems(kw): + for name, value in kw.items(): setattr(self, name, value) if 'declarative_count' not in kw: self.declarative_count = next(self.counter) @@ -189,7 +186,7 @@ def __sourcerepr__(self, source, binding=None): else: break args.extend('%s=%s' % (name, source.makeRepr(value)) - for (name, value) in six.iteritems(vals)) + for (name, value) in vals.items()) return '%s(%s)' % (self.__class__.__name__, ', '.join(args)) diff --git a/src/formencode/doctest_xml_compare.py b/src/formencode/doctest_xml_compare.py index 94b490c8..395d697a 100644 --- a/src/formencode/doctest_xml_compare.py +++ b/src/formencode/doctest_xml_compare.py @@ -1,12 +1,7 @@ import doctest import xml.etree.ElementTree as ET -import six -from six.moves import map -from six.moves import zip -try: - XMLParseError = ET.ParseError -except AttributeError: # Python < 2.7 - from xml.parsers.expat import ExpatError as XMLParseError + +XMLParseError = ET.ParseError RealOutputChecker = doctest.OutputChecker @@ -71,7 +66,7 @@ def xml_compare(x1, x2, reporter=None): if reporter: reporter('Tags do not match: %s and %s' % (x1.tag, x2.tag)) return False - for name, value in six.iteritems(x1.attrib): + for name, value in x1.attrib.items(): if x2.attrib.get(name) != value: if reporter: reporter('Attributes do not match: %s=%r, %s=%r' @@ -122,7 +117,7 @@ def make_xml(s): def make_string(xml): - if isinstance(xml, six.string_types): + if isinstance(xml, str): xml = make_xml(xml) s = ET.tostring(xml) if s == '': diff --git a/src/formencode/foreach.py b/src/formencode/foreach.py index f3b45886..96c064a1 100644 --- a/src/formencode/foreach.py +++ b/src/formencode/foreach.py @@ -4,7 +4,6 @@ from .api import NoDefault, Invalid from .compound import CompoundValidator, from_python -import six __all__ = ['ForEach'] @@ -84,7 +83,7 @@ def _attempt_convert(self, value, state, validate): new_list = set(new_list) return new_list else: - raise Invalid('Errors:\n%s' % '\n'.join(six.text_type(e) + raise Invalid('Errors:\n%s' % '\n'.join(str(e) for e in errors if e), value, state, error_list=errors) finally: if state is not None: @@ -106,15 +105,14 @@ def _attempt_convert(self, value, state, validate): def empty_value(self, value): return [] - class _IfMissing(object): + class _IfMissing: def __get__(self, obj, cls=None): if obj is None: return [] - elif obj._if_missing is ForEach._if_missing: + if obj._if_missing is ForEach._if_missing: return [] - else: - return obj._if_missing + return obj._if_missing def __set__(self, obj, value): obj._if_missing = value @@ -126,7 +124,7 @@ def __delete__(self, obj): del _IfMissing def _convert_to_list(self, value): - if isinstance(value, six.string_types): + if isinstance(value, str): return [value] elif value is None: return [] diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index 5c919d93..13b28629 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -5,7 +5,6 @@ import re from formencode.rewritingparser import RewritingParser, html_quote -import six __all__ = ['render', 'htmlliteral', 'default_formatter', 'none_formatter', 'escape_formatter', @@ -125,7 +124,7 @@ def render(form, defaults=None, errors=None, use_all_keys=False, return p.text() -class htmlliteral(object): +class htmlliteral: def __init__(self, html, text=None): if text is None: @@ -246,7 +245,7 @@ def __init__(self, defaults, errors=None, use_all_keys=False, self.in_select = None self.skip_next = False self.errors = errors or {} - if isinstance(self.errors, six.string_types): + if isinstance(self.errors, str): self.errors = {None: self.errors} self.in_error = None self.skip_error = False @@ -275,14 +274,11 @@ def str_compare(self, str1, str2): Compare the two objects as strings (coercing to strings if necessary). Also uses encoding to compare the strings. """ - if not isinstance(str1, six.string_types): - if hasattr(str1, '__unicode__'): - str1 = six.text_type(str1) - else: - str1 = str(str1) + if not isinstance(str1, str): + str1 = str(str1) if type(str1) is type(str2): return str1 == str2 - if isinstance(str1, six.text_type): + if isinstance(str1, str): str1 = str1.encode(self.encoding or self.default_encoding) else: str2 = str2.encode(self.encoding or self.default_encoding) @@ -296,7 +292,7 @@ def close(self): if key in unused_errors: del unused_errors[key] if self.auto_error_formatter: - for key, value in six.iteritems(unused_errors): + for key, value in unused_errors.items(): error_message = self.auto_error_formatter(value) error_message = '\n%s' % (key, error_message) self.insert_at_marker( @@ -319,9 +315,6 @@ def close(self): if self.encoding is not None: new_content = [] for item in self._content: - if (six.text_type is not str # Python 2 - and isinstance(item, str)): - item = item.decode(self.encoding) new_content.append(item) self._content = new_content self._text = self._get_text() @@ -421,11 +414,8 @@ def handle_input(self, attrs, startend): if self.prefix_error: self.write_marker(name) value = self.defaults.get(name) - if (six.text_type is not str # Python 2 - and isinstance(name, six.text_type) and isinstance(value, str)): - value = value.decode(self.encoding or self.default_encoding) if name in self.add_attributes: - for attr_name, attr_value in six.iteritems(self.add_attributes[name]): + for attr_name, attr_value in self.add_attributes[name].items(): if attr_name.startswith('+'): attr_name = attr_name[1:] self.set_attr(attrs, attr_name, @@ -571,7 +561,7 @@ def selected_multiple(self, obj, value): """ if obj is None: return False - if isinstance(obj, six.string_types): + if isinstance(obj, str): return obj == value if hasattr(obj, '__contains__'): if value in obj: diff --git a/src/formencode/htmlfill_schemabuilder.py b/src/formencode/htmlfill_schemabuilder.py index 03f7ade9..5519a505 100644 --- a/src/formencode/htmlfill_schemabuilder.py +++ b/src/formencode/htmlfill_schemabuilder.py @@ -60,7 +60,7 @@ def force_list(v): return [v] -class SchemaBuilder(object): +class SchemaBuilder: def __init__(self, validators=default_validators): self.validators = validators diff --git a/src/formencode/htmlgen.py b/src/formencode/htmlgen.py index 4e0090a0..c41d526c 100644 --- a/src/formencode/htmlgen.py +++ b/src/formencode/htmlgen.py @@ -57,14 +57,8 @@ """ import xml.etree.ElementTree as ET -import six -from six.moves import map - -try: - from html import escape -except ImportError: # Python < 3.2 - from cgi import escape +from html import escape __all__ = ['html'] @@ -92,26 +86,18 @@ def __call__(self, *args): def quote(self, arg): if arg is None: return '' - if six.text_type is not str: # Python 2 - arg = six.text_type(arg).encode(default_encoding) return escape(arg, True) def str(self, arg, encoding=None): - if isinstance(arg, six.string_types): - if not isinstance(arg, str): - arg = arg.encode(default_encoding) + if isinstance(arg, str): return arg - elif arg is None: + if isinstance(str, bytes): + return arg.encode(default_encoding) + if arg is None: return '' - elif isinstance(arg, (list, tuple)): + if isinstance(arg, (list, tuple)): return ''.join(map(self.str, arg)) - elif isinstance(arg, Element): - return str(arg) - else: - arg = six.text_type(arg) - if not isinstance(arg, str): # Python 2 - arg = arg.encode(default_encoding) - return arg + return str(arg) html = _HTML() @@ -134,7 +120,7 @@ def __call__(self, *args, **kw): if value is None: del kw[name] continue - kw[name] = six.text_type(value) + kw[name] = str(value) if name.endswith('_'): kw[name[:-1]] = value del kw[name] @@ -154,33 +140,22 @@ def __call__(self, *args, **kw): if not ET.iselement(arg): if last is None: if el.text is None: - el.text = six.text_type(arg) + el.text = str(arg) else: - el.text += six.text_type(arg) + el.text += str(arg) else: if last.tail is None: - last.tail = six.text_type(arg) + last.tail = str(arg) else: - last.tail += six.text_type(arg) + last.tail += str(arg) else: last = arg el.append(last) return el - if six.text_type is str: # Python 3 - - def __str__(self): - return ET.tostring( - self, default_encoding).decode(default_encoding) - - else: - - def __str__(self): - return ET.tostring(self, default_encoding) - - def __unicode__(self): - # This is lame! - return str(self).decode(default_encoding) + def __str__(self): + return ET.tostring( + self, default_encoding).decode(default_encoding) def __repr__(self): content = str(self) diff --git a/src/formencode/interfaces.py b/src/formencode/interfaces.py index 2d8cfae4..d110c974 100644 --- a/src/formencode/interfaces.py +++ b/src/formencode/interfaces.py @@ -3,14 +3,14 @@ """ -class Attribute(object): +class Attribute: def __init__(self, description, name=None): self.description = description self.name = name -class Interface(object): +class Interface: pass diff --git a/src/formencode/national.py b/src/formencode/national.py index 8c42fb1b..b78256f0 100644 --- a/src/formencode/national.py +++ b/src/formencode/national.py @@ -6,7 +6,6 @@ from .api import FancyValidator from .compound import Any from .validators import Regex, Invalid, _ -import six try: import pycountry @@ -207,7 +206,7 @@ def assembly_regex(self, partition_lengths, delimiter, strict): def __init__(self, partition_lengths, delimiter=None, strict=False, *args, **kw): - if isinstance(partition_lengths, six.integer_types): + if isinstance(partition_lengths, int): partition_lengths = [partition_lengths] if not delimiter: delimiter = '' @@ -415,19 +414,19 @@ class CountryValidator(FancyValidator): :: >>> CountryValidator.to_python('Germany') - u'DE' + 'DE' >>> CountryValidator.to_python('Finland') - u'FI' + 'FI' >>> CountryValidator.to_python('UNITED STATES') - u'US' + 'US' >>> CountryValidator.to_python('Krakovia') Traceback (most recent call last): ... Invalid: That country is not listed in ISO 3166 >>> CountryValidator.from_python('DE') - u'Germany' + 'Germany' >>> CountryValidator.from_python('FI') - u'Finland' + 'Finland' """ key_ok = True @@ -771,8 +770,7 @@ def _convert_to_python(self, value, state): value = value.encode('ascii', 'strict') except UnicodeEncodeError: raise Invalid(self.message('phoneFormat', state), value, state) - if six.text_type is str: # Python 3 - value = value.decode('ascii') + value = value.decode('ascii') value = self._mark_chars_re.sub('-', value) for f, t in [(' ', ' '), ('--', '-'), (' - ', '-'), ('- ', '-'), (' -', '-')]: @@ -813,17 +811,17 @@ class LanguageValidator(FancyValidator): >>> l = LanguageValidator() >>> l.to_python('German') - u'de' + 'de' >>> l.to_python('Chinese') - u'zh' + 'zh' >>> l.to_python('Klingonian') Traceback (most recent call last): ... Invalid: That language is not listed in ISO 639 >>> l.from_python('de') - u'German' + 'German' >>> l.from_python('zh') - u'Chinese' + 'Chinese' """ key_ok = True diff --git a/src/formencode/rewritingparser.py b/src/formencode/rewritingparser.py index 0342a6c4..ca53ff03 100644 --- a/src/formencode/rewritingparser.py +++ b/src/formencode/rewritingparser.py @@ -1,14 +1,8 @@ -from six.moves import html_parser import re -import six -from six.moves import range -try: - from html import escape -except ImportError: # Python < 3.2 - from cgi import escape - -from six.moves.html_entities import name2codepoint +from html import escape +from html.entities import name2codepoint +from html.parser import HTMLParser def html_quote(v): @@ -16,26 +10,19 @@ def html_quote(v): return '' if hasattr(v, '__html__'): return v.__html__() - if isinstance(v, six.string_types): + if isinstance(v, str): return escape(v, True) - if hasattr(v, '__unicode__'): - v = six.text_type(v) - else: - v = str(v) - return escape(v, True) + return escape(str(v), True) -class RewritingParser(html_parser.HTMLParser): +class RewritingParser(HTMLParser): listener = None skip_next = False def __init__(self): self._content = [] - try: - html_parser.HTMLParser.__init__(self, convert_charrefs=False) - except TypeError: # Python < 3.4 - html_parser.HTMLParser.__init__(self) + HTMLParser.__init__(self, convert_charrefs=False) def feed(self, data): self.data_is_str = isinstance(data, str) @@ -44,7 +31,7 @@ def feed(self, data): self.source_pos = 1, 0 if self.listener: self.listener.reset() - html_parser.HTMLParser.feed(self, data) + HTMLParser.feed(self, data) _entityref_re = re.compile(r'&([a-zA-Z][-.a-zA-Z\d]*);') _charref_re = re.compile(r'&#(\d+|[xX][a-fA-F\d]+);') @@ -60,7 +47,7 @@ def _sub_entityref(self, match): # If we don't recognize it, pass it through as though it # wasn't an entity ref at all return match.group(0) - return six.unichr(name2codepoint[name]) + return chr(name2codepoint[name]) def _sub_charref(self, match): num = match.group(1) @@ -68,7 +55,7 @@ def _sub_charref(self, match): num = int(num[1:], 16) else: num = int(num) - return six.unichr(num) + return chr(num) def handle_misc(self, whatever): self.write_pos() diff --git a/src/formencode/schema.py b/src/formencode/schema.py index 362c9c07..a4cfa089 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -3,9 +3,6 @@ from .api import _, is_validator, FancyValidator, Invalid, NoDefault from . import declarative from .exc import FERuntimeWarning -import six -from six.moves import map -from six.moves import zip __all__ = ['Schema'] @@ -84,7 +81,7 @@ def __classinit__(cls, new_attrs): # Scan through the class variables we've defined *just* # for this subclass, looking for validators (both classes # and instances): - for key, value in six.iteritems(new_attrs): + for key, value in new_attrs.items(): if key in ('pre_validators', 'chained_validators'): if is_validator(value): msg = "Any validator with the name %s will be ignored." % \ @@ -99,12 +96,12 @@ def __classinit__(cls, new_attrs): elif key in cls.fields: del cls.fields[key] - for name, value in six.iteritems(cls.fields): + for name, value in cls.fields.items(): cls.add_field(name, value) def __initargs__(self, new_attrs): self.fields = self.fields.copy() - for key, value in six.iteritems(new_attrs): + for key, value in new_attrs.items(): if key in ('pre_validators', 'chained_validators'): if is_validator(value): msg = "Any validator with the name %s will be ignored." % \ @@ -248,7 +245,7 @@ def _convert_from_python(self, value_dict, state): previous_full_dict = getattr(state, 'full_dict', None) state.full_dict = value_dict try: - for name, value in six.iteritems(value_dict): + for name, value in value_dict.items(): try: unused.remove(name) except ValueError: @@ -325,7 +322,7 @@ def subvalidators(self): result = [] result.extend(self.pre_validators) result.extend(self.chained_validators) - result.extend(six.itervalues(self.fields)) + result.extend(self.fields.values()) return result def is_empty(self, value): @@ -336,9 +333,9 @@ def empty_value(self, value): return {} def _value_is_iterator(self, value): - if isinstance(value, six.string_types + (six.binary_type, )): + if isinstance(value, (bytes, str)): return False - elif isinstance(value, (list, tuple)): + if isinstance(value, (list, tuple)): return True try: @@ -351,26 +348,18 @@ def _value_is_iterator(self, value): def format_compound_error(v, indent=0): if isinstance(v, Exception): - try: - return str(v) - except (UnicodeDecodeError, UnicodeEncodeError): - # There doesn't seem to be a better way to get a str() - # version if possible, and unicode() if necessary, because - # testing for the presence of a __unicode__ method isn't - # enough - return six.text_type(v) - elif isinstance(v, dict): + return str(v) + if isinstance(v, dict): return ('%s\n' % (' ' * indent)).join( '%s: %s' % (k, format_compound_error(value, indent=len(k) + 2)) - for k, value in sorted(six.iteritems(v)) if value is not None) - elif isinstance(v, list): + for k, value in sorted(v.items()) if value is not None) + if isinstance(v, list): return ('%s\n' % (' ' * indent)).join( '%s' % (format_compound_error(value, indent=indent)) for value in v if value is not None) - elif isinstance(v, six.string_types): + if isinstance(v, str): return v - else: - assert False, "I didn't expect something like %s" % repr(v) + raise TypeError("I didn't expect something like %r" % v) def merge_dicts(d1, d2): @@ -380,15 +369,14 @@ def merge_dicts(d1, d2): def merge_values(v1, v2): - if isinstance(v1, six.string_types) and isinstance(v2, six.string_types): + if isinstance(v1, str) and isinstance(v2, str): return v1 + '\n' + v2 - elif isinstance(v1, (list, tuple)) and isinstance(v2, (list, tuple)): + if isinstance(v1, (list, tuple)) and isinstance(v2, (list, tuple)): return merge_lists(v1, v2) - elif isinstance(v1, dict) and isinstance(v2, dict): + if isinstance(v1, dict) and isinstance(v2, dict): return merge_dicts(v1, v2) - else: - # @@: Should we just ignore errors? Seems we do... - return v1 + # @@: Should we just ignore errors? Seems we do... + return v1 def merge_lists(l1, l2): @@ -471,18 +459,15 @@ def to_python(self, value_dict, state): errors = self.func(value_dict, state, self) if not errors: return value_dict - if isinstance(errors, six.string_types): + if isinstance(errors, str): raise Invalid(errors, value_dict, state) - elif isinstance(errors, dict): + if isinstance(errors, dict): raise Invalid( format_compound_error(errors), value_dict, state, error_dict=errors) - elif isinstance(errors, Invalid): + if isinstance(errors, Invalid): raise errors - else: - raise TypeError( - "Invalid error value: %r" % errors) - return value_dict + raise TypeError("Invalid error value: %r" % errors) validate_partial = to_python diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 01d853e3..f6c10eef 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -10,9 +10,6 @@ import re import warnings from encodings import idna -import six -from six.moves import map -from six.moves import range try: # import dnspython import dns.resolver @@ -510,13 +507,13 @@ class Regex(FancyValidator): def __init__(self, *args, **kw): FancyValidator.__init__(self, *args, **kw) - if isinstance(self.regex, six.string_types): + if isinstance(self.regex, str): ops = 0 - assert not isinstance(self.regexOps, six.string_types), ( + assert not isinstance(self.regexOps, str), ( "regexOps should be a list of options from the re module " "(names, or actual values)") for op in self.regexOps: - if isinstance(op, six.string_types): + if isinstance(op, str): ops |= getattr(re, op) else: ops |= op @@ -524,13 +521,13 @@ def __init__(self, *args, **kw): def _validate_python(self, value, state): self.assert_string(value, state) - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): value = value.strip() if not self.regex.search(value): raise Invalid(self.message('invalid', state), value, state) def _convert_to_python(self, value, state): - if self.strip and isinstance(value, six.string_types): + if self.strip and isinstance(value, str): return value.strip() return value @@ -612,7 +609,7 @@ def _validate_python(self, value, state): try: items = '; '.join(map(str, self.list)) except UnicodeError: - items = '; '.join(map(six.text_type, self.list)) + items = '; '.join(map(str, self.list)) raise Invalid( self.message('notIn', state, items=items, value=value), value, state) @@ -687,16 +684,15 @@ def _convert_to_python(self, value, state): state, items=items), value, state) def _convert_from_python(self, value, state): - for k, v in six.iteritems(self.dict): + for k, v in self.dict.items(): if value == v: return k if self.hideDict: raise Invalid(self.message('valueNotFound', state), value, state) - else: - items = '; '.join(map(repr, six.itervalues(self.dict))) - raise Invalid( - self.message('chooseValue', state, - value=repr(value), items=items), value, state) + items = '; '.join(map(repr, self.dict.values())) + raise Invalid( + self.message('chooseValue', state, + value=repr(value), items=items), value, state) class IndexListConverter(FancyValidator): @@ -809,13 +805,6 @@ class DateValidator(FancyValidator): def _validate_python(self, value, state): date_format = self.message('date_format', state) - if (str is not six.text_type # Python 2 - and isinstance(date_format, six.text_type)): - # strftime uses the locale encoding, not Unicode - encoding = locale.getlocale(locale.LC_TIME)[1] or 'utf-8' - date_format = date_format.encode(encoding) - else: - encoding = None if self.earliest_date: if callable(self.earliest_date): earliest_date = self.earliest_date() @@ -823,8 +812,6 @@ def _validate_python(self, value, state): earliest_date = self.earliest_date if value < earliest_date: date_formatted = earliest_date.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('after', state, date=date_formatted), value, state) @@ -835,8 +822,6 @@ def _validate_python(self, value, state): latest_date = self.latest_date if value > latest_date: date_formatted = latest_date.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('before', state, date=date_formatted), value, state) @@ -845,8 +830,6 @@ def _validate_python(self, value, state): now = datetime_now(dt_mod) if value < now: date_formatted = now.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('future', state, date=date_formatted), value, state) @@ -1005,8 +988,6 @@ def _convert_to_python(self, value, state): class ByteString(FancyValidator): """Convert to byte string, treating empty things as the empty string. - Under Python 2.x you can also use the alias `String` for this validator. - Also takes a `max` and `min` argument, and the string length must fall in that range. @@ -1058,27 +1039,27 @@ def __initargs__(self, new_attrs): def _convert_to_python(self, value, state): if value is None: value = '' - elif not isinstance(value, six.string_types): + elif not isinstance(value, str): try: value = bytes(value) except UnicodeEncodeError: - value = six.text_type(value) - if self.encoding is not None and isinstance(value, six.text_type): + value = str(value) + if self.encoding is not None and isinstance(value, str): value = value.encode(self.encoding) return value def _convert_from_python(self, value, state): if value is None: value = '' - elif not isinstance(value, six.string_types): + elif not isinstance(value, str): if isinstance(value, (list, tuple)): value = self.list_joiner.join( self._convert_from_python(v, state) for v in value) try: value = str(value) except UnicodeEncodeError: - value = six.text_type(value) - if self.encoding is not None and isinstance(value, six.text_type): + value = str(value) + if self.encoding is not None and isinstance(value, str): value = value.encode(self.encoding) if self.strip: value = value.strip() @@ -1089,11 +1070,8 @@ def _validate_other(self, value, state): return if value is None: value = '' - elif not isinstance(value, six.string_types): - try: - value = str(value) - except UnicodeEncodeError: - value = six.text_type(value) + elif not isinstance(value, str): + value = str(value) if self.max is not None and len(value) > self.max: raise Invalid( self.message('tooLong', state, max=self.max), value, state) @@ -1110,10 +1088,10 @@ class UnicodeString(ByteString): This is implemented as a specialization of the ByteString class. - Under Python 3.x you can also use the alias `String` for this validator. + You can also use the alias `String` for this validator. In addition to the String arguments, an encoding argument is also - accepted. By default the encoding will be utf-8. You can overwrite + accepted. By default, the encoding will be utf-8. You can overwrite this using the encoding parameter. You can also set inputEncoding and outputEncoding differently. An inputEncoding of None means "do not decode", an outputEncoding of None means "do not encode". @@ -1122,11 +1100,11 @@ class UnicodeString(ByteString): :: - >>> UnicodeString().to_python(None) == u'' + >>> UnicodeString().to_python(None) == '' True - >>> UnicodeString().to_python([]) == u'' + >>> UnicodeString().to_python([]) == '' True - >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') == u'Ni Ni Ni' + >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni') == 'Ni Ni Ni' True """ @@ -1145,44 +1123,31 @@ def __init__(self, **kw): def _convert_to_python(self, value, state): if not value: - return u'' - if isinstance(value, six.text_type): + return '' + if isinstance(value, str): return value - if not isinstance(value, six.text_type): - if hasattr(value, '__unicode__'): - value = six.text_type(value) - return value - if not (six.text_type is str # Python 3 - and isinstance(value, bytes) and self.inputEncoding): - value = str(value) - if self.inputEncoding and not isinstance(value, six.text_type): + if isinstance(value, bytes): try: - value = six.text_type(value, self.inputEncoding) + return value.decode( + encoding=self.inputEncoding or self.encoding) except UnicodeDecodeError: raise Invalid(self.message('badEncoding', state), value, state) - except TypeError: - raise Invalid( - self.message('badType', state, - type=type(value), value=value), value, state) - return value + return str(value) def _convert_from_python(self, value, state): - if not isinstance(value, six.text_type): - if hasattr(value, '__unicode__'): - value = six.text_type(value) - else: - value = str(value) - if self.outputEncoding and isinstance(value, six.text_type): + if not isinstance(value, str): + value = str(value) + if self.outputEncoding and isinstance(value, str): value = value.encode(self.outputEncoding) return value def empty_value(self, value): - return u'' + return '' # Provide proper alias for native strings -String = UnicodeString if str is six.text_type else ByteString +String = UnicodeString class Set(FancyValidator): @@ -1350,8 +1315,7 @@ def _validate_python(self, value, state): value, state) try: idna_domain = [idna.ToASCII(p) for p in domain.split('.')] - if six.text_type is str: # Python 3 - idna_domain = [p.decode('ascii') for p in idna_domain] + idna_domain = [p.decode('ascii') for p in idna_domain] idna_domain = '.'.join(idna_domain) except UnicodeError: # UnicodeError: label empty or too long @@ -1453,13 +1417,13 @@ class URL(FancyValidator): You may set allow_idna to False to change this behavior:: >>> URL(allow_idna=True).to_python( - ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') + ... 'http://\u0433\u0443\u0433\u043b.\u0440\u0444') 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=True, add_http=True).to_python( - ... u'\u0433\u0443\u0433\u043b.\u0440\u0444') + ... '\u0433\u0443\u0433\u043b.\u0440\u0444') 'http://xn--c1aay4a.xn--p1ai' >>> URL(allow_idna=False).to_python( - ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444') + ... 'http://\u0433\u0443\u0433\u043b.\u0440\u0444') Traceback (most recent call last): ... Invalid: That is not a valid URL @@ -1523,7 +1487,7 @@ def _convert_to_python(self, value, state): def _encode_idna(self, url): global urlparse if urlparse is None: - from six.moves.urllib import parse as urlparse + from urllib import parse as urlparse try: scheme, netloc, path, params, query, fragment = urlparse.urlparse( url) @@ -1531,8 +1495,7 @@ def _encode_idna(self, url): return url try: netloc = netloc.encode('idna') - if six.text_type is str: # Python 3 - netloc = netloc.decode('ascii') + netloc = netloc.decode('ascii') return str(urlparse.urlunparse((scheme, netloc, path, params, query, fragment))) except UnicodeError: @@ -1541,9 +1504,9 @@ def _encode_idna(self, url): def _check_url_exists(self, url, state): global http_client, urlparse, socket if http_client is None: - from six.moves import http_client + from http import client as http_client if urlparse is None: - from six.moves.urllib import parse as urlparse + import parse as urlparse if socket is None: import socket scheme, netloc, path, params, query, fragment = urlparse.urlparse( @@ -1562,28 +1525,13 @@ def _check_url_exists(self, url, state): conn.close() except http_client.HTTPException as e: e = str(e) - if str is not six.text_type: # Python 2 - try: - e = e.decode('utf-8') - except UnicodeDecodeError: - try: - e = e.decode('latin-1') - except UnicodeDecodeError: - e = e.decode('ascii', 'replace') raise Invalid( - self.message('httpError', state, error=e), state, url) + self.message('httpError', state, error=str(e)), + state, url) except socket.error as e: - e = str(e) - if str is not six.text_type: # Python 2 - try: - e = e.decode('utf-8') - except UnicodeDecodeError: - try: - e = e.decode('latin-1') - except UnicodeDecodeError: - e = e.decode('ascii', 'replace') raise Invalid( - self.message('socketError', state, error=e), state, url) + self.message('socketError', state, error=str(e)), + state, url) else: if res.status == 404: raise Invalid( @@ -1703,7 +1651,7 @@ def _validate_python(self, value, state=None): is not valid. """ - if not isinstance(value, six.string_types): + if not isinstance(value, str): raise Invalid( self.message('badType', state, type=str(type(value)), value=value), value, state) @@ -1883,7 +1831,7 @@ def _convert_to_python(self, value, state): if isinstance(upload, cgi.FieldStorage): filename = upload.filename content = upload.value - elif isinstance(upload, six.string_types) and upload: + elif isinstance(upload, str) and upload: filename = None # @@: Should this encode upload if it is unicode? content = upload @@ -2300,7 +2248,7 @@ def _to_python_tuple(self, value, state): return hour, minute, second def _convert_from_python(self, value, state): - if isinstance(value, six.string_types): + if isinstance(value, str): return value if hasattr(value, 'hour'): hour, minute = value.hour, value.minute @@ -2438,7 +2386,7 @@ class StringBool(FancyValidator): # originally from TurboGears 1 string=_('Value should be %(true)r or %(false)r')) def _convert_to_python(self, value, state): - if isinstance(value, six.string_types): + if isinstance(value, str): value = value.strip().lower() if value in self.true_values: return True @@ -2871,9 +2819,9 @@ def _validate_python(self, field_dict, state): else: errors[name] = self.message('invalidNoMatch', state) if errors: - error_list = sorted(six.iteritems(errors)) error_message = '
\n'.join( - '%s: %s' % (name, value) for name, value in error_list) + '%s: %s' % (name, value) + for name, value in sorted(errors.items())) raise Invalid(error_message, field_dict, state, error_dict=errors) @@ -2930,10 +2878,9 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join('%s: %s' % (name, value) - for name, value in error_list), + for name, value in sorted(errors.items())), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): @@ -3045,10 +2992,9 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join('%s: %s' % (name, value) - for name, value in error_list), + for name, value in sorted(errors.items())), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): @@ -3121,11 +3067,10 @@ def validate_partial(self, field_dict, state): def _validate_python(self, field_dict, state): errors = self._validateReturn(field_dict, state) if errors: - error_list = sorted(six.iteritems(errors)) raise Invalid( '
\n'.join( '%s: %s' % (name, value) - for name, value in error_list), + for name, value in errors.items()), field_dict, state, error_dict=errors) def _validateReturn(self, field_dict, state): @@ -3145,7 +3090,7 @@ def _validateReturn(self, field_dict, state): def validators(): """Return the names of all validators in this module.""" - return [name for name, value in six.iteritems(globals()) + return [name for name, value in globals().items() if isinstance(value, type) and issubclass(value, Validator)] diff --git a/src/formencode/variabledecode.py b/src/formencode/variabledecode.py index fc0542a0..4fe9a1ce 100644 --- a/src/formencode/variabledecode.py +++ b/src/formencode/variabledecode.py @@ -21,8 +21,6 @@ """ from .api import FancyValidator -import six -from six.moves import range __all__ = ['variable_decode', 'variable_encode', 'NestedVariables'] @@ -30,7 +28,7 @@ def _sort_key(item): """Robust sort key that sorts items with invalid keys last. - This is used to make sorting behave the same across Python 2 and 3. + This is used to make sorting work with keys that have difference types. """ key = item[0] return not isinstance(key, int), key @@ -41,7 +39,7 @@ def variable_decode(d, dict_char='.', list_char='-'): result = {} dicts_to_sort = set() known_lengths = {} - for key, value in six.iteritems(d): + for key, value in d.items(): keys = key.split(dict_char) new_keys = [] was_repetition_count = False @@ -103,10 +101,10 @@ def variable_decode(d, dict_char='.', list_char='-'): to_sort = to_sort[sub_key] if None in to_sort: none_values = [(0, x) for x in to_sort.pop(None)] - none_values.extend(six.iteritems(to_sort)) + none_values.extend(to_sort.items()) to_sort = none_values else: - to_sort = six.iteritems(to_sort) + to_sort = to_sort.items() to_sort = [x[1] for x in sorted(to_sort, key=_sort_key)] if key in known_lengths: if len(to_sort) < known_lengths[key]: @@ -122,7 +120,7 @@ def variable_encode(d, prepend='', result=None, add_repetitions=True, if result is None: result = {} if isinstance(d, dict): - for key, value in six.iteritems(d): + for key, value in d.items(): if key is None: name = prepend elif not prepend: diff --git a/tests/__init__.py b/tests/__init__.py index a380a390..d452675a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,7 +8,7 @@ # in this case you must set it manually before running the tests). os.environ['LANGUAGE'] = 'C' -# Enable deprecation warnings (disabled by default in Python > 2.6). +# Enable deprecation warnings (which are disabled by default) import warnings warnings.simplefilter('default') diff --git a/tests/test_cc.py b/tests/test_cc.py index 580f4ae8..429ba114 100644 --- a/tests/test_cc.py +++ b/tests/test_cc.py @@ -21,7 +21,7 @@ def message(self, key): def test_validate(self): validate, message = self.validate, self.message - self.assertTrue(validate('visa', '4' + '1' * 15) is None) + self.assertIsNone(validate('visa', '4' + '1' * 15)) self.assertEqual(validate('visa', '5' + '1' * 12), message('invalidNumber')) self.assertEqual(validate('visa', '4' + '1' * 11 + '2'), @@ -49,7 +49,7 @@ def message(self, key): def test_validate(self): validate, message = self.validate, self.message - self.assertTrue(validate('11', '2250') is None) + self.assertIsNone(validate('11', '2250')) self.assertEqual(validate('11', 'test'), message('notANumber')) self.assertEqual(validate('test', '2250'), message('notANumber')) self.assertEqual(validate('10', '2005'), message('invalidNumber')) diff --git a/tests/test_compound.py b/tests/test_compound.py index 928163a1..c8f838ea 100644 --- a/tests/test_compound.py +++ b/tests/test_compound.py @@ -11,8 +11,8 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) - self.assertTrue('validators=[]' in r) + self.assertNotIn('validatorArgs', r) + self.assertIn('validators=[]', r) def test_to_python(self): self.assertRaises(NotImplementedError, @@ -35,7 +35,7 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) + self.assertNotIn('validatorArgs', r) self.assertEqual(r.count('DictConverter'), 2) def test_to_python(self): @@ -58,7 +58,7 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) + self.assertNotIn('validatorArgs', r) self.assertEqual(r.count('DictConverter'), 3) def test_to_python(self): @@ -73,7 +73,7 @@ def test_to_python_error(self): try: self.validator.to_python(3) except Invalid as e: - self.assertTrue('Enter a value from: 2' in str(e)) + self.assertIn('Enter a value from: 2', str(e)) else: self.fail('Invalid should be raised when no validator succeeds.') @@ -90,7 +90,7 @@ def setUp(self): def test_repr(self): r = repr(self.validator) - self.assertFalse('validatorArgs' in r) + self.assertNotIn('validatorArgs', r) self.assertEqual(r.count('DictConverter'), 2) def test_to_python(self): diff --git a/tests/test_declarative.py b/tests/test_declarative.py index f985ad6e..1217621c 100644 --- a/tests/test_declarative.py +++ b/tests/test_declarative.py @@ -22,8 +22,8 @@ def test_call(self): D = declarative.Declarative obj_bar = D(foo='bar', woo='par') obj_baz = obj_bar(foo='baz') - self.assertTrue(type(obj_bar) is type(obj_baz)) - self.assertTrue(obj_bar is not obj_baz) + self.assertIs(type(obj_bar), type(obj_baz)) + self.assertIsNot(obj_bar, obj_baz) self.assertEqual(obj_baz.foo, 'baz') self.assertEqual(obj_baz.woo, 'par') diff --git a/tests/test_doctests.py b/tests/test_doctests.py index d69e3737..4b85dd15 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -8,7 +8,6 @@ from formencode import national from formencode import schema from formencode import validators -import six import pytest @@ -29,24 +28,22 @@ base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -if six.text_type is str: # Python 3 +OutputChecker = doctest.OutputChecker - OutputChecker = doctest.OutputChecker +class OutputChecker3(OutputChecker): + def check_output(self, want, got, optionflags): + if want.startswith("u'"): + want = want[1:] + elif want.startswith("set(["): + want = ( + want[3:] + .replace("([", "{") + .replace("])", "}") + .replace("{}", "set()") + ) + return OutputChecker.check_output(self, want, got, optionflags) - class OutputChecker3(OutputChecker): - def check_output(self, want, got, optionflags): - if want.startswith("u'"): - want = want[1:] - elif want.startswith("set(["): - want = ( - want[3:] - .replace("([", "{") - .replace("])", "}") - .replace("{}", "set()") - ) - return OutputChecker.check_output(self, want, got, optionflags) - - doctest.OutputChecker = OutputChecker3 +doctest.OutputChecker = OutputChecker3 # needed?? def doctest_file(document, verbose, raise_error): diff --git a/tests/test_email.py b/tests/test_email.py index 4f9873c1..ac40b7a6 100644 --- a/tests/test_email.py +++ b/tests/test_email.py @@ -3,7 +3,6 @@ from formencode import Invalid from formencode.validators import Email -import six class TestEmail(unittest.TestCase): @@ -15,7 +14,7 @@ def validate(self, *args): try: return self.validator.to_python(*args) except Invalid as e: - return six.text_type(e) + return str(e) def message(self, message_name, username, domain): email = '@'.join((username, domain)) diff --git a/tests/test_htmlfill.py b/tests/test_htmlfill.py index afa624b0..5e575517 100644 --- a/tests/test_htmlfill.py +++ b/tests/test_htmlfill.py @@ -2,14 +2,11 @@ import re import sys +from html.entities import name2codepoint + import xml.etree.ElementTree as ET -import six -try: - XMLParseError = ET.ParseError -except AttributeError: # Python < 2.7 - from xml.parsers.expat import ExpatError as XMLParseError -from six.moves.html_entities import name2codepoint +XMLParseError = ET.ParseError base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname( os.path.abspath(__file__))))) @@ -46,7 +43,7 @@ def test_runfile(filename): data_content = '' namespace = {} if data_content: - six.exec_(data_content, namespace) + exec(data_content, namespace) data = namespace.copy() data['defaults'] = data.get('defaults', {}) if 'check' in data: @@ -89,7 +86,7 @@ def test_no_trailing_newline(): def test_escape_defaults(): - rarr = six.unichr(name2codepoint['rarr']) + rarr = chr(name2codepoint['rarr']) assert (htmlfill.render('', {}, {}) == '' % rarr) assert (htmlfill.render('', {}, {}) @@ -457,7 +454,7 @@ def test_error_class_textarea(): def test_mix_str_and_unicode(): html = '' - uhtml = six.text_type(html) + uhtml = str(html) cheese = dict(cheese='Käse') ucheese = dict(cheese='Käse') expected = '' diff --git a/tests/test_htmlgen.py b/tests/test_htmlgen.py index 05c7f3a5..0af2421b 100644 --- a/tests/test_htmlgen.py +++ b/tests/test_htmlgen.py @@ -1,11 +1,9 @@ import doctest from formencode.htmlgen import html -import six # A test value that can't be encoded as ascii: -uni_value = u'\xff' -str_value = uni_value if str is six.text_type else uni_value.encode('utf-8') +uni_value = '\xff' def test_basic(): @@ -37,12 +35,12 @@ def test_unicode(): assert False, ( "We need something that can't be ASCII-encoded: %r (%r)" % (uni_value, uni_value.encode('ascii'))) - assert six.text_type(html.b(uni_value)) == u'%s' % uni_value + assert str(html.b(uni_value)) == '%s' % uni_value def test_quote(): assert html.quote('!') == '<hey>!' - assert html.quote(uni_value) == str_value + assert html.quote(uni_value) == uni_value assert html.quote(None) == '' assert html.str(None) == '' assert str(html.b('')) == '<hey>' @@ -58,7 +56,7 @@ def strip(s): return s assert strip(html.comment('test')) == '' - assert strip(html.comment(uni_value)) == '' % str_value + assert strip(html.comment(uni_value)) == '' % uni_value assert strip(html.comment('test')('this')) == '' @@ -76,7 +74,7 @@ def test_namespace(): if __name__ == '__main__': # It's like a super-mini py.test... - for name, value in six.iteritems(globals()): + for name, value in globals().items(): if name.startswith('test'): print(name) value() diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 6f4c8f21..77ad988a 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -1,5 +1,4 @@ import formencode -import six ne = formencode.validators.NotEmpty() @@ -7,15 +6,15 @@ def _test_builtins(func): def dummy(s): return "builtins dummy" - import six.moves.builtins - six.moves.builtins._ = dummy + import builtins + builtins._ = dummy try: ne.to_python("") except formencode.api.Invalid as e: func(e) - del six.moves.builtins._ + del builtins._ def test_builtins(): @@ -34,7 +33,7 @@ def withoutbuiltins(e): def test_state(): - class st(object): + class st: def _(self, s): return "state dummy" @@ -51,7 +50,7 @@ def _test_lang(language, notemptytext): try: ne.to_python("") except formencode.api.Invalid as e: - assert six.text_type(e) == notemptytext + assert str(e) == notemptytext formencode.api.set_stdtranslation() # set back to defaults diff --git a/tests/test_schema.py b/tests/test_schema.py index 9188f4c8..f49cb013 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,6 +1,6 @@ import unittest -from six.moves.urllib.parse import parse_qsl +from urllib.parse import parse_qsl from formencode import Invalid, Validator, compound, foreach, validators from formencode.schema import Schema, merge_dicts, SimpleFormValidator @@ -16,14 +16,14 @@ def _notranslation(s): def setup_module(module): """Disable i18n translation""" - import six.moves.builtins - six.moves.builtins._ = _notranslation + import builtins + builtins._ = _notranslation def teardown_module(module): """Remove translation function""" - import six.moves.builtins - del six.moves.builtins._ + import builtins + del builtins._ def d(**kw): @@ -44,16 +44,15 @@ def cgi_parse(qs): return d -class DecodeCase(object): +class DecodeCase: error_expected = False def __init__(self, schema, input, **output): - self.raw_input = input - self.schema = schema if isinstance(input, str): input = cgi_parse(input) self.input = input + self.schema = schema self.output = output all_cases.append(self) @@ -74,7 +73,7 @@ def __init__(self, *args, **kw): self.output = self.output['text'] def test(self): - print(repr(self.raw_input)) + print(repr(self.input)) try: print(repr(self.schema.to_python(self.input))) except Invalid as e: @@ -211,7 +210,7 @@ def f(value_dict, state, validator): assert f.__doc__ == g.__doc__, "Docstrings don't match!" -class State(object): +class State: pass @@ -261,13 +260,12 @@ def from_python(self, value, state): assert state.key == old_key, "key not restored" -class TestAtLeastOneCheckboxIsChecked(object): +class TestAtLeastOneCheckboxIsChecked: """Tests to address SourceForge bug #1777245 The reporter is trying to enforce agreement to a Terms of Service agreement, with failure to check the 'I agree' checkbox handled as a validation failure. The tests below illustrate a working approach. - """ def setup(self): diff --git a/tests/test_subclassing.py b/tests/test_subclassing.py index 6fb23db2..ec6c2cbd 100644 --- a/tests/test_subclassing.py +++ b/tests/test_subclassing.py @@ -47,23 +47,20 @@ def test_to_python(self): try: cv.to_python('1') except Invalid as e: - self.assertTrue( - 'one is invalid' in str(e), e) + self.assertIn('one is invalid', str(e), e) else: self.fail("one should be invalid") self.assertEqual(cv.to_python('2'), 2) try: cv.to_python('3') except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.to_python('4') except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.to_python('5'), 5) @@ -75,8 +72,7 @@ def test_from_python(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") self.assertEqual(cv.from_python(3), '3') @@ -90,22 +86,19 @@ def test_from_python_no_accept(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") try: cv.from_python(3) except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.from_python(4) except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.from_python(5), '5') @@ -141,8 +134,7 @@ def test_to_python(self): try: nov.to_python('1') except Invalid as e: - self.assertTrue( - 'must not be 1' in str(e), e) + self.assertIn('must not be 1', str(e), e) else: self.fail("1 should be invalid") self.assertEqual(nov.to_python('2'), 2) @@ -156,8 +148,7 @@ def test_to_python_number(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") @@ -172,8 +163,7 @@ def test_to_python_range(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") self.assertEqual(nov.to_python('43'), 43) @@ -241,8 +231,7 @@ def test_to_python(self): try: cav.to_python('4') except Invalid as e: - self.assertTrue( - 'must not be 4' in str(e), e) + self.assertIn('must not be 4', str(e), e) else: self.fail("4 should be invalid") self.assertEqual(cav.to_python('5'), 5) diff --git a/tests/test_subclassing_old.py b/tests/test_subclassing_old.py index 54051c24..f47bd0de 100644 --- a/tests/test_subclassing_old.py +++ b/tests/test_subclassing_old.py @@ -4,8 +4,6 @@ from formencode.api import is_validator, FancyValidator, Invalid from formencode.compound import CompoundValidator, All from formencode.validators import Int -from six.moves import map - with warnings.catch_warnings(record=True) as custom_warnings: warnings.simplefilter('default') @@ -51,7 +49,7 @@ def test_1_warnings(self): output = '\n'.join(map(str, custom_warnings)) for old, new in deprecated: msg = '%s is deprecated; use %s instead' % (old, new) - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedCustomValidator)) @@ -63,23 +61,20 @@ def test_to_python(self): try: cv.to_python('1') except Invalid as e: - self.assertTrue( - 'one is invalid' in str(e), e) + self.assertIn('one is invalid', str(e), e) else: self.fail("one should be invalid") self.assertEqual(cv.to_python('2'), 2) try: cv.to_python('3') except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.to_python('4') except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.to_python('5'), 5) @@ -91,8 +86,7 @@ def test_from_python(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") self.assertEqual(cv.from_python(3), '3') @@ -106,22 +100,19 @@ def test_from_python_no_accept(self): try: cv.from_python(2) except Invalid as e: - self.assertTrue( - 'two is invalid' in str(e), e) + self.assertIn('two is invalid', str(e), e) else: self.fail("two should be invalid") try: cv.from_python(3) except Invalid as e: - self.assertTrue( - 'three is invalid' in str(e), e) + self.assertIn('three is invalid', str(e), e) else: self.fail("three should be invalid") try: cv.from_python(4) except Invalid as e: - self.assertTrue( - 'four is invalid' in str(e), e) + self.assertIn('four is invalid', str(e), e) else: self.fail("four should be invalid") self.assertEqual(cv.from_python(5), '5') @@ -157,7 +148,7 @@ def test_1_warnings(self): # must run first for output in runtime_warnings, not_one_warnings: output = '\n'.join(map(str, output)) msg = '_to_python is deprecated; use _convert_to_python instead' - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedNotOneValidator)) @@ -171,8 +162,7 @@ def test_to_python(self): try: nov.to_python('1') except Invalid as e: - self.assertTrue( - 'must not be 1' in str(e), e) + self.assertIn('must not be 1', str(e), e) else: self.fail("1 should be invalid") self.assertEqual(nov.to_python('2'), 2) @@ -187,8 +177,7 @@ def test_to_python_number(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") @@ -204,8 +193,7 @@ def test_to_python_range(self): try: nov.to_python('42') except Invalid as e: - self.assertTrue( - 'must not be 42' in str(e), e) + self.assertIn('must not be 42', str(e), e) else: self.fail("42 should be invalid") self.assertEqual(nov.to_python('43'), 43) @@ -232,7 +220,7 @@ def setUp(self): def test_1_warnings(self): output = '\n'.join(map(str, custom_compound_warnings)) msg = 'attempt_convert is deprecated; use _attempt_convert instead' - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedCustomCompoundValidator)) @@ -280,7 +268,7 @@ def test_1_warnings(self): # must run first for output in runtime_warnings, all_and_not_one_warnings: output = '\n'.join(map(str, output)) msg = 'attempt_convert is deprecated; use _attempt_convert instead' - self.assertTrue(msg in output, output or 'no warnings') + self.assertIn(msg, output, output or 'no warnings') def test_is_validator(self): self.assertTrue(is_validator(DeprecatedAllAndNotOneValidator)) @@ -295,8 +283,7 @@ def test_to_python(self): try: cav.to_python('4') except Invalid as e: - self.assertTrue( - 'must not be 4' in str(e), e) + self.assertIn('must not be 4', str(e), e) else: self.fail("4 should be invalid") self.assertEqual(cav.to_python('5'), 5) diff --git a/tests/test_validators.py b/tests/test_validators.py index 02f02b8f..5db4e085 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -8,24 +8,18 @@ from formencode.schema import Schema from formencode.foreach import ForEach from formencode.api import NoDefault -import six - -if not six.PY2: - unicode = str def validate(validator, value): try: return validator.to_python(value) - return None except Invalid as e: return e.unpack_errors() def validate_from(validator, value): try: - validator.from_python(value) - return None + return validator.from_python(value) except Invalid as e: return e.unpack_errors() @@ -35,13 +29,13 @@ class TestValidators(unittest.TestCase): def testHelp(self): from pydoc import text, plain s = plain(text.document(validators)) - self.assertTrue('Validator/Converters for use with FormEncode.' in s) - self.assertTrue('class Bool' in s) - self.assertTrue('Always Valid, returns True or False' in s) - self.assertTrue('class Email' in s) - self.assertTrue('Validate an email address.' in s) - self.assertTrue('class FieldsMatch' in s) - self.assertTrue('Tests that the given fields match' in s) + self.assertIn('Validator/Converters for use with FormEncode.', s) + self.assertIn('class Bool', s) + self.assertIn('Always Valid, returns True or False', s) + self.assertIn('class Email', s) + self.assertIn('Validate an email address.', s) + self.assertIn('class FieldsMatch', s) + self.assertIn('Tests that the given fields match', s) class TestByteStringValidator(unittest.TestCase): @@ -51,13 +45,12 @@ def setUp(self): self.messages = self.validator.message def test_alias(self): - if str is not six.text_type: # Python 2 - self.assertTrue(self.validator is validators.String) + self.assertIsNot(self.validator, validators.String) def test_docstring(self): doc = self.validator.__doc__ - self.assertTrue( - 'Enter a value not more than ``%(max)i`` characters long' in doc) + self.assertIn( + 'Enter a value not more than ``%(max)i`` characters long', doc) def test_sv_min(self): sv = self.validator(min=2, accept_python=False) @@ -89,56 +82,55 @@ def setUp(self): self.validator = validators.UnicodeString def test_alias(self): - if str is six.text_type: # Python 3 - self.assertTrue(self.validator is validators.String) + self.assertIs(self.validator, validators.String) def test_docstring(self): doc = self.validator.__doc__ - self.assertTrue('Invalid data or incorrect encoding' in doc) + self.assertIn('Invalid data or incorrect encoding', doc) def test_unicode(self): un = self.validator() self.assertEqual(un.to_python(12), '12') - self.assertTrue(type(un.to_python(12)) is six.text_type) + self.assertIs(type(un.to_python(12)), str) self.assertEqual(un.from_python(12), '12'.encode('ascii')) - self.assertTrue(type(un.from_python(12)) is bytes) + self.assertIs(type(un.from_python(12)), bytes) def test_unicode_encoding(self): uv = self.validator() us = 'käse' u7s, u8s = us.encode('utf-7'), us.encode('utf-8') self.assertEqual(uv.to_python(u8s), us) - self.assertTrue(type(uv.to_python(u8s)) is six.text_type) + self.assertIs(type(uv.to_python(u8s)), str) self.assertEqual(uv.from_python(us), u8s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(encoding='utf-7') self.assertEqual(uv.to_python(u7s), us) - self.assertTrue(type(uv.to_python(u7s)) is six.text_type) + self.assertIs(type(uv.to_python(u7s)), str) self.assertEqual(uv.from_python(us), u7s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(inputEncoding='utf-7') self.assertEqual(uv.to_python(u7s), us) - self.assertTrue(type(uv.to_python(u7s)) is six.text_type) + self.assertIs(type(uv.to_python(u7s)), str) uv = self.validator(outputEncoding='utf-7') self.assertEqual(uv.from_python(us), u7s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(inputEncoding=None) self.assertEqual(uv.to_python(us), us) - self.assertTrue(type(uv.to_python(us)) is six.text_type) + self.assertIs(type(uv.to_python(us)), str) self.assertEqual(uv.from_python(us), u8s) - self.assertTrue(type(uv.from_python(us)) is bytes) + self.assertIs(type(uv.from_python(us)), bytes) uv = self.validator(outputEncoding=None) self.assertEqual(uv.to_python(u8s), us) - self.assertTrue(type(uv.to_python(u8s)) is six.text_type) + self.assertIs(type(uv.to_python(u8s)), str) self.assertEqual(uv.from_python(us), us) - self.assertTrue(type(uv.from_python(us)) is six.text_type) + self.assertIs(type(uv.from_python(us)), str) def test_unicode_empty(self): iv = self.validator() for value in [None, b"", ""]: result = iv.to_python(value) self.assertEqual(result, "") - self.assertTrue(isinstance(result, six.text_type)) + self.assertIsInstance(result, str) class TestIntValidator(unittest.TestCase): @@ -149,7 +141,7 @@ def setUp(self): def test_docstring(self): doc = self.validator.__doc__ - self.assertTrue('Please enter an integer value' in doc) + self.assertIn('Please enter an integer value', doc) def test_int_min(self): iv = self.validator(min=5) @@ -165,8 +157,8 @@ def test_int_max(self): def test_int_minmax_optional(self): iv = self.validator(min=5, max=10, if_empty=None) - self.assertTrue(iv.to_python("") is None) - self.assertTrue(iv.to_python(None) is None) + self.assertIsNone(iv.to_python("")) + self.assertIsNone(iv.to_python(None)) self.assertEqual(iv.to_python('7'), 7) self.assertEqual(validate(iv, "1"), self.messages('tooLow', None, min=5)) @@ -208,15 +200,15 @@ def test_bad_dates(self): try: dc.to_python('20/12/150') except Invalid as e: - self.assertTrue( - 'Please enter a four-digit year after 1899' in unicode(e)) + self.assertIn( + 'Please enter a four-digit year after 1899', str(e)) else: self.fail('Date should be invalid') try: dc.to_python('oh/happy/day') except Invalid as e: - self.assertTrue( - 'Please enter the date in the form DD/MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form DD/MM/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -233,17 +225,16 @@ def test_month_style_alias(self): dc = self.validator(month_style=style) self.assertEqual(dc.month_style, 'ymd') try: - dc = self.validator(month_style='Klingon') + self.validator(month_style='Klingon') except TypeError as e: - self.assertTrue( - unicode(e).replace("u'", "'") == "Bad month_style: 'klingon'") + self.assertEqual(str(e), "Bad month_style: 'klingon'") else: self.fail('month_style should be invalid') try: - dc = self.validator(month_style='ydm') + self.validator(month_style='ydm') except TypeError as e: - self.assertTrue( - unicode(e).replace("u'", "'") == "Bad month_style: 'ydm'") + self.assertEqual( + str(e), "Bad month_style: 'ydm'") else: self.fail('month_style should be invalid') @@ -257,14 +248,14 @@ def test_us_style(self): try: self.assertEqual(dc.to_python('20/12/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('12/Dec/2007'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form MM/DD/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form MM/DD/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -278,14 +269,14 @@ def test_euro_style(self): try: self.assertEqual(dc.to_python('12/20/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('Dec/12/2007'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form DD/MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form DD/MM/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -299,14 +290,14 @@ def test_iso_style(self): try: self.assertEqual(dc.to_python('2013/30/06'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('2013/06/Jun'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form YYYY/MM/DD' in unicode(e)) + self.assertIn( + 'Please enter the date in the form YYYY/MM/DD', str(e)) else: self.fail('Date should be invalid') @@ -320,21 +311,21 @@ def test_no_day(self): try: self.assertEqual(dc.to_python('20/2007'), d) except Invalid as e: - self.assertTrue('Please enter a month from 1 to 12' in unicode(e)) + self.assertIn('Please enter a month from 1 to 12', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('12/20/2007'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form MM/YYYY', str(e)) else: self.fail('Date should be invalid') try: self.assertEqual(dc.to_python('2007/Dec'), d) except Invalid as e: - self.assertTrue( - 'Please enter the date in the form MM/YYYY' in unicode(e)) + self.assertIn( + 'Please enter the date in the form MM/YYYY', str(e)) else: self.fail('Date should be invalid') @@ -384,30 +375,30 @@ def test_time(self): try: tc.to_python('25:30:15') except Invalid as e: - self.assertTrue( - 'You must enter an hour in the range 0-23' in unicode(e)) + self.assertIn( + 'You must enter an hour in the range 0-23', str(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:75:15') except Invalid as e: - self.assertTrue( - 'You must enter a minute in the range 0-59' in unicode(e)) + self.assertIn( + 'You must enter a minute in the range 0-59', str(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:30:75') except Invalid as e: - self.assertTrue( - 'You must enter a second in the range 0-59' in unicode(e)) + self.assertIn( + 'You must enter a second in the range 0-59', str(e)) else: self.fail('Time should be invalid') try: tc.to_python('20:30:zx') except Invalid as e: - self.assertTrue( - 'The second value you gave is not a number' in unicode(e)) - self.assertTrue('zx' in unicode(e)) + self.assertIn( + 'The second value you gave is not a number', str(e)) + self.assertIn('zx', str(e)) else: self.fail('Time should be invalid') @@ -617,8 +608,8 @@ class TestOneOfValidator(unittest.TestCase): def test_docstring(self): doc = validators.OneOf.__doc__ - self.assertTrue( - 'Value must be one of: ``%(items)s`` (not ``%(value)r``)' in doc) + self.assertIn( + 'Value must be one of: ``%(items)s`` (not ``%(value)r``)', doc) def test_unicode_list(self): o = validators.OneOf(['ö', 'a']) @@ -639,34 +630,27 @@ class foo(Schema): self.assertEqual(expected, value) def test_mixed_list(self): - """before `_message_vars_decode`, the errors were: - Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'c') - Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'\xa7') - """ o = validators.OneOf(['a', 'b', 'ö', '™', 1, 2, 3]) try: o.to_python('c') raise ValueError("exception expected") except Invalid as e: - self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'c')" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'c')", str(e)) try: o.to_python(9) raise ValueError("exception expected") except Invalid as e: - self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 9)" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 9)", str(e)) try: - o.to_python(u'd') + o.to_python('d') raise ValueError("exception expected") except Invalid as e: - self.assertTrue("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'd')" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not 'd')", str(e)) try: o.to_python('§') raise ValueError("exception expected") except Invalid as e: - if six.PY2: - self.assertTrue(r"Value must be one of: a; b; ö; ™; 1; 2; 3 (not u'\xa7')" in unicode(e)) - else: - self.assertTrue(r"Value must be one of: a; b; ö; ™; 1; 2; 3 (not '§')" in unicode(e)) + self.assertIn("Value must be one of: a; b; ö; ™; 1; 2; 3 (not '§')", str(e)) class TestIPAddressValidator(unittest.TestCase): @@ -691,13 +675,13 @@ def test_leading_zeros(self): try: validate('1.2.3.037') except Invalid as e: - self.assertTrue('The octets must not have leading zeros' in unicode(e)) + self.assertIn('The octets must not have leading zeros', str(e)) else: self.fail('IP address octets with leading zeros should be invalid') try: validate('1.2.3.0377') except Invalid as e: - self.assertTrue('The octets must not have leading zeros' in unicode(e)) + self.assertIn('The octets must not have leading zeros', str(e)) else: self.fail('IP octets with leading zeros should be invalid') @@ -710,9 +694,9 @@ def test_leading_zeros_allowed(self): try: validate('1.2.3.0377') except Invalid as e: - self.assertTrue( - "The octets must be within the range of 0-255 (not '377')" - in unicode(e).replace("u'", "'")) + self.assertIn( + "The octets must be within the range of 0-255 (not '377')", + str(e)) else: self.fail( 'IP address octets should not be interpreted as octal numbers') diff --git a/tox.ini b/tox.ini index d578ee9c..90c823a5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,12 @@ [tox] -envlist=py37,py38,py39,py310,py311,py312,pypy3 +envlist=flake8,py37,py38,py39,py310,py311,py312,pypy3,docs + +[testenv:docs] +basepython = python3.11 +deps = + sphinx +commands = + sphinx-build -b html -nEW docs docs/_build/html [testenv:flake8] basepython = python3.11 @@ -15,5 +22,4 @@ deps= pycountry dnspython commands= - pip install -e . - pytest formencode + pytest tests {posargs} From 5ab17f3221961700684619c8499615af2020d093 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 16:20:08 +0100 Subject: [PATCH 099/123] Replace deprecated dnspython call --- requirements-test.txt | 2 +- setup.cfg | 2 +- src/formencode/validators.py | 4 ++-- tox.ini | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements-test.txt b/requirements-test.txt index 556e04cd..4582cadb 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ -dnspython +dnspython >= 2 pycountry pytest pytest-cov diff --git a/setup.cfg b/setup.cfg index d3fd4c90..ec6bfe79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,7 +40,7 @@ where = src [options.extras_require] testing = pytest - dnspython + dnspython >= 2 pycountry # Babel configuration diff --git a/src/formencode/validators.py b/src/formencode/validators.py index f6c10eef..3e82be7a 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1335,10 +1335,10 @@ def _validate_python(self, value, state): import socket try: try: - dns.resolver.query(domain, 'MX') + dns.resolver.resolve(domain, 'MX') except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): try: - dns.resolver.query(domain, 'A') + dns.resolver.resolve(domain, 'A') except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): raise Invalid( self.message('domainDoesNotExist', diff --git a/tox.ini b/tox.ini index 90c823a5..f7afdc4c 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,6 @@ deps= pytest wheel pycountry - dnspython + dnspython >= 2 commands= pytest tests {posargs} From d05b7955eb9c766febcf015413bb5842b6e63e36 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 16:31:49 +0100 Subject: [PATCH 100/123] Fix test case setup --- src/formencode/validators.py | 1 - tests/test_schema.py | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 3e82be7a..f1a607fa 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -6,7 +6,6 @@ """ import cgi -import locale import re import warnings from encodings import idna diff --git a/tests/test_schema.py b/tests/test_schema.py index f49cb013..4f57bb38 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -260,7 +260,7 @@ def from_python(self, value, state): assert state.key == old_key, "key not restored" -class TestAtLeastOneCheckboxIsChecked: +class TestAtLeastOneCheckboxIsChecked(unittest.TestCase): """Tests to address SourceForge bug #1777245 The reporter is trying to enforce agreement to a Terms of Service @@ -268,7 +268,7 @@ class TestAtLeastOneCheckboxIsChecked: a validation failure. The tests below illustrate a working approach. """ - def setup(self): + def setUp(self): self.not_empty_messages = {'missing': 'a missing value message'} class CheckForCheckboxSchema(Schema): @@ -279,7 +279,7 @@ class CheckForCheckboxSchema(Schema): def test_Schema_with_input_present(self): # result = self.schema.to_python({'agree': 'yes'}) - assert result['agree'] is True + self.assertTrue(result['agree']) def test_Schema_with_input_missing(self): # @@ -287,10 +287,10 @@ def test_Schema_with_input_missing(self): self.schema.to_python({}) except Invalid as exc: error_message = exc.error_dict['agree'].msg - assert self.not_empty_messages['missing'] == error_message, \ - error_message + self.assertEqual(self.not_empty_messages['missing'], error_message, + error_message) else: - assert False, 'missing input not detected' + self.fail('missing input not detected') class TestStrictSchemaWithMultipleEqualInputFields(unittest.TestCase): From ef7dbc3cea43d8dda55b77935405b3dbb12299e9 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 19:20:34 +0100 Subject: [PATCH 101/123] Avoid the deprecated pkg_resources module --- docs/conf.py | 12 ------- src/formencode/api.py | 55 +++++++++++++------------------ src/formencode/htmlfill.py | 4 +-- src/formencode/rewritingparser.py | 13 ++++---- src/formencode/validators.py | 2 -- tests/__init__.py | 18 +++++++--- tests/test_doctests.py | 4 ++- tox.ini | 3 +- 8 files changed, 50 insertions(+), 61 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 88cc6407..636bf3c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,14 +9,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import os -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('..')) - # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -42,12 +34,8 @@ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -# -# The short X.Y version. -version = '2.0.0' from pkg_resources import get_distribution release = get_distribution('formencode').version -# for example take major/minor version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/src/formencode/api.py b/src/formencode/api.py index 1f1cffc8..88c62b39 100644 --- a/src/formencode/api.py +++ b/src/formencode/api.py @@ -2,52 +2,43 @@ Core classes for validation. """ -from . import declarative import gettext import os import re import textwrap import warnings -try: - from pkg_resources import resource_filename -except ImportError: - resource_filename = None +from . import declarative __all__ = ['NoDefault', 'Invalid', 'Validator', 'Identity', 'FancyValidator', 'is_empty', 'is_validator'] def get_localedir(): - """Retrieve the location of locales. + """Retrieve the location of locales.""" + file_dir = os.path.join(os.path.dirname(__file__), 'i18n') + if not hasattr(os, 'access'): + # this happens on Google App Engine + return file_dir + + import importlib.resources as resources + # try to get it via the resource + pkg_name = __name__.split('.', 1)[0] + try: + resource_dir = resources.files(pkg_name) / 'i18n' + except AttributeError: # Python < 3.11 + with resources.path(pkg_name, 'i18n') as resource_dir: + pass + if os.access(resource_dir, os.R_OK | os.X_OK): + # if the resource is present, use it + return resource_dir - If we're built as an egg, we need to find the resource within the egg. - Otherwise, we need to look for the locales on the filesystem or in the - system message catalog. + # otherwise, search the filesystem + if os.access(file_dir, os.R_OK | os.X_OK): + return file_dir - """ - locale_dir = '' - # Check the egg first - if resource_filename is not None: - try: - locale_dir = resource_filename(__name__, "i18n") - except NotImplementedError: - # resource_filename doesn't work with non-egg zip files - pass - if not hasattr(os, 'access'): - # This happens on Google App Engine - return os.path.join(os.path.dirname(__file__), 'i18n') - if os.access(locale_dir, os.R_OK | os.X_OK): - # If the resource is present in the egg, use it - return locale_dir - - # Otherwise, search the filesystem - locale_dir = os.path.join(os.path.dirname(__file__), 'i18n') - if not os.access(locale_dir, os.R_OK | os.X_OK): - # Fallback on the system catalog - locale_dir = os.path.normpath('/usr/share/locale') - - return locale_dir + # fallback on the system catalog + return os.path.normpath('/usr/share/locale') def set_stdtranslation(domain="FormEncode", languages=None, diff --git a/src/formencode/htmlfill.py b/src/formencode/htmlfill.py index 13b28629..a9fd12d9 100644 --- a/src/formencode/htmlfill.py +++ b/src/formencode/htmlfill.py @@ -68,8 +68,8 @@ def render(form, defaults=None, errors=None, use_all_keys=False, ``listener`` can be an object that watches fields pass; the only one currently is in ``htmlfill_schemabuilder.SchemaBuilder`` - ``encoding`` specifies an encoding to assume when mixing str and - unicode text in the template. + ``encoding`` specifies an encoding to assume when mixing bytes and + str text in the template. ``prefix_error`` specifies if the HTML created by auto_error_formatter is put before the input control (default) or after the control. diff --git a/src/formencode/rewritingparser.py b/src/formencode/rewritingparser.py index ca53ff03..9d29b797 100644 --- a/src/formencode/rewritingparser.py +++ b/src/formencode/rewritingparser.py @@ -148,13 +148,12 @@ def _get_text(self): except UnicodeDecodeError as e: if self.data_is_str: e.reason += ( - " the form was passed in as an encoded string, but" - " some data or error messages were unicode strings;" - " the form should be passed in as a unicode string") + " the form was passed in as a byte string," + " but some data or error messages were normal strings;" + " the form should be passed in as a string") else: e.reason += ( - " the form was passed in as an unicode string, but" - " some data or error message was an encoded string;" - " the data and error messages should be passed in as" - " unicode strings") + " the form was passed in as a string, but some data" + " or error message was encoded; the data and error" + " messages should be passed in as strings") raise diff --git a/src/formencode/validators.py b/src/formencode/validators.py index f1a607fa..a38ee503 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -841,8 +841,6 @@ def _validate_python(self, value, state): dt_mod, value.year, value.month, value.day) if value_as_date < today: date_formatted = now.strftime(date_format) - if encoding: - date_formatted = date_formatted.decode(encoding) raise Invalid( self.message('future', state, date=date_formatted), value, state) diff --git a/tests/__init__.py b/tests/__init__.py index d452675a..922f3c1a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,6 @@ -import sys import os +import sys +import warnings sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) @@ -9,8 +10,17 @@ os.environ['LANGUAGE'] = 'C' # Enable deprecation warnings (which are disabled by default) -import warnings warnings.simplefilter('default') -import pkg_resources -pkg_resources.require('FormEncode') +try: + try: + import importlib.metadata as metadata + try: + metadata.distribution("FormEncode") + except metadata.PackageNotFoundError as error: + raise ImportError from error + except ImportError: # Python < 3.8 + import pkg_resources + pkg_resources.require('FormEncode') +except ImportError as error: + raise ImportError("Install FormEncode before running the tests") from error diff --git a/tests/test_doctests.py b/tests/test_doctests.py index 4b85dd15..5361cace 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -30,6 +30,7 @@ OutputChecker = doctest.OutputChecker + class OutputChecker3(OutputChecker): def check_output(self, want, got, optionflags): if want.startswith("u'"): @@ -43,7 +44,8 @@ def check_output(self, want, got, optionflags): ) return OutputChecker.check_output(self, want, got, optionflags) -doctest.OutputChecker = OutputChecker3 # needed?? + +doctest.OutputChecker = OutputChecker3 def doctest_file(document, verbose, raise_error): diff --git a/tox.ini b/tox.ini index f7afdc4c..64b7f69b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,10 @@ [tox] -envlist=flake8,py37,py38,py39,py310,py311,py312,pypy3,docs +envlist=py3{7,8,9,10,11,12},pypy3,flake8,docs [testenv:docs] basepython = python3.11 deps = + pycountry sphinx commands = sphinx-build -b html -nEW docs docs/_build/html From e27ec28c42791ceac4efdaa550d84c26a22ed5b1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 20:57:12 +0100 Subject: [PATCH 102/123] Clean up Sphinx references --- docs/Validator.txt | 266 ++++++++++++++++++----------------- docs/conf.py | 7 +- docs/modules/schema.txt | 2 + docs/whatsnew-0-to-1.2.4.txt | 4 +- docs/whatsnew-1.2.5.txt | 2 +- src/formencode/compound.py | 12 +- src/formencode/schema.py | 2 +- 7 files changed, 152 insertions(+), 143 deletions(-) diff --git a/docs/Validator.txt b/docs/Validator.txt index 5166fdcd..7dc671c1 100644 --- a/docs/Validator.txt +++ b/docs/Validator.txt @@ -35,9 +35,9 @@ The basic metaphor for validation is **to_python** and -- the trusted application, your own Python objects. The "other" may be a web form, an external database, an XML-RPC request, or any data source that is not completely trusted or does not map directly to -Python's object model. :meth:`to_python` is the process of taking -external data and preparing it for internal use, :meth:`from_python` -generally reverses this process (:meth:`from_python` is usually the less +Python's object model. ``to_python()`` is the process of taking +external data and preparing it for internal use, ``from_python()`` +generally reverses this process (``from_python()`` is usually the less interesting of the pair, but provides some important features). The core of this validation process is two methods and an exception:: @@ -52,8 +52,8 @@ The core of this validation process is two methods and an exception:: ... Invalid: Please enter an integer value -``"ten"`` isn't a valid integer, so we get a :class:`formencode.Invalid` -exception. Typically we'd catch that exception, and use it for some +``"ten"`` isn't a valid integer, so we get a :class:`~formencode.api.Invalid` +exception. Typically, we'd catch that exception, and use it for some sort of feedback. Like: .. comment (fake input): @@ -97,7 +97,7 @@ We can also generalize this kind of function:: Enter your email: bob@nowhere.com 'bob@nowhere.com' -:class:`Invalid` exceptions generally give a good, user-readable error +``Invalid`` exceptions generally give a good, user-readable error message about the problem with the input. Using the exception gets more complicated when you use compound data structures (dictionaries and lists), which we'll talk about later__. @@ -148,24 +148,26 @@ they'll be specific to your application:: ... return value .. note:: - The class :class:`formencode.FancyValidator` is the superclass + The class `formencode.FancyValidator` is the superclass for most validators in FormEncode, and it provides a number of useful features that most validators can use -- for instance, you can pass ``strip=True`` into any of these validators, and they'll strip whitespace from the incoming value before any other validation. -This overrides the internal :meth:`_convert_to_python()` method: -:class:`formencode.FancyValidator` adds a number of extra features, and then -calls the internal :meth:`_convert_to_python` method, which is the method you'll -typically write. Contrary to the external method :meth:`to_python`, its -only concern is the conversion part, not the validation part. If further -validation is necessary, this can be done in two other internal methods, -either :meth:`_validate_python()` or :meth:`_validate_other()`. We will give -an example for that later. When a validator finds an error, it raises an -exception (:class:`formencode.Invalid`), with the error message and the -value and "state" objects. We'll talk about state_ later. Here's the -other custom validator, that checks passwords against words in the -standard Unix word file:: +This overrides the internal ``_convert_to_python()`` method: +:class:`formencode.api.FancyValidator` adds a number of extra features, +and then calls the internal ``_convert_to_python`` method, +which is the method you'll typically write. +Contrary to the external method ``to_python()``, its only concern is +the conversion part, not the validation part. +If further validation is necessary, +this can be done in two other internal methods, +either ``_validate_python()`` or ``_validate_other()``. +We will give an example for that later. When a validator finds an error, +it raises an exception (:class:`formencode.api.Invalid`), with the error +message and the value and "state" objects. We'll talk about state_ later. +Here's the other custom validator, that checks passwords against words +in the standard Unix word file:: >>> class SecurePassword(formencode.FancyValidator): ... words_filename = '/usr/share/dict/words' @@ -192,8 +194,8 @@ And here's a schema:: ... chained_validators = [validators.FieldsMatch( ... 'password', 'password_confirm')] -Like any other validator, a :class:`Registration` instance will have the -:meth:`to_python` and :meth:`from_python` methods. The input should be a +Like any other validator, a ``Registration`` instance will have the +``to_python`` and ``from_python`` methods. The input should be a dictionary (or a Paste MultiDict), with keys like ``"first_name"``, ``"password"``, etc. The validators you give as attributes will be applied to each of the values of the dictionary. *All* the values @@ -201,26 +203,27 @@ will be validated, so if there are multiple invalid fields you will get information about all of them. Most validators (anything that subclasses -:class:`formencode.FancyValidator`) will take a certain standard -set of constructor keyword arguments. See -:class:`formencode.api.FancyValidator` for more -- here we use -``not_empty=True``. +:class:`formencode.api.FancyValidator`) will take a certain standard +set of constructor keyword arguments. +See :class:`formencode.api.FancyValidator` for more +-- here we use ``not_empty=True``. -Another notable validator is :class:`formencode.compound.All` -- this +Another notable validator is :class:`~formencode.compound.All` -- this is a *compound validator* -- that is, it's a validator that takes -validators as input. Schemas are one example; in this case :class:`All` -takes a list of validators and applies each of them in turn. -:class:`formencode.compound.Any` is its compliment, that uses the +validators as input. Schemas are one example; in this case +:class:`~formencode.compound.All` takes a list of validators +and applies each of them in turn. +:class:`~formencode.compound.Any` is its compliment, that uses the first passing validator in its list. .. _pre_validators: .. _chained_validators: -:attr:`chained_validators` are validators that are run on the entire -dictionary after other validation is done (:attr:`pre_validators` are -applied before the schema validation). chained_validators will also +``chained_validators`` are validators that are run on the entire +dictionary after other validation is done (``pre_validators`` are +applied before the schema validation). ``chained_validators`` will also allow for multiple validators to fail and report to the error_dict -so, for example, if you have an email_confirm and a password_confirm +so, for example, if you have an ``email_confirm`` and a ``password_confirm`` fields and use FieldsMatch on both of them as follows: >>> chained_validators = [ @@ -255,16 +258,17 @@ function that you write. For example:: Invalid: state: You must enter a state -The :func:`validate_state` function (or any validation function) returns +The ``validate_state()`` function (or any validation function) returns any errors in the form (or it may raise Invalid directly). It can -also modify the :obj:`value_dict` dictionary directly. When it returns +also modify the ``value_dict`` dictionary directly. When it returns None this indicates that everything is valid. You can use this with a -:class:`Schema` by putting :class:`ValidateState` in :attr:`pre_validators` -(all validation will be done before the schema's validation, and if there's -an error the schema won't be run). Or you can put it in -:attr:`chained_validators` and it will be run *after* the schema. If the -schema fails (the values are invalid) then :class:`ValidateState` will not -be run, unless you set :attr:`validate_partial_form` to True (like +:class:`formencode.schema.Schema` by putting ``ValidateState`` in +``pre_validators`` (all validation will be done before the schema's +validation, and if there's an error the schema won't be run). +Or you can put it in ``chained_validators`` and it will be run +*after* the schema. If the schema fails (the values are invalid) +then ``ValidateState`` will not be run, unless you set +``validate_partial_form`` to True (like ``ValidateState = SimpleFormValidator(validate_state, validate_partial_form=True)``. If you validate a partial form you should be careful that you handle missing keys and other @@ -283,12 +287,12 @@ is for:: ... title = validators.ByteString(not_empty=True) >>> validator = formencode.ForEach(BookSchema()) -The :obj:`validator` we've created will take a list of dictionaries as +The ``validator`` we've created will take a list of dictionaries as input (like ``[{"id": "1", "title": "War & Peace"}, {"id": "2", -"title": "Brave New World"}, ...]``). It applies the :class:`BookSchema` -to each entry, and collects any errors and reraises them. Of course, -when you are validating input from an HTML form you won't get well -structured data like this (we'll talk about that later__). +"title": "Brave New World"}, ...]``). It applies the ``BookSchema`` +to each entry, and collects any errors and re-raises them. +Of course, when you are validating input from an HTML form you won't +get well-structured data like this (we'll talk about that later__). .. __: `HTTP/HTML Form Input`_ @@ -296,9 +300,8 @@ Writing Your Own Validator -------------------------- We gave a brief introduction to creating a validator earlier -(:class:`UniqueUsername` and :class:`SecurePassword`). We'll discuss -that a little more. Here's a more complete implementation of -:class:`SecurePassword`:: +(``UniqueUsername`` and ``SecurePassword``). We'll discuss that a little +more. Here's a more complete implementation of ``SecurePassword``:: >>> import re >>> class SecurePassword(validators.FancyValidator): @@ -332,27 +335,27 @@ that a little more. Here's a more complete implementation of ... value, state) With all validators, any arguments you pass to the constructor will be -used to set instance variables. So :class:`SecureValidator(min=5)` will +used to set instance variables. So ``SecureValidator(min=5)`` will be a minimum-five-character validator. This makes it easy to also subclass other validators, giving different default values. Unlike the previous implementation we use the already mentioned -:meth:`_validate_python` method, which is another internal method -:class:`FancyValidator` allows us to override. :meth:`_validate_python` -doesn't have any return value, it simply raises an exception if it -needs to. It validates the value *after* it has been converted -(by :meth:`_convert_to_python`). :meth:`_validate_other` validates before +``_validate_python`` method, which is another internal method +:class:`~formencode.api.FancyValidator` allows us to override. +``_validate_python`` doesn't have any return value, it simply raises an +exception if it needs to. It validates the value *after* it has been +converted (by ``_convert_to_python``). ``_validate_other`` validates before conversion, but that's usually not that useful. The external method -:meth:`to_python` cares about the extra features such as the -:attr:`if_empty` parameter, and uses the internal methods to do the -actual conversion and validation; first it calls :meth:`_validate_other`, -then :meth:`_convert_to_python` and at last :meth:`_validate_python`. +``to_python`` cares about the extra features such as the ``if_empty`` +parameter, and uses the internal methods to do the actual conversion +and validation; first it calls ``_validate_other``, then +``_convert_to_python`` and at last ``_validate_python``. The use of ``self.message(...)`` is meant to make the messages easy to -format for different environments, and replacable (with translations, +format for different environments, and replaceable (with translations, or simply with different text). Each message should have an identifier (``"min"`` and ``"non_letter"`` in this example). The -keyword arguments to :meth:`message` are used for message substitution. +keyword arguments to ``message`` are used for message substitution. See Messages_ for more. Other Validator Usage @@ -368,10 +371,9 @@ to set these. These are (effectively) equivalent:: ... regex = '^[a-zA-Z]+$' >>> plain = Plain() -You can actually use classes most places where you could use an -instance; :meth:`.to_python()` and :meth:`.from_python()` will create -instances as necessary, and many other methods are available on both -the instance and the class level. +You can actually use classes most places where you could use an instance; +``to_python()`` and ``from_python()`` will create instances as necessary, +and many other methods are available on both the instance and the class level. When dealing with nested validators this class syntax is often easier to work with, and better displays the structure. @@ -379,41 +381,39 @@ to work with, and better displays the structure. .. _FancyValidator: There are several options that most validators support (including your -own validators, if you subclass from :class:`formencode.FancyValidator`): - -:attr:`if_empty`: - If set, then this value will be returned if the input evaluates - to false (empty list, empty string, None, etc), but not the 0 or - False objects. This only applies to ``.to_python()``. - -:attr:`not_empty`: - If true, then if an empty value is given raise an error. - (Both with ``.to_python()`` and also ``.from_python()`` - if ``.validate_python`` is true). - -:attr:`strip`: - If true and the input is a string, strip it (occurs before empty - tests). - -:attr:`if_invalid`: - If set, then when this validator would raise Invalid during - ``.to_python()``, instead return this value. - -:attr:`if_invalid_python`: - If set, when the Python value (converted with - ``.from_python()``) is invalid, this value will be returned. - -:attr:`accept_python`: - If True (the default), then ``._validate_python()`` and - ``._validate_other()`` will not be called when - ``.from_python()`` is used. - -:attr:`if_missing`: - Typically when a field is missing the schema will raise an - error. In that case no validation is run -- so things like - ``if_invalid`` won't be triggered. This special attribute (if - set) will be used when the field is missing, and no error will - occur. (``None`` or ``()`` are common values) +own validators, if you subclass from :class:`formencode.api.FancyValidator`): + +``if_empty``: + If set, then this value will be returned if the input evaluates + to false (empty list, empty string, None, etc), but not the 0 or + False objects. This only applies to ``.to_python()``. + +``not_empty``: + If true, then if an empty value is given raise an error. + (Both with ``.to_python()`` and also ``.from_python()`` + if ``.validate_python`` is true). + +``strip``: + If true and the input is a string, strip it (occurs before empty tests). + +``if_invalid``: + If set, then when this validator would raise Invalid during + ``.to_python()``, instead return this value. + +``if_invalid_python``: + If set, when the Python value (converted with + ``.from_python()``) is invalid, this value will be returned. + +``accept_python``: + If True (the default), then ``._validate_python()`` and + ``._validate_other()`` will not be called when ``.from_python()`` is used. + +``if_missing``: + Typically when a field is missing the schema will raise an + error. In that case no validation is run -- so things like + ``if_invalid`` won't be triggered. This special attribute (if + set) will be used when the field is missing, and no error will + occur. (``None`` or ``()`` are common values) State ----- @@ -431,47 +431,49 @@ validator know the locale? State! Whatever else you need to pass in, just put it in the state object as an attribute, then look for that attribute in your validator. -Also, during compound validation (a :class:`formencode.schema.Schema` -or :class:`formencode.foreach.ForEach`) the state (if not None) will -have more instance variables added to it. During a :class:`Schema` -(dictionary) validation the instance variable ``key`` and -``full_dict`` will be added -- ``key`` is the current key (i.e., -validator name), and ``full_dict`` is the rest of the values being -validated. During a :class:`ForEeach` (list) validation, ``index`` and -``full_list`` will be set. +Also, during compound validation (a :class:`~formencode.schema.Schema` +or :class:`~formencode.foreach.ForEach`) the state (if not None) will +have more instance variables added to it. +During a :class:`~formencode.schema.Schema` (dictionary) validation +the instance variable ``key`` and ``full_dict`` will be added -- ``key`` +is the current key (i.e., validator name), and ``full_dict`` is the rest +of the values being validated. +During a :class:`~formencode.foreach.ForEach` (list) validation, +``index`` and ``full_list`` will be set. Invalid Exceptions ------------------ -Besides the string error message, :class:`formencode.Invalid` +Besides the string error message, :class:`~formencode.api.Invalid` exceptions have a few other instance variables: -:attr:`value`: +``value``: The input to the validator that failed. -:attr:`state`: +``state``: The associated state_. -:attr:`msg`: +``msg``: The error message (``str(exc)`` returns this) -:attr:`error_list`: - If the exception happened in a ``ForEach`` (list) validator, then - this will contain a list of ``Invalid`` exceptions. Each item - from the list will have an entry, either None for no error, or an - exception. +``error_list``: + If the exception happened in a :class:`~formencode.foreach.ForEach` + (list) validator, then this will contain a list of + :class:`~formencode.api.Invalid` exceptions. Each item from the list + will have an entry, either None for no error, or an exception. -:attr:`error_dict`: - If the exception happened in a :class:`Schema` (dictionary) validator, - then this will contain :class:`Invalid` exceptions for each failing - field. Passing fields not be included in this dictionary. +``error_dict``: + If the exception happened in a :class:`~formencode.schema.Schema` + (dictionary) validator, then this will contain + :class:`~formencode.api.Invalid` exceptions for each failing field. + Passing fields not be included in this dictionary. -:meth:`.unpack_errors()`: +``.unpack_errors()``: This method returns a set of lists and dictionaries containing - strings, for each error. It's an unpacking of :attr:`error_list`, - :attr:`error_dict` and :attr:`msg`. If you get an Invalid exception - from a :class:`Schema`, you probably want to call this method on the - exception object. + strings, for each error. It's an unpacking of ``error_list``, + ``error_dict`` and ``msg``. If you get an Invalid exception + from a :class:`~formencode.schema.Schema`, you probably want to call + this method on the exception object. .. _Messages: @@ -492,7 +494,7 @@ for each substitution. This way you can reorder or even ignore placeholders in your new message. When you are creating a validator, for maximum flexibility you should -use the :meth:`message` method, like:: +use the ``message`` method, like:: messages = { 'key': 'my message (with a %(substitution)s)', @@ -515,13 +517,13 @@ the found gettext function. To serve a standard translation mechanism and to enable custom translations it looks in the following order to find a gettext (``_``) function: -1. method of the :obj:`state` object +1. method of the ``state`` object -2. function :func:`__builtin__._`. This function is only used when:: +2. function ``builtin._``. This function is only used when:: - Validator.use_builtin_gettext == True #True is default + Validator.use_builtin_gettext == True # True is default -3. formencode builtin :func:`_stdtrans` function +3. formencode builtin ``_stdtrans`` function for standalone use of FormEncode. The language to use is determined out of the locale system (see gettext documentation). Optionally you @@ -534,7 +536,7 @@ find a gettext (``_``) function: messages in the directory ``localedir/language/LC_MESSAGES/FormEncode.mo`` -4. Custom gettext function and addtional parameters +4. Custom gettext function and additional parameters If you use a custom gettext function and you want FormEncode to call your function with additional parameters you can set the diff --git a/docs/conf.py b/docs/conf.py index 636bf3c2..ac4a06dd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,6 +9,11 @@ # All configuration values have a default; values that are commented out # serve to show the default. +# import os +# import sys +# sys.path.insert(0, os.path.join(os.path.dirname( +# os.path.dirname(os.path.abspath(__file__))), 'src')) + # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -40,7 +45,7 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/docs/modules/schema.txt b/docs/modules/schema.txt index f877abcb..c78d9856 100644 --- a/docs/modules/schema.txt +++ b/docs/modules/schema.txt @@ -8,3 +8,5 @@ Module Contents .. autoclass:: Schema +.. autoclass:: SimpleFormValidator + diff --git a/docs/whatsnew-0-to-1.2.4.txt b/docs/whatsnew-0-to-1.2.4.txt index b5e5ca47..94a4d6d9 100644 --- a/docs/whatsnew-0-to-1.2.4.txt +++ b/docs/whatsnew-0-to-1.2.4.txt @@ -60,7 +60,7 @@ What's New In FormEncode 0 to 1.2.4 * Added :class:`formencode.validators.IPAddress`, validating IP addresses, from Leandro Lucarella. -* Added :meth:`formencode.api.Invalid.__unicode__` +* Added method ``Invalid.__unicode__`` * In :mod:`formencode.htmlfill` use a default encoding of utf8 when handling mixed ``str``/``unicode`` content. Also do not modify @@ -125,7 +125,7 @@ What's New In FormEncode 0 to 1.2.4 ```` -- this will cause the error to be swallowed, not shown to the user. -* Added :class:`formencode.validators.XRI` for validation i-names, +* Added ``formencode.validators.XRI`` for validation i-names, i-numbers, URLs, etc (as used in OpenID). * Look in ``/usr/share/locale`` for locale files, in addition to the diff --git a/docs/whatsnew-1.2.5.txt b/docs/whatsnew-1.2.5.txt index c4ea4d30..9b4a8104 100644 --- a/docs/whatsnew-1.2.5.txt +++ b/docs/whatsnew-1.2.5.txt @@ -33,5 +33,5 @@ Backwards Incompatibilities Documentation Enhancements -------------------------- -- Superceded news with whatsnew documents that will be archived for each +- Superseded news with whatsnew documents that will be archived for each version. Archived all news prior to 1.2.5 in :doc:`/whatsnew-0-to-1.2.4`. diff --git a/src/formencode/compound.py b/src/formencode/compound.py index 4b3be890..5caf950d 100644 --- a/src/formencode/compound.py +++ b/src/formencode/compound.py @@ -130,12 +130,12 @@ def accept_iterator(self): class All(CompoundValidator): - """Check if all of the specified validators are valid. + """Check that specified validators are valid. - This class is like an 'and' operator for validators. All - validators must work, and the results are passed in turn through - all validators for conversion in the order of evaluation. All - is the same as `Pipe` but operates in the reverse order. + This class is like an 'and' operator for validators. + All validators must work, and the results are passed in turn through + all validators for conversion in the order of evaluation. + ``All`` is the same as ``Pipe`` but operates in the reverse order. The order of evaluation differs depending on if you are validating to Python or from Python as follows: @@ -144,7 +144,7 @@ class All(CompoundValidator): The validators are evaluated left to right when validating from Python. - `Pipe` is more intuitive when predominantly validating to Python. + ``Pipe`` is more intuitive when predominantly validating to Python. Examples:: diff --git a/src/formencode/schema.py b/src/formencode/schema.py index a4cfa089..7a14494d 100644 --- a/src/formencode/schema.py +++ b/src/formencode/schema.py @@ -4,7 +4,7 @@ from . import declarative from .exc import FERuntimeWarning -__all__ = ['Schema'] +__all__ = ['Schema', 'SimpleFormValidator'] class Schema(FancyValidator): From 9d4356f83a00a92baf6df5f971352c14cb4dd402 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 21:03:34 +0100 Subject: [PATCH 103/123] Simplify doc output test --- src/formencode/validators.py | 6 +++--- tests/non_empty.txt | 2 -- tests/test_doctests.py | 20 -------------------- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index a38ee503..f2011b47 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1168,11 +1168,11 @@ class Set(FancyValidator): ['this', 'that'] >>> s = Set(use_set=True) >>> s.to_python(None) - set([]) + set() >>> s.to_python('this') - set(['this']) + {'this'} >>> s.to_python(('this',)) - set(['this']) + {'this'} """ use_set = False diff --git a/tests/non_empty.txt b/tests/non_empty.txt index 8db8ec1d..bbed33e6 100644 --- a/tests/non_empty.txt +++ b/tests/non_empty.txt @@ -1,6 +1,4 @@ michele@ionic:~/Progetti/TurboGears/svn/thirdparty/formencode/formencode$ python -Python 2.4.3 (#2, Mar 30 2006, 14:45:01) -[GCC 4.0.3 (Ubuntu 4.0.3-1ubuntu3)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from formencode import validators >>> int = validators.Int() diff --git a/tests/test_doctests.py b/tests/test_doctests.py index 5361cace..b02d22db 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -28,26 +28,6 @@ base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -OutputChecker = doctest.OutputChecker - - -class OutputChecker3(OutputChecker): - def check_output(self, want, got, optionflags): - if want.startswith("u'"): - want = want[1:] - elif want.startswith("set(["): - want = ( - want[3:] - .replace("([", "{") - .replace("])", "}") - .replace("{}", "set()") - ) - return OutputChecker.check_output(self, want, got, optionflags) - - -doctest.OutputChecker = OutputChecker3 - - def doctest_file(document, verbose, raise_error): failure_count, test_count = doctest.testfile( document, From 50c866289169ea005bad3753a53725e5d616135f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 21:40:06 +0100 Subject: [PATCH 104/123] Allow Contexts to be used via context manager --- src/formencode/context.py | 12 +++++++++--- tests/test_context.py | 13 +++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/formencode/context.py b/src/formencode/context.py index 1f3ac8ea..f1c0b68b 100644 --- a/src/formencode/context.py +++ b/src/formencode/context.py @@ -44,9 +44,9 @@ def do_stuff(): variables to default values. This will not affect the stack of scopes, but will only add defaults. -this syntax would certainly be useful:: +A context can be also used like this:: - with context(page='view'): + with context.set(page='view'): do stuff... And ``page`` will be set to ``'view'`` only inside that ``with`` block. @@ -93,7 +93,7 @@ def __getattr__(self, attr): def __setattr__(self, attr, value): raise AttributeError( - "You can only write attribute on context object with the .set() method") + "You can only set attributes on context objects with the .set() method") def set(self, **kw): state_id = next(_restore_ids) @@ -158,6 +158,12 @@ def __init__(self, context, state_id): self.context = context self.restored = False + def __enter__(self): + return self.context + + def __exit__(self, _exc_type, _exc_value, _exc_tb): + self.restore() + def restore(self): if self.restored: # @@: Should this really be allowed? diff --git a/tests/test_context.py b/tests/test_context.py index 6e203f4d..34da8fa5 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -53,3 +53,16 @@ def test_default(): assert con.a == 2 res.restore() assert con.a == 4 + + +def test_context_manager(): + with c1.set(foo=1): + assert_is(c1, 'foo', 1) + assert_is(c1, 'foo', None) + with c1.set(foo=2), c2.set(foo='test'): + assert_is(c1, 'foo', 2) + assert_is(c2, 'foo', 'test') + change_state(c1, assert_is, c1, 'foo', 3, foo=3) + assert_is(c1, 'foo', 2) + assert_is(c1, 'foo', None) + assert not hasattr(c2, 'foo') From 7c3f1751c16a47ff934ec336674871b4272421ab Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 22:00:28 +0100 Subject: [PATCH 105/123] Fix deprecated setup.cfg setting --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ec6bfe79..74dc8a23 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,7 @@ maintainer = Chris Lambacher maintainer_email = chris@kateandchris.net url = "http://formencode.org" license = MIT -license_file = LICENSE.txt +license_files = LICENSE.txt classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers From 4f4ce89bd1264fcdf936c520a416544c849d2e66 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 22:00:49 +0100 Subject: [PATCH 106/123] Add changelog --- docs/whatsnew-2.0.txt | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 7b5adcc8..a4ff9cae 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -1,14 +1,22 @@ What's New In FormEncode 2.0 ============================ -This article explains the latest changes in `FormEncode` version 2.0.0 -compared to its predecessor, `FormEncode` 1.3.0 +This article explains the latest changes in `FormEncode` version 2.0 +compared to its predecessor, `FormEncode` version 1.3. +2.1.0 +----- + + - Add support for Python 3.7 to 3.22, end support for older Python versions + - Context.set() now works as a context manager + - Fix binary of swedish translation + - Some internal code cleanup and modernization 2.0.1 ----- - - Add support for 3.10 - - use Pytest instead of Nose and Github Actions instead of Travis for tests + + - Add support for Python 3.10 + - use Pytest instead of Nose and GitHub Actions instead of Travis for tests - Documentation updates - Note this will be the last version to support Python 2.7. The next version will be 2.1 to signal this change. If you want to keep support for Python @@ -17,12 +25,12 @@ compared to its predecessor, `FormEncode` 1.3.0 2.0.0 ----- - - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. - If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. - You might also try `FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. + - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. + - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. + If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. + You might also try FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. - This will be the last major version to support Python 2.7 - - Add strict flag to USPostalCode to raise error on postal codes that has too + - Add strict flag to ``USPostalCode`` to raise error on postal codes that has too many digits instead of just truncating - Various Python 3 fixes - Serbian latin translation @@ -30,6 +38,6 @@ compared to its predecessor, `FormEncode` 1.3.0 - Dutch, UK, Greek and South Korean postal code format fixes - Add postal code formats for Switzerland, Cyprus, Faroe Islands, San Marino, Ukraine and Vatican City. - Add ISODateTimeConverter validator - - Add ability to target htmlfill to particular form or ignore a form + - Add ability to target ``htmlfill`` to particular form or ignore a form - Fix format errors in some translations - - The version of the library can be checked using formencode.__version__ + - The version of the library can be checked using ``formencode.__version__`` From 8090410afc645933c8d1f855fa88223c991893d5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 22:11:02 +0100 Subject: [PATCH 107/123] Cleanup manifest and license files --- LICENSE.txt | 6 ------ MANIFEST.in | 1 - 2 files changed, 7 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index a611276f..20bd89b8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,5 @@ Copyright (c) 2015 Ian Bicking and FormEncode Contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -9,13 +7,9 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/MANIFEST.in b/MANIFEST.in index 44df65a8..8991e94a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,2 @@ exclude .gitignore exclude .gitattributes -exclude .travis.yml From a78247da3e9913769451700555084fa04854df5e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:08:43 +0100 Subject: [PATCH 108/123] Fix build configuration --- MANIFEST.in | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- setup.cfg | 14 ++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 8991e94a..81153722 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,25 @@ exclude .gitignore exclude .gitattributes + +include MANIFEST.in + +include LICENSE.txt +include README.rst + +include tox.ini + +graft src/formencode +graft src/formencode/javascript +include src/formencode/i18n/*.pot +recursive-include src/formencode/i18n *.po *.mo + +graft examples +recursive-include examples *.py + +graft tests +graft tests/htmlfill_data +recursive-include docs *.rst conf.py Makefile make.bat +prune docs/_build + +global-exclude */__pycache__/* +global-exclude *.py[cod] diff --git a/pyproject.toml b/pyproject.toml index c19dbc32..535641bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel", "setuptools_scm>=6.2"] +requires = ["setuptools", "wheel", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index 74dc8a23..16e5213e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,14 +28,17 @@ classifiers = Topic :: Software Development :: Libraries :: Python Modules [options] -packages = find: package_dir = - =src + = src +packages = + formencode python_requires = >=3.7 -include_package_data = True +include_package_data = False -[options.packages.find] -where = src +[options.package_data] +formencode = + javascript/*.js + i18n/**/*.mo [options.extras_require] testing = @@ -73,4 +76,3 @@ max_line_length=88 [flake8] max-line-length = 88 extend-ignore = E128,E402,E501,E731 - From 1681aa2651b5965aaa42dc72dd352a8598b3ebf3 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:15:51 +0100 Subject: [PATCH 109/123] Allow building with Python 3.7 again --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 535641bb..8a0985f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel", "setuptools_scm>=8"] +requires = ["setuptools", "wheel", "setuptools_scm>=7"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] From be73f002dfd33f4e2953be6cfca8d2b5e23ab941 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:22:41 +0100 Subject: [PATCH 110/123] Fix Read The Docs --- .readthedocs.yaml | 22 ++++++++++++++++++++++ MANIFEST.in | 1 + docs/requirements.txt | 1 + 3 files changed, 24 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..9712e405 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in index 81153722..549bdeb8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ exclude .gitignore exclude .gitattributes +exclude .readthedocs.yaml include MANIFEST.in diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..9cd8b2f5 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx>=7,<8 From 7d233ff7a3ff16c772d1a2f74accb0dff19063de Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:27:56 +0100 Subject: [PATCH 111/123] Install package in docs requirements --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 9cd8b2f5..fe64bbd4 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ sphinx>=7,<8 +-e .. From ec5ad13e581e6828fae27dc6962c8b3f93abcb1b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:34:13 +0100 Subject: [PATCH 112/123] Fix docs requirements --- .readthedocs.yaml | 2 +- docs/requirements.txt | 2 -- requirements-docs.txt | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 docs/requirements.txt create mode 100644 requirements-docs.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9712e405..3dd84c58 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -19,4 +19,4 @@ sphinx: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - - requirements: docs/requirements.txt + - requirements: requirements-docs.txt diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index fe64bbd4..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -sphinx>=7,<8 --e .. diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 00000000..ac1ea171 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,2 @@ +sphinx >=7, <8 +-e . From 450ebe9025c65f88f826738811deb203ec32b104 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:40:46 +0100 Subject: [PATCH 113/123] Fix changelog --- docs/whatsnew-2.0.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index a4ff9cae..370f805a 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -7,7 +7,7 @@ compared to its predecessor, `FormEncode` version 1.3. 2.1.0 ----- - - Add support for Python 3.7 to 3.22, end support for older Python versions + - Add support for Python 3.7 to 3.12, end support for older Python versions - Context.set() now works as a context manager - Fix binary of swedish translation - Some internal code cleanup and modernization @@ -18,18 +18,17 @@ compared to its predecessor, `FormEncode` version 1.3. - Add support for Python 3.10 - use Pytest instead of Nose and GitHub Actions instead of Travis for tests - Documentation updates - - Note this will be the last version to support Python 2.7. The next version - will be 2.1 to signal this change. If you want to keep support for Python - 2.7 update your dependencies spec to be below 2.1 + - Note this will be the last version to support Python 2. + The next version will be 2.1.0 to signal this change. + For compatibility with older Python versions, please use versions < 2.1. 2.0.0 ----- - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. - If you need Python 2.6 or 3.2 to 3.5 compatibility please use `FormEncode` 1.3. - You might also try FormEncode` 2.0.0a1 which supports Python 2.6 and Python 3.3-3.5. - - This will be the last major version to support Python 2.7 + For compatibility with older Python versions, please use versions < 1.3. + - This will be the last major version to support Python 2. - Add strict flag to ``USPostalCode`` to raise error on postal codes that has too many digits instead of just truncating - Various Python 3 fixes From aa823839a1036eb5c25b0be42486b2ffe8f16645 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Nov 2023 23:52:47 +0100 Subject: [PATCH 114/123] Add badges and fix homepage --- README.rst | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9b556630..2ebd9bdc 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,14 @@ FormEncode ========== +.. |PyPI| image:: https://img.shields.io/pypi/v/formencode + :target: https://pypi.org/project/formencode + :alt: PyPI + +.. |Python| image:: https://img.shields.io/pypi/pyversions/formencode + :target: https://pypi.org/project/formencode + :alt: PyPI - Python Version + .. image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg :target: https://github.com/formencode/formencode/actions :alt: Test Status diff --git a/setup.cfg b/setup.cfg index 16e5213e..a2fd5767 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ author = Ian Bicking author_email = ianb@colorstudy.com maintainer = Chris Lambacher maintainer_email = chris@kateandchris.net -url = "http://formencode.org" +url = https://www.formencode.org/ license = MIT license_files = LICENSE.txt classifiers = From 64f6726a630f37aad65e6ecaa4145d1c88a711a2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:16:24 +0100 Subject: [PATCH 115/123] Use https for the documentation --- README.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 2ebd9bdc..958f0712 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,14 @@ FormEncode ========== .. |PyPI| image:: https://img.shields.io/pypi/v/formencode - :target: https://pypi.org/project/formencode - :alt: PyPI + :target: https://pypi.org/project/formencode + :alt: PyPI .. |Python| image:: https://img.shields.io/pypi/pyversions/formencode - :target: https://pypi.org/project/formencode - :alt: PyPI - Python Version + :target: https://pypi.org/project/formencode + :alt: PyPI - Python Version -.. image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg +.. |Tests| image:: https://github.com/formencode/formencode/actions/workflows/run-tests.yml/badge.svg :target: https://github.com/formencode/formencode/actions :alt: Test Status @@ -17,16 +17,16 @@ FormEncode Introduction ------------ -FormEncode is a validation and form generation package. The -validation can be used separately from the form generation. The -validation works on compound data structures, with all parts being -nestable. It is separate from HTTP or any other input mechanism. +FormEncode is a validation and form generation package. +The validation can be used separately from the form generation. +The validation works on compound data structures, with all parts being nestable. +It is separate from HTTP or any other input mechanism. Documentation ------------- -The latest documentation is available at http://www.formencode.org/ +The latest documentation is available at https://www.formencode.org/ Testing @@ -39,4 +39,4 @@ Use `tox` to run the test suite for all supported Python versions. Changes ------- -See the `What's new section of the documentation `_. +See the `What's new section of the documentation `_. From 43368d5eb1bed385040577beee01a072da48685f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:19:05 +0100 Subject: [PATCH 116/123] Fix badges --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 958f0712..3b9a3ee2 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,8 @@ FormEncode ========== +|PyPI| |Python| |Tests| + .. |PyPI| image:: https://img.shields.io/pypi/v/formencode :target: https://pypi.org/project/formencode :alt: PyPI From ec2ea1b0617601885ddaee0ec499820166bfed58 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:30:49 +0100 Subject: [PATCH 117/123] Make README a bit nicer --- README.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 3b9a3ee2..4d9f6870 100644 --- a/README.rst +++ b/README.rst @@ -28,17 +28,19 @@ It is separate from HTTP or any other input mechanism. Documentation ------------- -The latest documentation is available at https://www.formencode.org/ +The latest documentation is available at +`www.formencode.org `. Testing ------- -Use `pytest formencode` to run the test suite. -Use `tox` to run the test suite for all supported Python versions. +Use ``pytest formencode`` to run the test suite. +Use ``tox`` to run the test suite for all supported Python versions. Changes ------- -See the `What's new section of the documentation `_. +See the `What's new `_ +section of the documentation. From 28c90007e10bc520bcdc664abe806e59c7672d9e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Nov 2023 00:32:20 +0100 Subject: [PATCH 118/123] Fix syntax in README file --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4d9f6870..76d97684 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ Documentation ------------- The latest documentation is available at -`www.formencode.org `. +`www.formencode.org `_. Testing @@ -42,5 +42,5 @@ Use ``tox`` to run the test suite for all supported Python versions. Changes ------- -See the `What's new `_ +See the `What's New `_ section of the documentation. From 43edbb5fd007ff352a6b75719aee466ae916dbab Mon Sep 17 00:00:00 2001 From: Oleg Broytman Date: Fri, 10 Nov 2023 18:46:03 +0300 Subject: [PATCH 119/123] Prepare for Python 3.13 which removes the cgi module (#176) --- src/formencode/validators.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index f2011b47..6f272b78 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -5,7 +5,10 @@ Validator/Converters for use with FormEncode. """ -import cgi +try: + import cgi +except ImportError: # Python >= 3.13 + cgi = None import re import warnings from encodings import idna @@ -1772,7 +1775,7 @@ class FieldStorageUploadConverter(FancyValidator): no upload was given). """ def _convert_to_python(self, value, state=None): - if isinstance(value, cgi.FieldStorage): + if cgi and isinstance(value, cgi.FieldStorage): if getattr(value, 'filename', None): return value raise Invalid('invalid', value, state) @@ -1780,7 +1783,7 @@ def _convert_to_python(self, value, state=None): return value def is_empty(self, value): - if isinstance(value, cgi.FieldStorage): + if cgi and isinstance(value, cgi.FieldStorage): return not bool(getattr(value, 'filename', None)) return FancyValidator.is_empty(self, value) @@ -1825,7 +1828,7 @@ def _convert_to_python(self, value, state): upload = value.get(self.upload_key) static = value.get(self.static_key, '').strip() filename = content = None - if isinstance(upload, cgi.FieldStorage): + if cgi and isinstance(upload, cgi.FieldStorage): filename = upload.filename content = upload.value elif isinstance(upload, str) and upload: From 298bbb4d600b5cedbfe699b378332ee3b04036f2 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Thu, 30 Jan 2025 08:38:08 -0500 Subject: [PATCH 120/123] Fix test automation This fixes tests on Github that are failing for several reasons: - Python 3.7 is not supported on the latest Ubuntu and MacOS images - The URL doctest is failing on a redirect from http to https --- .github/workflows/run-tests.yml | 13 ++++++++++--- setup.cfg | 1 + src/formencode/validators.py | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 0aaad708..602ac332 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,14 +13,21 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] os: [ubuntu-latest, macOS-latest, windows-latest] include: # pypy3 on Windows currently fails trying to # run dnspython tests. Moving pypy3 to only test linux. - - python-version: 'pypy-3.9' + - python-version: 'pypy3.10' os: ubuntu-latest - - python-version: 'pypy-3.10' + - python-version: '3.7' + os: ubuntu-22.04 + - python-version: '3.7' + os: macos-13 + exclude: + - python-version: '3.7' + os: macOS-latest + - python-version: '3.7' os: ubuntu-latest steps: diff --git a/setup.cfg b/setup.cfg index a2fd5767..7ebd9621 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ classifiers = Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 6f272b78..4666f70a 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1397,7 +1397,7 @@ class URL(FancyValidator): Traceback (most recent call last): ... Invalid: You must start your URL with http://, https://, etc - >>> u.to_python('http://www.formencode.org/does/not/exist/page.html') + >>> u.to_python('https://httpbin.org/status/404') Traceback (most recent call last): ... Invalid: The server responded that the page could not be found @@ -1660,7 +1660,7 @@ def _validate_python(self, value, state=None): if value.startswith('xri://'): value = value[6:] - if not value[0] in ('@', '=') and not ( + if value[0] not in ('@', '=') and not ( self.xri_type == 'i-number' and value[0] == '!'): raise Invalid(self.message('noType', state), value, state) From a98fcd88469abbfa997847bacc31d022dd1b6bf9 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Thu, 30 Jan 2025 09:20:20 -0500 Subject: [PATCH 121/123] Add automation for releases when a tag is created --- .github/workflows/publish-to-pypi.yml | 88 +++++++++++++++++++++++++++ Makefile | 6 -- 2 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/publish-to-pypi.yml diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml new file mode 100644 index 00000000..7cb567a4 --- /dev/null +++ b/.github/workflows/publish-to-pypi.yml @@ -0,0 +1,88 @@ +name: Publish Python distribution to PyPI + +on: push + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: python3 -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + publish-to-pypi: + name: Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/FormEncode + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + github-release: + name: >- + Sign the Python distribution with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + "$GITHUB_REF_NAME" + --repo "$GITHUB_REPOSITORY" + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + "$GITHUB_REF_NAME" dist/** + --repo "$GITHUB_REPOSITORY" diff --git a/Makefile b/Makefile index b27f4fd5..4a5d06db 100644 --- a/Makefile +++ b/Makefile @@ -13,12 +13,6 @@ flake8: coverage: pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=formencode formencode -publish: - pip install "twine>=4" build - python -m build - twine upload dist/* - rm -fr build dist .egg src/FormEncode.egg-info - .PHONY: docs docs: From 8375977e58f1ceea7542334f2777c01901525ed9 Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Fri, 31 Jan 2025 09:45:07 -0500 Subject: [PATCH 122/123] Don't allow FieldStorageUploadConverter without cgi module In Python 3.13 and later the cgi module has been removed and this meant that importing the `cgi` module started making it so `formencode.validators` could not be imported. This was resolved in #176 by detecting the import error, setting `cgi` to `None`, and then skipping behaviour that used `cgi.FieldStorage`. Using `FieldStorageUploadConverter` without `cgi.FieldStorage` does not make sense. This change adds a check to `FieldStorageUploadConverter.__init_` to prevent instantiating it like we do for DNS validators so that using it doesn't silently do nothing. Probably if you are using FieldStorageUploadConverter then you'll have `legacy-cgi` installed because you are either already using it directly, or you are getting it transitively through something like WebOb. --- src/formencode/validators.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/formencode/validators.py b/src/formencode/validators.py index 4666f70a..e17777f1 100644 --- a/src/formencode/validators.py +++ b/src/formencode/validators.py @@ -1773,7 +1773,19 @@ class FieldStorageUploadConverter(FancyValidator): This doesn't do any conversion, but it can detect empty upload fields (which appear like normal fields, but have no filename when no upload was given). + + Requires the legacy-cgi package on Python 3.13 and later. """ + + def __init__(self, *args, **kw): + if cgi is None: + warnings.warn( + "legacy-cgi is not" + " installed on your system (or the cgi package cannot be" + " found). I cannot convert FieldStorage") + raise ImportError("no module named cgi") + super().__init__(*args, **kw) + def _convert_to_python(self, value, state=None): if cgi and isinstance(value, cgi.FieldStorage): if getattr(value, 'filename', None): @@ -1819,6 +1831,9 @@ class MyScheme(Scheme): Note that big file uploads mean big hidden fields, and lots of bytes passed back and forth in the case of an error. + + Note: requires the legacy-cgi package on Python 3.13 and later to be able to handle + ``cgi.FieldStorage`` values. """ upload_key = 'upload' From c2969467b209d93f09e32ebaf75409da72d9ecff Mon Sep 17 00:00:00 2001 From: Chris Lambacher Date: Fri, 31 Jan 2025 10:00:42 -0500 Subject: [PATCH 123/123] Update docs for 2.1.1 release --- docs/whatsnew-2.0.txt | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/whatsnew-2.0.txt b/docs/whatsnew-2.0.txt index 370f805a..9a54e515 100644 --- a/docs/whatsnew-2.0.txt +++ b/docs/whatsnew-2.0.txt @@ -1,9 +1,24 @@ -What's New In FormEncode 2.0 +What's New In FormEncode 2.x ============================ -This article explains the latest changes in `FormEncode` version 2.0 +This article explains the latest changes in `FormEncode` version 2.x compared to its predecessor, `FormEncode` version 1.3. +2.1.1 +----- + + - Add support for 3.13 + - Don't require `legacy-cgi` to be installed on 3.13 and later + (`#176 `_) + - Don't permit `FieldStorageUploadConverter` to be instantiated without + having `legacy-cgi` installed since it does not make sense + - Releases are now automated through GitHub Actions + (`#184 `_) + + +Note: This is the last version that will support Python 3.7 and 3.8 as +those are now out of support. + 2.1.0 ----- @@ -25,8 +40,8 @@ compared to its predecessor, `FormEncode` version 1.3. 2.0.0 ----- - - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. - - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. + - `FormEncode` can now run on Python 3.6 and higher without needing to run 2to3 first. + - `FormEncode` 2.0 is no longer compatible with Python 2.6 and 3.2 to 3.5. For compatibility with older Python versions, please use versions < 1.3. - This will be the last major version to support Python 2. - Add strict flag to ``USPostalCode`` to raise error on postal codes that has too