diff --git a/CHANGELOG.md b/CHANGELOG.md index 44209eb..0b84c1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.9.18 - 2022-??-?? - Internationalization + +* Added octodns.idna idna_encode/idna_decode helpers + ## v0.9.17 - 2022-04-02 - Registration required #### Noteworthy changes diff --git a/octodns/idna.py b/octodns/idna.py new file mode 100644 index 0000000..65e3f53 --- /dev/null +++ b/octodns/idna.py @@ -0,0 +1,26 @@ +# +# +# + +from idna import decode as _decode, encode as _encode + + +def idna_encode(name): + if not name: + # idna.encode doesn't handle '' + return name + elif name.startswith('*'): + # idna.encode doesn't like the * + name = _encode(name[2:]).decode('utf-8') + return f'*.{name}' + return _encode(name).decode('utf-8') + + +def idna_decode(name): + if not name: + # idna.decode doesn't handle '' + return name + elif name.startswith('*'): + # idna.decode doesn't like the * + return f'*.{_decode(name[2:])}' + return _decode(name) diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index ddb8b92..9c520f5 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -100,7 +100,7 @@ class Record(EqualityTupleMixin): @classmethod def new(cls, zone, name, data, source=None, lenient=False): - name = str(name) + name = str(name).lower() fqdn = f'{name}.{zone.name}' if name else zone.name try: _type = data['type'] diff --git a/requirements-dev.txt b/requirements-dev.txt index 0fec700..e6f42c3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,16 +1,15 @@ -Pygments==2.11.2 +Pygments==2.12.0 attrs==21.4.0 -bleach==4.1.0 +bleach==5.0.0 build==0.7.0 -certifi==2021.10.8 +certifi==2022.5.18.1 cffi==1.15.0 charset-normalizer==2.0.12 cmarkgfm==0.8.0 -colorama==0.4.4 -coverage==6.3.2 +commonmark==0.9.1 +coverage==6.3.3 docutils==0.18.1 -idna==3.3 -importlib-metadata==4.11.2 +importlib-metadata==4.11.3 iniconfig==1.1.1 keyring==23.5.0 packaging==21.3 @@ -24,19 +23,19 @@ pycountry-convert==0.7.2 pycountry==22.3.5 pycparser==2.21 pyflakes==2.4.0 -pyparsing==3.0.7 +pyparsing==3.0.9 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-network==0.0.1 -pytest==7.0.1 -readme-renderer==33.0 +pytest==7.1.2 +readme-renderer==35.0 repoze.lru==0.7 requests-toolbelt==0.9.1 requests==2.27.1 rfc3986==2.0.0 +rich==12.4.1 tomli==2.0.1 -tqdm==4.63.0 -twine==3.8.0 -urllib3==1.26.8 +twine==4.0.0 +urllib3==1.26.9 webencodings==0.5.1 -zipp==3.7.0 +zipp==3.8.0 diff --git a/requirements.txt b/requirements.txt index ddb3b37..112aa0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ PyYAML==6.0 dnspython==2.2.1 fqdn==1.5.1 +idna==3.3 natsort==8.1.0 python-dateutil==2.8.2 six==1.16.0 diff --git a/setup.py b/setup.py index 3d5faf7..d2887f9 100644 --- a/setup.py +++ b/setup.py @@ -96,6 +96,7 @@ setup( 'PyYaml>=4.2b1', 'dnspython>=1.15.0', 'fqdn>=1.5.0', + 'idna>=3.3', 'natsort>=5.5.0', 'python-dateutil>=2.8.1', ), diff --git a/tests/test_octodns_idna.py b/tests/test_octodns_idna.py new file mode 100644 index 0000000..b41ee84 --- /dev/null +++ b/tests/test_octodns_idna.py @@ -0,0 +1,43 @@ +# +# +# + +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from unittest import TestCase + +from octodns.idna import idna_decode, idna_encode + + +class TestIdna(TestCase): + + def assertIdna(self, value, expected): + got = idna_encode(value) + self.assertEqual(expected, got) + # round tripped + self.assertEqual(value, idna_decode(value)) + + def test_noops(self): + # empty + self.assertIdna('', '') + + # noop + self.assertIdna('unit.tests.', 'unit.tests.') + + # wildcard noop + self.assertIdna('*.unit.tests.', '*.unit.tests.') + + def test_unicode(self): + # encoded + self.assertIdna('zajęzyk.pl.', 'xn--zajzyk-y4a.pl.') + + # encoded with wildcard + self.assertIdna('*.zajęzyk.pl.', '*.xn--zajzyk-y4a.pl.') + + # encoded with simple name + self.assertIdna('noop.zajęzyk.pl.', 'noop.xn--zajzyk-y4a.pl.') + + # encoded with encoded name + self.assertIdna('zajęzyk.zajęzyk.pl.', + 'xn--zajzyk-y4a.xn--zajzyk-y4a.pl.')